Chapter 7 Visualizing Spatial Data
7.1 Lesson Goals
- Explore several mapping libraries in R
- Construct a couple example visualizations with spatial data in R
R is fantastic for making publication quality static maps, and for generating repetitive graphics through scripts; we’ve already seen how to make simple maps using base plotting,ggplot
, and tmap
. There are also a number of packages in R that link R code to plotting libraries developed in Javascript (or other languages) for interactive plotting and web integration.
It can be hard to decide which mapping packages to learn and use - some nice advice from Martin Tennekes who created tmap
:
- If you know some ggplot
, don’t care about interactive maps, and don’t want to spend a lot of time learning new packages, use ggplot
- If you want interactive maps as flexible as possible, use leaflet
- If you want to simply explore spatial objects ineractively as easily as possible, use mapview
- Otherwise, use tmap
!
7.2 tmap
Load tidycensus
- you’ll need to set your Census API key. A key can be obtained from here.
library(sf,quietly = T)
library(tidycensus)
# census_api_key("YOUR API KEY GOES HERE")
Here we’ll start with a simple choropleth map of median household income in Benton county in Oregon.
library(tidycensus)
library(tmap)
options(tigris_use_cache = FALSE)
<- get_acs(state = 'OR', county = 'Benton', geography = "tract",
benton_tracts variables = "B19013_001", geometry = TRUE)
# Rename a column
<- benton_tracts %>%
benton_tracts ::rename(MedianIncome=estimate)
dplyrtm_shape(benton_tracts) + tm_polygons(col="MedianIncome")
That’s a pretty basic map - we can adjust a number of settings: - breaks: we can set different breaks for our map - n: we can control the number of bins - palette: we can change the color palette
These are just a few - let’s play with those to start.
= c(0, 40, 80, 120, 140) * 1000
breaks <- tm_shape(benton_tracts) + tm_polygons(col="MedianIncome", breaks=breaks)
t1 <- tm_shape(benton_tracts) + tm_polygons(col = "MedianIncome", n = 10)
t2 <- tm_shape(benton_tracts) + tm_polygons(col = "MedianIncome", palette = "BuGn")
t3 tmap_arrange(t1, t2, t3, nrow = 1)
tmap
also has view
mode which can be handy
tmap_mode("view")
tm_shape(benton_tracts) + tm_polygons(col="MedianIncome")
We can make multi-layered maps (shout out to Amalia Handler who used this in tmap
demo for our lab group)
# Load data
data(World, metro, rivers, land)
# Set to static mode
tmap_mode("plot")
# Make map that is multilayered
tm_shape(land) +
tm_raster("elevation", palette = terrain.colors(10)) +
tm_shape(World) +
tm_borders("white", lwd = .5) +
tm_text("iso_a3", size = "AREA") +
tm_shape(metro) +
tm_symbols(col = "red", size = "pop2020", scale = .5) +
tm_legend(show = FALSE)
We can make faceted maps with tmap
# Set mode to interactive
tmap_mode("view")
# Plot it out
tm_shape(World) +
tm_polygons(c("pop_est", "HPI","life_exp","well_being")) +
tm_facets(sync = TRUE, ncol = 2)
We can again plot our elevation and Crater Lake boundary data in tmap
as we did in the Raster Section:
library(Rspatialworkshop)
library(terra)
data(CraterLake)
<- system.file("extdata", "elevation.tif", package = "Rspatialworkshop")
raster_filepath <- rast(raster_filepath)
elevation <- terra::mask(elevation, vect(CraterLake))
elevation <- project(elevation, '+proj=utm +zone=10 +ellps=GRS80 +units=m +no_defs')
elevation <- st_transform(CraterLake, '+proj=utm +zone=10 +ellps=GRS80 +units=m +no_defs')
CraterLake <- tm_shape(CraterLake) + tm_polygons(lwd = 2)
map_crlk = map_crlk +
map_crlkel tm_shape(elevation) + tm_raster(alpha = 0.7,palette = terrain.colors(12)) + tm_layout(legend.position = c("left","bottom"),
legend.width = 1)
map_crlkel
7.2.1 Exercise
Look at some of the links in the tmap
GitHub repository and specifically tmap: get started! and see what modifications to plotting elevation and the Crater Lake park boundary you can come up with
7.2.2 Solution
Just an example incorporating a number of settings available in tmap
to produce cartographic output
tm_shape(elevation) +
tm_raster(title = "Elevation",
style = "cont",
palette = "-Spectral") +
tm_shape(CraterLake) +
tm_borders(col = "black",
lwd = 1)+
tm_scale_bar(breaks = c(0, 10, 20),
text.size = .5,
position = c("left", "bottom")) +
tm_grid(n.x = 4, n.y = 3) +
tm_compass(position = c("right", "top"),
type = "arrow",
size = 1.5)
7.3 leaflet
Leaflet is an extremely popular open-source javascript library for interactive web mapping, and the leaflet
R package allows R users to create Leaflet maps from R. Leaflet
can plot sf
or sp
objects, or x / y coordinates, and can plot points, lines or polygons. There are a number of base layers you can choose from. It’s worth spending some time exploring the excellent Leaflet for R site.
Here we make the simplest of leaflet maps:
library(leaflet)
<- leaflet() %>%
m addTiles() %>% # Add default OpenStreetMap map tiles
addMarkers(lng=-123.26720, lat=44.5810, popup="Here's my house")
# Print the map m
7.4 mapview
Mapview is a package designed for quick and easy interactive visualizations of spatial data - it makes use of leaflet
but simplifies mapping functions compared to the leaflet
package.
It’s easy to layer features with mapview
- you can supply a list of objects to mapview
or use + syntax as with ggplot
.
Here we plot gages within Benton County:
library(Rspatialworkshop)
library(mapview)
library(readr)
library(sf)
library(tigris)
<- counties("Oregon", cb = TRUE)
counties <- counties[counties$NAME=='Benton',]
benton <- system.file("extdata", "Gages_flowdata.csv", package="Rspatialworkshop")
fpath
<- read_csv(fpath,show_col_types = FALSE)
gages <- gages %>%
gages_sf st_as_sf(coords = c("LON_SITE", "LAT_SITE"), crs = 4269, remove = FALSE)
st_crs(gages_sf)==st_crs(benton)
# remember spatial indexing?
<- gages_sf[benton,]
gages_benton mapview(gages_benton) + benton
7.4.1 Exercise
Glance through the mapview
basics and adjust legend and attributes. Take a look at mapview
advanced controls as well and try plotting stations and the county polygon together, this time modifying features such as thicker black outline and transparent fill for the county outline and colorizing gages by their average flow (‘AVE’).
7.4.2 Solution
mapview(gages_benton,zcol='AVE') + mapview(benton, alpha.regions=.07, color='black', lwd=2)
7.5 Adding Web Map services in mapview
We’ll visualize data with mapview and load web map layers alongside using mapview
and underlying leaflet
functionality.
First we load load an excel file containing coordinate information in a known projection and promote to an sf
spatial data frame.
library(Rspatialworkshop)
library(sf)
library(dplyr)
library(readxl)
library(mapview)
<- system.file("extdata", "Station_Locations.xlsx", package="Rspatialworkshop")
fpath <- read_xlsx(fpath)
stations glimpse(stations)
## Rows: 31
## Columns: 3
## $ Station <chr> "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12",~
## $ x <dbl> -2140749, -2140111, -2124688, -2125545, -1664112, 1606578, -17~
## $ y <dbl> 2502887, 2469697, 2533842, 2556987, 2770644, 2698398, 2664873,~
summary(stations$x)
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## -2259078 -2124688 -1561956 -1630593 -1454137 1606578 2
# common clean up steps for spatial data - we can't use data missing coordinates so drop those records
<- stations[complete.cases(stations),]
stations # often spatial data in projected coordinates will have missing negative values for x values - common thing to fix:
$x[stations$x > 0] <- 0 - stations$x[stations$x > 0]
stations<- stations %>%
stations st_as_sf(coords = c("x", "y"), remove = FALSE)
# in this case we know the particular Albers projection and have the information as a proj string
st_crs(stations) <- "+proj=aea +lat_1=29.5 +lat_2=45.5 +lat_0=23 +lon_0=-96 +x_0=0 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs"
Basic interactive map of our spatial stations with mapview
:
mapview(stations)
We know of web mapping services for the National Hydrography dataset - these are stream site locations so imagine we want to visualize how closely these sites match a known rivers and stream network:
library(leaflet)
# create a mapview object with our stations:
<- mapview(stations, legend=FALSE)
m
# we configure the map attribute of our mapview object - try:
# 'attributes(m)
# to see those attributes
# The map attribute for mapview accepts leaflet methods - in this case we use addWMSTiles to add web map service tiles to the map
@map <- m@map %>% addWMSTiles(group = 'NHDPlus',
m"https://watersgeo.epa.gov/arcgis/services/NHDPlus_NP21/NHDSnapshot_NP21/MapServer/WmsServer?",
layers = 4,
options = WMSTileOptions(format = "image/png", transparent = TRUE),
attribution = "") %>% addWMSTiles(group = 'NHDPlusHR',
"https://hydro.nationalmap.gov/arcgis/services/NHDPlus_HR/MapServer/WMSServer?",
layers = 9,
options = WMSTileOptions(format = "image/png", transparent = TRUE),
attribution = "") %>% mapview:::mapViewLayersControl(names = c("NHDPlus","NHDPlusHR"))
m
7.6 ggplot2
We’ve used this in simple examples throughout the workshop using geom_sf
with ggplot2
. If time we can try to make a polished map using ggplot
using this post as a guide.
As we’ve seen, you can make a very simple map with sf
features using geom_sf
and a fill parameter
library(ggplot2)
library(maps)
library(sf)
<- st_as_sf(map("state", plot = FALSE, fill = TRUE))
states ggplot() +
geom_sf(data=states, color="blue", fill=NA)+
ggtitle(label = "The world's simplest ggplot2 map")
7.6.1 Exercise
Using the example in post above, get counties for your state using maps
package as shown in example - igonore the extra packages and some of the bells and whistles in the blog post, just try using a few of their ideas and adding a few of the ggplot parameters to make an interesting map with counties. Feel free to try adding some cities too if you want.
7.6.2 Solution
One idea
library(tmap)
library(tmaptools)
library(maps)
library(ggplot2)
<- st_as_sf(map("county", plot = FALSE, fill = TRUE))
counties <- subset(counties, grepl("oregon", counties$ID))
counties $area <- as.numeric(st_area(counties))
countiesggplot() +
geom_sf(data = counties, fill = NA, color = gray(.5)) +
coord_sf(xlim = c(-125, -115), ylim = c(41, 47), expand = FALSE)
library(ggspatial)
ggplot() +
geom_sf(data = counties, aes(fill = area)) +
scale_fill_viridis_c(trans = "sqrt", alpha = .4) +
coord_sf(xlim = c(-125, -115), ylim = c(41, 47), expand = FALSE) +
theme(panel.grid.major = element_line(color = gray(0.5), linetype = "dashed",
size = 0.5), panel.background = element_rect(fill = "aliceblue")) +
annotation_scale(location = "bl", width_hint = 0.4) +
annotation_north_arrow(location = "tr", which_north = "true",
style = north_arrow_fancy_orienteering)