Post #32. ggplot Map Series No.5: Scale bar and north arrow with ggsn

2023 Map Series

Enhance your map with a scale bar and a north arrow using the package ggsn!

Gen-Chang Hsu
2023-12-24

Introduction

Have you ever created a map in ggplot and wondered how to add a scale bar and a north arrow to it? Well, you can do it easily using the package ggsn, and this is the topic for this short post.

An annotated topographic map of Taiwan

(1) Create a topographic map

We’ll create a topographic map of Taiwan as our example here. First, we’ll use the function get_elev_raster() from the package elevatr to retrieve the elevation tiles of Taiwan from the online map databases. Basically, what we need to do is to read in a shapefile of the area of interest and pass it to the function. It’ll return a “RasterLayer” object, which is then converted to a dataframe for plotting in ggplot.

library(tidyverse)

### Download and read in the shapefile of Taiwan's administrative boundary
### The zipped shapefile is available in the GitHub folder of this post
library(sf)

Taiwan_boundary_admin0_url <- "https://github.com/GenChangHSU/ggGallery/blob/master/_posts/2023-12-24-post-32-ggplot-map-series-no5-map-annotation-with-ggsn/geoBoundaries-TWN-ADM0-all.zip?raw=TRUE"
download.file(Taiwan_boundary_admin0_url, "geoBoundaries-TWN-ADM0-all.zip", mode = "wb")
unzip("geoBoundaries-TWN-ADM0-all.zip")

Taiwan_boundary_admin0 <- st_read("geoBoundaries-TWN-ADM0-all/geoBoundaries-TWN-ADM0.shp", quiet = T)

### Get the elevation tiles of Taiwan
library(elevatr)

Taiwan_elevation <- get_elev_raster(locations = Taiwan_boundary_admin0,  # the shapefile
                                    z = 9,  # z controls the resolution of the elevation tiles
                                    clip = "locations"  # clip the tiles to the polygon area
                                    ) %>%  
  terra::as.data.frame(xy = T, na.rm = T) %>%  # convert the RasterLayer to a dataframe
  `colnames<-`(c("lon", "lat", "elevation")) %>%  # rename the columns 
  mutate(elevation = if_else(elevation < 0 | is.na(elevation), 0, elevation))  # set the negative elevation values and NAs to 0

### Make the topographic map
library(tidyterra)  # for the function hypso.colors()
topo_pal <- hypso.colors(n = 10, palette = "dem_poster")  # a topographic color palette

Taiwan_topographic_map <- ggplot() + 
  geom_raster(data = Taiwan_elevation, aes(x = lon, y = lat, fill = elevation), show.legend = F) +
  scale_fill_gradientn(colors = topo_pal,
                       values = c(seq(0, 0.35, length.out = 5), seq(0.5, 1, length.out = 5))) +
  coord_sf(xlim = c(119, 123), ylim = c(21.5, 25.5)) +
  labs(x = NULL, y = NULL) + 
  theme_bw() + 
  theme(panel.grid = element_blank())

Taiwan_topographic_map

(2) Add a scale bar

It’s time to add a scale bar! ggsn has the function scalebar() for it. There are several things we can customize: the position of the scale bar, the distance and unit of the bar segment, the appearance of the bar and bar labels, etc. Let’s see how it works:

library(ggsn)

Taiwan_topographic_map_annotated <- Taiwan_topographic_map + 
  scalebar(x.min = 121.9, 
           x.max = 122.9,
           y.min = 21.6,
           y.max = 22.1,
           dist = 50,  # the distance of the bar segment
           dist_unit = "km",  # the unit of the bar segment
           transform = TRUE,  # "TRUE" if the coordinates are in the long-lat format
           model = "WGS84",  # ellipsoid model for the transformation if "transform = TRUE"
           height = 0.2,  # the height of the scale bar
           st.dist = 0.2,  # the distance between the scale bar and the bar labels
           st.size = 2.5,  # the size of the bar labels
           border.size = 0.25  # the width of the bar border
           )  

Taiwan_topographic_map_annotated

(3) Add a north arrow

Next, the north arrow. This is also easy-peasy: Just use the function north() and specify the position as well as the type of the arrow you like (there are various types of north arrows available; use northSymbols() to see them), and you’re all set! We’ll also add an “N” above the north arrow. It does take some experimentation to get the desired arrow position and size, so be patient!

Taiwan_topographic_map_annotated <- Taiwan_topographic_map_annotated + 
  north(x.min = 122.58, 
        x.max = 122.68, 
        y.min = 22.4, 
        y.max = 22.6, 
        symbol = 10,  # the type of the north arrow
        scale = 5  # the size of the north arrow
        ) + 
  annotate(geom = "text", x = 122.45, y = 22.5, label = "N", size = 5)

Taiwan_topographic_map_annotated

Done!

(4) Fun with map

Let’s play around with the map (a bit extra and just for fun)!

### A vector of individual characters in the title
title_vec <- str_split("A Topographic Map of Taiwan", pattern = "")[[1]]

### Italicize the letters
title_vec_italic <- if_else(title_vec == " ", title_vec, str_glue("_{title_vec}_"))

### A dataframe for mapping the characters to the plot
x_seq <- seq(119.2, 121.8, by = 0.1)
x_nudge <- c(-0.04, 0.00, 0.02, 0.02, -0.04, 0.00, -0.03, 0.00, 0.00, -0.03, 
             0.02, 0.01, 0.01, 0.00, 0.02, 0.07, 0.04, 0.00, 0.04, -0.01, 
             0.05, 0.05, 0.05, 0.05, 0.07, 0.09, 0.11)
x_position <- x_seq + x_nudge

title_df <- data.frame(x = x_position, 
                       y = 25.6, 
                       label = title_vec_italic)

### The font
library(showtext)
font_add_google("IM FELL English")
showtext_auto()

### The map
library(ggtext)

ggplot() + 
  geom_raster(data = Taiwan_elevation, aes(x = lon, y = lat, fill = elevation), show.legend = F) +
  geom_richtext(data = title_df, aes(x = x, y = y, label = label, color = x),
                fill = NA, 
                label.color = NA, 
                family = "IM FELL English",
                size = 7,
                show.legend = F) + 
  scale_fill_gradientn(colors = topo_pal,
                       values = c(seq(0, 0.35, length.out = 5), seq(0.5, 1, length.out = 5))) +
  scale_color_gradientn(colors = topo_pal, limits = c(NA, 122.5)) + 
  coord_sf(xlim = c(119, 123), ylim = c(21.5, 25.73)) +
  theme_void() + 
  theme(panel.background = element_rect(fill = "#FEFEDF", color = NA)) + 
  scalebar(x.min = 122.1, 
           x.max = 122.9,
           y.min = 21.6,
           y.max = 22.2,
           dist = 50,
           dist_unit = "km",
           transform = TRUE,
           model = "WGS84",
           height = 0.1,
           st.dist = 0.15,
           st.size = 4.5,
           border.size = 0.2,
           family = "IM FELL English") + 
  north(x.min = 122.51, 
        x.max = 122.61, 
        y.min = 22.2, 
        y.max = 22.4, 
        symbol = 10,
        scale = 4) + 
  annotate(geom = "text", x = 122.41, y = 22.35, label = "N", size = 7, family = "IM FELL English", fontface = "italic")

Summary

To summarize what we did, we used the package elevatr to get the elevation tiles and made a topographic map of Taiwan, and then we added a scale bar and a north arrow to the map using the scalebar() and north() function from the package ggsn. Now, it’s your turn to make use of what we learned here!

Hope you learn something useful from this post and don’t forget to leave your comments and suggestions below if you have any!

Corrections

If you see mistakes or want to suggest changes, please create an issue on the source repository.