routematrix
Large origin–destination (OD) travel-time and distance matrices from open routing engines, for accessibility, logistics and planning analysis.
- One client for OSRM, Valhalla, GraphHopper and openrouteservice, plus a clearly labelled great-circle estimate fallback.
- Automatic tiling of N × M jobs within each engine's request limits, stitched back
into dense numpy arrays:
durations(s) anddistances(m),NaN= unreachable. - Polite parallelism: bounded concurrency, rate limiting for hosted APIs, retries
with exponential backoff (honours
Retry-After), timeouts. - Persistent SQLite cache and resume: every finished tile is stored at once; rerun an interrupted job and only the missing pairs are requested.
- Snapping diagnostics: distance between each input point and where the engine snapped it, with flags for points that are probably off the network.
- Analysis: nearest-k destinations, cumulative opportunities, gravity accessibility, symmetry checks and engine-to-engine comparison.
- Dependencies: the standard library (
urllib,sqlite3) and numpy. pandas and geopandas are optional extras.
pip install routematrix # core
pip install "routematrix[pandas]" # DataFrame export / input
pip install "routematrix[geo]" # GeoDataFrame input, isochrones as GeoDataFrame
Quick start
from routematrix import RouteMatrix, cumulative_opportunities
with RouteMatrix("osrm", url="http://localhost:5000", profile="driving",
cache="od_cache.sqlite", progress=True) as rm:
res = rm.compute("homes.csv", "clinics.csv") # CSV, (N, 2) lon/lat array,
# DataFrame or GeoDataFrame
res.durations # (N, M) float seconds, NaN = unreachable
res.distances # (N, M) float metres
res.origin_snap_m # distance from each origin to its snapped location
res.snap_flags(500) # (origin_flags, destination_flags) for snaps > 500 m
res.to_dataframe() # long format: origin_id, destination_id, duration_s, distance_m, ...
res.to_csv("matrix.csv") # long CSV
res.to_csv("wide.csv", wide=True) # one row per origin
# Clinics reachable within 15 minutes, per home
counts = cumulative_opportunities(res, threshold=15 * 60)
Coordinates are always longitude, latitude (x, y) in EPSG:4326, like GeoJSON. CSV
columns are detected from common names (lon/lng/longitude/x,
lat/latitude/y, and id/name for labels).
Other engines
RouteMatrix("valhalla", url="http://localhost:8002", profile="walking") # -> pedestrian
RouteMatrix("ors", url="http://localhost:8080/ors", profile="cycling") # -> cycling-regular
RouteMatrix("ors", profile="driving") # hosted API, key from ORS_API_KEY
RouteMatrix("graphhopper", profile="car") # hosted API, key from GRAPHHOPPER_API_KEY
RouteMatrix("estimate", profile="walking", detour_factor=1.3) # NOT routed: an estimate
driving / walking / cycling are mapped to each engine's own profile names; any
other value is passed through unchanged (e.g. truck, foot-hiking, motor_scooter).
Valhalla isochrones
from routematrix import isochrones_to_geodataframe
rm = RouteMatrix("valhalla", url="http://localhost:8002", profile="pedestrian")
fc = rm.isochrone((28.97, 41.01), minutes=[5, 10, 15]) # GeoJSON FeatureCollection
gdf = isochrones_to_geodataframe(fc) # needs the geo extra
Accessibility and diagnostics
from routematrix import (nearest_k, cumulative_opportunities, gravity_accessibility,
asymmetry, compare_engines)
idx, secs = nearest_k(res, k=3) # 3 closest destinations per origin
jobs_30 = cumulative_opportunities(res, 1800, opportunities=jobs)
hansen = gravity_accessibility(res, jobs, function="exponential", beta=0.1, scale=60)
asymmetry(square_result) # one-way streets vs. data problems
compare_engines(origins, dests, RouteMatrix("osrm", url=A), RouteMatrix("valhalla", url=B))
Command line
routematrix compute origins.csv destinations.csv --engine osrm \
--url http://localhost:5000 --profile driving -o matrix.csv --cache od.sqlite
routematrix compute origins.csv --engine estimate --profile walking --wide -o walk.csv
routematrix nearest homes.csv clinics.csv --engine valhalla --url http://localhost:8002 \
--profile auto -k 3 -o nearest.csv
routematrix engines # default tile limits per engine
Exit codes: 0 success, 2 error, 3 finished with failed tiles (--on-error nan).
Progress and a summary (reachability, cache hits, snap flags) go to stderr.
Engines
| Engine | Endpoint | Default tile | Snapping info | Units returned | Key |
|---|---|---|---|---|---|
| OSRM | GET /table/v1/{profile}/… |
100 × 100 (osrm-routed --max-table-size default 100) |
yes | s, m | – |
| Valhalla | POST /sources_to_targets |
50 × 50 = 2 500 pairs (max_matrix_location_pairs) |
no | s, km → m | – |
| GraphHopper | POST /matrix |
25 × 25 (plan dependent) | no | s, m | GRAPHHOPPER_API_KEY (hosted) |
| openrouteservice | POST /v2/matrix/{profile} |
50 × 50 = 2 500 routes (maximum_routes) |
yes | s, m | ORS_API_KEY (hosted) |
| estimate | none (local) | 2 000 × 2 000 | no | s, m (estimated) | – |
If your server allows larger requests (e.g. osrm-routed --max-table-size 1000), pass
max_sources=, max_destinations= and max_elements= (CLI: --max-sources …). The
open-source GraphHopper server has no /matrix endpoint; "self-hosted" there means a
server exposing the same API. Valhalla also enforces max_matrix_distance (400 km for
auto by default); such requests fail with an engine error.
Defaults: concurrency 4 for your own servers, 1 for hosted APIs and demo servers; hosted openrouteservice is spaced 1.6 s per request (free plan: 40 matrix requests/min), hosted GraphHopper 0.5 s.
Run an engine locally (Docker)
OSRM (example extract: Monaco):
wget https://download.geofabrik.de/europe/monaco-latest.osm.pbf
docker run -t -v "${PWD}:/data" ghcr.io/project-osrm/osrm-backend osrm-extract -p /opt/car.lua /data/monaco-latest.osm.pbf
docker run -t -v "${PWD}:/data" ghcr.io/project-osrm/osrm-backend osrm-partition /data/monaco-latest.osrm
docker run -t -v "${PWD}:/data" ghcr.io/project-osrm/osrm-backend osrm-customize /data/monaco-latest.osrm
docker run -t -p 5000:5000 -v "${PWD}:/data" ghcr.io/project-osrm/osrm-backend osrm-routed --algorithm mld --max-table-size 1000 /data/monaco-latest.osrm
Valhalla:
mkdir -p custom_files
wget -O custom_files/monaco-latest.osm.pbf https://download.geofabrik.de/europe/monaco-latest.osm.pbf
docker run -dt --name valhalla -p 8002:8002 -v "${PWD}/custom_files:/custom_files" ghcr.io/valhalla/valhalla-scripted:latest
Then use --url http://localhost:5000 (OSRM) or --url http://localhost:8002 (Valhalla).
Responsible use
- No public server by default. OSRM and Valhalla always need an explicit
url. The public demo servers (router.project-osrm.org,valhalla1.openstreetmap.de) are run by volunteers for testing; routematrix accepts them only for tiny jobs (at most 10 points and 25 pairs), forces one request at a time with a 1 s gap, and warns. Anything larger raisesDemoServerLimitError: run your own server. - Hosted APIs (openrouteservice, GraphHopper) have quotas and terms of use. Keys are read from environment variables, never written to the cache, and redacted from error messages.
- Every request carries an identifying
User-Agent; set your own contact viaRouteMatrix(..., user_agent="myproject/1.0 (+mailto:me@example.org)"). - Respect the OpenStreetMap ODbL: credit "© OpenStreetMap contributors" when you publish results derived from OSM-based routing.
- The
estimateengine is not routing. Results carryis_estimate=True, a warning is emitted, and the CLI printsESTIMATE. Use it for sanity checks or as a clearly labelled fallback only. - Check snapping: points snapped hundreds of metres away (parks, water, private
ground, outside the extract) produce misleading times. Use
snap_flags().
Caching and resume
The cache key is (engine + server URL, profile, origin, destination) with
coordinates rounded to 1e-6 degrees. Unreachable pairs are cached as known-unreachable.
Rerunning a job after a crash, a Ctrl+C or with on_error="nan" requests only the
origin rows that still have gaps. Cached data does not expire: delete the file (or use
MatrixCache(path).clear()) after updating the road network.
Development
pip install -e ".[dev]"
pytest # uses local mock servers only, never the network
ruff check . && ruff format --check src tests && mypy src
License
MIT © Yusuf Eminoğlu
Metadata
Release files for routematrix 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| routematrix-0.1.1.tar.gz | 49.4 kB | With provenance | Details for routematrix-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| routematrix-0.1.1-py3-none-any.whl | Python 3 | none | any | Details for routematrix-0.1.1-py3-none-any.whl |
Total release size: 85.4 kB
Release files / routematrix-0.1.1.tar.gz
| Download URL | routematrix-0.1.1.tar.gz |
|---|---|
| Size | 49.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5f1c406c8d37606f79d54130f44923ef35d7a40c10007b31c0e2c457aa335d9c
|
|
BLAKE2b-256 checksum How to use checksums |
1ba537d963606836285eb56e37b6685a75af06b9bc27a4f7ea2d0f1b8310ba48
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 9, 2026.
Transparency logRelease files / routematrix-0.1.1-py3-none-any.whl
| Download URL | routematrix-0.1.1-py3-none-any.whl |
|---|---|
| Size | 36.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
38558ddacf24c3825ed4d6225cb7e3be679dd0f987443f5b327b5082c4a683f6
|
|
BLAKE2b-256 checksum How to use checksums |
25cd049e608dbcb21323e66d20cce3ea24a792232ee63e29e873d6c48d9601b3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 9, 2026.
Transparency logSecurity for routematrix 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for routematrix-0.1.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 9, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.