Authors

Andree Valle Campos

Laure Vancauwenberghe

Published

December 22, 2024


1 Introduction

Until now, we have learnt general concepts about geospatial visualization in independent lessons. The modular approach of {ggplot2} allows to successively add all of them in different layers.

However, these enriched thematic maps also require to contain text and labels referring to specific places or regions, and important map elements like scale bars and a north arrow, as will be illustrated in this part.

Figure 1. Ggplot map with multiple layers.

In this lesson, we will learn to use functions from the {ggspatial} and {ggsflabel} packages to add all of these additional elements to our maps!


2 Learning objectives

  1. Access hospital data coordinates using the {afrihealthsites} package.

  2. Add text and labels to ggplot maps using the {ggsflabel} package.

  3. Add arrow and scale annotations in ggplot maps using the {ggspatial} package.


3 Prerequisites

This lesson requires the following packages:

Code
if(!require('pacman')) install.packages('pacman')

pacman::p_load(tidyverse,
               colorspace,
               ggspatial,
               janitor,
               ggplot2,
               readxl,
               spData,
               units,
               here,
               sf, 
               rgeoboundaries)

pacman::p_load_gh("yutannihilation/ggsflabel",
                  "afrimapr/afrihealthsites",
                  "afrimapr/afrilearndata")

4 Build an informative map

• How are Tier 3 Hospitals distributed in the Western Province of Sierra Leone?

• Retrieve real Hospital information from a public repository.

4.1 Access hospital sites with {afrihealthsites}

afrihealthsites() from {afrihealthsites}

• Coordinates of African health facilities from different sources.

• Health facilities in Sierra Leone available in the who database:

Code
sle_healthsites_all <- 
  afrihealthsites(country = "Sierra Leone", 
                  datasource = 'who',
                  plot = FALSE) %>% 
  janitor::clean_names()

sle_healthsites_all
Simple feature collection with 1120 features and 10 fields
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: -13.271 ymin: 6.9679 xmax: -10.3065 ymax: 9.9745
Geodetic CRS:  WGS 84
# A tibble: 1,120 × 11
   country      admin1  facility_name    facility_type ownership ll_source iso3c
 * <chr>        <chr>   <chr>            <chr>         <chr>     <chr>     <chr>
 1 Sierra Leone Eastern Ahmadiyya Missi… Mission Hosp… FBO       GPS       SLE  
 2 Sierra Leone Eastern Baama Community… Community He… MoH       GPS       SLE  
 3 Sierra Leone Eastern Baiama Communit… Community He… MoH       GPS       SLE  
 4 Sierra Leone Eastern Baiima Communit… Community He… MoH       GPS       SLE  
 5 Sierra Leone Eastern Baiwala Communi… Community He… MoH       GPS       SLE  
 6 Sierra Leone Eastern Bambara Kaima C… Community He… MoH       GPS       SLE  
 7 Sierra Leone Eastern Bambara Materna… Maternal & C… MoH       GPS       SLE  
 8 Sierra Leone Eastern Bambawulo Commu… Community He… MoH       GPS       SLE  
 9 Sierra Leone Eastern Bandajuma Commu… Community He… MoH       GPS       SLE  
10 Sierra Leone Eastern Bandajuma Kpoli… Community He… MoH       GPS       SLE  
# ℹ 1,110 more rows
# ℹ 4 more variables: facility_type_9 <chr>, tier <dbl>, tier_name <chr>,
#   geometry <POINT [°]>

Access to all the health facilities of Zimbabwe from the healthsites data source using the afrihealthsites() function.

• According to the three-tier health delivery classification, the highest tier (Tier 3) are responsible for most inpatient services as well as teaching and research missions (Wang, 2021).}

• Let’s keep Tier 3 health facilities inside the Western province:

Code
sle_healthsites_set <- 
  
  sle_healthsites_all %>%
  
  filter(admin1 == "Western Area") %>% # 👈👈👈👈👈👈👈👈👈👈👈👈👈👈👈
  filter(tier == 3)

sle_healthsites_set
Simple feature collection with 11 features and 10 fields
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: -13.2614 ymin: 8.3178 xmax: -13.0216 ymax: 8.4753
Geodetic CRS:  WGS 84
# A tibble: 11 × 11
   country      admin1     facility_name facility_type ownership ll_source iso3c
 * <chr>        <chr>      <chr>         <chr>         <chr>     <chr>     <chr>
 1 Sierra Leone Western A… 34 Military … Hospital      Govt.     Other     SLE  
 2 Sierra Leone Western A… Adventist He… Mission Hosp… FBO       GPS       SLE  
 3 Sierra Leone Western A… African Musl… Mission Hosp… FBO       GPS       SLE  
 4 Sierra Leone Western A… Cannaught Ho… Hospital      Govt.     GPS       SLE  
 5 Sierra Leone Western A… Iran Sierra … Hospital      NGO       GPS       SLE  
 6 Sierra Leone Western A… Lakka Govern… Hospital      Govt.     GPS       SLE  
 7 Sierra Leone Western A… Mercyship-Si… Hospital      NGO       GPS       SLE  
 8 Sierra Leone Western A… Princess Chr… Hospital      Govt.     GPS       SLE  
 9 Sierra Leone Western A… Rokupa Gover… Hospital      Govt.     GPS       SLE  
10 Sierra Leone Western A… Shepard Hosp… Mission Hosp… FBO       GPS       SLE  
11 Sierra Leone Western A… United Metho… Mission Hosp… FBO       GPS       SLE  
# ℹ 4 more variables: facility_type_9 <chr>, tier <dbl>, tier_name <chr>,
#   geometry <POINT [°]>

• These sites belong to Sierra Leone,

• Let’s get their province boundaries with {rgeoboundaries}:

Code
sierra_leone <- geoboundaries(country = "Sierra Leone", adm_lvl = 1)

• Let’s map sierra_leone and sle_healthsites_set:

Code
ggplot() +
  
  # country map
  geom_sf(data = sierra_leone) +
  
  # hospital points
  geom_sf(data = sle_healthsites_set)

• Let’s use coord_sf() to zoom in in area of interest:

Code
ggplot() +
  
  # country map
  geom_sf(data = sierra_leone) +
  
  # hospital points
  geom_sf(data = sle_healthsites_set) +
  
  # map extent
  coord_sf(xlim = c(-13.5,-12.7), ylim = c(8.0,8.7)) # 👈👈👈👈👈👈👈

sf objects can handle on the fly the coordinate system (both projection and extent), which can be very useful if more than one spatial objects are not in the same projection.

4.2 Add a Scale bar and North arrow with {ggspatial}

• Let’s use {ggspatial} with a {ggplot} map:

annotation_north_arrow() adds a north symbol, and

annotation_scale() a scale bar.

Code
ggplot() +
  
  # country map
  geom_sf(data = sierra_leone) +
  
  # hospital points
  geom_sf(data = sle_healthsites_set) +
  
  # map annotations
  annotation_north_arrow(location = "tl") + # 👈👈👈👈👈👈👈👈👈👈👈👈
  annotation_scale(location = "bl") +
  
  # map extent
  coord_sf(xlim = c(-13.5,-12.7), ylim = c(8.0,8.7))

location are by default in the bottom left ("bl")

location uses "tr" for top right, "bl" for bottom left, etc.

The zimbabwe_adm1 object contains the boundaries of all the provinces in Zimbabwe.

Code
zimbabwe_adm1 <- geoboundaries(country = "Zimbabwe", adm_lvl = 1) 

To this ggplot map with the zimbabwe_adm1 object:

Add a Scale bar located in the bottom right of the map, and a North arrow in the top left.

Code
q4 <- 
  zimbabwe_adm1 %>% 
  ggplot() + 
  geom_sf() +
  annotation_north_arrow(location = "tl") +
  annotation_scale(location = "br")

q4

• The North arrow style can be adjusted with style.

scale distance is set to "km" by default, you can also set it in “m”, “cm”, “mi”, “ft”, or “in”.

4.3 Add hospital names with geom_sf_text_repel()

• Use geom_sf_text() to add hospital locations and names as text:

Code
sle_healthsites_set %>% 
  ggplot() +
  # geometry
  geom_sf() +
  # label
  geom_sf_text(mapping = aes(label= facility_name)) # 👈👈👈👈👈👈👈

• This is not really satisfactory, as the names overlap on the points, and it’s not easy to read on the background.

{ggsflabel} inspired in {ggrepel}deal with label placement in ggplot maps with sf objects, including automated movement of labels in case of overlap.

geom_sf_text_repel and geom_sf_label_repel

• We “nudge” facility_name away, and connect them to the city locations:

Code
sle_healthsites_set %>% 
  ggplot() +
  # geometry
  geom_sf() +
  # label
  geom_sf_text_repel(aes(label = facility_name)) # 👈👈👈👈

zimbabwe_hospitals contains all the hospital facilities of Zimbabwe:

Code
zimbabwe_hospitals <- 
  afrihealthsites(country = 'Zimbabwe', 
                  datasource='healthsites',
                  plot = FALSE) %>% 
  filter(amenity == 'hospital')

zimbabwe_hospitals

Add the name of these facilities as text without overlaps using the geom_sf_text_repel() function.

Code
q5 <- 
  zimbabwe_hospitals %>% 
  ggplot() +
  geom_sf() +
  geom_sf_text_repel(aes(label = name))

q5

• We can use {ggrepel} arguments to improve its output:

• The size (argument size);

• The type of font (fontface);

• The force of repulsion between overlapping text labels (force);

• The additional padding around the each text label (box.padding).

Code
sle_healthsites_set %>% 
  ggplot() +
  # geometry
  geom_sf() +
  # label
  geom_sf_text_repel(mapping = aes(label= facility_name),
                     size = 2,         # 👈👈👈👈👈👈👈👈👈👈👈👈👈👈
                     fontface = "bold",
                     force = 40, 
                     box.padding = 0.6)

• Adding this layer to the current map, we obtain:

Code
ggplot() +
  
  # country map
  geom_sf(data = sierra_leone) +
  
  # hospital points 
  geom_sf(data = sle_healthsites_set) +         
  # hospital names with repelled text 
  geom_sf_text_repel(data = sle_healthsites_set,         # 👈👈👈👈👈
                     mapping = aes(label = facility_name),
                     size         = 2,
                     fontface     = "bold",
                     force        = 0.5,
                     box.padding  = 0.6,
                     nudge_x      = -0.25,
                     direction    = "y",
                     hjust        = 1,
                     segment.size = 0.2) +
  
  # map annotation
  annotation_north_arrow(location="tr") +
  annotation_scale(location="bl") +
  
  # map extent
  coord_sf(xlim = c(-13.5,-12.7), 
           ylim = c(8.0,8.7))

4.4 Add province labels with geom_sf_label_repel()

• Let’s add names of administrative borders, available in shapeName:

Code
sierra_leone %>% 
  ggplot() +
  
  # country map
  geom_sf() +
  
  # province labels
  geom_sf_label_repel(aes(label = shapeName)) # 👈👈👈👈👈👈👈👈👈👈👈

The zimbabwe_adm1 object contains the boundaries of all the provinces in Zimbabwe.

Code
zimbabwe_adm1 <- geoboundaries(country = "Zimbabwe", adm_lvl = 1)

Create a ggplot map of Zimbabwe with labels for the name of each of its provinces.

Code
q6 <- 
  zimbabwe_adm1 %>% 
  ggplot() + 
  
  # country map
  geom_sf() + 
  
  # province labels
  geom_sf_label_repel(aes(label = shapeName))

q6

• Let’s drop Eastern label using filter() from {dplyr}:

Code
ggplot() +
  
  # country map
  geom_sf(data = sierra_leone) +
  
  # province labels
  geom_sf_label_repel(
  data = sierra_leone %>% filter(shapeName != "Eastern"),
  mapping = aes(label = shapeName)
) +         # 👈👈👈👈👈👈
  
  # hospital points 
  geom_sf(data = sle_healthsites_set) +         
  # hospital names with repelled text 
  geom_sf_text_repel(data = sle_healthsites_set,
                     mapping = aes(label = facility_name),
                     size         = 2,
                     fontface     = "bold",
                     force        = 0.5,
                     box.padding  = 0.6,
                     nudge_x      = -0.25,
                     direction    = "y",
                     hjust        = 1,
                     segment.size = 0.2) +
  
  # map annotation
  annotation_north_arrow(location="tl") +
  annotation_scale(location="bl") +
  
  # map extent
  coord_sf(xlim = c(-13.5,-12.7), ylim = c(8.0,8.7))


5 Final map

• Let’s put everything together:

• Adding district boundaries filled by area, and field sited recorded by a GPS device:

Code
# districts
sierra_leone_shp <- 
  sf::read_sf(here("data/boundaries/sle_adm3.shp")) %>% 
  filter(admin1Name=="Western")

# field site points
sites_sf <- readr::read_csv(
  here("data/gps_healthsites.csv")) %>% 
  st_as_sf(coords = c("gps_longitude","gps_latitude"), crs = 4326)
Code
ggplot() +
  
  # country map
  geom_sf(data = sierra_leone) +
  
  # districts filled by area
  geom_sf(data = sierra_leone_shp, # 👈👈👈👈👈👈👈👈👈👈👈👈👈👈👈
          mapping = aes(fill = area_km2)) +
  # color scale for fill
  scale_fill_continuous_sequential(palette="Reds 3", 
                                   alpha = 0.8) +
  # field site points
  geom_sf(data = sites_sf,              # 👈👈👈👈👈👈👈👈👈👈👈👈👈
          mapping = aes(color = gps_name),
          shape = 18, size = 4) +
  
  # province names with repelled labels
  geom_sf_label_repel(data = sierra_leone %>% 
                        filter(shapeName!="Eastern"),
                      mapping = aes(label=shapeName)) +
  
  # hospital points 
  geom_sf(data = sle_healthsites_set) +         
  # hospital names with repelled text 
  geom_sf_text_repel(data = sle_healthsites_set,
                     mapping = aes(label = facility_name),
                     size         = 2,
                     fontface     = "bold",
                     force        = 0.5,
                     box.padding  = 0.6,
                     nudge_x      = -0.25,
                     direction    = "y",
                     hjust        = 1,
                     segment.size = 0.2) +
  
  # map annotation
  annotation_north_arrow(location="tl") +
  annotation_scale(location="bl") +
  
  # map extent
  coord_sf(xlim = c(-13.5,-12.7), 
           ylim = c(8.0,8.7)) +
  
  # ggplot labels
  labs(x = "Longitude",
       y = "Latitude",
       fill = expression(Area~km^2),
       color = "GPS data",
       title = "How are Tier 3 Hospitals distributed in the Western Province of Sierra Leone?")


6 Wrap up

• Adding layers on ggplot2 is relatively straightforward, as long as the data is properly stored in an sf object.

• Adding additional layers like point or polygon labels and map annotations would simply follow the same logic, with additional calls after geom_sf() and at the right place in the ggplot2 sequence.


Contributors

The following team members contributed to this lesson:


References

Some material in this lesson was adapted from the following sources:

This work is licensed under the Creative Commons Attribution Share Alike license. Creative Commons License