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")

Buy Me A Coffee

Dr. Dominic Royé
Dr. Dominic Royé
Researcher and responsible for data science

Related