In this practical, we will build on the routing techniques from Practical 4 by exploring data visualization methods for transport analysis. By the end of this practical, you should be able to:
Load and preprocess OD flow data
Visualize OD lines and proportional symbol maps
Compare walking, driving, and cycling flows
Aggregate flows along the road network
Identify critical road segments via network centrality
2 Setup
Below are the libraries we will use throughout this practical:
Linking to GEOS 3.12.1, GDAL 3.8.4, PROJ 9.4.0; sf_use_s2() is TRUE
library(tmap) # Thematic mappinglibrary(stplanr) # Transport data functionslibrary(dplyr) # Data manipulation
Attaching package: 'dplyr'
The following objects are masked from 'package:stats':
filter, lag
The following objects are masked from 'package:base':
intersect, setdiff, setequal, union
library(osmextract) # OSM data handling
Data (c) OpenStreetMap contributors, ODbL 1.0. https://www.openstreetmap.org/copyright.
Check the package website, https://docs.ropensci.org/osmextract/, for more details.
library(dodgr) # Network analysis
# Set interactive mapping modetmap_mode("view")
3 Flow Map Visualization
Flow maps are useful for understanding the volume of travel between origins and destinations. In this section, we will:
Load desire lines (flows) data from a GeoJSON file.
Visualize these lines with widths or colors proportional to demand.
Optionally aggregate route geometries for more realistic depiction of flows along an actual road network.
# Load Demand Datadesire_lines =read_sf("https://github.com/ITSLeeds/TDS/releases/download/22/NTEM_flow.geojson") |>select(from, to, all, walk, drive, cycle)dim(desire_lines)
[1] 502 7
# Let's take the top 50 car trips for demonstrationdesire_lines_top = desire_lines |>arrange(desc(drive)) |>head(50)# Quick map to see the distribution of car tripstm_shape(desire_lines_top) +tm_lines(lwd ="drive",lwd.scale =tm_scale_continuous(values.scale =9) ) +tm_layout(legend.bg.color ="white")
4 Proportional Symbol Flow Maps
Now, let’s illustrate an alternative method: proportional symbols at origin or destination points. This is useful when you want to quickly see where demand is concentrated.
# Summarize total flows by originorigin_flows = desire_lines |>group_by(from) |>summarise(total_drive =sum(drive, na.rm =TRUE),total_walk =sum(walk, na.rm =TRUE),total_cycle =sum(cycle, na.rm =TRUE),`% drive`= total_drive /sum(all, na.rm =TRUE),geometry =st_centroid(st_union(geometry)) )# Simple map with proportional circles for drive volumestm_shape(origin_flows) +tm_bubbles(size ="total_drive", # bubble size ~ drive volumesize.scale =tm_scale_intervals(values.scale =2, values.range =c(0.5, 2)),fill ="% drive",fill.scale =tm_scale_continuous(values ="brewer.reds") ) +tm_title("Proportional Symbol Map of Drive Demand by Origin")
Each origin is represented by a circle whose radius and color intensity reflect the total number of driving trips. You can modify palettes, breaks, and scaling to highlight variations.
5 Mode-Specific Analysis
We have have columns walk, drive, cycle in desire_lines. We can map them separately or side-by-side. We can also color lines by the dominant mode.
This tmap_arrange() will output a single figure with three columns, each illustrating flows by one mode. Students can visually compare the differences: maybe driving flows are much thicker on longer corridors, while walking flows are concentrated in the city center.
6 Aggregating Flows with Actual Routes
Rather than drawing direct origin-destination lines, we can route each flow along the road network and then aggregate them to see which streets carry the most traffic. This uses stplanr::overline() to merge lines that overlap.
# Download pre-routed lines for demonstrationu ="https://github.com/ITSLeeds/TDS/releases/download/22/routes_drive_25.geojson"routes_drive =read_sf(u)# Inspect the summary of the drive.x variable (car trips)summary(routes_drive$drive.x)
Min. 1st Qu. Median Mean 3rd Qu. Max.
41.0 171.2 327.0 415.1 546.5 2346.0
Betweenness centrality indicates how often a road (or node) lies on the shortest path between other points in a network. Roads with high centrality are typically crucial for overall connectivity.
Here, we demonstrate how to:
Download roads from OpenStreetMap (OSM) for the Isle of Wight.
Weight the network for motor vehicle usage.
Compute betweenness centrality with dodgr.
Convert the results back to sf for mapping.
# Get Isle of Wight road network# We choose 'primary', 'secondary', 'tertiary' roads for demonstrationroads =oe_get("Isle of Wight", extra_tags =c("maxspeed", "oneway")) |>filter(highway %in%c("primary", "secondary", "tertiary"))# Weight the street network for motorcar usagegraph =weight_streetnet( roads,wt_profile ="motorcar",type_col ="highway",id_col ="osm_id",keep_cols =c("maxspeed", "oneway"))# Calculate betweenness centralitycentrality =dodgr_centrality(graph)# Convert to sf for visualizationcentrality_sf =dodgr_to_sf(centrality)# Visualize critical linkstm_shape(centrality_sf) +tm_lines(col ="centrality",col.scale =tm_scale_intervals(style ="fisher", values ="-viridis"),col.legend =tm_legend(title ="Betweenness Centrality"),lwd =3 )
The code above should generate a map that looks something like this:
High values in the centrality column indicate roads that act as vital connectors in the regional transport network.
8 Extra Exercises: 3D Visualization
A 3D perspective can often reveal relationships between travel flows and the underlying topography more effectively. Below, we demonstrate how to retrieve elevation data and render a 3D hillshade using the rayshader package. You may also be interested in overlaying flow lines onto a 3D terrain model to enhance visualization.
Note: the following code requires you to install the rayshaderelevatrgifskirgl package. Results not shown in website.
library(rayshader) # 3D data visualizationlibrary(elevatr) # Elevation datalibrary(gifski) # Creating GIF animationslibrary(rgl) # 3D visualization deviceassign("has_internet_via_proxy", TRUE, environment(curl::has_internet))curl::has_internet()# Example: Elevation data near a location in the UKcoords =data.frame(x =-2.087918, y =53.71534)coords_sf =st_as_sf(coords, coords =c("x", "y"), crs =4326)# Get an elevation raster at zoom level 11 (~ 10m resolution, depending on region)elevation = elevatr::get_elev_raster(locations = coords_sf,z =11)# Convert the raster to a matrix for rayshaderelev_matrix = rayshader::raster_to_matrix(elevation)# Create a hillshade layerhillshade_matrix = rayshader::ray_shade(elev_matrix, zscale =15)# Clear existing rgl devicergl::rgl.clear()# Render a 3D plot of the terrainrayshader::plot_3d(heightmap = elev_matrix,hillshade = hillshade_matrix,zscale =15,windowsize =c(1000, 800))# Adjust camera viewrgl::view3d(theta =30, phi =30, zoom =0.75)rgl::rglwidget()
9 Conclusions
In this practical, you learned how to:
Create flow maps to visualize travel demand from an OD dataset.
Compare flows by mode (driving, walking, cycling) to understand differences in spatial patterns.
Aggregate routes along the road network (using stplanr::overline) to highlight heavily used corridors.
Compute betweenness centrality (using dodgr) to pinpoint critical road segments crucial for connectivity.