How to create ggplot maps with mapbox images as background.
Author
Roland Schmidt
Published
25 Nov 2020
1 Context
Partly triggered by my last post on replicating NYT’s electoral maps, I came across the nascent/experimental snapbox package (link) by Anthony North and Miles McBain. The package provides a convenient way to add mapbox tiles as basemaps to maps created with ggplot (did I mention ‘maps’?`).
The package is quite straightforward and not much to explain about. What caused me some hassles though was to figure out how to combine the basemap with my data, plotted with geom_segment or geom_sf. The critical issue here is to ensure that the maps projection of the coordiante system are the same. Hence, this post is somewhat a note to myself, graphs are not polished and I don’t dig into substantive matters. For the purpose of demonstration, I’ll use electoral data from the city of Vienna and want to highlight how the number of eligible voters per precinct (‘Wahlsprengel’) changed between 2015 and 2020.
2 SETUP
Code: Load packages
library(snapbox)library(sf)library(tidyverse)#library(magick)knitr::knit_hooks$set(crop = knitr::hook_pdfcrop) #allows to crop white frame from ggplots.#for details see https://bookdown.org/yihui/rmarkdown-cookbook/crop-plot.htmlplot_bg_color <-"white"
3 ELECTORAL DATA
Get the election results from 2015 and 2020, retrieve the number of eligible voters per precinct (`wber``), and calculate the change.
Code: Get electoral data + change in number of elegible voters
Let’s have a quick look at the distribution of the change rates:
Code: Distribution of changes
library(ggridges)pl_dist <- df_wber_diff %>%ggplot()+ ggridges::geom_density_line(aes(x=wber_diff_rel))+labs(x="% change number of eligible voters 2020 vs 2015")+theme(axis.text.y=element_blank())table(df_wber_diff$change_indicator)
decrease increase stable
978 451 54
decrease increase stable
978 451 54
So, while the purpose of this post is not to dig into substantive matters, I was a bit surprised to see that a large majority of precincts features a decreased number of eligible voters.
Now let’s calculate the center of each precinct. Note that I transform the coordination projection from WGS84 to 3857. The latter is ‘a Spherical Mercator projection coordinate system popularized by web services such as Google and later OpenStreetMap.’1 After the transformation I take the x and y values from the centroids’ geometry. The function st_centroid gives us the centre of each polygon.
Calculate the length of the arrow indicating the change: Here the arrows should have an angle of 20 degrees; positive changes point towards the right, negative changes towards the left. With the sin and cos function we calculate the horizontal and vertical distance from the centroids. In addition, to make the arrows sufficiently long to be visible, I multiply them with a scale factor. Note that I add coord_sf() to the plot which ‘ensures that all layers use a common CRS.’2
Code: Calculate length of arrows indicating change
Now, let’s add a mapbox basemap by using the layer_mapbox function of snapbox. To so, we first have to define the scope or boundary of the basemap. I do this by extracting the minimum and maximum x and y values from our map of the precincts. And then create a bbox object. Note that this boundary box is has the 4326 projection. The basemap for this boundary box is then added to the plot by using the layer_mapbox function. Note that you need an API-key to access basemaps of mapbox.
Et voila.
Code: Add mapbox basemap
#get boundaries of plot#map boundaries: get max/min x, y valuesd <- precincts$geometryd <-attributes(d)$bboxmy_xmin <- d[[1]]my_ymin <- d[[2]]my_xmax <- d[[3]]my_ymax <- d[[4]]my_xmin
You can quickly change the background image by specifying a different map_style value, e.g. below I use the decimal style. And also combine with a different aesthetic, e.g. using fill to color the precincts according to their respective change.