I Del Toro
I Del Toro

Reputation: 943

Calculating percent cover from shapefiles

I normally do not work with shapefiles, so I am a bit lost here. I have two shapefiles each with multiple objects. The first is a set of 32 polygons (each one is a plot). The second shapefile has >10,000 objects which represent vegetation clusters of different sizes within each plot. I am trying to figure out.

1) How do I calculate percent cover of total vegetation cover within each site?

2) What percentage of each the vegetation cover is less than 5 meters in area in each plot?

This is what my data looks like in ArcGIS for a single plot.

enter image description here

Upvotes: 0

Views: 2730

Answers (1)

jlhoward
jlhoward

Reputation: 59355

The following code will do what you want, I think.

NB: This uses the area information stored in the shapefile polygons (as explained below). It does not use the Area column in your vegetation shapefile data section. In most cases, your Area is identical to the area stored in the shapefile, but in some cases your Area is much larger. Since I don't know where your Area data came from, it seemed safer to use the information stored with the shapefile polygons.

library(rgdal)
library(ggplot2)

setwd("<directory containing all your shapefiles>")
plt.map <- readOGR(dsn=".",layer="plots")
veg.map <- readOGR(dsn=".",layer="veg_in_plots")
# associate LocCode with polygon IDs
plt.data <- cbind(id=rownames(plt.map@data), LocCode=plt.map@data$LocCode)
veg.data <- cbind(id=rownames(veg.map@data), LocCode=veg.map@data$LocCode)
# function to extract area from polygon data
get.area <- function(polygon) {
  row <- data.frame(id=polygon@ID, area=polygon@area, stringsAsFactors=F)
  return(row)
}
# area of each plot polygon
plt.areas <- do.call(rbind,lapply(plt.map@polygons, get.area))
plt.data  <- merge(plt.data,plt.areas, by="id")  # append area column to plt.data
# area of each vegetation polygon
veg.areas <- do.call(rbind,lapply(veg.map@polygons, get.area))
veg.data  <- merge(veg.data,veg.areas, by="id")  # append area column to veg.data
# total area of vegetation polygons by LocCode
veg.smry  <- aggregate(area~LocCode,data=veg.data,sum)
smry      <- merge(plt.data,veg.smry,by="LocCode")
smry$coverage <- with(smry,100*area.y/area.x)    # coverage percentage
# total area for vegetation object with A < 5 msq
veg.lt5   <- aggregate(area~LocCode,data=veg.data[veg.data$area<5,],sum)
smry      <- merge(smry, veg.lt5, by="LocCode") 
# fraction of covered area coming from veg. obj. with A < 5 msq
smry$pct.lt5 <- with(smry, 100*area/area.y)

Produces this:

# head(smry)
#   LocCode id   area.x   area.y coverage      area  pct.lt5
# 1       1  3 1165.916 259.2306 22.23408  60.98971 23.52720
# 2      10 11 1242.770 366.3222 29.47626  88.21827 24.08216
# 3      11 12 1181.366 213.2105 18.04779 129.21612 60.60496
# 4      12 13 1265.352 577.6037 45.64767 236.83946 41.00380
# 5      13 14 1230.662 226.2686 18.38593  48.09509 21.25575
# 6      14 15 1274.538 252.0577 19.77640  46.94874 18.62619

Explanation:

Shapefiles can be imported into R using readOGR(...) in the rgdal package. When importing a polygon shapefile, the result is a "SpatialPolygonDataFrame" object. These objects basically have two sections: a polygon section, which has the coordinates needed to plot each polygon, and a data section, which has data for each polygon (so, one row per polygon). If the shapefile is imported as, e.g., map,

map <- readOGR(dsn=".",layer="myShapeFile")

then the polygon and data sections can be accessed as map@polygon and map@data. It turns out that the polygon areas are stored in the polygon section. To get the areas, we define a function, get.area(...) that extracts the area and polygon ID from a polygon. Then we call that function for all polygons using lapply(...), and bind all the returned values together row-wise using rbind(...):

plt.areas <- do.call(rbind,lapply(plt.map@polygons, get.area))
veg.areas <- do.call(rbind,lapply(veg.map@polygons, get.area))

Now we need to associate vegetation areas with plot polygons. This is done through column LocCode, which is present in the data section of each shapefile. So we first associate polygon ID's with LocCode for both plots and vegetation areas:

plt.data <- cbind(id=rownames(plt.map@data), LocCode=plt.map@data$LocCode)
veg.data <- cbind(id=rownames(veg.map@data), LocCode=veg.map@data$LocCode)

Then we append the area column based on polygon ID:

plt.data  <- merge(plt.data,plt.areas, by="id")  # append area column to plt.data
veg.data  <- merge(veg.data,veg.areas, by="id")  # append area column to veg.data

Then we need to sum the vegetation areas by LocCode:

veg.smry  <- aggregate(area~LocCode,data=veg.data,sum)

And finally merge this with the plot polygon areas:

smry      <- merge(plt.data,veg.smry,by="LocCode")

In the smry dataframe, area.x is the area of the plot, and area.y is the total area covered by vegetation in that plot. Since, for both shapefiles, the projection is:

 +proj=utm +zone=13 +datum=NAD83 +units=m +no_defs +ellps=GRS80 +towgs84=0,0,0

the units are in meters and the areas are in msq. To determine how much of the vegetation is coming from areas < 5 msq, we total the vegetation areas with area < 5 and merge that result with smry:

veg.lt5   <- aggregate(area~LocCode,data=veg.data[veg.data$area<5,],sum)
smry      <- merge(smry, veg.lt5, by="LocCode") 

Finally, with the data we have it's straightforward to render maps for each plot area:

cols   <- c("id","LocCode")
plt.df <- fortify(plt.map)
plt.df <- merge(plt.df,plt.data[cols],by="id")
veg.df <- fortify(veg.map)
veg.df <- merge(veg.df,veg.data[cols],by="id")
ggp <- ggplot(plt.df, aes(x=long, y=lat, group=group))
ggp <- ggp + geom_path()
ggp <- ggp + geom_polygon(data=veg.df, fill="green")
ggp <- ggp + facet_wrap(~LocCode,scales="free")
ggp <- ggp + theme(axis.text=element_blank())
ggp <- ggp + labs(x="",y="")
ggp <- ggp + coord_fixed()
ggp

Upvotes: 3

Related Questions