routematrix 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. routematrix-0.1.0/CHANGELOG.md +28 -0
  2. routematrix-0.1.0/CITATION.cff +19 -0
  3. routematrix-0.1.0/LICENSE +21 -0
  4. routematrix-0.1.0/MANIFEST.in +8 -0
  5. routematrix-0.1.0/PKG-INFO +222 -0
  6. routematrix-0.1.0/README.md +182 -0
  7. routematrix-0.1.0/pyproject.toml +122 -0
  8. routematrix-0.1.0/setup.cfg +4 -0
  9. routematrix-0.1.0/src/routematrix/__init__.py +84 -0
  10. routematrix-0.1.0/src/routematrix/_version.py +1 -0
  11. routematrix-0.1.0/src/routematrix/analysis.py +217 -0
  12. routematrix-0.1.0/src/routematrix/cache.py +154 -0
  13. routematrix-0.1.0/src/routematrix/cli.py +218 -0
  14. routematrix-0.1.0/src/routematrix/client.py +368 -0
  15. routematrix-0.1.0/src/routematrix/engines.py +506 -0
  16. routematrix-0.1.0/src/routematrix/errors.py +50 -0
  17. routematrix-0.1.0/src/routematrix/geo.py +28 -0
  18. routematrix-0.1.0/src/routematrix/http.py +189 -0
  19. routematrix-0.1.0/src/routematrix/points.py +143 -0
  20. routematrix-0.1.0/src/routematrix/py.typed +0 -0
  21. routematrix-0.1.0/src/routematrix/result.py +217 -0
  22. routematrix-0.1.0/src/routematrix/tiling.py +55 -0
  23. routematrix-0.1.0/src/routematrix.egg-info/PKG-INFO +222 -0
  24. routematrix-0.1.0/src/routematrix.egg-info/SOURCES.txt +32 -0
  25. routematrix-0.1.0/src/routematrix.egg-info/dependency_links.txt +1 -0
  26. routematrix-0.1.0/src/routematrix.egg-info/entry_points.txt +2 -0
  27. routematrix-0.1.0/src/routematrix.egg-info/requires.txt +15 -0
  28. routematrix-0.1.0/src/routematrix.egg-info/top_level.txt +1 -0
  29. routematrix-0.1.0/tests/conftest.py +44 -0
  30. routematrix-0.1.0/tests/mock_engines.py +328 -0
  31. routematrix-0.1.0/tests/test_analysis_points.py +197 -0
  32. routematrix-0.1.0/tests/test_cache_http.py +253 -0
  33. routematrix-0.1.0/tests/test_cli.py +161 -0
  34. routematrix-0.1.0/tests/test_engines.py +268 -0
@@ -0,0 +1,28 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [Unreleased]
6
+
7
+ ## [0.1.0] - 2026-11-09
8
+ ### Added
9
+ - `RouteMatrix` client with one interface for OSRM (`/table/v1`), Valhalla
10
+ (`/sources_to_targets`, `/isochrone`), GraphHopper (`/matrix`, self-hosted or hosted
11
+ with `GRAPHHOPPER_API_KEY`) and openrouteservice (`/v2/matrix`, `ORS_API_KEY`).
12
+ - `estimate` engine: great-circle distance x detour factor / speed, always flagged as an
13
+ estimate (`MatrixResult.is_estimate`, a warning, and CLI output).
14
+ - Automatic tiling of N x M jobs within each engine's source, destination and element
15
+ limits; tiles are stitched back into dense numpy arrays (seconds, metres, `NaN` for
16
+ unreachable pairs).
17
+ - Bounded parallel requests, polite rate limiting for hosted services, retries with
18
+ exponential backoff and `Retry-After`, per-request timeouts, API keys redacted from errors.
19
+ - Persistent SQLite pair cache keyed by engine + server, profile, origin and destination;
20
+ interrupted jobs resume and only request missing rows.
21
+ - Snapping distances (OSRM, openrouteservice) with `snap_flags()`; long-format records,
22
+ pandas DataFrame and CSV export (long or wide).
23
+ - Analysis: `nearest_k`, `cumulative_opportunities`, `gravity_accessibility` (exponential,
24
+ power, Gaussian, linear decay), `asymmetry`, `compare_matrices`, `compare_engines`.
25
+ - Public demo servers (`router.project-osrm.org`, `valhalla1.openstreetmap.de`) are
26
+ accepted only for tiny jobs (10 points, 25 pairs) with a warning; OSRM and Valhalla
27
+ always need an explicit base URL.
28
+ - CLI: `routematrix compute`, `routematrix nearest`, `routematrix engines`.
@@ -0,0 +1,19 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software, please cite it as below."
3
+ authors:
4
+ - family-names: "Eminoğlu"
5
+ given-names: "Yusuf"
6
+ email: "yusufeminoglu@gmail.com"
7
+ orcid: "https://orcid.org/0009-0005-6000-2934"
8
+ title: "routematrix"
9
+ version: "0.1.0"
10
+ date-released: "2026-11-09"
11
+ license: "MIT"
12
+ repository-code: "https://github.com/YusufEminoglu/routematrix_sdk"
13
+ url: "https://pypi.org/project/routematrix/"
14
+ abstract: "Large origin-destination travel-time and distance matrices from open routing engines (OSRM, Valhalla, GraphHopper, openrouteservice) with automatic tiling, polite parallel requests, a persistent SQLite cache with resume, snapping diagnostics and accessibility measures."
15
+ keywords:
16
+ - routing
17
+ - accessibility
18
+ - origin-destination matrix
19
+ - travel time
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yusuf Eminoğlu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,8 @@
1
+ include LICENSE
2
+ include README.md
3
+ include CHANGELOG.md
4
+ include CITATION.cff
5
+ include pyproject.toml
6
+ include src/routematrix/py.typed
7
+ recursive-include src/routematrix *.py
8
+ recursive-include tests *.py
@@ -0,0 +1,222 @@
1
+ Metadata-Version: 2.4
2
+ Name: routematrix
3
+ Version: 0.1.0
4
+ Summary: Large origin-destination travel-time and distance matrices from OSRM, Valhalla, GraphHopper and openrouteservice, with tiling, caching, resume and accessibility tools.
5
+ Author-email: Yusuf Eminoğlu <yusufeminoglu@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/YusufEminoglu/routematrix_sdk
8
+ Project-URL: Repository, https://github.com/YusufEminoglu/routematrix_sdk
9
+ Project-URL: Issues, https://github.com/YusufEminoglu/routematrix_sdk/issues
10
+ Project-URL: Changelog, https://github.com/YusufEminoglu/routematrix_sdk/blob/main/CHANGELOG.md
11
+ Keywords: routing,travel-time,od-matrix,distance-matrix,accessibility,osrm,valhalla,graphhopper,openrouteservice,isochrone,logistics,transport-planning,gis
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Topic :: Scientific/Engineering :: GIS
21
+ Classifier: Intended Audience :: Science/Research
22
+ Classifier: Intended Audience :: Developers
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.9
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: numpy>=1.20.0
28
+ Provides-Extra: pandas
29
+ Requires-Dist: pandas>=1.3.0; extra == "pandas"
30
+ Provides-Extra: geo
31
+ Requires-Dist: pandas>=1.3.0; extra == "geo"
32
+ Requires-Dist: geopandas>=0.12.0; extra == "geo"
33
+ Provides-Extra: dev
34
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
35
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
36
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
37
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
38
+ Requires-Dist: pandas>=1.3.0; extra == "dev"
39
+ Dynamic: license-file
40
+
41
+ # routematrix
42
+
43
+ Large origin–destination (OD) travel-time and distance matrices from open routing
44
+ engines, for accessibility, logistics and planning analysis.
45
+
46
+ - One client for **OSRM**, **Valhalla**, **GraphHopper** and **openrouteservice**, plus a
47
+ clearly labelled great-circle **estimate** fallback.
48
+ - **Automatic tiling** of N × M jobs within each engine's request limits, stitched back
49
+ into dense numpy arrays: `durations` (s) and `distances` (m), `NaN` = unreachable.
50
+ - **Polite parallelism**: bounded concurrency, rate limiting for hosted APIs, retries
51
+ with exponential backoff (honours `Retry-After`), timeouts.
52
+ - **Persistent SQLite cache and resume**: every finished tile is stored at once; rerun
53
+ an interrupted job and only the missing pairs are requested.
54
+ - **Snapping diagnostics**: distance between each input point and where the engine
55
+ snapped it, with flags for points that are probably off the network.
56
+ - **Analysis**: nearest-k destinations, cumulative opportunities, gravity accessibility,
57
+ symmetry checks and engine-to-engine comparison.
58
+ - Dependencies: the standard library (`urllib`, `sqlite3`) and numpy. pandas and
59
+ geopandas are optional extras.
60
+
61
+ ```bash
62
+ pip install routematrix # core
63
+ pip install "routematrix[pandas]" # DataFrame export / input
64
+ pip install "routematrix[geo]" # GeoDataFrame input, isochrones as GeoDataFrame
65
+ ```
66
+
67
+ ## Quick start
68
+
69
+ ```python
70
+ from routematrix import RouteMatrix, cumulative_opportunities
71
+
72
+ with RouteMatrix("osrm", url="http://localhost:5000", profile="driving",
73
+ cache="od_cache.sqlite", progress=True) as rm:
74
+ res = rm.compute("homes.csv", "clinics.csv") # CSV, (N, 2) lon/lat array,
75
+ # DataFrame or GeoDataFrame
76
+ res.durations # (N, M) float seconds, NaN = unreachable
77
+ res.distances # (N, M) float metres
78
+ res.origin_snap_m # distance from each origin to its snapped location
79
+ res.snap_flags(500) # (origin_flags, destination_flags) for snaps > 500 m
80
+ res.to_dataframe() # long format: origin_id, destination_id, duration_s, distance_m, ...
81
+ res.to_csv("matrix.csv") # long CSV
82
+ res.to_csv("wide.csv", wide=True) # one row per origin
83
+
84
+ # Clinics reachable within 15 minutes, per home
85
+ counts = cumulative_opportunities(res, threshold=15 * 60)
86
+ ```
87
+
88
+ Coordinates are always **longitude, latitude** (x, y) in EPSG:4326, like GeoJSON. CSV
89
+ columns are detected from common names (`lon`/`lng`/`longitude`/`x`,
90
+ `lat`/`latitude`/`y`, and `id`/`name` for labels).
91
+
92
+ ### Other engines
93
+
94
+ ```python
95
+ RouteMatrix("valhalla", url="http://localhost:8002", profile="walking") # -> pedestrian
96
+ RouteMatrix("ors", url="http://localhost:8080/ors", profile="cycling") # -> cycling-regular
97
+ RouteMatrix("ors", profile="driving") # hosted API, key from ORS_API_KEY
98
+ RouteMatrix("graphhopper", profile="car") # hosted API, key from GRAPHHOPPER_API_KEY
99
+ RouteMatrix("estimate", profile="walking", detour_factor=1.3) # NOT routed: an estimate
100
+ ```
101
+
102
+ `driving` / `walking` / `cycling` are mapped to each engine's own profile names; any
103
+ other value is passed through unchanged (e.g. `truck`, `foot-hiking`, `motor_scooter`).
104
+
105
+ ### Valhalla isochrones
106
+
107
+ ```python
108
+ from routematrix import isochrones_to_geodataframe
109
+
110
+ rm = RouteMatrix("valhalla", url="http://localhost:8002", profile="pedestrian")
111
+ fc = rm.isochrone((28.97, 41.01), minutes=[5, 10, 15]) # GeoJSON FeatureCollection
112
+ gdf = isochrones_to_geodataframe(fc) # needs the geo extra
113
+ ```
114
+
115
+ ### Accessibility and diagnostics
116
+
117
+ ```python
118
+ from routematrix import (nearest_k, cumulative_opportunities, gravity_accessibility,
119
+ asymmetry, compare_engines)
120
+
121
+ idx, secs = nearest_k(res, k=3) # 3 closest destinations per origin
122
+ jobs_30 = cumulative_opportunities(res, 1800, opportunities=jobs)
123
+ hansen = gravity_accessibility(res, jobs, function="exponential", beta=0.1, scale=60)
124
+ asymmetry(square_result) # one-way streets vs. data problems
125
+ compare_engines(origins, dests, RouteMatrix("osrm", url=A), RouteMatrix("valhalla", url=B))
126
+ ```
127
+
128
+ ## Command line
129
+
130
+ ```bash
131
+ routematrix compute origins.csv destinations.csv --engine osrm \
132
+ --url http://localhost:5000 --profile driving -o matrix.csv --cache od.sqlite
133
+ routematrix compute origins.csv --engine estimate --profile walking --wide -o walk.csv
134
+ routematrix nearest homes.csv clinics.csv --engine valhalla --url http://localhost:8002 \
135
+ --profile auto -k 3 -o nearest.csv
136
+ routematrix engines # default tile limits per engine
137
+ ```
138
+
139
+ Exit codes: `0` success, `2` error, `3` finished with failed tiles (`--on-error nan`).
140
+ Progress and a summary (reachability, cache hits, snap flags) go to stderr.
141
+
142
+ ## Engines
143
+
144
+ | Engine | Endpoint | Default tile | Snapping info | Units returned | Key |
145
+ |---|---|---|---|---|---|
146
+ | OSRM | `GET /table/v1/{profile}/…` | 100 × 100 (`osrm-routed --max-table-size` default 100) | yes | s, m | – |
147
+ | Valhalla | `POST /sources_to_targets` | 50 × 50 = 2 500 pairs (`max_matrix_location_pairs`) | no | s, km → m | – |
148
+ | GraphHopper | `POST /matrix` | 25 × 25 (plan dependent) | no | s, m | `GRAPHHOPPER_API_KEY` (hosted) |
149
+ | openrouteservice | `POST /v2/matrix/{profile}` | 50 × 50 = 2 500 routes (`maximum_routes`) | yes | s, m | `ORS_API_KEY` (hosted) |
150
+ | estimate | none (local) | 2 000 × 2 000 | no | s, m (estimated) | – |
151
+
152
+ If your server allows larger requests (e.g. `osrm-routed --max-table-size 1000`), pass
153
+ `max_sources=`, `max_destinations=` and `max_elements=` (CLI: `--max-sources` …). The
154
+ open-source GraphHopper server has no `/matrix` endpoint; "self-hosted" there means a
155
+ server exposing the same API. Valhalla also enforces `max_matrix_distance` (400 km for
156
+ `auto` by default); such requests fail with an engine error.
157
+
158
+ Defaults: concurrency 4 for your own servers, 1 for hosted APIs and demo servers;
159
+ hosted openrouteservice is spaced 1.6 s per request (free plan: 40 matrix requests/min),
160
+ hosted GraphHopper 0.5 s.
161
+
162
+ ## Run an engine locally (Docker)
163
+
164
+ OSRM (example extract: Monaco):
165
+
166
+ ```bash
167
+ wget https://download.geofabrik.de/europe/monaco-latest.osm.pbf
168
+ docker run -t -v "${PWD}:/data" ghcr.io/project-osrm/osrm-backend osrm-extract -p /opt/car.lua /data/monaco-latest.osm.pbf
169
+ docker run -t -v "${PWD}:/data" ghcr.io/project-osrm/osrm-backend osrm-partition /data/monaco-latest.osrm
170
+ docker run -t -v "${PWD}:/data" ghcr.io/project-osrm/osrm-backend osrm-customize /data/monaco-latest.osrm
171
+ 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
172
+ ```
173
+
174
+ Valhalla:
175
+
176
+ ```bash
177
+ mkdir -p custom_files
178
+ wget -O custom_files/monaco-latest.osm.pbf https://download.geofabrik.de/europe/monaco-latest.osm.pbf
179
+ docker run -dt --name valhalla -p 8002:8002 -v "${PWD}/custom_files:/custom_files" ghcr.io/valhalla/valhalla-scripted:latest
180
+ ```
181
+
182
+ Then use `--url http://localhost:5000` (OSRM) or `--url http://localhost:8002` (Valhalla).
183
+
184
+ ## Responsible use
185
+
186
+ - **No public server by default.** OSRM and Valhalla always need an explicit `url`.
187
+ The public demo servers (`router.project-osrm.org`, `valhalla1.openstreetmap.de`) are
188
+ run by volunteers for testing; routematrix accepts them only for tiny jobs
189
+ (at most 10 points and 25 pairs), forces one request at a time with a 1 s gap, and
190
+ warns. Anything larger raises `DemoServerLimitError`: run your own server.
191
+ - **Hosted APIs** (openrouteservice, GraphHopper) have quotas and terms of use. Keys are
192
+ read from environment variables, never written to the cache, and redacted from
193
+ error messages.
194
+ - Every request carries an identifying `User-Agent`; set your own contact via
195
+ `RouteMatrix(..., user_agent="myproject/1.0 (+mailto:me@example.org)")`.
196
+ - Respect the OpenStreetMap ODbL: credit "© OpenStreetMap contributors" when you publish
197
+ results derived from OSM-based routing.
198
+ - The `estimate` engine is not routing. Results carry `is_estimate=True`, a warning is
199
+ emitted, and the CLI prints `ESTIMATE`. Use it for sanity checks or as a clearly
200
+ labelled fallback only.
201
+ - Check snapping: points snapped hundreds of metres away (parks, water, private
202
+ ground, outside the extract) produce misleading times. Use `snap_flags()`.
203
+
204
+ ## Caching and resume
205
+
206
+ The cache key is `(engine + server URL, profile, origin, destination)` with
207
+ coordinates rounded to 1e-6 degrees. Unreachable pairs are cached as known-unreachable.
208
+ Rerunning a job after a crash, a `Ctrl+C` or with `on_error="nan"` requests only the
209
+ origin rows that still have gaps. Cached data does not expire: delete the file (or use
210
+ `MatrixCache(path).clear()`) after updating the road network.
211
+
212
+ ## Development
213
+
214
+ ```bash
215
+ pip install -e ".[dev]"
216
+ pytest # uses local mock servers only, never the network
217
+ ruff check . && ruff format --check src tests && mypy src
218
+ ```
219
+
220
+ ## License
221
+
222
+ MIT © Yusuf Eminoğlu
@@ -0,0 +1,182 @@
1
+ # routematrix
2
+
3
+ Large origin–destination (OD) travel-time and distance matrices from open routing
4
+ engines, for accessibility, logistics and planning analysis.
5
+
6
+ - One client for **OSRM**, **Valhalla**, **GraphHopper** and **openrouteservice**, plus a
7
+ clearly labelled great-circle **estimate** fallback.
8
+ - **Automatic tiling** of N × M jobs within each engine's request limits, stitched back
9
+ into dense numpy arrays: `durations` (s) and `distances` (m), `NaN` = unreachable.
10
+ - **Polite parallelism**: bounded concurrency, rate limiting for hosted APIs, retries
11
+ with exponential backoff (honours `Retry-After`), timeouts.
12
+ - **Persistent SQLite cache and resume**: every finished tile is stored at once; rerun
13
+ an interrupted job and only the missing pairs are requested.
14
+ - **Snapping diagnostics**: distance between each input point and where the engine
15
+ snapped it, with flags for points that are probably off the network.
16
+ - **Analysis**: nearest-k destinations, cumulative opportunities, gravity accessibility,
17
+ symmetry checks and engine-to-engine comparison.
18
+ - Dependencies: the standard library (`urllib`, `sqlite3`) and numpy. pandas and
19
+ geopandas are optional extras.
20
+
21
+ ```bash
22
+ pip install routematrix # core
23
+ pip install "routematrix[pandas]" # DataFrame export / input
24
+ pip install "routematrix[geo]" # GeoDataFrame input, isochrones as GeoDataFrame
25
+ ```
26
+
27
+ ## Quick start
28
+
29
+ ```python
30
+ from routematrix import RouteMatrix, cumulative_opportunities
31
+
32
+ with RouteMatrix("osrm", url="http://localhost:5000", profile="driving",
33
+ cache="od_cache.sqlite", progress=True) as rm:
34
+ res = rm.compute("homes.csv", "clinics.csv") # CSV, (N, 2) lon/lat array,
35
+ # DataFrame or GeoDataFrame
36
+ res.durations # (N, M) float seconds, NaN = unreachable
37
+ res.distances # (N, M) float metres
38
+ res.origin_snap_m # distance from each origin to its snapped location
39
+ res.snap_flags(500) # (origin_flags, destination_flags) for snaps > 500 m
40
+ res.to_dataframe() # long format: origin_id, destination_id, duration_s, distance_m, ...
41
+ res.to_csv("matrix.csv") # long CSV
42
+ res.to_csv("wide.csv", wide=True) # one row per origin
43
+
44
+ # Clinics reachable within 15 minutes, per home
45
+ counts = cumulative_opportunities(res, threshold=15 * 60)
46
+ ```
47
+
48
+ Coordinates are always **longitude, latitude** (x, y) in EPSG:4326, like GeoJSON. CSV
49
+ columns are detected from common names (`lon`/`lng`/`longitude`/`x`,
50
+ `lat`/`latitude`/`y`, and `id`/`name` for labels).
51
+
52
+ ### Other engines
53
+
54
+ ```python
55
+ RouteMatrix("valhalla", url="http://localhost:8002", profile="walking") # -> pedestrian
56
+ RouteMatrix("ors", url="http://localhost:8080/ors", profile="cycling") # -> cycling-regular
57
+ RouteMatrix("ors", profile="driving") # hosted API, key from ORS_API_KEY
58
+ RouteMatrix("graphhopper", profile="car") # hosted API, key from GRAPHHOPPER_API_KEY
59
+ RouteMatrix("estimate", profile="walking", detour_factor=1.3) # NOT routed: an estimate
60
+ ```
61
+
62
+ `driving` / `walking` / `cycling` are mapped to each engine's own profile names; any
63
+ other value is passed through unchanged (e.g. `truck`, `foot-hiking`, `motor_scooter`).
64
+
65
+ ### Valhalla isochrones
66
+
67
+ ```python
68
+ from routematrix import isochrones_to_geodataframe
69
+
70
+ rm = RouteMatrix("valhalla", url="http://localhost:8002", profile="pedestrian")
71
+ fc = rm.isochrone((28.97, 41.01), minutes=[5, 10, 15]) # GeoJSON FeatureCollection
72
+ gdf = isochrones_to_geodataframe(fc) # needs the geo extra
73
+ ```
74
+
75
+ ### Accessibility and diagnostics
76
+
77
+ ```python
78
+ from routematrix import (nearest_k, cumulative_opportunities, gravity_accessibility,
79
+ asymmetry, compare_engines)
80
+
81
+ idx, secs = nearest_k(res, k=3) # 3 closest destinations per origin
82
+ jobs_30 = cumulative_opportunities(res, 1800, opportunities=jobs)
83
+ hansen = gravity_accessibility(res, jobs, function="exponential", beta=0.1, scale=60)
84
+ asymmetry(square_result) # one-way streets vs. data problems
85
+ compare_engines(origins, dests, RouteMatrix("osrm", url=A), RouteMatrix("valhalla", url=B))
86
+ ```
87
+
88
+ ## Command line
89
+
90
+ ```bash
91
+ routematrix compute origins.csv destinations.csv --engine osrm \
92
+ --url http://localhost:5000 --profile driving -o matrix.csv --cache od.sqlite
93
+ routematrix compute origins.csv --engine estimate --profile walking --wide -o walk.csv
94
+ routematrix nearest homes.csv clinics.csv --engine valhalla --url http://localhost:8002 \
95
+ --profile auto -k 3 -o nearest.csv
96
+ routematrix engines # default tile limits per engine
97
+ ```
98
+
99
+ Exit codes: `0` success, `2` error, `3` finished with failed tiles (`--on-error nan`).
100
+ Progress and a summary (reachability, cache hits, snap flags) go to stderr.
101
+
102
+ ## Engines
103
+
104
+ | Engine | Endpoint | Default tile | Snapping info | Units returned | Key |
105
+ |---|---|---|---|---|---|
106
+ | OSRM | `GET /table/v1/{profile}/…` | 100 × 100 (`osrm-routed --max-table-size` default 100) | yes | s, m | – |
107
+ | Valhalla | `POST /sources_to_targets` | 50 × 50 = 2 500 pairs (`max_matrix_location_pairs`) | no | s, km → m | – |
108
+ | GraphHopper | `POST /matrix` | 25 × 25 (plan dependent) | no | s, m | `GRAPHHOPPER_API_KEY` (hosted) |
109
+ | openrouteservice | `POST /v2/matrix/{profile}` | 50 × 50 = 2 500 routes (`maximum_routes`) | yes | s, m | `ORS_API_KEY` (hosted) |
110
+ | estimate | none (local) | 2 000 × 2 000 | no | s, m (estimated) | – |
111
+
112
+ If your server allows larger requests (e.g. `osrm-routed --max-table-size 1000`), pass
113
+ `max_sources=`, `max_destinations=` and `max_elements=` (CLI: `--max-sources` …). The
114
+ open-source GraphHopper server has no `/matrix` endpoint; "self-hosted" there means a
115
+ server exposing the same API. Valhalla also enforces `max_matrix_distance` (400 km for
116
+ `auto` by default); such requests fail with an engine error.
117
+
118
+ Defaults: concurrency 4 for your own servers, 1 for hosted APIs and demo servers;
119
+ hosted openrouteservice is spaced 1.6 s per request (free plan: 40 matrix requests/min),
120
+ hosted GraphHopper 0.5 s.
121
+
122
+ ## Run an engine locally (Docker)
123
+
124
+ OSRM (example extract: Monaco):
125
+
126
+ ```bash
127
+ wget https://download.geofabrik.de/europe/monaco-latest.osm.pbf
128
+ docker run -t -v "${PWD}:/data" ghcr.io/project-osrm/osrm-backend osrm-extract -p /opt/car.lua /data/monaco-latest.osm.pbf
129
+ docker run -t -v "${PWD}:/data" ghcr.io/project-osrm/osrm-backend osrm-partition /data/monaco-latest.osrm
130
+ docker run -t -v "${PWD}:/data" ghcr.io/project-osrm/osrm-backend osrm-customize /data/monaco-latest.osrm
131
+ 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
132
+ ```
133
+
134
+ Valhalla:
135
+
136
+ ```bash
137
+ mkdir -p custom_files
138
+ wget -O custom_files/monaco-latest.osm.pbf https://download.geofabrik.de/europe/monaco-latest.osm.pbf
139
+ docker run -dt --name valhalla -p 8002:8002 -v "${PWD}/custom_files:/custom_files" ghcr.io/valhalla/valhalla-scripted:latest
140
+ ```
141
+
142
+ Then use `--url http://localhost:5000` (OSRM) or `--url http://localhost:8002` (Valhalla).
143
+
144
+ ## Responsible use
145
+
146
+ - **No public server by default.** OSRM and Valhalla always need an explicit `url`.
147
+ The public demo servers (`router.project-osrm.org`, `valhalla1.openstreetmap.de`) are
148
+ run by volunteers for testing; routematrix accepts them only for tiny jobs
149
+ (at most 10 points and 25 pairs), forces one request at a time with a 1 s gap, and
150
+ warns. Anything larger raises `DemoServerLimitError`: run your own server.
151
+ - **Hosted APIs** (openrouteservice, GraphHopper) have quotas and terms of use. Keys are
152
+ read from environment variables, never written to the cache, and redacted from
153
+ error messages.
154
+ - Every request carries an identifying `User-Agent`; set your own contact via
155
+ `RouteMatrix(..., user_agent="myproject/1.0 (+mailto:me@example.org)")`.
156
+ - Respect the OpenStreetMap ODbL: credit "© OpenStreetMap contributors" when you publish
157
+ results derived from OSM-based routing.
158
+ - The `estimate` engine is not routing. Results carry `is_estimate=True`, a warning is
159
+ emitted, and the CLI prints `ESTIMATE`. Use it for sanity checks or as a clearly
160
+ labelled fallback only.
161
+ - Check snapping: points snapped hundreds of metres away (parks, water, private
162
+ ground, outside the extract) produce misleading times. Use `snap_flags()`.
163
+
164
+ ## Caching and resume
165
+
166
+ The cache key is `(engine + server URL, profile, origin, destination)` with
167
+ coordinates rounded to 1e-6 degrees. Unreachable pairs are cached as known-unreachable.
168
+ Rerunning a job after a crash, a `Ctrl+C` or with `on_error="nan"` requests only the
169
+ origin rows that still have gaps. Cached data does not expire: delete the file (or use
170
+ `MatrixCache(path).clear()`) after updating the road network.
171
+
172
+ ## Development
173
+
174
+ ```bash
175
+ pip install -e ".[dev]"
176
+ pytest # uses local mock servers only, never the network
177
+ ruff check . && ruff format --check src tests && mypy src
178
+ ```
179
+
180
+ ## License
181
+
182
+ MIT © Yusuf Eminoğlu
@@ -0,0 +1,122 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "routematrix"
7
+ version = "0.1.0"
8
+ authors = [
9
+ { name = "Yusuf Eminoğlu", email = "yusufeminoglu@gmail.com" }
10
+ ]
11
+ description = "Large origin-destination travel-time and distance matrices from OSRM, Valhalla, GraphHopper and openrouteservice, with tiling, caching, resume and accessibility tools."
12
+ keywords = [
13
+ "routing",
14
+ "travel-time",
15
+ "od-matrix",
16
+ "distance-matrix",
17
+ "accessibility",
18
+ "osrm",
19
+ "valhalla",
20
+ "graphhopper",
21
+ "openrouteservice",
22
+ "isochrone",
23
+ "logistics",
24
+ "transport-planning",
25
+ "gis",
26
+ ]
27
+ readme = "README.md"
28
+ license = "MIT"
29
+ license-files = ["LICENSE"]
30
+ requires-python = ">=3.9"
31
+ classifiers = [
32
+ "Development Status :: 4 - Beta",
33
+ "Programming Language :: Python :: 3",
34
+ "Programming Language :: Python :: 3.9",
35
+ "Programming Language :: Python :: 3.10",
36
+ "Programming Language :: Python :: 3.11",
37
+ "Programming Language :: Python :: 3.12",
38
+ "Programming Language :: Python :: 3.13",
39
+ "Operating System :: OS Independent",
40
+ "Topic :: Scientific/Engineering :: GIS",
41
+ "Intended Audience :: Science/Research",
42
+ "Intended Audience :: Developers",
43
+ "Typing :: Typed",
44
+ ]
45
+ dependencies = [
46
+ "numpy>=1.20.0",
47
+ ]
48
+
49
+ [project.optional-dependencies]
50
+ pandas = [
51
+ "pandas>=1.3.0",
52
+ ]
53
+ geo = [
54
+ "pandas>=1.3.0",
55
+ "geopandas>=0.12.0",
56
+ ]
57
+ dev = [
58
+ "pytest>=7.0.0",
59
+ "pytest-cov>=4.0.0",
60
+ "ruff>=0.1.0",
61
+ "mypy>=1.0.0",
62
+ "pandas>=1.3.0",
63
+ ]
64
+
65
+ [project.scripts]
66
+ routematrix = "routematrix.cli:main"
67
+
68
+ [project.urls]
69
+ Homepage = "https://github.com/YusufEminoglu/routematrix_sdk"
70
+ Repository = "https://github.com/YusufEminoglu/routematrix_sdk"
71
+ Issues = "https://github.com/YusufEminoglu/routematrix_sdk/issues"
72
+ Changelog = "https://github.com/YusufEminoglu/routematrix_sdk/blob/main/CHANGELOG.md"
73
+
74
+ [tool.setuptools]
75
+ package-dir = {"" = "src"}
76
+
77
+ [tool.setuptools.packages.find]
78
+ where = ["src"]
79
+
80
+ [tool.setuptools.package-data]
81
+ routematrix = ["py.typed"]
82
+
83
+ [tool.ruff]
84
+ line-length = 100
85
+ target-version = "py39"
86
+ exclude = [
87
+ "scratch",
88
+ ".venv",
89
+ "build",
90
+ "dist",
91
+ ]
92
+
93
+ [tool.ruff.lint]
94
+ select = ["E", "F", "W", "I", "B", "C4"]
95
+ ignore = ["E501"]
96
+
97
+ [tool.mypy]
98
+ files = ["src"]
99
+
100
+ [[tool.mypy.overrides]]
101
+ module = [
102
+ "pandas.*",
103
+ "geopandas.*",
104
+ ]
105
+ ignore_missing_imports = true
106
+
107
+ [tool.pytest.ini_options]
108
+ testpaths = ["tests"]
109
+ # Lets `pytest` run straight from a clone, without an editable install first.
110
+ pythonpath = ["src"]
111
+ filterwarnings = [
112
+ "ignore::DeprecationWarning",
113
+ "ignore::PendingDeprecationWarning",
114
+ ]
115
+
116
+ [tool.coverage.run]
117
+ source = ["routematrix"]
118
+ branch = true
119
+
120
+ [tool.coverage.report]
121
+ show_missing = true
122
+ fail_under = 85
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+