geom_sf legend with multiple map layers

Hello,
I am trying to create a map in ggplot with the following 3 layers:

  1. county fill and outline set to a categorical variable
  2. roads from open street maps
  3. rivers from open street maps

Currently I am able to create this map, but can't properly add an additional legend which includes the river and road features. Apologies for the large open street maps data download. I wanted to create a repex with an existing sf dataset, and the one I was able to find was for the entire state of North Carolina. Any help with this would be much appreciated!

# Repex for ggplot legend with multiple geom_sf layers
library(sf)
library(osmextract)
library(dplyr)
library(ggplot2)
library(stringr)
library(RColorBrewer)

# Using nc data because it is included with sf package
nc <- st_read(system.file("shape/nc.shp", package="sf"))

# Create random categorical variable for example county fill
nc <- nc %>% 
  mutate(bir74_cat = case_when(BIR74 < 1077 ~ "Low",
                               BIR74 >= 1077 & BIR74 < 2180.5 ~ "Low-Medium",
                               BIR74 >= 2180.5 & BIR74 < 3936 ~ "Medium-High",
                               BIR74 >= 3936 ~ "High"),
         bir74_cat = factor(bir74_cat,
                            levels = c("Low",
                                       "Low-Medium",
                                       "Medium-High",
                                       "High")))

# Get osmdata for the state
nc_lines <- oe_get("North Carolina", stringsAsFactors = FALSE, quiet = TRUE)

# Filter to highway df
nc_highway <- nc_lines %>% 
  filter(str_detect(name, "Highway|Freeway"))

# Filter to river df
nc_river <- nc_lines %>% 
  filter(waterway == "river")

# Restrict highway to state boundary
nc_highway_crs <- st_transform(nc_highway, 4267)
nc_highway_map <- st_intersection(nc, nc_highway_crs)

# Restrict rivers to state boundary
nc_river_crs <- st_transform(nc_river, 4267)
nc_river_map <- st_intersection(nc, nc_river_crs)

# Map fill and line color
map_colors <- brewer.pal(n = 4, "YlOrRd")
names(map_colors) <- c("Low", "Low-Medium", "Medium-High",
                     "High")

ggplot() +
  geom_sf(nc,
          mapping = aes(fill = bir74_cat,
                        color = bir74_cat)) +
  geom_sf(nc_river_map,
          mapping = aes(),
          color = "blue",
          fill = NA) +
  geom_sf(nc_highway_map,
          mapping = aes(),
          color = "black",
          fill = NA) +
  scale_fill_manual(values = map_colors) +
  scale_color_manual(values = map_colors) +
  theme_void() +
  labs(title = "Reproducible Example") +
  theme(legend.position = "bottom")
1 Like

Well I spent way too much time on troubleshooting this but I was able to solve it myself. There were several key steps.

  1. combining the river and road lines into one dataframe, and adding a categorical variable to indicate the line type (River or Road).
  2. Using new_scale_color() from the package ggnewscale allowed me to add a new manual color scale to the rive / road categorical variable. This package is great but frustrating when trying to modify legend elements. The issue is color within guides references two different legends. eg. guides(color = ...) doesn't function properly because we have two legends that both specify color. It sounds like the relayer package (GitHub - clauswilke/relayer: Rethinking layers in ggplot2) may get around this but I didn't try it. This was mainly an issue when trying to reorder which legend is displayed first. It also meant that I had to name my legends by making the color variable name a string eg. Number Births 1974.
  3. I used key_glyph = "smooth" to changed the legend glyph from a fill box to a horizontal line. I also used theme(legend.position = "bottom", legend.box="vertical", legend.margin=margin()) to stack the legends on top of one another.

Here is the complete modified repex:

# Repex for ggplot legend with multiple geom_sf layers
library(sf)
library(osmextract)
library(dplyr)
library(ggplot2)
library(stringr)
library(RColorBrewer)
library(ggnewscale)

nc <- st_read(system.file("shape/nc.shp", package="sf"))
nc <- nc %>% 
  mutate(bir74_cat = case_when(BIR74 < 1077 ~ "Low",
                               BIR74 >= 1077 & BIR74 < 2180.5 ~ "Low-Medium",
                               BIR74 >= 2180.5 & BIR74 < 3936 ~ "Medium-High",
                               BIR74 >= 3936 ~ "High"),
         bir74_cat = factor(bir74_cat,
                            levels = c("Low",
                                       "Low-Medium",
                                       "Medium-High",
                                       "High"))) %>% 
  rename(`Number Births 1974` = bir74_cat)

nc_lines <- oe_get("North Carolina", stringsAsFactors = FALSE, quiet = TRUE)

nc_highway <- nc_lines %>% 
  filter(str_detect(name, "Highway|Freeway"))

nc_river <- nc_lines %>% 
  filter(waterway == "river")

nc_highway_crs <- st_transform(nc_highway, 4267)
nc_highway_map <- st_intersection(nc, nc_highway_crs)
nc_highway_map <- nc_highway_map %>% 
  mutate(Line = "Road") %>% 
  select(geometry, Line)

nc_river_crs <- st_transform(nc_river, 4267)
nc_river_map <- st_intersection(nc, nc_river_crs)
nc_river_map <- nc_river_map %>% 
  mutate(Line = "River") %>% 
  select(geometry, Line)

nc_osm_features <- rbind(nc_highway_map,
                         nc_river_map)

map_colors <- brewer.pal(n = 4, "YlOrRd")
names(map_colors) <- c("Low", "Low-Medium", "Medium-High",
                     "High")

line_colors <- c("Black", "Blue")
names(line_colors) <- c("Road", "River")

ggplot() +
  geom_sf(nc,
          mapping = aes(fill = `Number Births 1974`,
                        color = `Number Births 1974`)) +
  scale_fill_manual(values = map_colors) +
  scale_color_manual(values = map_colors) +
  new_scale_color() +
  geom_sf(nc_osm_features,
          mapping = aes(color = Line),
          fill = NA,
          key_glyph = "smooth") +
  scale_color_manual(values = line_colors) +
  theme_void() +
  labs(title = "Reproducible Example") +
  theme(legend.position = "bottom",
        legend.box="vertical",
        legend.margin=margin())
1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.