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

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.1
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)

Table of built distributions (wheels) for routematrix 0.1.1
File Interpreter ABI Platform Actions
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 log

Release 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 log

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

Select from 2 files

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