Classwork 10

A step-by-step guide using ggmap::get_stadiamap(), osrm, osmdata, and tidygeocoder

Author

Byeong-Hak Choe

Published

April 23, 2025

Modified

April 23, 2025

Full code chunk

#–– package installation ––-----------------------------------------------------
# install.packages(c("osmdata", "osrm"))      # ← run once, then comment-out

library(sf)          # simple-features objects & spatial ops
library(ggmap)       # basemap tiles + ggplot2 integration
library(tidyverse)   
library(ggthemes)    # extra ggplot2 themes (incl. theme_map)
library(osmdata)     # bounding boxes
library(tidygeocoder) # tidy-style geocoding (Nominatim by default)
library(ggrepel)     
library(ggimage)     # drop-in PNG icons
library(showtext)    # Google-font rendering inside R graphics

#–– fonts ––--------------------------------------------------------------------
font_add_google("Alegreya Sans", "aleg")       
font_add_google("Annie Use Your Telescope", "annie")
font_add_google("Pacifico", "pacifico")        
showtext_auto()                                # enable showtext globally

#–– Stadia Maps key ––---------------------------------------------------------
stadia_api <- "YOUR_STADIA_API"     # replace w/ your key
register_stadiamaps(stadia_api, write = TRUE)            # store in env file

#–– Boundary box  --------------------------------------------------------------
bbox <- getbb("Manhattan, NY")   # returns 4-value named vector (xmin,…)

#–– Fetch basemap tiles ––------------------------------------------------------
man_map <- get_stadiamap(
  bbox,
  zoom    = 14,                 # ↑ zoom = ↑ detail (and ↑ tiles & time)
  maptype = "stamen_terrain"    # other options: stamen_toner, stamen_watercolor, …
)

#–– Geocode landmarks ––--------------------------------------------------------
locations <- 
  tibble(address = c("Times Square", "Central Park")) |>
  geocode(address)               # adds long / lat columns (CRS 4326)

# osrm & ggmap both attach a function named “mutate()”; avoid a clash by
# not attaching osrm – instead, call it with osrm:: prefix.
# (If you *must* attach osrm, load it *before* ggmap.)

# Pull lon/lat into a plain matrix expected by osrmRoute()
coords_mat <- as.matrix(locations[, c("long","lat")])   # 2×2 matrix

#–– Walking route ––------------------------------------------------------------
route_foot <- osrm::osrmRoute(
  src      = coords_mat[1, ],   # Times Square  (lon, lat)
  dst      = coords_mat[2, ],   # Central Park (lon, lat)
  overview = "full",            # full-resolution polyline
  osrm.profile = "foot"         # routing profile: foot / bike / car
) |>
  mutate(mode = "Foot")         # tag for legend

#–– Driving route ––------------------------------------------------------------
route_car <- osrm::osrmRoute(
  src      = coords_mat[1, ],
  dst      = coords_mat[2, ],
  overview = "full",
  osrm.profile = "car"
) |>
  mutate(mode = "Car")

# combine & prettify
routes <- rbind(route_foot, route_car) |>
  mutate(duration = round(duration, 2),                  # minutes
         mode = str_c(mode, "\n", duration, " min."))    # legend label

#–– Build plotting extent ––----------------------------------------------------
bb_routes <- st_bbox(routes)        # xmin, ymin, xmax, ymax (degrees)
pad_x <- (bb_routes$xmax - bb_routes$xmin) * 0.25   # 25 % padding
pad_y <- (bb_routes$ymax - bb_routes$ymin) * 0.25

xlim <- c(bb_routes$xmin - pad_x, bb_routes$xmax + pad_x)
ylim <- c(bb_routes$ymin - pad_y, bb_routes$ymax + pad_y)

# optional bbox for get_stadiamap()
bb_from_limits <- matrix(
  c(xlim["xmin"], xlim["xmax"],   # x-row (min, max)
    ylim["ymin"], ylim["ymax"]),  # y-row (min, max)
  nrow = 2, byrow = TRUE,
  dimnames = list(c("x", "y"), c("min", "max"))
)

#–– Plot ––---------------------------------------------------------------------
icon_url <- "https://bcdanl.github.io/lec_figs/marker-icon-red.png"  # pin PNG

ggmap(man_map) +
  geom_sf(
    data = routes,
    aes(color = mode),
    inherit.aes = FALSE,   # don’t map long/lat twice
    linewidth = rel(2),
    alpha = 0.75
  ) +
  geom_image(
    data = locations,
    aes(x = long, y = lat, image = icon_url)
  ) +
  geom_label_repel(
    data = locations,
    aes(long, lat, label = address),
    box.padding = 1.75,
    family = "pacifico"
  ) +
  coord_sf(                      # keep CRS 4326; clamp to padded bbox
    crs   = st_crs(4326),
    xlim  = xlim,
    ylim  = ylim,
    expand = FALSE
  ) +
  scale_color_colorblind() +
  guides(
    color = guide_legend(
      title.position = "top",
      keywidth = rel(3)
    )
  ) +
  labs(
    color    = "",
    title    = "Car vs. Foot",
    subtitle = "How would you like to go\nfrom Time Square to Central Park?",
    caption  = "Source: OpenStreetMap (OSM) and Open Source Routing Machine (OSRM)"
  ) +
  theme_map() +
  theme(
    legend.position  = "top",
    plot.title       = element_text(face = "bold",         size = rel(3.25)),
    plot.subtitle    = element_text(face = "bold.italic",  size = rel(2)),
    legend.text      = element_text(size = rel(2))
  )

Overview

This walk-through shows how to

  1. Geocode landmark addresses with tidygeocoder.
  2. Generate walking vs. driving routes between them via the public OSRM server.
  3. Fetch a Stadia Maps basemap (which plays nicely with ggmap).
  4. Assemble everything into a map using ggplot.

Why Stadia Maps?

  • get_stadiamap() is a wrapper around Stadia’s static-tile service that behaves like the classic get_map() helper in ggmap, but without requiring a Google billing account.

  • You still need an API key (free tier available), which we register once with register_stadiamaps().

Package & font setup

  • Spatial basics: sf, osmdata
  • Maps & plotting: ggmap, ggthemes, ggimage, ggrepel
  • Routing: only the namespaced call osrm::osrmRoute() (avoids the namespace clash that happens if you attach osrm after ggmap)

Bounding-box padding logic

Since ggmap() crops the map tiles to the bounding box you provide, here we add a 25% buffer (pad_x, pad_y) to prevent the route lines from sitting too close to the edges. We can adjust this buffer if we want more space for elements like a legend or title.

Fetching the basemap

  • maptype can be any of Stadia’s Stamen styles (“stamen_terrain”, “stamen_toner”, “stamen_watercolor”, …).
  • Higher zoom = more tiles = slower but sharper.
Back to top