Skip to main content

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) and distances (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 raises DemoServerLimitError: 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 via RouteMatrix(..., 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 estimate engine is not routing. Results carry is_estimate=True, a warning is emitted, and the CLI prints ESTIMATE. 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.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for routematrix 0.1.0
File Size Uploaded Provenance Actions
routematrix-0.1.0.tar.gz 48.4 kB With provenance Details for routematrix-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for routematrix 0.1.0
File Interpreter ABI Platform Actions
routematrix-0.1.0-py3-none-any.whl Python 3 none any Details for routematrix-0.1.0-py3-none-any.whl

Total release size: 84.4 kB

Release files / routematrix-0.1.0.tar.gz

Download URL routematrix-0.1.0.tar.gz
Size 48.4 kB
Tags Source
SHA-256 checksum
How to use checksums
7e13bd46302143039638278310a2d8ec7d625684574ea286cbdeff2410ac24d2
BLAKE2b-256 checksum
How to use checksums
b92d1461ad490f4cf0a4986c67e7ab8bb56456a4462c8ed4740a7f2ec5854f4e
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 8, 2026.

Transparency log

Release files / routematrix-0.1.0-py3-none-any.whl

Download URL routematrix-0.1.0-py3-none-any.whl
Size 36.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d21defb1a1c4036df8ec60c6778dd8d8d47ff543ca177b1c1fb0f659c1917405
BLAKE2b-256 checksum
How to use checksums
ec704dbfaebcc5f2c24f29e6f9e983f8277d7caad2186c669d8b9d99b0d15e4c
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 8, 2026.

Transparency log

Security for routematrix 0.1.0

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.

Select from 2 files

Attestation for routematrix-0.1.0.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 8, 2026.

Transparency log

A 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.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page