This short guide will demonstrate how to use the Forest Tools package to generate raster-based spatial statistics from a set of dominant treetops and then summarize the raster to a set of polygonal management units.
The analysis workflow is as follows:
The process for detecting dominant treetops (step 1) is explained in detail in the accompanying Canopy analysis vignette. In this document, we will simply use an existing set of treetops.
Begin by attaching the Forest Tools library and sample data. The sample data is derived from a photogrammetric survey of a 125 ha area in the Quesnel Timber Supply Area in British Columbia, Canada.
# Attach the 'ForestTools' and 'raster' libraries
library(ForestTools)
library(raster)
# Load sample canopy height model, treetops and block boundaries
data("quesnelCHM", "quesnelTrees", "quesnelBlocks")
View the canopy height model (CHM) and the polygonal management units (blocks) using the plot function.
# Remove plot margins (optional)
par(mar = rep(0.5, 4))
# Plot CHM and blocks (extra optional arguments remove labels and tick marks from the plot)
plot(quesnelCHM, xlab = "", ylab = "", xaxt='n', yaxt = 'n')
plot(quesnelBlocks, add = TRUE, border = "darkmagenta", lwd = 2)
Given the high number of treetops, visualizing the quesnelTrees
dataset in its entirety is difficult. Instead, it is more practical to view a rasterized representation of tree attributes. This can be done using the SpatialStatistics
function.
The first step is to define a function for computing the statistic that we’re interested in. In this case, we want the average height of the tallest 100 trees. It is important, however, that our function accept a number of trees lower than a 100 without returning an error (in this case, it should simply return the average height of all trees in the area). We should also consider the possibility that some trees will have NA (non-available) height values.
# Create custom function for computing top height
topHgtFun <- function(x, ...) mean(tail(sort(x), 100))
In this example x
would be a series of tree heights. The sort
function will arrange them in ascending order (and also remove NA values), and the tail
function will take the last 100 values (i.e.: the highest values). Finally, the mean
function will compute the average.
We will now apply the newly created topHgtFun
to our quesnelTrees
dataset using the SpatialStatistics
function. We set the grid
argument to 100 to generate a 100 m x 100 m (1 ha) grid. We enter topHgtFun
as a named list: the “Top100” will be the name given to the product of the function.
# Use SpatialStatistics to generate gridded statistics
sptStatRas <- SpatialStatistics(quesnelTrees, variables = "height", grid = 100, statFuns = list(Top100 = topHgtFun))
# View information about the result
sptStatRas
## class : RasterBrick
## dimensions : 13, 15, 195, 2 (nrow, ncol, ncell, nlayers)
## resolution : 100, 100 (x, y)
## extent : 492863, 494363, 5820051, 5821351 (xmin, xmax, ymin, ymax)
## coord. ref. : +proj=utm +zone=10 +datum=WGS84 +units=m +no_defs +ellps=WGS84 +towgs84=0,0,0
## data source : in memory
## names : TreeCount, heightTop100
## min values : 1.000000, 2.707648
## max values : 427.00000, 32.61098
We can see that the product of SpatialStatistics
is a RasterBrick, i.e.: a multi-layered raster. SpatialStatistics
automatically generates a tree count in addition to any custom-defined statistics. We can subset a single layer from the RasterBrick using the [[]]
operator
# Subset top height raster
topHgtRas <- sptStatRas[["heightTop100"]]
plot(topHgtRas, xlab = "", ylab = "", xaxt='n', yaxt = 'n')
Once our raster-based statistic has been generated, there are a variety of ways to summarize the raster to a set of polygons. In the following example, the rasterize
and zonal
functions from the raster package are used.
# Rasterize block boundaries
blockRas <- rasterize(quesnelBlocks, topHgtRas)
# View results
plot(blockRas, xlab = "", ylab = "", xaxt='n', yaxt = 'n')
# Use rasterized block boundaries to compute zonal statistics
zoneStat <- zonal(topHgtRas, blockRas, 'mean')
zoneStat
## zone mean
## [1,] 1 17.172809
## [2,] 2 26.085880
## [3,] 3 17.757272
## [4,] 4 21.807185
## [5,] 5 18.338353
## [6,] 6 19.080300
## [7,] 7 13.000464
## [8,] 8 18.143361
## [9,] 9 6.976992
We can now simply attach the zonal statistics to our original polygonal areas.
# Create new 'topHeight' attribute from zonal statistics
quesnelBlocks[["topHeight"]] <- zoneStat[,"mean"]
# Plot result
library(rgeos)
colRamp <- colorRampPalette(c('lightgoldenrod1', 'tomato2'))(10)
cols <- colRamp[as.numeric(cut(quesnelBlocks[["topHeight"]],breaks = 10))]
plot(quesnelBlocks, col = cols, xlab = "", ylab = "", xaxt='n', yaxt = 'n')
text(gCentroid(quesnelBlocks, byid = TRUE), round(quesnelBlocks[["topHeight"]],2), font = 2)