The goals and key concepts of this lab are:
We’ll follow parts of the R walkthrough in F+F (with some modifications as noted below) starting with the Examples in R, section 3.3.
Note that you’ll have to install some packages to follow along with the code.
Check the updated and verified Chapter 3 code in Moodle for the complete list of packages needed.
First, your life will be easier if you create a variable to hold the path pointing to the book’s data:
require(here)
data_dir = here(
"data",
"Fletcher_Fortin-2018-Supporting_Files",
"data")
Now you should be able to read in the NLCD example data:
require(raster)
nlcd = raster(file.path(
data_dir, "nlcd2011gv2sr"
))
Now you’re ready to proceed with section 3.3.3 of the book. Consult the updated chapter 3 code in Moodle, as well as the code in the book.
When you read and factorize the data, you should be able to create the following plot with levelplot()
from package rasterVis
:
Follow the instructions to create the forest layer raster mask:
You can do the patch delineation using clump
as in the book and plot your results:
## Loading required namespace: igraph
The packages SDMTools
, which the book uses, is no longer available. Check the updated code script for chapter 3 in Moodle for alternative options.
We’ll be using functions in the landscapemetrics
package, so make sure you install it!
landscapemetrics
has a lot of functions available for characterizing patch-level features. You can get a list of the available functions with the list_lsm()
function:
list_lsm(level = "patch")
## metric name type level function_name
## 1 area patch area area and edge metric patch lsm_p_area
## 2 cai core area index core area metric patch lsm_p_cai
## 3 circle related circumscribing circle shape metric patch lsm_p_circle
## 4 contig contiguity index shape metric patch lsm_p_contig
## 5 core core area core area metric patch lsm_p_core
## 6 enn euclidean nearest neighbor distance aggregation metric patch lsm_p_enn
## 7 frac fractal dimension index shape metric patch lsm_p_frac
## 8 gyrate radius of gyration area and edge metric patch lsm_p_gyrate
## 9 ncore number of core areas core area metric patch lsm_p_ncore
## 10 para perimeter-area ratio shape metric patch lsm_p_para
## 11 perim patch perimeter area and edge metric patch lsm_p_perim
## 12 shape shape index shape metric patch lsm_p_shape
Let’s try out the patch area stats using the 4- and 8-neighbor rules:
patches_area_4 = lsm_p_area(nlcd.forest, directions = 4)
patches_area_8 = lsm_p_area(nlcd.forest, directions = 8)
You can plot histograms, noting that the area is held in the value
column:
par(mfrow = c(1, 2))
hist(
patches_area_8$value,
main = "8-neighbor rule", xlab = "Patch Area")
hist(
patches_area_4$value,
main = "4-neighbor rule", xlab = "Patch Area")
Those look pretty awful, but perhaps doing a log transformation on the areas would be more informative. Code not shown, you should try to re-create these on your own.
It seems that with the 4-neighbor rule, you get many more smaller patches. Can you reason out why this is?
You’ll use the ideas from the F+F walkthrough to contrast agricultural landscapes in two cranberry-producing regions in Massachusetts and Wisconsin.
Navigate to https://www.mrlc.gov/viewer/ to view and download NLCD data.
It may also be useful to look at some aerial imagery of the two regions. Google Earth is a convenient tool (Google Maps is difficult to access on campus).
You should explore the regions to get a feel for what the cranberry bogs look like from above.
Next, you’ll need to retrieve some NLCD data:
For both regions, make sure to download the 2019 Land Cover data
In the interactive map, locate the Tomah, WI region. It’s centered around 44.01 degrees North and 90.44 degrees West.
Trace out a square-ish rectangle of about 100km2.
Repeat the procedure for the region west of Carver, MA., centered near 41.89 degrees North and 70.82 degrees West.
When the data are ready for download, you’ll get an email. Be sure to unzip them into your data subfolder and rename the directories with more descriptive names! I used tomah_bogs
and carver_bogs
.
You’ll need to look inside of the unzipped directories to get the full names of raster files.
They will be distributed as .tiff
files (tagged image format file), a.k.a. GeoTIFF. Note that there will be two different tiff files (with associated metadata files):
Note also that the filenames will be long and horrible (that’s why you renamed the containing directories).
To read the Carver, I used the following code on my computer:
require(raster)
carver = raster(here(
"data", "carver_bogs",
"NLCD_2019_Land_Cover_L48_20210604_ahKJ7ZCAqgiHxH5xKCUr.tiff"))
NOTE: the exact filenames will be different for your downloaded data.
We’ll need to factorize our tomah and carver rasters (see the book example in the walkthrough for how to do this using as.factor()
)
I’ve created new objects called tomah_f
and carver_f
to hold the factorized versions of the rasters (code not shown).
Once we’ve done that, we need some R trickery to set the factor level names.
You may have noticed the NLCD_landcover_legen...
csv files. You should open them up and take a look at the contents. They contain the names of the factor levels and the corresponding numeric codes.
We would like to associate the landcover classes with the cell values in the raster, just like F+F did in their walkthrough.
To do so, we can read in the levels and use a trick to keep only the entries that are present in the data.
First, read the file and notice that there are many cell codes (the Value column) that don’t have corresponding level names (the Legend column):
carver_levels = read.csv(
here(
"data", "carver_bogs",
"NLCD_landcover_legend_2018_12_17_ahKJ7ZCAqgiHxH5xKCUr.csv"),
na.strings = "")
head(carver_levels)
## Value Legend
## 1 0 Unclassified
## 2 1 <NA>
## 3 2 <NA>
## 4 3 <NA>
## 5 4 <NA>
## 6 5 <NA>
When you looked in the csv file, you probably noticed that in the Legend
column, there were lots of missing entries. We’d like these to be coded as missing data by R, i.e. NA
values
The na.strings = ""
argument is the key: it specifies that any empty cells should be read as NA
rather than empty strings.
Here’s the trick: we can use the complete.cases()
function to remove all the rows that have a NA
in the second column.
complete.cases()
is quite a handy function for filtering out unused rows in a data.frame.
# Returns a TRUE for each row that has entries in both columns:
complete.cases(carver_levels)
## [1] TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE FALSE FALSE FALSE FALSE
## [18] FALSE FALSE FALSE FALSE TRUE TRUE TRUE TRUE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE
## [35] FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [52] FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [69] FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE FALSE FALSE
## [86] FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE TRUE
# Use the Boolean vector to do a logical subsetting operation via an intermediate vector of row indices (created using the which() function:
carver_lvl_rows =
which(complete.cases(carver_levels))
Now we’re ready to set the factor levels, just like they did in the book!
levels(carver_f) = carver_levels[carver_lvl_rows, ]
## Error in .checkLevels(levels(x)[[1]], value): the first column name of the raster attributes (factors) data.frame should be "ID"
Or not…
Take at the column names of carver_levels
names(carver_levels)
## [1] "Value" "Legend"
You’ll have to change the second column name (code not shown).
Once you’ve made the change, you can run this again:
levels(carver_f) = carver_levels[carver_lvl_rows, ]
## Warning in .checkLevels(levels(x)[[1]], value): the number of rows in the raster attributes (factors)
## data.frame is unexpected
## Warning in sort(newv[, 1]) == sort(old[, 1]): longer object length is not a multiple of shorter object
## length
## Warning in .checkLevels(levels(x)[[1]], value): the values in the "ID" column in the raster attributes
## (factors) data.frame have changed
When you’ve got your code correct, you should see the correct table of levels:
levels(carver_f)
## [[1]]
## ID Legend
## 1 0 Unclassified
## 12 11 Open Water
## 13 12 Perennial Snow/Ice
## 22 21 Developed, Open Space
## 23 22 Developed, Low Intensity
## 24 23 Developed, Medium Intensity
## 25 24 Developed, High Intensity
## 32 31 Barren Land
## 42 41 Deciduous Forest
## 43 42 Evergreen Forest
## 44 43 Mixed Forest
## 53 52 Shrub/Scrub
## 72 71 Herbaceuous
## 82 81 Hay/Pasture
## 83 82 Cultivated Crops
## 91 90 Woody Wetlands
## 96 95 Emergent Herbaceuous Wetlands
Creating a nice-looking levelplot of the cranberry bog data isn’t as easy as for the nlcd data in the F+F examples.
I’ve included the complete code I used to create this plot of the Tomah region landcover classes.
You should use this code as a template to create a similar plot for the Carver MA data.
# Tomah
tomah = raster(here(
"data", "tomah_bogs",
"NLCD_2019_Land_Cover_L48_20210604_ZOZXa4B4SSSqU6Qj6lBf.tiff"))
# Factorizing removes the color information from the "levels" attribute.
tomah_f = as.factor(tomah)
# Read the level legend data
tomah_levels = read.csv(
here(
"data", "tomah_bogs",
"NLCD_landcover_legend_2018_12_17_ZOZXa4B4SSSqU6Qj6lBf.csv"),
na.strings = "")
# Set the level names
names(tomah_levels) = c("ID", "Landcover")
# Get the row numbers of the levels used in the data:
tomah_lvl_rows = which(complete.cases(tomah_levels))
# The colortable() function can retrieve the color information distributed with the raster metadata.
# Use the row indices to extract only the colors you need.
col_tomah = colortable(tomah_f)[tomah_lvl_rows]
# set the landcover type levels for Tomah
# Note that the labels must be the second column for levelplot() to find them.
levels(tomah_f) = tomah_levels[tomah_lvl_rows, ]
# Create a plot with the original colors and correctly labeled landcover types
require(rasterVis)
levelplot(
tomah_f,
col.regions = col_tomah,
main = "Landcover Classes in Tomah, WI",
scales = list(draw = F))
We want to reclassify the Carver and Tomah landscapes into binary rasters showing agriculture/non-agriculture.
As a first step, examine the levels table again:
levels(carver_f)[[1]]
## ID Legend
## 1 0 Unclassified
## 12 11 Open Water
## 13 12 Perennial Snow/Ice
## 22 21 Developed, Open Space
## 23 22 Developed, Low Intensity
## 24 23 Developed, Medium Intensity
## 25 24 Developed, High Intensity
## 32 31 Barren Land
## 42 41 Deciduous Forest
## 43 42 Evergreen Forest
## 44 43 Mixed Forest
## 53 52 Shrub/Scrub
## 72 71 Herbaceuous
## 82 81 Hay/Pasture
## 83 82 Cultivated Crops
## 91 90 Woody Wetlands
## 96 95 Emergent Herbaceuous Wetlands
Note that the double brackets are needed because the object returned by levels(carver_f)
is a list:
class(levels(carver_f))
## [1] "list"
and we want the dataframe that it contains:
class(levels(carver_f)[[1]])
## [1] "data.frame"
It looks like the level of interest is “Cultivated Crops”.
We can use the reclassify
function to create our binary raster, we just need a reclassification raster first.
Examine the help entry for reclassify
(it’s not too difficult compared to many R help entries). Based on the help entry, ti seems like we want to use the 2-column reclassification matrix option. This might seem a little bit like the raster reclass procedure in Arc!
Per the help entry:
A
2-column matrix
represents (“is”, “becomes”) which can be useful for integer values. In that case, the right argument is automatically set to NA
In plain English, that means we want the “becomes” column to hold values of zero in all positions corresponding to classes we don’t care about.
This sounds complicated, but the syntax to build the reclassification matrix is easy:
cbind(
levels(carver_f)[[1]][, 1],
levels(carver_f)[[1]][, 2] == "Cultivated Crops")
## [,1] [,2]
## [1,] 0 0
## [2,] 11 0
## [3,] 12 0
## [4,] 21 0
## [5,] 22 0
## [6,] 23 0
## [7,] 24 0
## [8,] 31 0
## [9,] 41 0
## [10,] 42 0
## [11,] 43 0
## [12,] 52 0
## [13,] 71 0
## [14,] 81 0
## [15,] 82 1
## [16,] 90 0
## [17,] 95 0
You should save the result in a matrix called reclass_cultivated
Now you can use it to reclassify both the Tomah and Carver rasters. You should factor them too and give the levels descriptive names, so that they will work with levelplot()
# tomah_reclass = reclassify(tomah_f, reclass_cultivated)
# carver_reclass = reclassify(carver_f, reclass_cultivated)
tomah_reclass = as.factor(reclassify(tomah_f, reclass_cultivated))
carver_reclass = as.factor(reclassify(carver_f, reclass_cultivated))
levels(carver_reclass)[[1]]$Cover = c("Other", "Cultivated")
levels(tomah_reclass)[[1]]$Cover = c("Other", "Cultivated")
When you are successful, your rasters should look something like these:
levelplot(
tomah_reclass,
main = "Mike's plot of Tomah, WI",
col.regions = c("white", "forestgreen"),
# This suppresses the axes:
scales = list(draw = F))
levelplot(
carver_reclass,
main = "Mike's plot of Carver, MA",
col.regions = c("white", "forestgreen"),
# This suppresses the axes:
scales = list(draw = F))
I called my rasters tomah_reclass
and carver_reclass
That was a lot of work to get our rasters ready for analysis! The good news is that now we are ready to proceed.
Finally, let’s calculate some patch-level statistics and see how the agricultural patches in the two regions differ.
Remember our friend from landscapemetrics
:
list_lsm(level = "patch")
## # A tibble: 12 x 5
## metric name type level function_name
## <chr> <chr> <chr> <chr> <chr>
## 1 area patch area area and edge metric patch lsm_p_area
## 2 cai core area index core area metric patch lsm_p_cai
## 3 circle related circumscribing circle shape metric patch lsm_p_circle
## 4 contig contiguity index shape metric patch lsm_p_contig
## 5 core core area core area metric patch lsm_p_core
## 6 enn euclidean nearest neighbor distance aggregation metric patch lsm_p_enn
## 7 frac fractal dimension index shape metric patch lsm_p_frac
## 8 gyrate radius of gyration area and edge metric patch lsm_p_gyrate
## 9 ncore number of core areas core area metric patch lsm_p_ncore
## 10 para perimeter-area ratio shape metric patch lsm_p_para
## 11 perim patch perimeter area and edge metric patch lsm_p_perim
## 12 shape shape index shape metric patch lsm_p_shape
We can use these functions to characterize the differences between agricultural patches in the two regions
Examine the maps of your Tomah and Carver binary agricultural landscapes.
Explore some of the patch statistics for your Tomah and Carver binary agricultural landscapes.
Choose two patch-level metrics.
Hint: use list_lsm(level = "patch")
to print a list of available metrics.