2018-11-13-a-few-mapping-tips.utf8.md

Some More Watershed Mapping Tricks with sf and USGS code

Just to make sure that I post something at least annually, I’m jump-starting my posts again. It’s been quite a journey to finish my PhD, and I’m still re-calibrating/re-adjusting. Hopefully I’ll be posting a bit more regularly, instead of annually or semi-annually. For the half-dozen folks that actually read this, if you have additional topics you’d be interested in seeing/reading, please let me know!

But let’s get on to content before this turns into a food blog where the recipe is at the very bottom after 100 pictures of diced tomatoes and artsy grains.

Mapping with sf

About a year ago, I wrote a series of posts on using the sf and riverdist packages (among others) to do some stream analysis and make stream maps. Since that time, I feel like the capabilities and content for spatial work in R has exploded. There have been many excellent examples, and kudos to the folks tweeting, posting, and sharing all the code! If you haven’t seen some of the really fantastic visualizations and data wrangling that the USGS OWI folks have been putting out (along with the code!), you’re missing out. Same goes for many others who have posted some great stuff. Here’s just a few interesting examples:

Main objectives of the post:

Assume all of these will be using sf:

  • How to grab NHD Streamline data using any spatial polygon
  • How to grab waterbodies for a given spatial polgyon
  • How to quickly add these to a interactive map in mapview or plot with ggplot
  • How to modify a polygon (e.g., a species range) with the smoothr package

Packages

The main packages I’ll be using for this blog are as follows:

  • dplyr and ggplot2 for wrangling and plotting
  • cowplot: an amazingly useful package for formatting/manipulating/making multi plots
  • sf: core package for working with spatial data
  • mapview: easy way to make html maps with sf objects
  • smoothr: cool package for simplifying or smoothing polygons in R

Let’s load the packages:

# load libraries
library(dplyr) # data munging and piping
library(ggplot2) # plotting
library(sf) # spatial everything
library(mapview) # html mapping
library(smoothr) # smoothing spatial objects
library(purrr); # wrangling lists and such
library(ggrepel) # nice labels in ggplot
#library(sbtools)
#library(dataRetrieval)

Get a HUC Watershed Boundary

First, let’s use one of the NHD HUC boundaries that we can use as our background for grabbing other data. I’m just using something I have handy, which is HUC8 boundaries for the American/Yuba/Bear watersheds. Alternatively, thanks to the awesome folks over at USGS OWI (David Watkins, David Blodgett, Laura DeCicco, and many more), there’s already plenty of code we can adapt to use to download all sorts of NHD/Watershed things.

For example, we can use some of the code from very handy function courtesy of Laura DeCicco, in a github repository for an inactive package called hydroMap. To make that work, you’ll need a local USGS gaging station ID, as well as rgdal installed. If you don’t know a NWIS or USGS station ID, you can look them up via the USGS website. For this example, I’ll use 11396200 which is on the SF Feather River.

Option 1: Use existing zipped shapefile

# read in zipped shapefile with sf
huc8 <- read_sf(unzip("data/h8_AMR_BEA_YUB.zip"), quiet = F) %>%
  st_transform(crs=4326) #%>% 
## Reading layer `h8_AMR_BEA_YUB' from data source `./h8_AMR_BEA_YUB.dbf' using driver `ESRI Shapefile'
## Simple feature collection with 4 features and 5 fields
## geometry type:  POLYGON
## dimension:      XY
## bbox:           xmin: -121.5976 ymin: 38.61455 xmax: -119.9829 ymax: 39.77696
## epsg (SRID):    4326
## proj4string:    +proj=longlat +datum=WGS84 +no_defs
# then remove raw files since file is added in memory
file.remove(list.files(pattern = "h8_AMR_BEA_YUB*",recursive = F))
## [1] TRUE TRUE TRUE TRUE
# filter to the Bear only
h8 <- huc8 %>% filter(HU_8_NAME=="Upper Bear") 

plot(h8$geometry, border="orange", lwd=2)

Option 2: Use function to download basin for USGS Station location

We’ll load a function that grabs the upstream watershed for a given station ID. Here it’s the SF Feather (or most of the watershed). We plot with mapview at the end and add both layers to the same map with a “+” similar to ggplot2.

First we need to load the function:

# the original function is here: https://github.com/USGS-R/hydroMap/blob/master/R/plotWSB.R

# function assumes you have sf installed locally
get_basins <- function(sites, filePath = NA){
  library(httr)
  postURL <- "https://cida.usgs.gov/nwc/geoserver/NWC/ows"
  # postURL <- "http://cida-test.er.usgs.gov/nwc/geoserver/NWC/ows"
  filterXML <- paste0('<?xml version="1.0"?>',
                      '<wfs:GetFeature xmlns:wfs="http://www.opengis.net/wfs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gml="http://www.opengis.net/gml" service="WFS" version="1.1.0" outputFormat="shape-zip" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd">',
                      '<wfs:Query xmlns:feature="https://owi.usgs.gov/NWC" typeName="feature:epa_basins" srsName="EPSG:4326">')
  
  
  if(length(sites) > 1){
    siteText <- ""
    for(i in sites){
      siteText <- paste0(siteText,'<ogc:PropertyIsEqualTo  matchCase="true">',
                         '<ogc:PropertyName>site_no</ogc:PropertyName>',
                         '<ogc:Literal>',i,'</ogc:Literal>',
                         '</ogc:PropertyIsEqualTo>')
    }
    
    filterXML <- paste0(filterXML,'<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">',
                        '<ogc:Or>',siteText,'</ogc:Or>',
                        '</ogc:Filter>')
    
  } else {
    filterXML <- paste0(filterXML,
                        '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">',
                        '<ogc:PropertyIsEqualTo matchCase="true">',
                        '<ogc:PropertyName>site_no</ogc:PropertyName>',
                        '<ogc:Literal>',sites,'</ogc:Literal>',
                        '</ogc:PropertyIsEqualTo>',
                        '</ogc:Filter>')
  }
  
  filterXML <- paste0(filterXML,'</wfs:Query>',
                      '</wfs:GetFeature>')
  
  destination = tempfile(pattern = 'basins_shape', fileext='.zip')
  
  file <- POST(postURL, body = filterXML, write_disk(destination, overwrite=T))
  if(is.na(filePath)){
    filePath <- tempdir()
  }
  
  unzip(destination, exdir = filePath)
  basins <- st_read(filePath, layer='epa_basins')
  
  #basins = readOGR(filePath, layer='epa_basins')
  return(basins)
}

# set a station ID or IDs:
ids <- c(11396200) # S Feather
fea_huc <- get_basins(ids) # use function to get boundary
## Reading layer `epa_basins' from data source `/private/var/folders/jv/56h34hjn2l302ypnnsphwnx00000gn/T/Rtmpa0CSzl' using driver `ESRI Shapefile'
## Simple feature collection with 1 feature and 4 fields
## geometry type:  POLYGON
## dimension:      XY
## bbox:           xmin: -121.2132 ymin: 39.54753 xmax: -120.8683 ymax: 39.78771
## epsg (SRID):    4326
## proj4string:    +proj=longlat +datum=WGS84 +no_defs
# use mapview to show both
mapview(fea_huc) + mapview(h8)

Get NHD Streamline

I’ve shown this in previous posts, but here’s a slight modification. Now we can simply provide our polygons, and the function will automagically create the bounding box to grab flowlines for whatever level of detail (stream order) we choose. Higher stream order = more detail, with 1 being the finest scale you can get. First we load the function, again courtesy of the USGS folks. Note, the view_polygon should be a sf polygon, which gets transformed to a WGS84 lat/lon projection before downloading.

Here’s the function:

# the function
get_flowlines <- function(streamorder, view_polygon){
  library(httr)
  
  bbox <- st_bbox(st_transform(view_polygon, 4326))
  
  postURL <- "https://cida.usgs.gov/nwc/geoserver/nhdplus/ows"
  
  filterXML <- paste0('<?xml version="1.0"?>',
                      '<wfs:GetFeature xmlns:wfs="http://www.opengis.net/wfs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gml="http://www.opengis.net/gml" service="WFS" version="1.1.0" outputFormat="shape-zip" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd">',
                      '<wfs:Query xmlns:feature="https://gov.usgs.cida/nhdplus" typeName="feature:nhdflowline_network" srsName="EPSG:4326">',
                      '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">',
                      '<ogc:And>',
                      '<ogc:PropertyIsGreaterThan>',
                      '<ogc:PropertyName>streamorde</ogc:PropertyName>',
                      '<ogc:Literal>',streamorder-1,'</ogc:Literal>',
                      '</ogc:PropertyIsGreaterThan>',
                      '<ogc:BBOX>',
                      '<ogc:PropertyName>the_geom</ogc:PropertyName>',
                      '<gml:Envelope>',
                      '<gml:lowerCorner>',bbox[2]," ",bbox[1],'</gml:lowerCorner>',
                      '<gml:upperCorner>',bbox[4]," ",bbox[3],'</gml:upperCorner>',
                      '</gml:Envelope>',
                      '</ogc:BBOX>',
                      '</ogc:And>',
                      '</ogc:Filter>',
                      '</wfs:Query>',
                      '</wfs:GetFeature>')
  
  destination = file.path(tempdir(),"nhdflowline_network.zip")
  file <- POST(postURL, body = filterXML, write_disk(destination, overwrite=T))
  
  filePath <- tempdir()
  print("unzipping...")
  unzip(destination, exdir = filePath)
  
  flowLines <- sf::st_read(filePath, layer = 'nhdflowline_network')
  
  return(flowLines)
}

Then we grab our data…here at two different scales, stream order = 1, and stream order = 3.

# use the function to get streamlines
fea1 <- get_flowlines(1, fea_huc) # stream order 1
## [1] "unzipping..."
## Reading layer `nhdflowline_network' from data source `/private/var/folders/jv/56h34hjn2l302ypnnsphwnx00000gn/T/Rtmpa0CSzl' using driver `ESRI Shapefile'
## Simple feature collection with 397 features and 90 fields
## geometry type:  LINESTRING
## dimension:      XYZ
## bbox:           xmin: -121.2286 ymin: 39.52225 xmax: -120.8529 ymax: 39.8031
## epsg (SRID):    4326
## proj4string:    +proj=longlat +datum=WGS84 +no_defs
fea3 <- get_flowlines(3, fea_huc) # stream order 3
## [1] "unzipping..."
## Reading layer `nhdflowline_network' from data source `/private/var/folders/jv/56h34hjn2l302ypnnsphwnx00000gn/T/Rtmpa0CSzl' using driver `ESRI Shapefile'
## Simple feature collection with 84 features and 90 fields
## geometry type:  LINESTRING
## dimension:      XYZ
## bbox:           xmin: -121.2165 ymin: 39.52225 xmax: -120.8667 ymax: 39.79227
## epsg (SRID):    4326
## proj4string:    +proj=longlat +datum=WGS84 +no_defs
# Plot the Feather River
plot(fea1$geometry,col="skyblue") # plot stream order 1
plot(fea3$geometry, add=T,col="blue3", lwd=1.4) # stream order 3
# add HUC with some transparency using the scale::alpha() function
plot(fea_huc$geometry, border=scales::alpha("black", 0.5), lwd=2, add=T) 
title(main = "SF Feather Watershed with NHD Streamlines")

# Now same with the Bear River
bear1 <- get_flowlines(1, h8)
## [1] "unzipping..."
## Reading layer `nhdflowline_network' from data source `/private/var/folders/jv/56h34hjn2l302ypnnsphwnx00000gn/T/Rtmpa0CSzl' using driver `ESRI Shapefile'
## Simple feature collection with 2189 features and 90 fields
## geometry type:  LINESTRING
## dimension:      XYZ
## bbox:           xmin: -121.5895 ymin: 38.8825 xmax: -120.6062 ymax: 39.36088
## epsg (SRID):    4326
## proj4string:    +proj=longlat +datum=WGS84 +no_defs
bear3 <- get_flowlines(3, h8)
## [1] "unzipping..."
## Reading layer `nhdflowline_network' from data source `/private/var/folders/jv/56h34hjn2l302ypnnsphwnx00000gn/T/Rtmpa0CSzl' using driver `ESRI Shapefile'
## Simple feature collection with 828 features and 90 fields
## geometry type:  LINESTRING
## dimension:      XYZ
## bbox:           xmin: -121.5895 ymin: 38.91882 xmax: -120.6307 ymax: 39.36088
## epsg (SRID):    4326
## proj4string:    +proj=longlat +datum=WGS84 +no_defs
plot(bear1$geometry,col="skyblue")
plot(bear3$geometry, add=T,col=scales::alpha("blue3", 0.8), lwd=1.4)
# add HUC with some transparency using the scale::alpha() function
plot(h8$geometry, border=scales::alpha("maroon", 0.8), lwd=2, add=T)
title(main = "Bear River Watershed with NHD Streamlines")

Get Waterbodies

Great, we have a watershed boundary and a streamline. But let’s add some lakes. Same steps, load function, provide polygon/shapefile of interest (in sf format), a threshold for the minimum area (sq km) waterbody you want, and then wait patiently for something to happen.

Here’s the function:

get_waterbodies <- function(view_polygon, fetch_waterbody_areasqkm) {
  # view_polygon, ind_file were in function 
  bbox <- st_bbox(st_transform(view_polygon, 4326))
  
  postURL <- "https://cida.usgs.gov/nwc/geoserver/nhdplus/ows"
  
  filterXML <- paste0('<?xml version="1.0"?>',
                      '<wfs:GetFeature xmlns:wfs="http://www.opengis.net/wfs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gml="http://www.opengis.net/gml" service="WFS" version="1.1.0" outputFormat="application/json" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd">',
                      '<wfs:Query xmlns:feature="http://gov.usgs.cida/nhdplus" typeName="feature:nhdwaterbody" srsName="EPSG:4326">',
                      '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">',
                      '<ogc:And>',
                      '<ogc:PropertyIsGreaterThan>',
                      '<ogc:PropertyName>areasqkm</ogc:PropertyName>',
                      '<ogc:Literal>',fetch_waterbody_areasqkm,'</ogc:Literal>',
                      '</ogc:PropertyIsGreaterThan>',
                      '<ogc:BBOX>',
                      '<ogc:PropertyName>the_geom</ogc:PropertyName>',
                      '<gml:Envelope>',
                      '<gml:lowerCorner>',bbox[2]," ",bbox[1],'</gml:lowerCorner>',
                      '<gml:upperCorner>',bbox[4]," ",bbox[3],'</gml:upperCorner>',
                      '</gml:Envelope>',
                      '</ogc:BBOX>',
                      '</ogc:And>',
                      '</ogc:Filter>',
                      '</wfs:Query>',
                      '</wfs:GetFeature>')
  
  out <- httr::POST(postURL, body = filterXML)
  
  sf_waterbodies <- read_sf(rawToChar(out$content)) %>%
    st_transform(st_crs(view_polygon))
  
}

Now let’s use the function!

# GET LAKES with 0.2 sqkm area or greater
bear_wbodies<-get_waterbodies(h8, 0.2)

# take a look at the plot:
plot(bear1$geometry,col="skyblue")
plot(bear3$geometry, add=T,col=scales::alpha("blue3", 0.8), lwd=1.4)
# add HUC with some transparency using the scale::alpha() function
plot(h8$geometry, border=scales::alpha("maroon", 0.8), lwd=2, add=T) 
plot(bear_wbodies$geometry, col="cyan4", border=scales::alpha("blue3",0.8), add=T)
title("Bear River Watershed with Lakes")

This time lets use a mapview map.

# do the same thing for feather
fea_wbodies<-get_waterbodies(fea_huc, 0.1)

# let's make a big mapview map
mapview(fea_huc, border="blue", col.region=NA) + 
  mapview(fea1, color="blue3") +
  mapview(fea_wbodies, col.region="blue3")

Put it Together

Now we have 3 separate layers which may be useful together. Try plotting all with mapview and ggplot2.

Customizing the Maps: Using ggplot2

Let’s make a map of all these layers as-is, using ggplot2. The newest version of ggplot natively plots sf objects with geom_sf, so if this doesn’t work, try updating.

gg1b <- ggplot() +
  geom_sf(data=fea1, col="blue3", lwd=0.4) +
  geom_sf(data=fea_huc, fill=NA, color="gray20", lwd=1.2) +
  geom_sf(data=fea_wbodies, fill="cyan3") +
  theme_bw()
gg1b

Customizing a Mapview Map

Great, now let’s add a mapview map, and add some advanced trickery to make it look different. See here for some advanced mapview options.

# make a list of the map layers of interest, first layer is what gets plotted
maplayers <- c( "CartoDB.Positron","OpenTopoMap", "OpenStreetMap", "Esri.WorldImagery","Esri.WorldTopoMap", "Stamen.TonerLite")

mapview(fea1, zcol="streamorde", map.types=maplayers, layer.name=c(paste0("Stream <br> Order"))) + 
  mapview(fea_huc, color="gray", col.region=NA, lwd=2.4,map.types=maplayers) +
  mapview(fea_wbodies, col.region="cyan3", map.types=maplayers)

Clean Things Up and Smooth Polygons

We made some different maps, which is great but ultimately, it would be nice to crop this by just the watershed, and maybe smooth out the edges a bit so it doesn’t look so much like a rasterized polygon. Depending on how the data is going to be used, this may be helpful if you plan on buffering for analysis, or you simply want a slightly more aesthetically pleasing map.

Crop by Watershed

First let’s crop the data by the watershed boundary…because originally these data were pulled in using a bounding box around the watershed (the X/Y coordinates at the corners of a box that completely encapsulates the polygon). Thankfully the sf package makes it easy to clip data down to the area of interest. From here on out I’ll just show the smaller SF Feather watershed and associated data. One important point: make sure the data are in the same projection before doing intersection/joining type operations.

# check crs:
st_crs(fea1)
## Coordinate Reference System:
##   EPSG: 4326 
##   proj4string: "+proj=longlat +datum=WGS84 +no_defs"
st_crs(fea_huc)
## Coordinate Reference System:
##   EPSG: 4326 
##   proj4string: "+proj=longlat +datum=WGS84 +no_defs"
# make a cropped version:
fea_streamline_clipped <- st_intersection(fea1, fea_huc)
fea_lakes_clipped <- st_intersection(fea_wbodies, fea_huc) # nothing to clip

# plot
clipPlot <- ggplot() +
  geom_sf(data=fea_streamline_clipped, aes(color=streamorde), lwd=0.4) +
  geom_sf(data=fea_huc, fill=NA, color="gray20", lwd=1.2) +
  geom_sf(data=fea_lakes_clipped, fill="cyan3", color="blue3") +
  theme_bw() + scale_color_viridis_c("Stream \n Order")
clipPlot

# NOTE: not uncommon to get an Error:
# "Error in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y,  : 
#  polygon edge not found"
# SUGGESTION: Just try plotting again, or try dev.off() and then replot, 
# typically resolves.

Smooth the Boundaries!

One final step…we have a nice watershed boundary and a some lake boundaries which may benefit from smoothing or simplification using the smoothr and sf packages. There are many uses/purposes, so if you want to read more, check out the smoothr package page. There are lots of cool functions to play with, I’ll just show a couple here. But know there are a few that are helpful for cleaning/tidying data with holes, jagged edges, etc.

Simplify and Smooth

Note, we’ll be getting warnings here because of smoothing data that is in decimal degrees…easy to switch to a different transformation (i.e., CRS=3310) and rerun. Let’s use the watershed boundary. The first step I"m using is to simplify the shape boundary. Again, there will be some warnings which relate to the projection, no worries, it will still work. We could use many different levels of simplification here. I’m picking a value (dTolerance) that still roughly provides the shape. Feel free to play with different values and see what happens.

library(smoothr)

# simplify
fea_huc_simple <- st_simplify(fea_huc, preserveTopology = TRUE, dTolerance = .007)
#plot(fea_huc_simple$geometry, border="black", lwd=1.5)
#plot(fea_huc$geometry, border="blue", add=T)

# smooth
fea_huc_smooth1 <- smooth(fea_huc_simple, method = "ksmooth")
fea_huc_smooth2 <- smooth(fea_huc_simple, method = "spline")
fea_huc_smooth3 <- smooth(fea_huc_simple, method = "chaikin")

plot(fea_huc$geometry, border=scales::alpha("#FDE725FF", 0.8), lwd=3)
plot(fea_huc_smooth1$geometry, border="#440154FF", lwd=1, add=T)
plot(fea_huc_smooth2$geometry, border="#31688EFF", lwd=1,lty=2, add=T)
plot(fea_huc_smooth3$geometry, border="#35B779FF", lwd=2, lty=5, add=T)
legend(title= paste0("Different Smoothed \n HUC Outlines"),"bottomright", col=c("#FDE725FF", "#440154FF", "#31688EFF", "#35B779FF"), legend = c("original", "k-smooth","spline", "chaikin"), lty = c(1, 1, 2, 4), lwd = c(3, 1, 1, 2), bty = "n", cex=0.8)

Put it all together in a Map

Let’s use all these pieces to make one final map in ggplot2 and mapview to close this loop. I’ll add a trick where we label the lakes by adding a centroid point to the sf object, which does require the purrr package.

library(purrr)

# get COUNTY data for a given state
fea_lakes_clipped <- fea_lakes_clipped %>% 
  mutate(lon=map_dbl(geometry, ~st_centroid(.x)[[1]]), # add cent. for labels
         lat=map_dbl(geometry, ~st_centroid(.x)[[2]])) # add cent. for labels

Ok, here’s the ggplot version:

finalPlot1 <- ggplot() +
  geom_sf(data=fea_streamline_clipped, aes(color=streamorde), lwd=0.4) +
  geom_sf(data=fea_huc_smooth2, fill=NA, color="black", lwd=0.5) +
  geom_sf(data=fea_lakes_clipped, fill="cyan3", color="slateblue", alpha=0.8) +
  geom_label_repel(data=fea_lakes_clipped, aes(x=lon, y=lat, label=gnis_name), nudge_x = .08, nudge_y = -.01, alpha=0.65) +
  theme_bw() + 
  scale_color_viridis_c("Stream \n Order", option = "D", direction = -1,
                        limits = c(1,3), breaks = c(1, 2, 3),
                        guide = guide_colourbar(nbin=3, 
                                                draw.ulim = FALSE, 
                                                draw.llim = FALSE)) + 
  theme(legend.position = c(0.1, 0.77)) +
  labs(title = "South Feather River Basin", 
       subtitle = "(Spline smoothed watershed boundary)")
finalPlot1

finalPlot2 <- ggplot() +
  geom_sf(data=fea_streamline_clipped, aes(color=streamorde), lwd=0.4) +
  geom_sf(data=fea_huc, fill=NA, color="gray30", lwd=0.8, lty=2) +
  geom_sf(data=fea_lakes_clipped, fill="cyan3", color="slateblue", alpha=0.8) +
  geom_label_repel(data=fea_lakes_clipped, aes(x=lon, y=lat, label=gnis_name), nudge_x = .08, nudge_y = -.01, alpha=0.65) +
  theme_bw() + 
  scale_color_viridis_c("Stream \n Order", option = "D", direction = -1,
                        limits = c(1,3), breaks = c(1, 2, 3),
                        guide = guide_colourbar(nbin=3, 
                                                draw.ulim = FALSE, 
                                                draw.llim = FALSE)) + 
  theme(legend.position = c(0.1, 0.8)) +
  labs(title = "South Feather River Basin",
       subtitle = "(HUC watershed boundary)")
finalPlot2

And the mapview:

maplayers <- c( "CartoDB.Positron","OpenTopoMap", "OpenStreetMap", "Esri.WorldImagery","Esri.WorldTopoMap", "Stamen.TonerLite")

mapview(fea_streamline_clipped, zcol="streamorde", map.types=maplayers, layer.name=c(paste0("Stream <br> Order"))) + 
  mapview(fea_huc, color="gray", col.region=NA, lwd=2.4,map.types=maplayers) +
  mapview(fea_wbodies, col.region="cyan3", map.types=maplayers)

Wrap it Up

Hopefully some of this has been helpful for someone. Largely I’ve put these things down in a post so I can refer back to them instead of googling the same task again. Stay tuned for an upcoming post on using the aggiedown package, which is for creating a UC Davis formatted dissertation in RMarkdown with the bookdown package.