Network
Road/rail-network-constrained distance: build a routable graph from Overture Maps transportation data, snap trajectory points to it, and query network (not straight-line) distance via a Rust contraction-hierarchy router.
| API | Description |
|---|---|
RoadNetwork |
A road/rail network prepared once (contraction hierarchy), reused for many distance queries. |
fetch_road_network |
Fetch and build a car-routable graph from Overture road segments. |
build_road_graph |
Load a cached road graph from disk, or fetch and cache it. |
fetch_rail_network |
Fetch and build a bidirectional rail graph from Overture segments. |
build_rail_graph |
Load a cached rail graph from disk, or fetch and cache it. |
snap_locations_to_graph |
Snap each row to its nearest road/rail graph node. |
RoadNetwork.batch_routes |
Route geometry (waypoint coordinates) for (from_node, to_node) queries. |
od_desire_lines |
Aggregate OD-pair flows onto graph edges (desire lines). |
haversine_m_batch |
Vectorized Haversine distance (metres) between two arrays of points. |
fetch_road_network and fetch_rail_network require DuckDB: install it with
pip install duckdb. Snapping uses Fastmob's native Rust spatial index and
has no additional Python dependency.
Network-aware distance measures
fastmob.measures.individual.jump_lengths_road/radius_of_gyration_road mirror
jump_lengths/radius_of_gyration but measure distance along a prepared
RoadNetwork instead of straight-line, falling back to Haversine per-pair
wherever a point is unsnapped or the graph is disconnected between the two
points:
import pandas as pd
from fastmob.network import RoadNetwork, fetch_road_network
from fastmob.measures.individual import jump_lengths_road, radius_of_gyration_road
nodes_df, edges_df = fetch_road_network(2.34, 48.85, 2.36, 48.86, "2026-05-20.0")
network = RoadNetwork.build(edges_df, nodes_df)
traj = pd.DataFrame(...) # uid, datetime, lat, lng columns
jumps_km = jump_lengths_road(traj, network=network)
rg_km = radius_of_gyration_road(traj, network=network)
Route geometry and OD desire lines
RoadNetwork.batch_routes returns the actual waypoint path (not just total
distance) for a batch of (from_node, to_node) queries, decimated to at
most max_waypoints points per route (always keeping the first and last).
od_desire_lines is the Overture-native analogue of stplanr's
overline/overline2: it aggregates many origin-destination flows onto
the road/rail graph's edges, so overlapping trips accumulate onto shared
segments instead of remaining one separate desire line per pair.
import numpy as np
from fastmob.network import RoadNetwork, od_desire_lines
network = RoadNetwork.build(edges_df, nodes_df)
routes = network.batch_routes(np.array([0, 5]), np.array([12, 3]), max_waypoints=50)
# columns: query_id, lat, lng, cum_weight_ds
edges_with_flow, dropped_flow = od_desire_lines(
network, np.array([0, 1]), np.array([12, 12]), np.array([5.0, 3.0])
)
# columns: edge_from, edge_to, from_lat, from_lng, to_lat, to_lng, total_flow
Both fall back gracefully for unsnapped (negative node id) or
graph-disconnected queries: batch_routes contributes zero rows for that
query, and od_desire_lines adds that query's flow to dropped_flow
instead of any edge.
fastmob.network.RoadNetwork
A road (or rail) network prepared once (contraction hierarchy) and reused for many point-to-point physical-distance queries.
Bundles the snap-target nodes together with the routing handle (unlike
a split handle + separate nodes_df pair), since a caller always
needs both to go from raw lat/lng to a routed distance.
batch_distances(from_nodes, to_nodes)
Batch physical-distance (metres) query for (from_node, to_node) pairs.
Returns (distances_m, connected), connected as a bool array;
False for negative/unsnapped node ids or a disconnected graph
component (fall back to straight-line Haversine in that case).
batch_routes(from_nodes, to_nodes, max_waypoints=50)
Batch route-geometry query for (from_node, to_node) pairs.
Returns a flat pyarrow.Table with one row per waypoint: columns
query_id (0-based index into from_nodes/to_nodes), lat,
lng, cum_weight_ds (cumulative travel-time weight from the
route's start), following the same flat-output + boundary convention
used by other variable-rows-per-group measures in this library
(rather than one Python object per query). A query with connected
== False (unsnapped/disconnected) contributes zero rows; join back
on query_id against a connected array from batch_distances
if you need to distinguish "no route" from "route with no
waypoints" (the latter cannot happen: every connected route has at
least its two endpoints).
Waypoints are decimated to at most max_waypoints per query
(always keeping the first and last), so a caller wanting the full,
undecimated node path should pass a large max_waypoints.
build(edges_df, nodes_df)
classmethod
Prepare a contraction hierarchy from a road/rail graph's edges.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
edges_df
|
Columns |
required | |
nodes_df
|
Columns |
required |
fastmob.network.fetch_road_network(min_lon, min_lat, max_lon, max_lat, overture_release)
Fetch and build a car-routable graph from Overture road segments.
Returns:
| Type | Description |
|---|---|
nodes_df, edges_df:
|
|
fastmob.network.build_road_graph(min_lon, min_lat, max_lon, max_lat, overture_release, nodes_output, edges_output)
Load a cached road graph from disk, or fetch and cache it.
fastmob.network.fetch_rail_network(min_lon, min_lat, max_lon, max_lat, overture_release, classes=None, speed_kmh_by_class=None, default_speed_kmh=35.0)
Fetch and build a simple bidirectional rail graph from Overture segments.
fastmob.network.build_rail_graph(min_lon, min_lat, max_lon, max_lat, overture_release, nodes_output, edges_output, classes=None, speed_kmh_by_class=None, default_speed_kmh=35.0)
Load a cached rail graph from disk, or fetch and cache it.
fastmob.network.snap_locations_to_graph(tessellation_df, nodes_df, max_distance_m, lat_col='lat', lng_col='lng')
Snap each tessellation row to its nearest road/rail graph node.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tessellation_df
|
Rows with lat/lng columns to snap. |
required | |
nodes_df
|
Graph nodes with columns |
required | |
max_distance_m
|
float
|
Maximum snap distance; farther rows are reported unsnapped. |
required |
lat_col
|
str
|
Column names on |
'lat'
|
lng_col
|
str
|
Column names on |
'lat'
|
Returns:
| Type | Description |
|---|---|
Int64Array
|
int64 array aligned 1:1 with |
fastmob.network.haversine_m_batch(lat1, lng1, lat2, lng2)
builtin
Elementwise Haversine distance (metres) between two same-length arrays of
points, computed in parallel. See
[fastmob_core::utils::haversine::haversine_m_batch].
fastmob.network.od_desire_lines(road_network, from_nodes, to_nodes, flows)
Aggregate OD-pair flows onto the road/rail graph's edges (desire lines).
The Overture-native analogue of stplanr's overline/overline2:
for each (from_node, to_node, flow) triple, walks the time-optimal
route between the two nodes and adds flow to every edge it crosses,
so many overlapping OD pairs accumulate onto shared road segments
instead of remaining one separate desire line per pair.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
road_network
|
A prepared network (see |
required | |
from_nodes
|
Node ids per OD pair; a negative value marks an unsnapped origin or destination. |
required | |
to_nodes
|
Node ids per OD pair; a negative value marks an unsnapped origin or destination. |
required | |
flows
|
Flow volume (e.g. trip count) per OD pair. |
required |
Returns:
| Type | Description |
|---|---|
(edges_df, dropped_flow)
|
|
fastmob.measures.individual.network_distance.jump_lengths_road(traj, *, network, uid_col=None, lat_col=None, lng_col=None, datetime_col=None, snap_max_distance_m=750.0)
Road-network jump lengths (km): distance between consecutive stops
for the same user, sorted by datetime -- mirrors
:func:~fastmob.measures.individual.jump_lengths.jump_lengths's sort
key and its inclusion of zero-length jumps, but measures along the
network instead of straight-line, falling back to Haversine per-pair
when unsnapped or disconnected.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
traj
|
Any
|
Trajectory dataframe; any Narwhals-compatible eager backend. |
required |
network
|
RoadNetwork
|
A prepared :class: |
required |
uid_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
lat_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
lng_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
datetime_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
snap_max_distance_m
|
float
|
Maximum distance (metres) to snap a stop to the network; farther stops fall back to Haversine entirely for any jump touching them. |
750.0
|
Returns:
| Type | Description |
|---|---|
ndarray
|
One value per consecutive same-user pair (length: |
fastmob.measures.individual.network_distance.radius_of_gyration_road(traj, *, network, uid_col=None, lat_col=None, lng_col=None, snap_max_distance_m=750.0)
Road-network radius of gyration (km) per user: RMS network distance
from each of a user's stops to the arithmetic-mean centroid of their
stops -- mirrors the unweighted-centroid formula
r_g(u) = sqrt(mean(d(r_i, r_cm)^2)) used by
:func:~fastmob.measures.individual.radius_of_gyration.radius_of_gyration,
but measures d along the network instead of straight-line.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
traj
|
Any
|
Trajectory dataframe; any Narwhals-compatible eager backend. |
required |
network
|
RoadNetwork
|
A prepared :class: |
required |
uid_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
lat_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
lng_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
snap_max_distance_m
|
float
|
Maximum distance (metres) to snap a stop to the network. |
750.0
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
|