An interactive map
InteractiveMap.RmdIntroduction
The ExploreDataSets vignette walks through building
static ggplot2 maps of the collection route. One of the
stated advantages of the data set is that it follows a sequential series
of events, which lends itself just as well to an interactive treatment —
a reader can pan the Sea of Cortez, click a stop to see its name in
Spanish and English, and trace the Western Flyer’s path day by
day.
Here we build a leaflet map with a time slider (via
leaflet.extras2::addTimeslider) that reveals the collection
stops progressively by arrival date. The full route stays drawn in the
background as visual context, colored by arrival date, while the
play/scrub control lets a reader replay the expedition day by day.
Preparing the data
The route object only carries a destination
field, so we join it to places to bring the arrival date
onto each segment — the same pattern used in the static vignette.
route <- left_join(
route,
st_drop_geometry(places),
by = c('destination' = 'location_english')
) |>
relocate(geometry, .after = last_col())Building the map
We use a continuous palette on date_arrive for the route
segments, and a small handful of quantile-based tick marks for the
legend so the labels stay readable.
date_domain <- as.numeric(route$date_arrive)
pal <- colorNumeric(palette = 'viridis', domain = date_domain, na.color = 'transparent')
legend_breaks <- quantile(date_domain, probs = c(0.1, 0.5, 0.9), na.rm = TRUE)
legend_labels <- format(as.Date(legend_breaks, origin = '1970-01-01'), '%b %d')A few notes on interaction: label is the small text that
appears on hover, popup is the richer HTML card
that appears on click, and the time slider (top-right of the
map, once knit) is a two-handle range slider that filters which markers
are shown between a start and end date.
addTimeslider expects a single time attribute per
feature and — under the hood — caps the slider at the count of
distinct times minus one. Because a few stops share an arrival
date (e.g. two places both dated March 19), we synthesize per-feature
unique timestamps by adding a small seconds offset within same-date
groups. That keeps the slider from stranding the last few features. We
also set timeStrLength = 10 so only the date portion is
displayed in the slider readout.
places <- places |>
arrange(date_arrive) |>
group_by(date_arrive) |>
mutate(seq = row_number() - 1L) |>
ungroup() |>
mutate(
arrive_str = format(date_arrive, '%b %d, %Y'),
depart_str = format(date_depart, '%b %d, %Y'),
stay = ifelse(
date_arrive == date_depart,
arrive_str,
paste(arrive_str, '–', depart_str)
),
time = format(as.POSIXct(date_arrive) + seq, '%Y-%m-%dT%H:%M:%S')
) |>
select(-seq)
leaflet(width = '100%', height = '650px') |>
addProviderTiles(providers$Esri.OceanBasemap) |>
addPolylines(
data = route,
color = ~pal(as.numeric(date_arrive)),
weight = 3,
opacity = 0.9,
label = ~paste0(destination, ' · ', format(date_arrive, '%b %d'))
) |>
addTimeslider(
data = places,
radius = 5,
color = '#222823',
fillColor = '#D35269',
fillOpacity = 0.9,
weight = 1,
label = ~paste0(location_english, ' · ', format(date_arrive, '%b %d')),
popup = ~paste0(
'<b>', location_english, '</b><br/>',
'<i>', location_espanol, '</i><br/>',
'<hr style="margin:4px 0" />',
'Arrived: ', arrive_str, '<br/>',
'Departed: ', depart_str, '<br/>',
'Stay: ', stay, '<br/>',
'Collection site: ', ifelse(collect, 'yes', 'no')
),
options = timesliderOptions(
timeAttribute = 'time',
timeStrLength = 10,
range = TRUE,
alwaysShowDate = TRUE,
position = 'topright'
)
) |>
addLegend(
position = 'bottomright',
colors = pal(legend_breaks),
labels = legend_labels,
title = 'Date arrived',
opacity = 0.9
)
#> Loading required namespace: yyjsonr