Code
# install.packages(c("tidyverse","sf","plotly","geobr","odbr","flowmapper"))
library(tidyverse)
library(sf)
library(plotly)
library(geobr)
library(odbr)
library(flowmapper)
sf::sf_use_s2(TRUE)This tutorial documents how I build an interactive desire lines map for the São Paulo Metropolitan Area (2017) using odbr for OD survey data and flowmapper for the flow network, with dplyr, ggplot2, sf, and plotly for wrangling and visualization.
We load the required packages. Uncomment install lines if needed.
# install.packages(c("tidyverse","sf","plotly","geobr","odbr","flowmapper"))
library(tidyverse)
library(sf)
library(plotly)
library(geobr)
library(odbr)
library(flowmapper)
sf::sf_use_s2(TRUE)Generating three inputs from odbr: (i) trip table with expansion factor fe_via; (ii) traffic zones; and (iii) municipal boundaries:
# Trip table for São Paulo, 2017
rmsp <- odbr::read_od(city = "São Paulo", year = 2017)Downloading file to: C:\Users\jorge\AppData\Local\Temp\RtmpWiRgqH/od_sao_paulo_2017_not_harmonized.csv.gz
# Traffic zone polygons
zonas_rmsp <- odbr::read_map(city = "São Paulo", year = 2017, geometry = "zone")Downloading file to: C:\Users\jorge\AppData\Local\Temp\RtmpWiRgqH/zone_sao_paulo_2017_not_harmonized.gpkg
# Municipal boundaries
rmsp_geo <- odbr::read_map(city = "São Paulo", year = 2017, geometry = "municipality")Downloading file to: C:\Users\jorge\AppData\Local\Temp\RtmpWiRgqH/municipality_sao_paulo_2017_not_harmonized.gpkg
Since flowmapper expects a node table with name, x, and y., we compute centroids on surface and extract their coordinates:
cent_zonas_rmsp <- st_point_on_surface(zonas_rmsp) %>%
mutate(x = st_coordinates(.)[,1],
y = st_coordinates(.)[,2]) %>%
rename('name' = 'NumeroZona')Warning: st_point_on_surface assumes attributes are constant over geometries
We aggregate trips by origin and destination, summing the expansion factors (fe_via) from all trips between 05:00 AM and 09:00 AM to obtain total flows. Then then rename to the columns expected by flowmapper.
od_rmsp <- rmsp %>%
filter(h_saida >= 5 & h_saida <= 9) %>%
group_by(zona_o, zona_d) %>%
summarize(n_via = sum(fe_via, na.rm = TRUE), .groups = "drop") %>% arrange(desc(n_via))
od_flows <- od_rmsp %>%
rename(o = zona_o, d = zona_d, value = n_via)Filtering out small flows improves readability of the network:
min_flow <- 2000 # adjust to taste
od_flows_filt <- od_flows |>
filter(value >= min_flow)Simple zonal outline and municipal boundaries for orientation.
p_base <- ggplot() +
geom_sf(data = zonas_rmsp, fill = NA, color = "gray30", linewidth = 0.1) +
geom_sf(data = rmsp_geo, fill = NA, color = "darkblue", linewidth = 1) +
geom_sf_text(data = rmsp_geo, aes(label = NomeMunici), size = 2) +
theme_void()
p_base
Key controls: - node_radius_factor controls node symbol size - edge_width_factor controls edge thickness - edge_offset_factor allows parallel offsets if desired
p_flow <- add_flowmap(
p = p_base,
od = od_flows_filt,
nodes = cent_zonas_rmsp,
node_radius_factor = 0.5,
edge_width_factor = 0.2,
edge_offset_factor = 0.0
) +
scale_fill_gradient(
name = "# Trips",
low = "white",
high = "darkred"
)Warning in prep_flowmap(flowdat = flowdat, od = od, nodes = nodes, k_nodes =
k_nodes, : Number of flows very high. Consider setting k_nodes to cluster
nodes.
p_flow
ggplotly converts the map into an interactive figure. Tooltips show OD and value.
p_interactive <- ggplotly(p_flow, tooltip = c("o", "d", "value"))
p_interactive