#–– 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 ––---------------------------------------------------------
<- "YOUR_STADIA_API" # replace w/ your key
stadia_api register_stadiamaps(stadia_api, write = TRUE) # store in env file
#–– Boundary box --------------------------------------------------------------
<- getbb("Manhattan, NY") # returns 4-value named vector (xmin,…)
bbox
#–– Fetch basemap tiles ––------------------------------------------------------
<- get_stadiamap(
man_map
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()
<- as.matrix(locations[, c("long","lat")]) # 2×2 matrix
coords_mat
#–– Walking route ––------------------------------------------------------------
<- osrm::osrmRoute(
route_foot 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 ––------------------------------------------------------------
<- osrm::osrmRoute(
route_car src = coords_mat[1, ],
dst = coords_mat[2, ],
overview = "full",
osrm.profile = "car"
|>
) mutate(mode = "Car")
# combine & prettify
<- rbind(route_foot, route_car) |>
routes mutate(duration = round(duration, 2), # minutes
mode = str_c(mode, "\n", duration, " min.")) # legend label
#–– Build plotting extent ––----------------------------------------------------
<- st_bbox(routes) # xmin, ymin, xmax, ymax (degrees)
bb_routes <- (bb_routes$xmax - bb_routes$xmin) * 0.25 # 25 % padding
pad_x <- (bb_routes$ymax - bb_routes$ymin) * 0.25
pad_y
<- c(bb_routes$xmin - pad_x, bb_routes$xmax + pad_x)
xlim <- c(bb_routes$ymin - pad_y, bb_routes$ymax + pad_y)
ylim
# optional bbox for get_stadiamap()
<- matrix(
bb_from_limits c(xlim["xmin"], xlim["xmax"], # x-row (min, max)
"ymin"], ylim["ymax"]), # y-row (min, max)
ylim[nrow = 2, byrow = TRUE,
dimnames = list(c("x", "y"), c("min", "max"))
)
#–– Plot ––---------------------------------------------------------------------
<- "https://bcdanl.github.io/lec_figs/marker-icon-red.png" # pin PNG
icon_url
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 attachosrm
afterggmap
)
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.