Inserted maps with ggplot2
Today I present a short post on how we can position an outermost territory near the main map or insert an orientation map. In this example we use the typical map of Spain where the Canary Islands are located in the southwest of the peninsula.
Packages
Paquete | Descripción |
---|---|
tidyverse | Collection of packages (visualization, manipulation): ggplot2, dplyr, purrr, etc. |
mapSpain | Administrative boundaries of Spain at different levels |
sf | Simple Feature: import, export and manipulate vector data |
giscoR | Administrative boundaries of the world |
patchwork | Simple grammar to combine separate ggplots into the same graphic |
rmapshaper | mapshaper library client for geospatial operations |
# install the packages if necessary
if(!require("tidyverse")) install.packages("tidyverse")
if(!require("mapSpain")) install.packages("mapSpain")
if(!require("sf")) install.packages("sf")
if(!require("giscoR")) install.packages("giscoR")
if(!require("patchwork")) install.packages("patchwork")
if(!require("rmapshaper")) install.packages("rmapshaper")
# packages
library(sf)
library(giscoR)
library(mapSpain)
library(tidyverse)
library(patchwork)
library(rmapshaper)
Option 1
We can easily find some administrative boundaries of states such as Spain, where the actual geographical position of a remote territory has been changed, such as the Canary Islands. The default mapSpain
package shifts the islands to the southwest of the Iberian Peninsula, a common position we see in many maps. However, these vector boundaries with displacement cannot be used in all assumptions, as this is a false geographical position and is not suitable for spatial calculations or other projections.
We obtain the vector boundaries using the esp_get_prov()
function for the provincial level with the projection code EPSG:4326 (WGS84). In the construction of the map via ggplot2
we simply add the object to the geom_sf()
geometry specifically designed for handling vector objects of class sf
.
# province boundaries with Canary Islands displacement
esp <- esp_get_prov(epsg = 4326)
# simple map
ggplot(esp) +
geom_sf(colour = "white", linewidth = .2) +
theme_void()
mapSpain
also includes a function to get the separator box (esp_get_can_box()
) in order to indicate the false location. With the gisco_get_countries()
function we get the global country boundaries to add as geographical context, although we clipped it to the extent of the Iberian Peninsula. It may be surprising to see a curved cutout using the WGS84 projection, but this is because spherical geometry is used by default in all sf
operations (sf_use_s2()
).
# we add the Canary Islands box and the boundaries of the environment
can_bx <- esp_get_can_box(epsg = 4326)
entorno <- gisco_get_countries(resolution = "10") %>%
st_crop(xmin = -10, xmax = 5, ymin = 34, ymax = 45)
# with displacement
ggplot(esp) +
geom_sf(data = entorno, fill = "grey70", colour = NA) +
geom_sf(colour = "white", linewidth = .2) +
geom_sf(data = can_bx, linewidth = .3, colour = "grey80") +
theme_void()
Option 2
The most correct way is to create an object for the inserted map, here the Canary Islands, and another one for the main map, mainland Spain and the Balearic Islands. In the esp_get_prov()
function we must indicate that it returns the limits without displacement with the agrument moveCAN = FALSE
. First, we build the map of the Canary Islands, filtering the autonomous community. The geometries geom_hline()
and geom_vline()
will draw the separation line to the peninsula. The second step is to create the main map excluding the Canary Islands. Then the map of the Canary Islands needs to be included as an object using the annotation_custom()
function. The ggplot
object must be converted to a grob with ggplotGrob()
and the position area (the X and Y extreme points) must be indicated in the coordinate system of the main map. This form can be used for all types of maps.
# boundaries of provinces without displacement of the Canary Islands
esp <- esp_get_prov(epsg = 4326, moveCAN = F)
# Canary Island map
can <- filter(esp, nuts2.name == "Canarias") %>%
ggplot() +
geom_vline(xintercept = -13.3, colour = "grey80") +
geom_hline(yintercept = 29.5, colour = "grey80") +
geom_sf(fill = "red", colour = "white") +
coord_sf(expand = F) +
theme_void()
can
# add ggplot map with annotation_custom() absolute position according to the SRC
filter(esp, nuts2.name != "Canarias") %>%
ggplot() +
geom_sf(data = entorno, fill = "grey70", colour = NA) +
geom_sf(colour = "white", linewidth = .2) +
annotation_custom(ggplotGrob(can),
xmin = -14, xmax = -9,
ymin = 33, ymax = 38) +
theme_void()
If we want to project the main map, we only need to project the position area of the inserted map first.
# position box with some adjustment
pos <- c(xmin = -13.5, ymin = 32.5, xmax = -8.5, ymax = 37.5)
class(pos) <- "bbox" # definimos como bbox
# reproject to LAEA Europe EPSG:3035
pos_prj <- st_as_sfc(pos) %>%
st_set_crs(4326) %>%
st_transform(3035) %>%
st_bbox()
# create the final map
filter(esp, nuts2.name != "Canarias") %>%
ggplot() +
geom_sf(data = entorno, fill = "grey70", colour = NA) +
geom_sf(colour = "white", linewidth = .2) +
annotation_custom(ggplotGrob(can),
xmin = pos_prj[1], xmax = pos_prj[3],
ymin = pos_prj[2], ymax = pos_prj[4]) +
coord_sf(crs = 3035) +
theme_void()
Option 3
The last option for inserting a secondary map is to use the inset_element()
function of the patchwork
package. The difference with the previous method is the relative position, which limits the use. In this case proportional symbols should not be represented as the relative insertion does not maintain the same dimensions as the main map.
# provincial boundaries
esp <- esp_get_prov(epsg = 4326, moveCAN = F)
# Canary Island map
can <- filter(esp, nuts2.name == "Canarias") %>%
ggplot() +
geom_vline(xintercept = -13.3, colour = "grey80") +
geom_hline(yintercept = 29.5, colour = "grey80") +
geom_sf(fill = "red", colour = "white") +
coord_sf(expand = F) +
theme_void()
# main map
m <- filter(esp, nuts2.name != "Canarias") %>%
ggplot() +
geom_sf(colour = "white", linewidth = .2) +
theme_void()
# insert with relative position
m + inset_element(can, left = -.1, bottom = 0,
right = .2, top = .2,
align_to = "full")
Earth globe as inset map
The only difficulty here is the orthogonal projection while preserving the visible geometry of the earth.
The first step is the creation of the “ocean” using the radius of the earth from the point 0,0. Then we only have to cut out the visible part and reproject the boundaries. In the definition of the orthogonal projection it is possible to centre at different latitudes and longitudes by changing the +lat_0
and +lon_0
values. The ms_innerlines()
functions of the rmapshaper
package easily create the inner boundaries of polygons, which is recommended to avoid blurring small areas.
# overall country boundaries
wld <- gisco_get_countries(resolution = "20")
# definition of orthogonal projection
ortho_crs <-'+proj=ortho +lat_0=30 +lon_0=0.5 +x_0=0 +y_0=0 +R=6371000 +units=m +no_defs +type=crs'
# creation of the ocean
ocean <- st_point(x = c(0,0)) %>%
st_buffer(dist = 6371000) %>% # radio Tierra
st_sfc(crs = ortho_crs)
plot(ocean)
# cut out the visible land and reproject it
world <- st_intersection(wld, st_transform(ocean, 4326)) %>%
st_transform(crs = ortho_crs) %>%
mutate(dummy = ifelse(NAME_ENGL == "Spain", "yes", "no"))
plot(world)
# obtain only the inner limits
world_line <- ms_innerlines(world)
plot(world_line)
# main map of Spain
wld_map <- ggplot(world) +
geom_sf(data = ocean, fill = "#deebf7", linewidth = .2) +
geom_sf(aes(fill = dummy),
colour = NA,
show.legend = F) +
geom_sf(data = world_line, linewidth = .05, colour = "white") +
scale_fill_manual(values = c("grey50", "red")) +
theme_void()
# insert the globe marking the location of Spain
m + inset_element(wld_map, left = 0.65, bottom = 0.82, right = 1.1, top = 1, align_to = "full")