library(sf)
library(terra)
library(tidyverse)
library(spData)
#install.packages("spDataLarge", repos = "https://nowosad.r-universe.dev")
library(spDataLarge)
## Warning: package 'spDataLarge' was built under R version 4.3.3
library(magrittr)
library(tmap) # for static and interactive maps
library(leaflet) # for interactive maps
library(ggplot2) # tidyverse data visualization package
Time to make some maps. We’ll focus on the ‘tmap’ package here, but there are a number of packages dedicated to making maps. We’ll also introduce ‘leaflet’ and the mapping capabilites of ‘ggplot2’. First, we will go over static maps and then move on to dynamic and interactive maps.
Static maps are the most common result when visualizing spatial data. Base R’s plot() can be used for static maps as we’ve demonstrated throughout the workshop. However, ‘tmap’ offers more options for aesthetics and follows a similar syntax with ggplot.
Like ggplot, tmap follows a ‘grammar of graphics’ (the gg in ggplot), where input data is separated from the aesthetics. Input data is defined using tm_shape() and aesthetics are layered on using a wide variety of functions such as tm_fill() and tm_dots().
# Add fill layer to nz shape
tm_shape(nz) +
tm_fill()
# Add border layer to nz shape
tm_shape(nz) +
tm_borders()
# Add fill and border layers to nz shape
tm_shape(nz) +
tm_fill() +
tm_borders()
Play around with tmap. Type help(“tmap-element”) into your console for a full list of functions.
A useful feature of tmap is its ability to store objects representing maps.
map_nz <- tm_shape(nz) + tm_polygons()
class(map_nz)
## [1] "tmap"
Storing maps as map objects is useful for adding aesthetic layers and new spatial objects on top of each other. You can now add layers to ‘map_nz’ by simply using the + operator. If you add + tm_shape() then you can add another spatial object.
nz_elev = rast(system.file("raster/nz_elev.tif", package = "spDataLarge"))
map_nz1 = map_nz +
tm_shape(nz_elev) + tm_raster(alpha = 0.7)
nz_water = st_union(nz) %>% st_buffer(22200) %>%
st_cast(to = "LINESTRING")
map_nz2 = map_nz1 +
tm_shape(nz_water) + tm_lines()
map_nz3 = map_nz2 +
tm_shape(nz_height) + tm_dots()
tmap_arrange(map_nz1, map_nz2, map_nz3)
## stars object downsampled to 877 by 1140 cells. See tm_shape manual (argument raster.downsample)
## stars object downsampled to 877 by 1140 cells. See tm_shape manual (argument raster.downsample)
## stars object downsampled to 877 by 1140 cells. See tm_shape manual (argument raster.downsample)
## stars object downsampled to 877 by 1140 cells. See tm_shape manual (argument raster.downsample)
## Some legend labels were too wide. These labels have been resized to 0.63, 0.55, 0.55, 0.55, 0.55, 0.55. Increase legend.width (argument of tm_layout) to make the legend wider and therefore the labels larger.
## stars object downsampled to 877 by 1140 cells. See tm_shape manual (argument raster.downsample)
## Some legend labels were too wide. These labels have been resized to 0.63, 0.55, 0.55, 0.55, 0.55, 0.55. Increase legend.width (argument of tm_layout) to make the legend wider and therefore the labels larger.
## stars object downsampled to 877 by 1140 cells. See tm_shape manual (argument raster.downsample)
## Some legend labels were too wide. These labels have been resized to 0.63, 0.55, 0.55, 0.55, 0.55, 0.55. Increase legend.width (argument of tm_layout) to make the legend wider and therefore the labels larger.
Like ggplot, the default aesthetics of each layer can be changed. Unlike ggplot, tmap does not use the aes() function but layers can accept arguments that are fixed values or variable fields (based on column names).
Here is an example of fixed values
ma1 = tm_shape(nz) + tm_fill(col = "red")
ma2 = tm_shape(nz) + tm_fill(col = "red", alpha = 0.3)
ma3 = tm_shape(nz) + tm_borders(col = "blue")
ma4 = tm_shape(nz) + tm_borders(lwd = 3)
ma5 = tm_shape(nz) + tm_borders(lty = 2)
ma6 = tm_shape(nz) + tm_fill(col = "red", alpha = 0.3) +
tm_borders(col = "blue", lwd = 3, lty = 2)
tmap_arrange(ma1, ma2, ma3, ma4, ma5, ma6)
And here is an example of how you would use values from a variable. The argument will take the character string associated with the column you are interested in.
tm_shape(nz) + tm_fill(col = "Land_area")
Finally, you can add nicer titles. expression() allows for superscripts
legend_title = expression("Area (km"^2*")")
map_nza = tm_shape(nz) +
tm_fill(col = "Land_area", title = legend_title) + tm_borders()
map_nza
Colors are useful. Here are a few ways to manipulate color in your maps. Play around and figure out what each of these arguments (breaks, n, palette) do.
col1 <- tm_shape(nz) + tm_polygons(col = "Median_income")
breaks <- c(0, 3, 4, 5) * 10000
col2 <- tm_shape(nz) + tm_polygons(col = "Median_income", breaks = breaks)
col3 <- tm_shape(nz) + tm_polygons(col = "Median_income", n = 10)
col4 <- tm_shape(nz) + tm_polygons(col = "Median_income", palette = "BuGn")
tmap_arrange(col1, col2, col3, col4)
tmap also allows for setting breaks using the style argument rather than manual setting breaks. Try out a few of the different style options: “pretty”, “equal”, “quantile”, “jenks”, “cont”, “cat”
tm_shape(nz) + tm_polygons(col = "Median_income", style = "pretty")
Palettes define the color range used and can be critical for interpretability of your figures. Palettes can be categorical, sequential, or diverging and each of these serve different purposes. To adjust the palette, you can use the palette argument in tmap.
tm_shape(nz) + tm_polygons("Population", palette = "Blues")
## Some legend labels were too wide. These labels have been resized to 0.64, 0.59, 0.59. Increase legend.width (argument of tm_layout) to make the legend wider and therefore the labels larger.
tm_shape(nz) + tm_polygons("Population", palette = "YlOrBr")
## Some legend labels were too wide. These labels have been resized to 0.64, 0.59, 0.59. Increase legend.width (argument of tm_layout) to make the legend wider and therefore the labels larger.
Please reference section 8.2.4 of the online textbook to learn more about good habits for choosing palettes and when it is appropriate to use each.
The map layout refers to the combination of all map elements into a cohesive map. Map elements include among others the objects to be mapped, the title, the scale bar, margins and aspect ratios.
These additional elements can have their own functions for easy layering onto your maps.
map_nz +
tm_compass(type = "8star", position = c("left", "top")) +
tm_scale_bar(breaks = c(0, 100, 200), text.size = 1)
tm_layout allows for more customization of layout elements
layout1 <- map_nz + tm_layout(title = "New Zealand")
layout2 <- map_nz + tm_layout(scale = 5)
layout3 <- map_nz + tm_layout(bg.color = "lightblue")
layout4 <- map_nz + tm_layout(frame = FALSE)
tmap_arrange(layout1, layout2, layout3, layout4)
tmap also provides predetermined style options for layout that you can access using the style argument.
map_nza + tm_style("bw")
map_nza + tm_style("classic")
map_nza + tm_style("cobalt")
map_nza + tm_style("col_blind")
Please reference section 8.2.5 of the online textbook for more information on what you can do to the layout.
Faceted maps are useful for representing how data change in relationship to another variable, such as time. Here is an example of population size of large cities changing over time (including predictions of the future).
Play around with the number of years included in this figure and the arguments for the tm_facets() function.
urb_1970_2030 = urban_agglomerations %>%
filter(year %in% c(1970, 1990, 2010, 2030))
tm_shape(world) +
tm_polygons() +
tm_shape(urb_1970_2030) +
tm_symbols(col = "black", border.col = "white", size = "population_millions") +
tm_facets(by = "year", nrow = 2, free.coords = FALSE)
An inset map is a smaller map rendered within or next to the main map and can be useful for adding more detail to your maps.
As an example, we can start by making a new spatial object
nz_region = st_bbox(c(xmin = 1340000, xmax = 1450000,
ymin = 5130000, ymax = 5210000),
crs = st_crs(nz_height)) %>%
st_as_sfc()
Next, we can make the elevation map
nz_height_map = tm_shape(nz_elev, bbox = nz_region) +
tm_raster(style = "cont", palette = "YlGn", legend.show = TRUE) +
tm_shape(nz_height) + tm_symbols(shape = 2, col = "red", size = 1) +
tm_scale_bar(position = c("left", "bottom"))
Now we can make the inset map
nz_map = tm_shape(nz) + tm_polygons() +
tm_shape(nz_height) + tm_symbols(shape = 2, col = "red", size = 0.1) +
tm_shape(nz_region) + tm_borders(lwd = 3)
And put it all together
library(grid)
##
## Attaching package: 'grid'
## The following object is masked from 'package:terra':
##
## depth
nz_height_map
## stars object downsampled to 877 by 1140 cells. See tm_shape manual (argument raster.downsample)
print(nz_map, vp = viewport(0.8, 0.27, width = 0.5, height = 0.5))
Another purpose of inset maps is to include create a single map of non-contiguous areas such as a map of the US.
First, we can get a map of the contiguous US
us_states_map = tm_shape(us_states, projection = 2163) + tm_polygons() +
tm_layout(frame = FALSE)
Next, we can define maps for hawaii and alaska
hawaii_map = tm_shape(hawaii) + tm_polygons() +
tm_layout(title = "Hawaii", frame = FALSE, bg.color = NA,
title.position = c("LEFT", "BOTTOM"))
alaska_map = tm_shape(alaska) + tm_polygons() +
tm_layout(title = "Alaska", frame = FALSE, bg.color = NA)
Finally, we can print these maps and define where we want them to go on the map
us_states_map
print(hawaii_map, vp = grid::viewport(0.35, 0.1, width = 0.2, height = 0.1))
print(alaska_map, vp = grid::viewport(0.15, 0.15, width = 0.3, height = 0.3))
Rather than using faceted plots, animated maps can help visualize the same data using less space and in a more intuitive way.
urb_anim = tm_shape(world) + tm_polygons() +
tm_shape(urban_agglomerations) + tm_dots(size = "population_millions") +
tm_facets(along = "year", free.coords = FALSE)
This will create the animation and save it onto your computer. You’ll want to designate the filepath so that you can easily find it. This will default into your current working directory. If you are using MacOS then you can view it as a gif by opening it using cmd+y
#tmap_animation(urb_anim, filename = "urb_anim.gif", delay = 25)
The development of packages for interactive maps in R has made interactivity extremely accessible. ‘tmap’ makes this easy by allowing most static maps easily transferrable to interactive maps using tmap_mode()
See below for an example of turning ‘map_nz’ into a simple interactive map using tmap_mode(“view”)
tmap_mode("view")
## tmap mode set to interactive viewing
map_nz
Now that tmap_mode() has been set to “view”, all of the maps generated with tmap will show up as interactive maps
map_nza
You can return to static maps by using tmap_mode(“plot”)
tmap_mode("plot")
## tmap mode set to plotting
map_nza
Another package that allows for quick generation of interactive maps is ‘mapview’
library(mapview)
trails %>%
st_transform(st_crs(franconia)) %>%
st_intersection(franconia[franconia$district == "Oberfranken", ]) %>%
st_collection_extract("LINE") %>%
mapview(color = "red", lwd = 3, layer.name = "trails") +
mapview(franconia, zcol = "district", burst = TRUE) +
breweries
## Warning: attribute variables are assumed to be spatially constant throughout
## all geometries
leaflet is another option that is more low-level allowing for more customization
pal = colorNumeric("RdYlBu", domain = cycle_hire$nbikes)
leaflet(data = cycle_hire) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircles(col = ~pal(nbikes), opacity = 0.9) %>%
addPolygons(data = lnd, fill = FALSE) %>%
addLegend(pal = pal, values = ~nbikes) %>%
setView(lng = -0.1, 51.5, zoom = 12) %>%
addMiniMap()
To see more mapping options, you can check out the rest of the material in Chapter 8 of the online textbook. For example, this section goes into using leaflet and shiny to make interactive web apps.
These exercises rely on a new object, africa. Create it using the world and worldbank_df datasets from the spData package as follows:
africa = world %>%
filter(continent == "Africa", !is.na(iso_a2)) %>%
left_join(worldbank_df, by = "iso_a2") %>%
dplyr::select(name, subregion, gdpPercap, HDI, pop_growth) %>%
st_transform("+proj=aea +lat_1=20 +lat_2=-23 +lat_0=0 +lon_0=25")
We will also use zion and nlcd datasets from spDataLarge:
zion = read_sf((system.file("vector/zion.gpkg", package = "spDataLarge")))
nlcd = rast(system.file("raster/nlcd.tif", package = "spDataLarge"))
Create a map showing the geographic distribution of the Human
Development Index (HDI) across Africa with base
graphics (hint: use plot()) and
tmap packages (hint: use
tm_shape(africa) + ...).
Extend the tmap created for the previous
exercise so the legend has three bins: “High” (HDI above
0.7), “Medium” (HDI between 0.55 and 0.7) and “Low”
(HDI below 0.55).
Represent africa’s subregions on the map. Change the
default color palette and legend title. Next, combine this map and the
map created in the previous exercise into a single plot.
Create a land cover map of the Zion National Park.
us_states dataset.)Create facet maps of countries in Eastern Africa:
HDI and
pop_growth, respectively)Building on the previous facet map examples, create animated maps of East Africa:
Create an interactive map of Africa: