#–– 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))
)Classwork 10
A step-by-step guide using ggmap::get_stadiamap(), osrm, osmdata, and tidygeocoder
Full code chunk

Overview
This walk-through shows how to
- Geocode landmark addresses with
tidygeocoder.
- Generate walking vs. driving routes between them via the public OSRM server.
- Fetch a Stadia Maps basemap (which plays nicely with
ggmap).
- 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 classicget_map()helper inggmap, 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 attachosrmafterggmap)
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
maptypecan be any of Stadia’s Stamen styles (“stamen_terrain”, “stamen_toner”, “stamen_watercolor”, …).- Higher
zoom= more tiles = slower but sharper.