0

I need to clip the national landcover database raster to a watershed boundary vector. This post works for some of the watersheds I am working with, but not all of them. For the ones that it does not work with, the issue appears to be that the mask step results in an entirely empty raster. For sake of brevity of this post, I am not including an example of two watersheds where this works and does not work. The example below is for a watershed that did not work in the workflow in the post mentioned above.

My solution for the watersheds that the mask step does not work for is to instead reproject the raster to the vector's CRS, then perform the clip and mask. To do so, I need to use terra::rast to get a SpatRaster so I can use terra::project. While this works to clip and mask the raster, a new issue has come up, where terra::rast is changing the cell values of the raster from numeric (i.e. the NLCD ID, which I want) to factor (i.e. the NLCD Class), which would be okay BUT the strings are different...e.g. the NLCD legend has ID 81 as "Pasture/Hay" but this gets changed to "Hay/Pasture" when using rast. Thus, this prevents me from using left_join correctly.

My end goal is to reclassify (i.e. simplify to 8 classes) the NLCD using a custom legend and then calculate the percent land for each reclassified land use.

Here is my reproducible example:

library(streamstats) # devtools::install_github("markwh/streamstats")
library(FedData)
library(terra)
library(sf)
library(tidyverse)

# create the custom reclassified legend (needed below):

legend<-pal_nlcd()%>%mutate(ID2=c(1,2,3,3,3,3,2,4,4,4,4,4,5,5,2,2,6,7,8,8))
legend_2<-data.frame(ID2 = unique(legend$ID2))%>% mutate(Class2 = c("Open Water", "Other", "Developed", "Forest", "Grassland", "Pasture/Hay", "Cultivated Crops", "Wetlands"))
legend<-left_join(legend, legend_2, by = 'ID2')

# download the watershed boundary and convert to sf:

setTimeout(400)
ws1 <- delineateWatershed(xlocation = -73.65167, ylocation = 42.93556, crs = 4326,includeparameters = "false") # takes a minute to run
ws1_sp<-toSp(ws1, what = 'boundary') # also takes a minute to run
ws1_sf<-st_as_sf(ws1_sp)

# download the NLCD raster data:

nlcd<-get_nlcd(template = ws1_sf,label = 'HUDSON RIVER AT STILLWATER NY',year = 2016)

# convert to SpatRaster, reproject to vector's crs, then clip and mask:

nlcd_rast<-rast(nlcd)
nlcd_rast<-project(nlcd_rast, crs(ws1_sf))
nlcd_rast<-crop(nlcd_rast, raster::extent(ws1_sf))
nlcd_rast<-mask(nlcd_rast, ws1_sf)

# calculate the percent land of the reclassified land uses:

nlcd_df<-as.data.frame(nlcd_rast, xy = FALSE)%>%
  drop_na()%>%
  rename(ID = NLCD.Land.Cover.Class)%>%
  left_join(.,legend[,c(1,2,5,6)], by = c('ID'='Class'))%>%
  group_by(Class2)%>%
  summarise(n = n())%>%
  mutate(pland = round(n / sum(n), 4))%>%
  arrange(Class2)%>%
  dplyr::select(c(1,3))%>%
  complete(., Class2 = unique(legend$Class2), fill = list(pland = NA))  

What I am trying to understand is 1) why terra::rast actually changes the rasters cell values in the first place:

class(as.data.frame(nlcd, xy = FALSE)$NLCD.Land.Cover.Class) # numeric
class(as.data.frame(nlcd_rast, xy = FALSE)$NLCD.Land.Cover.Class) # factor

and 2) why are the string values changed from the NLCD legend (which comes from the function pal_nlcd() used above)? If the class names were the same after using terra::rast as the NLCD legend, the left_join would work by 'Class', but this is not the case since I am missing some classes in my final result, nlcd_df. Here you can see how Pasture/Hay in the NLCD legend is switched to Hay/Pasture when using terra::rast:

unique(as.character(as.data.frame(nlcd_rast, xy = FALSE)$NLCD.Land.Cover.Class))
 [1] "Evergreen Forest"             "Herbaceous"                  
 [3] "Open Water"                   "Woody Wetlands"              
 [5] "Emergent Herbaceous Wetlands" "Shrub/Scrub"                 
 [7] "Deciduous Forest"             "Barren Land"                 
 [9] "Mixed Forest"                 "Developed, Open Space"       
[11] "Developed, Low Intensity"     "Developed, Medium Intensity" 
[13] "Developed, High Intensity"    "Hay/Pasture"                 
[15] "Cultivated Crops"            
> legend$Class
 [1] "Open Water"                   "Perennial Ice/Snow"          
 [3] "Developed, Open Space"        "Developed, Low Intensity"    
 [5] "Developed, Medium Intensity"  "Developed High Intensity"    
 [7] "Barren Land (Rock/Sand/Clay)" "Deciduous Forest"            
 [9] "Evergreen Forest"             "Mixed Forest"                
[11] "Dwarf Scrub"                  "Shrub/Scrub"                 
[13] "Grassland/Herbaceous"         "Sedge/Herbaceous"            
[15] "Lichens"                      "Moss"                        
[17] "Pasture/Hay"                  "Cultivated Crops"            
[19] "Woody Wetlands"               "Emergent Herbaceous Wetlands"

Also if you are having trouble installing streamstats you may need to try these steps first:

# step 1 - restart R

# Session->Restart R

# step 2 - add Rtools to PATH

old_path <- Sys.getenv("PATH")

Sys.setenv(PATH = paste(old_path, "C:\\rtools40\\usr\\bin", sep = ";"))

# now try:

devtools::install_github("markwh/streamstats")

library(streamstats)
ruggntub
  • 95
  • 5

1 Answers1

0

I am not quite sure what you are asking, but here is how you can compute proportions of land cover classes.

library(FedData)
library(sf)
library(terra)

xy <- matrix(c(-73.9, 42.9, -73.9, 43, -73.92, 42.9), ncol=2, byrow=TRUE)
v <- vect(xy, "polygon", crs="+proj=lonlat")

nlcd <- get_nlcd(template=v,label = 'HUDSON RIVER AT STILLWATER NY',year = 2016)
nlcd <- rast(nlcd)

vr <- project(v, crs(nlcd))
nlcd2 <- crop(nlcd, vr, mask=TRUE)

f2 = freq(nlcd2)
f2$perc <- round(100 * f2$count / sum(f2$count), 1)
f2
#   layer                        value count perc
#1      1                   Open Water     4  0.0
#2      1        Developed, Open Space  1489 14.1
#3      1     Developed, Low Intensity  1066 10.1
#4      1  Developed, Medium Intensity   251  2.4
#5      1     Developed High Intensity    60  0.6
#6      1 Barren Land (Rock/Sand/Clay)    20  0.2
#7      1             Deciduous Forest   385  3.7
#8      1             Evergreen Forest   174  1.7
#9      1                 Mixed Forest  2228 21.1
#10     1                  Shrub/Scrub    29  0.3
#11     1         Grassland/Herbaceous    34  0.3
#12     1                  Pasture/Hay  2812 26.7
#13     1             Cultivated Crops   235  2.2
#14     1               Woody Wetlands  1713 16.3
#15     1 Emergent Herbaceous Wetlands    36  0.3

Note that I project the vector data. It is a mistake to project the raster data (it is inefficient and leads to data quality loss).

Robert Hijmans
  • 40,301
  • 4
  • 55
  • 63
  • I appreciate the response. I asked this question to better understand the behavior of `terra::rast` because I need to reclassify the NLCD classes. Also for some reason when I run the exact code you posted, it still is Hay/Pasture instead of Pasture/Hay, which is a big deal for the reclassification via `left_join`. I understand there are ways of working around this specifically, but I am still very interested why the cell values of `rast(RasterLayer)` are different than the original `RasterLayer`. I can't find it anywhere in `?rast` why this would be the case. – ruggntub Jun 24 '23 at 02:24
  • I do not see any of that. If you see "Hay/Pasture" instead of "Pasture/Hay" you probably have some old or other data in the same download folder that creates a problem – Robert Hijmans Jun 24 '23 at 05:28
  • Thank you - uninstalling and reinstalling FedData solved my issues. – ruggntub Jul 17 '23 at 21:10