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.
|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)
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
# 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 (
# 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()
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_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, xmax = pos_prj, ymin = pos_prj, ymax = pos_prj) + coord_sf(crs = 3035) + theme_void()
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
+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")