2  Imagery and Digital Terrain Data

2.1 Topics Covered

  1. Band ratios from multispectral imagery
  2. Tasseled cap transformation
  3. Principal component analysis (PCA) on raster data for feature reduction
  4. Spatial enhancement via convolution filters
  5. Land surface parameters (LSPs) from a digital terrain model (DTM)
  6. Customizing moving windows

2.2 Introduction

Continuing from the last chapter, we now explore means to create derived products from image bands and digital terrain models (DTMs). For multispectral image data, we explore normalized difference band ratios, the tasseled cap transformation, principal component analysis (PCA), and convolution. For DTMs, we explore creating a wide variety of land surface parameters (LSPs).

We primarily rely on the terra package; however, we also load in the tidyverse for general data manipulation and tmap and tmaptools for map creation and geospatial data visualization. Additional raster processing functionality is provided by RStoolbox, spatialEco, and MultiscaleDTM.

For our demonstrations, we use a Landsat 8 Operational Land Imager (OLI) scene from March of 2024 representing leaf-off conditions and a Landsat 9 Operational Land Imager-2 (OLI-2) scene from September of 2024 representing leaf-on conditions. The images cover the area around Philadelphia, Pennsylvania, USA. They are read in using the rast() from terra. The bands are renamed using names(), and the images are visualized using different band combinations and plotRGB(). Comparing the two images, difference in spectral reflectance between the leaf-on and leaf-off data are apparent.

folderPath <- "gslrData/chpt2/data/"

lOff <- rast(str_glue("{folderPath}ls8_3_11_2024_SR.tif"))
lOn <- rast(str_glue("{folderPath}ls9_9_11_2024_SR.tif"))

names(lOff) <- c("Edge", 
                 "Blue", 
                 "Green", 
                 "Red", 
                 "NIR", 
                 "SWIR1", 
                 "SWIR2")
names(lOn) <- c("Edge", 
                "Blue", 
                "Green", 
                "Red", 
                "NIR", 
                "SWIR1", 
                "SWIR2")

plotRGB(lOff, r=5, g=4, b=3, stretch="lin")

plotRGB(lOn, r=5, g=4, b=3, stretch="lin")

plotRGB(lOff, r=7, g=5, b=3, stretch="lin")

plotRGB(lOn, r=7, g=5, b=3, stretch="lin")

2.3 Image Processing

2.3.1 Band Ratios

Table 2.1 lists and describes some common band ratios calculated from multispectral satellite data, such as those from the Landsat and Sentinel-2 missions and associated sensors. In the following code blocks, we demonstrate calculating example ratios using raster math and terra.

Table 2.1. Common multispectral image band ratios.
Band Ratio Equation Use
Normalized Difference Vegetation Index (NDVI) (NIR-Red)/(NIR+Red) Vegetation occurrence and health
Normalized Difference Built-Up Index (NDBI) (SWIR-NIR)/(SWIR+NIR) Ubanized areas; built-up areas; impervious surface
Normalized Difference Snow Index (NDSI) (Green-SWIR)/(Green+SWIR) Prescence of snow or ice
Normalized Difference Water Index (NDWI) after McFeeter (Green-NIR)/(Green+NIR) Presence of standing water
Normalized Difference Moisture Index (NDMI) (NIR-SWIR)/(NIR_SWIR) Vegetation water content

In our first example, we calculate the normalized difference vegetation index (NDVI) for both the leaf-off and leaf-on data using the near infrared (NIR) and red channels. We visualize the resulting single-band raster grids using tmap.

Instead of saving the result to a new spatRaster object, the ratio could be added as a new channel to the image stack using c(). Since the ratio is derived from the input multispectral data, it will align with the input data had have the same extent and number of rows and columns of cells.

It is generally preferred to calculate band ratios using surface reflectance as opposed to digital number (DN) values.

ndviLOff <- (lOff$NIR-lOff$Red)/((lOff$NIR+lOff$Red)+.001)
ndviLOn  <- (lOn$NIR-lOn$Red)/((lOn$NIR+lOn$Red)+.001)
tm_shape(ndviLOff)+
  tm_raster(col.scale = tm_scale_intervals(n=7,
                                          style="quantile",
                                          values = "brewer.greens"))

tm_shape(ndviLOn)+
  tm_raster(col.scale = tm_scale_intervals(n=7,
                                          style="quantile",
                                          values = "brewer.greens"))

Other band ratios can be calculated using the same general method. In the next example, we calculate the normalized difference built-up index (NDBI) using the shortwave infrared 2 (SWIR2) and NIR bands. Next, we calculate a normalized difference water index (NDWI) using the green and NIR bands.

ndbiLOff <- (lOff$SWIR2-lOff$NIR)/((lOff$SWIR2+lOff$NIR)+.001)
ndbiLOn  <- (lOn$SWIR2-lOn$NIR)/((lOn$SWIR2+lOn$NIR)+.001)
tm_shape(ndbiLOff)+
  tm_raster(col.scale = tm_scale_intervals(n=7,
                                          style="quantile",
                                          values = "brewer.reds"))

tm_shape(ndbiLOn)+
  tm_raster(col.scale = tm_scale_intervals(n=7,
                                          style="quantile",
                                          values = "brewer.reds"))

ndwiLOff <- (lOff$Green-lOff$NIR)/((lOff$Green+lOff$NIR)+.001)
ndwiLOn  <- (lOn$Green-lOn$NIR)/((lOn$Green+lOn$NIR)+.001)
tm_shape(ndwiLOff)+
  tm_raster(col.scale = tm_scale_intervals(n=7,
                                          style="quantile",
                                          values = "brewer.blues"))

tm_shape(ndwiLOn)+
  tm_raster(col.scale = tm_scale_intervals(n=7,
                                          style="quantile",
                                          values = "brewer.blues"))

2.3.2 Tasseled Cap Transformation

The tasseled cap transformation is used to convert image bands into the following measures:

  • Brightness: correlates with overall spectral brightness and soil brightness
  • Greenness: correlates with the presence of photosynthetically active vegetation
  • Wetness: correlates with the presence of standing water and water in soil and plant tissue

These measures are derived by multiplying the blue, green, red, NIR, SWIR1, and SWIR2 bands by pre-defined coefficients and summing the results. This transformation is commonly applied to Landsat Thematic Mapper (TM), Enhanced Thematic Mapper Plus (ETM+), OLI, or OLI-2 data. Coefficients have also been published for the Sentinel-2 Multispectral Instrument (MSI) sensor. The coefficients are generally meant to be applied to surface reflectance estimates as opposed to DN values.

Below, we use the published coefficients for the OLI and OLI-2 sensors to generate brightness, greenness, and wetness raster grids. We then merge the results to a single, multiband raster and display them using a false color composite. Areas with water generally show up as blue since wetness is mapped to the blue channel. Spectrally bright areas, such as impervious surfaces and bare soil, show up a red.

brightness <- 0.3029*lOff$Blue + 
  0.2786*lOff$Green + 
  0.4733*lOff$Red + 
  0.5599*lOff$NIR + 
  0.508*lOff$SWIR1 + 
  0.1887*lOff$SWIR2

greenness <- -0.2951*lOff$Blue + 
  -0.243*lOff$Green + 
  -0.5424*lOff$Red + 
  0.7276*lOff$NIR + 
  0.0713*lOff$SWIR1 + 
  -0.1608*lOff$SWIR2

wetness <- 0.1511*lOff$Blue + 
  0.1973*lOff$Green + 
  0.3283*lOff$Red + 
  0.3407*lOff$NIR + 
  -0.7117*lOff$SWIR1 + 
  -0.4559*lOff$SWIR2

tcOff <- c(brightness, greenness, wetness)
plotRGB(tcOff, r=1, g=2, b=3, stretch="lin")

brightness <- 0.3029*lOn$Blue + 
  0.2786*lOn$Green + 
  0.4733*lOn$Red + 
  0.5599*lOn$NIR + 
  0.508*lOn$SWIR1 + 
  0.1887*lOn$SWIR2

greenness <- -0.2951*lOn$Blue + 
  -0.243*lOn$Green + 
  -0.5424*lOn$Red + 
  0.7276*lOn$NIR + 
  0.0713*lOn$SWIR1 + 
  -0.1608*lOn$SWIR2

wetness <- 0.1511*lOn$Blue + 
  0.1973*lOn$Green + 
  0.3283*lOn$Red + 
  0.3407*lOn$NIR + 
  -0.7117*lOn$SWIR1 + 
  -0.4559*lOn$SWIR2

tcOn <- c(brightness, greenness, wetness)
plotRGB(tcOn, r=1, g=2, b=3, stretch="lin")

2.3.3 Principal Component Analysis

We explore the application of principal component analysis (PCA) to tabulated data in the next chapter. Here, it is applied to multiband raster data using the rasterPCA() function from the RStoolbox package. We first read in the leaf-on and leaf-off data again, rename the bands, then stack the two images into a single spatRaster object. We then perform the PCA to extract 14 components. Remember that the goal of PCA is to create linearly decorrelated data from the original, correlated image bands. The underlying idea is that variance equals information, so capturing the variance in the input data using a smaller number of bands results in feature reduction while maintaining the information content in the original image bands.

lOff <- rast(str_glue("{folderPath}ls8_3_11_2024_SR.tif"))
lOn <- rast(str_glue("{folderPath}ls9_9_11_2024_SR.tif"))

names(lOff) <- c("off_Edge", 
                 "off_Blue", 
                 "off_Green", 
                 "off_Red", 
                 "off_NIR", 
                 "off_SWIR1", 
                 "off_SWIR2")

names(lOn) <- c("on_Edge", 
                "on_Blue", 
                "on_Green", 
                "on_Red", 
                "on_NIR", 
                "on_SWIR1", 
                "on_SWIR2")

allBands <- c(lOff, lOn)

pcaOut <- rasterPCA(allBands, nSamples = NULL, nComp = 14, spca = FALSE)

Once the PCA data are generated, we can visualize the result as a false color composite. In our example, we use the first three principal component bands. The rasterPCA() function returns a list object where the PCA image is stored as a spatRaster in the map object.

plotRGB(pcaOut$map, r=1, b=2, g=3, stretch="lin")

We can obtain PCA eigenvalues from the model object stored within the list object created by rasterPCA(). Larger values indicate more variance explained. By dividing the eigenvalue for a specific PCA band by the sum of all eigenvalues, we can obtain the percent of variance explained by that principal component. The results suggest that component one captures ~40% of the total variance in the data while component two captures ~18.5%. The first six principal components collectively capture ~91% of the variance from the original bands.

pcaOut$model
Call:
princomp(cor = spca, covmat = covMat)

Standard deviations:
    Comp.1     Comp.2     Comp.3     Comp.4     Comp.5     Comp.6     Comp.7 
7114.87746 3297.11634 2017.43885 1603.42419 1499.60854  622.83532  445.30067 
    Comp.8     Comp.9    Comp.10    Comp.11    Comp.12    Comp.13    Comp.14 
 364.26454  257.78072  186.06735  149.72014  109.27942   70.31829   56.08057 

 14  variables and  14108647 observations.
pcaOut$model$sdev/(sum(pcaOut$model$sdev))
     Comp.1      Comp.2      Comp.3      Comp.4      Comp.5      Comp.6 
0.399844471 0.185292543 0.113376762 0.090109816 0.084275546 0.035002326 
     Comp.7      Comp.8      Comp.9     Comp.10     Comp.11     Comp.12 
0.025025169 0.020471071 0.014486855 0.010456681 0.008414027 0.006141325 
    Comp.13     Comp.14 
0.003951773 0.003151636 
cumsum(pcaOut$model$sdev)/(sum(pcaOut$model$sdev))
   Comp.1    Comp.2    Comp.3    Comp.4    Comp.5    Comp.6    Comp.7    Comp.8 
0.3998445 0.5851370 0.6985138 0.7886236 0.8728991 0.9079015 0.9329266 0.9533977 
   Comp.9   Comp.10   Comp.11   Comp.12   Comp.13   Comp.14 
0.9678846 0.9783412 0.9867553 0.9928966 0.9968484 1.0000000 

The loadings or eigenvectors are the coefficients that each band must be multiplied by in order to obtain each principal component. There are unique eigenvectors for each component and band combination. Eigenvectors serve a similar purpose as the coefficients used in the tasseled cap transformation demonstrated above: they allow for transforming the original bands into the new PC bands.

pcaOut$model$loadings

Loadings:
          Comp.1 Comp.2 Comp.3 Comp.4 Comp.5 Comp.6 Comp.7 Comp.8 Comp.9
off_Edge          0.179  0.158  0.256         0.118  0.331  0.478  0.368
off_Blue          0.215  0.169  0.281         0.168  0.218  0.338  0.214
off_Green         0.261  0.131  0.325         0.314 -0.106 -0.106 -0.221
off_Red           0.283  0.264  0.297         0.392 -0.178 -0.371 -0.247
off_NIR    0.445        -0.570  0.339 -0.567 -0.146                     
off_SWIR1  0.391         0.480 -0.201 -0.358 -0.269  0.293 -0.242  0.160
off_SWIR2  0.248         0.455 -0.237 -0.213 -0.125 -0.409  0.309 -0.133
on_Edge           0.185         0.112  0.206 -0.393  0.151  0.198 -0.500
on_Blue           0.234         0.123  0.216 -0.369         0.127 -0.347
on_Green          0.283         0.124  0.234 -0.252 -0.113 -0.197  0.192
on_Red     0.110  0.388                0.245 -0.270 -0.174 -0.331  0.469
on_NIR     0.581 -0.547         0.256  0.505  0.104 -0.135              
on_SWIR1   0.396  0.215 -0.214 -0.417  0.153  0.326  0.523 -0.149 -0.175
on_SWIR2   0.223  0.324 -0.199 -0.408  0.110  0.230 -0.419  0.351       
          Comp.10 Comp.11 Comp.12 Comp.13 Comp.14
off_Edge           0.103   0.144   0.415   0.430 
off_Blue                          -0.540  -0.560 
off_Green         -0.302  -0.704   0.198         
off_Red   -0.111   0.280   0.520                 
off_NIR                                          
off_SWIR1 -0.436  -0.105                         
off_SWIR2  0.572                                 
on_Edge   -0.164   0.154           0.440  -0.439 
on_Blue                           -0.541   0.543 
on_Green   0.172  -0.727   0.348                 
on_Red     0.127   0.493  -0.271                 
on_NIR                                           
on_SWIR1   0.347                                 
on_SWIR2  -0.513                                 

               Comp.1 Comp.2 Comp.3 Comp.4 Comp.5 Comp.6 Comp.7 Comp.8 Comp.9
SS loadings     1.000  1.000  1.000  1.000  1.000  1.000  1.000  1.000  1.000
Proportion Var  0.071  0.071  0.071  0.071  0.071  0.071  0.071  0.071  0.071
Cumulative Var  0.071  0.143  0.214  0.286  0.357  0.429  0.500  0.571  0.643
               Comp.10 Comp.11 Comp.12 Comp.13 Comp.14
SS loadings      1.000   1.000   1.000   1.000   1.000
Proportion Var   0.071   0.071   0.071   0.071   0.071
Cumulative Var   0.714   0.786   0.857   0.929   1.000

2.3.4 Convolution

As opposed to the above image processing routines, which are all forms of spectral enhancement, convolution is a form of spatial enhancement. For example, a low pass filter can be used to reduce local differences in pixel values and often entails smoothing or averaging values. In contrast, high pass filters are used to enhance differences. An example is an edge detection filter.

Convolution operations are applied by passing a local moving window or kernel of values over a raster grid. The values stored in the filter are configured to obtain a desired result. For example, the Sobel filters demonstrated in the code are used to detect or enhance vertical and horizontal edges in the image. Convolution filters can be defined as R matrices then applied to image data using the focal() function from terra. The fun parameter indicates how the results at each cell location are to be aggregated. In our example, the sum is returned at each cell location.

Convolution operations may result in decreasing the spatial extent of the output since edge rows and columns do not have a full set of neighbors over which to perform the convolution. Due to such issues, we recommend using input data that cover an extent larger than the study area of interest.

lOff <- rast(str_glue("{folderPath}ls8_3_11_2024_SR.tif"))
lOn <- rast(str_glue("{folderPath}ls9_9_11_2024_SR.tif"))

names(lOff) <- c("Edge", 
                 "Blue", 
                 "Green", 
                 "Red", 
                 "NIR", 
                 "SWIR1", 
                 "SWIR2")
names(lOn) <- c("Edge", 
                "Blue", 
                "Green", 
                "Red", 
                "NIR", 
                "SWIR1", 
                "SWIR2")

gy <- c(2, 2, 4, 2, 2, 
        1, 1, 2, 1, 1, 
        0, 0, 0, 0, 0, 
        -1, -1, -2, -1, -1, 
        -2, -2, -4, -2, -2) 
gx <- c(2, 1, 0, -1, -2, 
        2, 1, 0, -1, -2, 
        4, 2, 0, -2, -4, 
        2, 1, 0, -1, -2, 
        2, 1, 0, -1, -2, 
        2, 1, 0, -1, -2)

gx_m <- matrix(gx, nrow=5, ncol=5, byrow=TRUE)
gx_m
     [,1] [,2] [,3] [,4] [,5]
[1,]    2    1    0   -1   -2
[2,]    2    1    0   -1   -2
[3,]    4    2    0   -2   -4
[4,]    2    1    0   -1   -2
[5,]    2    1    0   -1   -2
gy_m <- matrix(gy, nrow=5, ncol=5, byrow=TRUE)
gy_m
     [,1] [,2] [,3] [,4] [,5]
[1,]    2    2    4    2    2
[2,]    1    1    2    1    1
[3,]    0    0    0    0    0
[4,]   -1   -1   -2   -1   -1
[5,]   -2   -2   -4   -2   -2
nir_edgex <- terra::focal(lOff$NIR, w=gx_m, fun=sum, na.rm=TRUE) 
nir_edgey <- terra::focal(lOff$NIR, w=gy_m, fun=sum, na.rm=TRUE) 
tm_shape(nir_edgex)+
    tm_raster(col.scale = tm_scale_intervals(n=7,
                                          style="quantile",
                                          values = "brewer.greys"))

tm_shape(nir_edgey)+
      tm_raster(col.scale = tm_scale_intervals(n=7,
                                          style="quantile",
                                          values = "brewer.greys"))

2.4 Digital Terrain Analysis

In this last section of the chapter, we explore variables that can be derived from a DTM. These surface are commonly termed digital terrain variables or land surface parameters (LSPs). For our example data, we read in a DTM provided by the USGS 3DEP. In order to speed up the computation, the DTM is degraded from a 1 m to a 5 m spatial resolution by calculating the average of the 25 cells that fall within the new larger cells.

dtm <- rast(str_glue("{folderPath}dtm.tif")) |> 
  aggregate(fact=5, fun=mean)

tm_shape(dtm)+
        tm_raster(col.scale = tm_scale_continuous(values="-brewer.greys"))

2.4.1 Common Land Surface Parameters

Table 2.2 summarizes some LSPs that can be calculated using R. The terra package can be used to calculate topographic slope and aspect while the spatialEco package adds additional functionality. If you are interested in reading more about the use of LSPs in remote sensing and spatial predictive modeling, we recommend the following papers:

Franklin, S.E., 2020. Interpretation and use of geomorphometry in remote sensing: a guide and review of integrated applications. International Journal of Remote Sensing, 41(19), pp.7700-7733.

Maxwell, A.E. and Shobe, C.M., 2022. Land-surface parameters for spatial predictive mapping and modeling. Earth-Science Reviews, 226, p.103944.

The geodl package can calculate several LSPs use fast GPU-based computation by leveraging the torch package. We will discuss this in the third section of the text.

The terra package provides the terrain() function that can calculate topographic slope, topographic aspect, the topographic position index (TPI), the topographic roughness index (TRI), and several other measures. Below, we have used this function to calculate slope and aspect in radian units. Radian units are used, as opposed to degrees, since the shade() function from terra, which is used to calculate a hillshade in the same code block, expects radian units. We specifically calculate the hillshade using a sun angle of 45-degrees and a sun azimuth or direction of 315-degrees (i.e., northwest). The hillshade is then plotted using tmap.

library(readr)
library(gt)
#| eval: true
#| echo: false
#| include: true
#| error: false
#| message: false
#| warning: false
#| results: false

dTypes <- read_csv("tables/terrainDerivatives.csv")
Rows: 13 Columns: 4
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (4): Land Surface Parameter, Description, Package, Function

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
dTypes |> gt() |>
  fmt_markdown(columns=c(Package, Function)) |>
   tab_style(
    style = list(
      cell_text(align="center", weight="bold")
    ),
    locations = cells_column_labels()) |>
    tab_style(
      style = list(
      cell_text(align="center")),
    locations = cells_body(-Description)) |>
  tab_caption(caption=md("**Table 2.2.** Land surface parameters (LSPs)."))
Table 2.2. Land surface parameters (LSPs).
Land Surface Parameter Description Package Function
Topographic Slope Steepness

terra

terrain()

Topographic Aspect Slope orientation

terra

terrain()

Hillshade Shaded relief surface

terra

shade()

Profile Curvature Curvature in the direction of maximum slope

spatialEco

curvature()

Planform Curvature Curvature in the direction perpendicular to the direction of maximum slope

spatialEco

curvature()

Total Curvature Sigma of profile and planform curvature

spatialEco

curvature()

Topographic Position Index (TPI) (Elev - Mean); relative topographic position

spatialEco

tpi()

Topographic Roughness Index (TRI) Square root of standard deviation of slope in local window; measure of rugosity

spatialEco

tri()

Topographic Dissection Index (TDI) (Elev - Min)/(Max - Min); measure of incision

spatialEco

dissection()

Surface Area Ratio (SAR) Resolution^2 * cos( (degrees(slope) * (pi / 180); transform of steepness

spatialEco

sar()

Surface Relief Ratio (Mean - Min)/(Max - Min); measure of rugosity

spatialEco

srr()

Heat Load Index (HLI) 0 (coolest) to 1(hottest) based on aspect, slope, and latitude

spatialEco

hli()

Solar-Radiation Aspect Index (TRASP) (1 - cos((pi/180)(aspect in degrees-30) ) / 2; aspect transformation

spatialEco

trasp()

slp <- terrain(dtm, v ="slope", unit="radians") 
asp <- terrain(dtm, v ="aspect", unit="radians") 

hs <- shade(slp, asp, angle=45, direction=315)

tm_shape(hs)+
        tm_raster(col.scale = tm_scale_continuous(values="-brewer.greys"))

A multidirectional hillshade can be calculated by averaging hillshades created using multiple sun positions. In our example, we create hillshades using north, northwest, west, and southeast illuminations. We then average these but with double-weighting applied to the northwest illumination.

hsN <- shade(slp, asp, angle=45, direction=0)
hsNW <- shade(slp, asp, angle=45, direction=315)
hsW <- shade(slp, asp, angle=45, direction=270)
hsSE <- shade(slp, asp, angle=45, direction=135)

hsMD <- (hsN + (2*hsNW) + hsW + hsSE)/5

tm_shape(hsMD)+
        tm_raster(col.scale = tm_scale_continuous(values="-brewer.greys"))

2.4.2 Other Land Surface Parameters

The spatialEco package provides additional functions for calculating land surface parameters including surface curvatures, the topographic dissection index (TDI), the heat load index (HLI), the surface area ratio (SAR), the surface relief ratio (SRR), the topographic position index (TPI), the topographic roughness index (TRI), and the topographic radiation aspect index after Roberts and Cooper (1989) (TRASP). Several of these surfaces are generated and visualized below. Some of the measures allow for defining the size of the moving window. Larger values generally yield more generalized results.

crvT <- curvature(dtm, 
                  type="total")

tm_shape(crvT)+
          tm_raster(col.scale = tm_scale_intervals(n=7,
                                                   style="quantile",
                                                   values="-brewer.greys"),
                    col.legend = tm_legend(title="Total Curvature"))

tdi <- dissection(dtm, s=7)

tm_shape(tdi)+
          tm_raster(col.scale = tm_scale_continuous(values="-brewer.greys"),
                    col.legend = tm_legend(title="TDI"))

hli <- hli(dtm)

tm_shape(hli)+
          tm_raster(col.scale = tm_scale_intervals(n=7,
                                                   style="quantile",
                                                   values="-brewer.greys"),
                    col.legend = tm_legend(title="HLI"))

sar <- sar(dtm)
tm_shape(sar)+
        tm_raster(col.scale = tm_scale_intervals(n=7,
                                                   style="quantile",
                                                   values="-brewer.greys"),
                  col.legend = tm_legend(title="SAR"))

srr <- srr(dtm, s=7)
tm_shape(srr)+
        tm_raster(col.scale = tm_scale_continuous(values="-brewer.greys"),
                  col.legend = tm_legend(title="SRR"))

tpi <- tpi(dtm, 
           scale=35, 
           win="circle")

tm_shape(tpi)+
        tm_raster(col.scale = tm_scale_continuous(values="-brewer.greys"),
                  col.legend = tm_legend(title="TPI"))

trasp <- trasp(dtm)

tm_shape(trasp)+
        tm_raster(col.scale = tm_scale_continuous(values="-brewer.greys"),
                  col.legend = tm_legend(title="TRASP"))

As an aside, you may be interested in removing extreme values from the generated LSPs or truncating the range of values maintained in the output. This can be accomplished using the clamp() function from terra. In the example below, we truncate the range of TPI values to be between -10 and 10.

tpi2 <- clamp(tpi, -10, 10, values=TRUE)
tm_shape(tpi2)+
        tm_raster(col.scale = tm_scale_continuous(values="-brewer.greys"),
                  col.legend = tm_legend(title="TPI"))

2.4.3 Customizing Moving Windows

The multiscaleDTM package allows for defining custom moving windows with varying sizes and shapes. These user-defined moving windows can be used to generate LSPs. In the final example below, we create an annulus moving window with a 10 m inner and 25 m outer radius at a 5 m spatial resolution to match the input DTM data. We then manually calculate the TPI using this custom moving window. Specifically, the mean of the cells in the moving window are subtracted from the center cell value. Next, we clamp the range from -10 to 10 and rescale the TPI to values between 0 and 1.

fAnnulus <- MultiscaleDTM::annulus_window(radius = c(10,25), 
                                          unit = "map", 
                                          resolution = 5)
tpiA <- dtm - terra::focal(dtm, 
                           fAnnulus, 
                           "mean", 
                           na.rm = TRUE)

tpiA2 <- terra::clamp(tpiA, 
                      -10, 
                      10, 
                      values = TRUE)
tpiA3 <- (tpiA2 - (-10))/(10-(-10))

tm_shape(tpiA3)+
          tm_raster(col.scale = tm_scale_continuous(values="-brewer.greys"),
                    col.legend = tm_legend(title="TPI"))

2.5 Concluding Remarks

The goal of the first two chapters of this text was to introduce you to general raster processing in R with a focus on the terra package. This is important since input predictor variables for spatial predictive modeling tasks are commonly represented as raster grids. Further, we find that we spend a lot of time obtaining and preprocessing raster data. In other words, this is an important skill set for geospatial scientists and modelers. The next chapter wraps up our discussion of preprocessing with a more general discussion of feature engineering using the parsnips package.

2.6 Questions

  1. Based on the spectral reflectance properties of vegetation, explain why the normalized difference vegetation index (NDVI) is valuable for assessing for the presence of vegetation and vegetation health.
  2. Based on the spectral reflectance properties of snow, explain why the normalized difference snow index (NDSI) is valuable for assessing for the presence of snow.
  3. When performing a principal component analysis (PCA), explain the difference between eigenvalues and eigenvectors.
  4. Explain why principal component analysis is a linear transformation of the original variables.
  5. Explain the difference between profile and planform curvature.
  6. Explain how topographic slope is calculated from a digital terrain model (DTM) using a 3x3 cell moving window.
  7. Topographic aspect in radian units can be converted to a measure of “northwardness” as the cosine of the aspect while it can be converted to a measure of “eastwardness” as the sine of the aspect. Mathematically, explain why using sine and cosine transformations can accomplish these desired transformations.
  8. Explain the equation used to calculate a hillshade from a slope and aspect surfaces.

2.7 Exercises

You have been provided with the following files in the exercise folder for the chapter. All raster grids/images have a 10 m spatial resolution. All layers are projected into WGS84 UTM Zone 18N.

  1. loff.tif: Sentinel-2 Multispectral Instrument (MSI) leaf-off image for portion of central Pennsylvania
  2. lon.tif: Sentinel-2 Multispectral Instrument (MSI) leaf-on image for portion of central Pennsylvania
  3. dtm2.tif: digital terrain model (DTM) with elevation in meters
  4. chpt2.gpkg/extent2: study area extent in central Pennsylvania
  5. chpt2.gpkg/pnts2: plot locations

Complete the following tasks:

  1. Read in all of the files
  2. Use tmap to display the extent and point features
  3. Rename the leaf-off image bands as follows: “offBlue”, “offGreen”, “offRed”, “offNIR”, “offSWIR1”, “offSWIR2”
  4. Rename the leaf-on image bands as follows: “onBlue”, “onGreen”, “onRed”, “onNIR”, “onSWIR1”, “onSWIR2”
  5. Visualize the two images using terra as false color composites
  6. Use tmap to visualize the DTM data
  7. Align the two images to the DTM data using cubic convolution
  8. Calculate the normalized difference vegetation index (NDVI) separately for both images
  9. From the DTM, calculate topographic slope
  10. Stack the leaf-off image bands, leaf-on image bands, leaf-off NDVI, leaf-on NDVI, elevation, and slope into a single object
  11. Make sure all bands have meaningful names
  12. Extract all the band values at the point locations
  13. Save a copy of the point layer to disk with all the raster layer values include in the attribute table