# Load packages
library(sf)
library(tidyverse)
library(mapview)
# install.packages("od")
library(od)OD Jittering
Note
This page is only relevant when using travel survey data.
When having OD pairs with a macro-scale zoning, we can jitter these desire lines by randomly spread the origins and destinations points at a given area, making the trips (in particular active modes) more realistic (Lovelace, Félix, and Carlino 2022).
For that, zones, OD pairs, as well as the trip volume are needed.
Jittered desire lines
With od package
See the example bellow, using OD tirps between districts in Lisbon, with the od package (Lovelace and Morgan 2024).
# Load data
# road network
road_network = st_read("data/Lisbon/Lisbon_road_network.gpkg")
# polygons
lisbon_zones = st_read("https://github.com/U-Shift/Traffic-Simulation-Models/releases/download/2025/Freguesias_Lx.gpkg")
# OD data
od_lisbon = readRDS(url("https://github.com/U-Shift/Traffic-Simulation-Models/releases/download/2025/ODtrips_Freguesias_Lx.rds"))
od_lisbon = od_lisbon |>
filter(Bike > 0) # keep only with more than 0 bike trips
head(od_lisbon) DICOFREor11 DICOFREde11 Car CarP Bike Walk Other Total Active
1 110601 110601 143.98 43.67 12.76 622.82 116.68 939.91 67.621368
2 110601 110602 136.85 36.10 2.74 403.72 231.32 810.73 50.135063
3 110601 110607 21.64 18.90 0.00 0.00 23.75 64.29 0.000000
4 110601 110608 133.25 5.44 0.00 8.65 377.05 524.39 1.649536
5 110601 110610 48.55 0.00 0.00 0.00 63.55 112.10 0.000000
6 110601 110611 31.18 22.83 0.00 0.00 7.28 61.29 0.000000
# create desire lines between centroids
od_lisbon_dl = od::od_to_sf(od_lisbon, lisbon_zones)
mapview(od_lisbon_dl, zcol = "Bike")
od_lisbon_jit = od::od_jitter(
od = od_lisbon,
z = lisbon_zones,
population_column = 8, #total trips
disag = FALSE
)
od_lisbon_jit_disag = od::od_jitter(
od = od_lisbon,
z = lisbon_zones,
population_column = 8, #total trips
disag = TRUE,
max_per_od = 200 # max trips per line
)
mapview::mapview(od_lisbon_jit, lwd = 0.2)
mapview::mapview(od_lisbon_jit_disag, lwd = 0.2)

See the od::od_jitter() function for more options.
With odjitter package
The same but with Rust, which is faster:
sudo apt install cargo
# (restart current shell)
# sudo apt install cmake
# install odjitter in rust
cargo install odjitter
# git clone https://github.com/dabreegster/odjitter && cd odjitter && cargo build --release && cp ./target/release/odjitter /usr/local/bin/# install odjitter in R
remotes::install_github("itsleeds/odjitter", subdir = "r")
# Load packages
library(odjitter)# Jitter with disagregation threshold of 200 trips
od_lisbon_jittered = odjitter::jitter( #jitter
od = od_lisbon,
zones = lisbon_zones,
subpoints = road_network, # road network verices. we can choose buildings, or so
disaggregation_key = "Total",
disaggregation_threshold = 200
)
mapview::mapview(od_lisbon_jittered, lwd = 0.2)
Jittered origins and destinations
From the jittered desire lines to the points of origin and destination.
library(stplanr)
# add an id to the jittered pairs, so we can join later
od_lisbon_jittered_id = od_lisbon_jittered
od_lisbon_jittered_id$id = 1:nrow(od_lisbon_jittered_id)
#with stplanr
od_lisbon_jittered_points = line2df(od_lisbon_jittered)
od_lisbon_jittered_points_OR = od_lisbon_jittered_points |>
select(L1, fx, fy) |> # from
rename(id = L1,
lon = fx,
lat = fy)
od_lisbon_jittered_points_DE = od_lisbon_jittered_points |>
select(L1, tx, ty) |> # to
rename(id = L1,
lon = tx,
lat = ty)
# as sf
od_lisbon_jittered_points_OR_geo = st_as_sf(od_lisbon_jittered_points_OR,
coords = c("lon", "lat"),
crs = 4326)
od_lisbon_jittered_points_DE_geo = st_as_sf(od_lisbon_jittered_points_DE,
coords = c("lon", "lat"),
crs = 4326)
mapview(od_lisbon_jittered_points_OR_geo, col.regions = "red") +
mapview(od_lisbon_jittered_points_DE_geo, col.regions = "blue")
After routing with r5r, you may want to add the original O and D codes.
od_lisbon_jittered_r5r = od_lisbon_jittered_r5r |>
mutate(id = as.integer(from_id)) |>
select(id, total_duration, total_distance, route) |>
left_join(od_lisbon_jittered_id |>
st_drop_geometry(), # drop geom for left_join
by="id")
# get geometry back
od_lisbon_jittered_r5r = st_as_sf(as.data.frame(st_drop_geometry(od_lisbon_jittered_r5r)),
geometry = od_lisbon_jittered_r5r$geometry)References
Lovelace, Robin, Rosa Félix, and Dustin Carlino. 2022. “Jittering: A Computationally Efficient Method for Generating Realistic Route Networks from Origin-Destination Data.” Findings, April. https://doi.org/10.32866/001c.33873.
Lovelace, Robin, and Malcolm Morgan. 2024. Od: Manipulate and Map Origin-Destination Data. https://github.com/itsleeds/od.