By default, R5 routes cars at the legal speed limit of
each OSM road edge, a “free flow” scenario without congestion. Real
average speeds are usually lower because of traffic and driving
behavior. Similarly, R5 infers the cycling Level of Traffic
Stress (LTS) of each road segment from road speeds, road hierarchy and
cycling infrastructure.
This vignette shows how to calculate travel times and accessibility
with custom OSM car speeds or LTS, to simulate traffic congestion, road
closures or interventions in cycling infrastructure. Changes can be
passed as (1) a data.frame, for individual road segments,
or (2) an sf object, for roads within / touching its
geometries. Examples use the Porto Alegre (Brazil) sample data included
in r5r.
# increase Java memory
options(java.parameters = "-Xmx2G")
# load libraries
library(r5r)
library(dplyr)
library(data.table)
library(ggplot2)
# data path
data_path <- system.file("extdata/poa", package = "r5r")
# build network
r5r_network <- r5r::build_network(
data_path = data_path,
verbose = FALSE
)All routing and accessibility functions in {r5r} accept custom OSM car speeds through two parameters:
new_carspeeds: here, users must pass either a
data.frame that indicates the new car speed for each OSM
edge id, OR an sf data.frame polygon that indicates the new
car speed for all the roads that fall within each polygon.carspeed_scale: this parameter allows one to set the
default car speed for all of the road segments not specified in
new_carspeeds. By default, carspeed_scale = 1
and the speeds of the unlisted roads are kept unchanged.Here we pass new car speeds in a sample data.frame
shipped with the package. It must contain the columns
"osm_id", "max_speed" and
"speed_type". "speed_type" is character,
either "scale" or "km/h": whether
"max_speed" is relative to the original speed
("scale") or an absolute speed ("km/h").
# read data.frame with new car speeds
edge_speed_factors <- read.csv(
file.path(data_path, "poa_osm_congestion.csv")
)
head(edge_speed_factors)Here all "max_speed" values are 0.5 with
speed_type == "scale": the listed OSM edges are driven at
50% of their original OSM speed. Pass new_carspeeds and
carspeed_scale to any routing or accessibility function.
Here, carspeed_scale = 0.8 also slows all unlisted roads to
80% of their speed:
# origins and destination points
points <- read.csv(file.path(data_path, "poa_points_of_interest.csv"))
# travel time matrix
ttm_congestion <- r5r::travel_time_matrix(
r5r_network = r5r_network,
origins = points,
destinations = points,
mode = 'car',
departure_datetime = Sys.time(),
max_trip_duration = 30,
new_carspeeds = edge_speed_factors,
carspeed_scale = 0.8
)Obs. Halving speeds does not necessarily double travel times: car travel times also depend on intersections, and slower roads can change the route and hence the trip distance.
Here we assume congestion is heavier on roads of higher hierarchy.
First, read the OSM roads from the .pbf file and keep the
road types of interest:
# path to OSM pbf
pbf_path <- paste0(data_path, "/poa_osm.pbf")
# read layer of lines from pbf
roads <- sf::st_read(
pbf_path,
layer = 'lines',
quiet = TRUE
)
# Filter only road types of interest
rt <- c("motorway", "primary", "secondary", "tertiary")
roads <- roads |>
select(osm_id, highway) |>
filter(highway %in% rt)
head(roads)The selected roads:
Then add a "max_speed" column by road type and a
speed_type column set to "scale", and make
osm_id numeric:
new_edge_speeds <- roads |>
mutate(
osm_id = as.numeric(osm_id),
max_speed = case_when(
highway == "motorway" ~ 0.75,
highway == "primary" ~ 0.8,
highway == "secondary" ~ 0.85,
highway == "tertiary" ~ 0.9)) |>
sf::st_drop_geometry()
new_edge_speeds$speed_type <- "scale"
head(new_edge_speeds)Travel times with the modified car speeds:
To set a 40 km/h speed limit on the same road types (motorway,
primary, secondary, tertiary), set max_speed = 40 and
speed_type = "km/h" in the previous table:
# edit table with custom speeds to 40 km/h
new_edge_speeds40 <- new_edge_speeds |>
mutate(max_speed = 40,
speed_type = "km/h")
# travel time matrix
ttm_congestion <- r5r::travel_time_matrix(
r5r_network = r5r_network,
origins = points,
destinations = points,
mode = 'car',
departure_datetime = Sys.time(),
max_trip_duration = 30,
new_carspeeds = new_edge_speeds40
)"max_speed" value to 0. This can
be quite handy for studies that try to measure the resilience of
transport systems to network disruptions.Instead of listing OSM edges, you can set the speed of all roads within one or more polygons. The sample data has two polygons: one covering the extended city center, and one covering a few roads that connect two major avenues.
# read sf with congestion polygons
congestion_poly <- readRDS(file.path(data_path, "poa_poly_congestion.rds"))
# preview
mapview::mapview(congestion_poly, zcol="scale")The sf data.frame must have these columns:
"poly_id": a unique id for each polygon"scale": the speed scaling factor for each polygon,
relative to original speeds. Absolute speeds in km/h are not
supported."priority": a number ranking which polygon should be
considered in case of overlapping polygons.Here, roads in the city center run at 70% of the speed limit, roads
in the second polygon at 80%, and, with
carspeed_scale = 0.95, all other roads at 95%:
LTS is changed like car speeds: with a data.frame of new
LTS values per OSM edge id, or with an sf data.frame. Here
the sf must be of type LINESTRING; R5 finds the nearest
road to each line and updates its LTS.
Suppose protected cycle lanes or tracks were built on a few roads.
The data.frame must contain an "lts" column
with the new LTS value.
# read data.frame with new lts
edge_lts <- read.csv(
file.path(data_path, "poa_osm_lts.csv")
)
head(edge_lts)Pass it to new_lts in any routing/accessibility
function:
Alternatively, pass an sf LINESTRING of the roads with
the intervention. Here, dedicated cycle lanes along all secondary
roads:
# read sf with congestion polygons
lts_lines <- readRDS(file.path(data_path, "poa_ls_lts.rds"))
# preview
mapview::mapview(lts_lines, zcol="lts")Pass it to new_lts as before:
Stop the network and run Java’s garbage collector to free the memory it used:
# stop an specific r5r network
r5r::stop_r5(r5r_network)
# or stop all r5r networks at once
r5r::stop_r5()
rJava::.jgc(R.gc = TRUE)If you have any suggestions or want to report an error, please visit the package GitHub page.