aperta 0.1.0a0__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.
- aperta-0.1.0a0/LICENSE +21 -0
- aperta-0.1.0a0/PKG-INFO +185 -0
- aperta-0.1.0a0/README.md +134 -0
- aperta-0.1.0a0/pyproject.toml +145 -0
- aperta-0.1.0a0/setup.cfg +4 -0
- aperta-0.1.0a0/src/aperta/__init__.py +70 -0
- aperta-0.1.0a0/src/aperta/accessibility.py +481 -0
- aperta-0.1.0a0/src/aperta/calibration.py +758 -0
- aperta-0.1.0a0/src/aperta/errors.py +19 -0
- aperta-0.1.0a0/src/aperta/geo_mapping.py +336 -0
- aperta-0.1.0a0/src/aperta/geo_processing.py +452 -0
- aperta-0.1.0a0/src/aperta/network_processing.py +1016 -0
- aperta-0.1.0a0/src/aperta/od_pairs.py +1503 -0
- aperta-0.1.0a0/src/aperta/osm_helpers.py +254 -0
- aperta-0.1.0a0/src/aperta/overhead.py +647 -0
- aperta-0.1.0a0/src/aperta/routing.py +1195 -0
- aperta-0.1.0a0/src/aperta/topography.py +211 -0
- aperta-0.1.0a0/src/aperta/traffic_flows.py +260 -0
- aperta-0.1.0a0/src/aperta/utility.py +371 -0
- aperta-0.1.0a0/src/aperta/visualization.py +517 -0
- aperta-0.1.0a0/src/aperta.egg-info/PKG-INFO +185 -0
- aperta-0.1.0a0/src/aperta.egg-info/SOURCES.txt +36 -0
- aperta-0.1.0a0/src/aperta.egg-info/dependency_links.txt +1 -0
- aperta-0.1.0a0/src/aperta.egg-info/requires.txt +29 -0
- aperta-0.1.0a0/src/aperta.egg-info/top_level.txt +1 -0
- aperta-0.1.0a0/tests/test_accessibility.py +589 -0
- aperta-0.1.0a0/tests/test_calibration.py +327 -0
- aperta-0.1.0a0/tests/test_geo_pairs.py +692 -0
- aperta-0.1.0a0/tests/test_geo_processing.py +149 -0
- aperta-0.1.0a0/tests/test_network_processing.py +530 -0
- aperta-0.1.0a0/tests/test_od_pairs.py +1169 -0
- aperta-0.1.0a0/tests/test_osm_helpers.py +202 -0
- aperta-0.1.0a0/tests/test_overhead.py +449 -0
- aperta-0.1.0a0/tests/test_routing.py +596 -0
- aperta-0.1.0a0/tests/test_traffic_flows.py +430 -0
- aperta-0.1.0a0/tests/test_utility.py +305 -0
- aperta-0.1.0a0/tests/test_visualization.py +307 -0
- aperta-0.1.0a0/tests/test_workflow.py +210 -0
aperta-0.1.0a0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Marco Miotti
|
|
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.
|
aperta-0.1.0a0/PKG-INFO
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aperta
|
|
3
|
+
Version: 0.1.0a0
|
|
4
|
+
Summary: Python toolkit for cross-modal accessibility analysis on transport networks.
|
|
5
|
+
Author-email: Marco Miotti <marco@miotti.me>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/mmiotti/aperta
|
|
8
|
+
Project-URL: Documentation, https://aperta.readthedocs.io/
|
|
9
|
+
Project-URL: Repository, https://github.com/mmiotti/aperta
|
|
10
|
+
Project-URL: Issues, https://github.com/mmiotti/aperta/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/mmiotti/aperta/blob/main/CHANGELOG.md
|
|
12
|
+
Keywords: accessibility,transport,mobility,urban,routing,networks,gis
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: numpy>=2.0
|
|
27
|
+
Requires-Dist: pandas>=2.0
|
|
28
|
+
Requires-Dist: geopandas>=1.0
|
|
29
|
+
Requires-Dist: networkx>=3.0
|
|
30
|
+
Requires-Dist: scipy>=1.10
|
|
31
|
+
Requires-Dist: statsmodels>=0.14
|
|
32
|
+
Requires-Dist: numba>=0.60
|
|
33
|
+
Requires-Dist: matplotlib>=3.8
|
|
34
|
+
Provides-Extra: osm
|
|
35
|
+
Requires-Dist: osmnx>=2.0; extra == "osm"
|
|
36
|
+
Provides-Extra: topo
|
|
37
|
+
Requires-Dist: rasterio>=1.3; extra == "topo"
|
|
38
|
+
Requires-Dist: requests>=2.28; extra == "topo"
|
|
39
|
+
Provides-Extra: h3
|
|
40
|
+
Requires-Dist: h3>=4.0; extra == "h3"
|
|
41
|
+
Provides-Extra: examples
|
|
42
|
+
Requires-Dist: aperta[osm]; extra == "examples"
|
|
43
|
+
Requires-Dist: aperta[h3]; extra == "examples"
|
|
44
|
+
Requires-Dist: jupytext>=1.16; extra == "examples"
|
|
45
|
+
Requires-Dist: contextily>=1.4; extra == "examples"
|
|
46
|
+
Provides-Extra: docs
|
|
47
|
+
Requires-Dist: sphinx>=8.0; extra == "docs"
|
|
48
|
+
Requires-Dist: furo>=2024.0; extra == "docs"
|
|
49
|
+
Requires-Dist: myst-parser>=4.0; extra == "docs"
|
|
50
|
+
Dynamic: license-file
|
|
51
|
+
|
|
52
|
+
# aperta
|
|
53
|
+
|
|
54
|
+
[](https://github.com/mmiotti/aperta/actions/workflows/test.yml)
|
|
55
|
+
[](https://aperta.readthedocs.io/en/latest/)
|
|
56
|
+
[](https://github.com/astral-sh/ruff)
|
|
57
|
+
[](LICENSE)
|
|
58
|
+
|
|
59
|
+
A Python toolkit for **cross-modal accessibility analysis on transport networks** — routing, distance/time computation, utility-based travel costs, and gravity- and logsum-based accessibility metrics on `networkx` graphs (routed via `scipy.sparse.csgraph`).
|
|
60
|
+
|
|
61
|
+

|
|
62
|
+
|
|
63
|
+
The name is Latin/Italian for *open* — the condition that accessibility, at root, measures.
|
|
64
|
+
|
|
65
|
+
## Status
|
|
66
|
+
|
|
67
|
+
**Pre-1.0, alpha.** Published alongside a toolkit paper (in submission). APIs may change without notice until v1.0.
|
|
68
|
+
|
|
69
|
+
## Install
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install aperta # algorithms only
|
|
73
|
+
pip install 'aperta[osm]' # + OSM ingestion (osmnx)
|
|
74
|
+
pip install 'aperta[examples]' # + everything needed to run the example notebooks
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Requires Python ≥ 3.11.
|
|
78
|
+
|
|
79
|
+
The `osm_helpers` and `topography` modules import their backing libraries
|
|
80
|
+
(`osmnx`, `rasterio`, `requests`) lazily — install the matching `[osm]` /
|
|
81
|
+
`[topo]` extra if you use them, otherwise an `ImportError` surfaces at first use.
|
|
82
|
+
|
|
83
|
+
For development:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
git clone git@github.com:mmiotti/aperta.git
|
|
87
|
+
cd aperta
|
|
88
|
+
pip install -e ".[osm,topo,h3]"
|
|
89
|
+
python -m unittest discover -s tests -t .
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
> If you plan to edit the example notebooks under `examples/`, run the
|
|
93
|
+
> [jupytext + nbstripout setup](CONTRIBUTING.md#editing-notebooks) once
|
|
94
|
+
> after cloning. Not needed if you're only using the library or
|
|
95
|
+
> modifying Python source.
|
|
96
|
+
|
|
97
|
+
## Workflow
|
|
98
|
+
|
|
99
|
+
Aperta is organized around a six-phase workflow. Phases 4 and 5's calibration sub-step are optional; the rest is the minimum end-to-end pipeline.
|
|
100
|
+
|
|
101
|
+
1. **Load and prepare data** — networks (one per mode), land use, topography, optional ground-truth data (traffic counters, travel-survey times).
|
|
102
|
+
2. **Map data to units** — aggregate source data into the `cells → zones` hierarchy; snap geo units to network nodes.
|
|
103
|
+
3. **Build sparse OD pairs** — the tiered OD structure (three distance tiers) with per-cell origins at near range and zone-aggregated destinations at far range, keeping per-origin compute bounded independently of network extent.
|
|
104
|
+
4. **(Optional) Estimate traffic flows** — sampled betweenness centrality; optionally calibrate against observed counter data.
|
|
105
|
+
5. **Estimate travel costs** — shortest paths on the routing graph plus per-cell trip overheads. Optionally: utility-based generalized costs and edge-weight calibration against observed travel times.
|
|
106
|
+
6. **Calculate accessibilities** — cumulative-opportunity, gravity, nearest-k, logsum (and cross-modal aggregation across per-mode results).
|
|
107
|
+
|
|
108
|
+
See the [API reference](https://aperta.readthedocs.io/en/latest/api/) for which module covers each phase and for the specific functions.
|
|
109
|
+
|
|
110
|
+
Runnable examples, in increasing depth:
|
|
111
|
+
|
|
112
|
+
- [examples/minimal/accessibility.ipynb](examples/minimal/accessibility.ipynb) — what aperta does in ~50 lines using only OpenStreetMap. Cambridge MA, ~10 s.
|
|
113
|
+
- [examples/walkthrough/accessibility.ipynb](examples/walkthrough/accessibility.ipynb) — guided tour of every primitive; walking + cycling, cross-modal logsum, path-first per-edge feature aggregation. Central Paris, ~1 min end-to-end.
|
|
114
|
+
- [examples/extended/](examples/extended/) — production-scale Bern + 40 km: prep pipeline, calibration against observed travel times, traffic-flow estimation, accessibility analysis. ~30 min.
|
|
115
|
+
|
|
116
|
+
The toy-world end-to-end test in [tests/test_workflow.py](tests/test_workflow.py) doubles as the smallest possible walk-through (~150 lines, runs in a second).
|
|
117
|
+
|
|
118
|
+
## Quick example
|
|
119
|
+
|
|
120
|
+
The three-line core of an accessibility analysis: build the tiered OD pairs, route shortest paths, count opportunities within a travel-time budget.
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from aperta import accessibility, od_pairs, routing
|
|
124
|
+
|
|
125
|
+
pairs = od_pairs.get_pairs(cells, r_cells=2000.0, node_column='node_id')
|
|
126
|
+
times = routing.tiered_path_costs(pairs, graph, weight='walk_time_s')
|
|
127
|
+
acc = accessibility.cumulative_opportunities(
|
|
128
|
+
times, {'supermarkets': weights}, {},
|
|
129
|
+
[accessibility.Bin('15min', 0, 15 * 60)],
|
|
130
|
+
)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
A complete, runnable version (OSM ingestion, plotting): [`examples/minimal/accessibility.ipynb`](examples/minimal/accessibility.ipynb).
|
|
134
|
+
|
|
135
|
+
## Modules
|
|
136
|
+
|
|
137
|
+
See the [API reference](https://aperta.readthedocs.io/en/latest/api/) for module-by-module documentation.
|
|
138
|
+
|
|
139
|
+
## Design
|
|
140
|
+
|
|
141
|
+
What aperta is:
|
|
142
|
+
|
|
143
|
+
- **Path-first.** Routing returns the realized route alongside the cost as a single primitive, so per-edge attributes (gradient, exposure, surface, perceived safety) aggregate along the path natively — the architectural prerequisite for utility-based and route-aware accessibility.
|
|
144
|
+
- **Cross-modal.** Mode and network are orthogonal: one network per mode, with `min` / `logsum` aggregation across modes as a first-class operation. Generalizes to any axis of network variation — time-of-day, congestion regime, infrastructure scenario.
|
|
145
|
+
- **Multi-scale.** A tiered cell / zone OD structure bounds per-origin computation independently of network extent — country-scale reach without country-scale destination counts.
|
|
146
|
+
- **Live-graph routing.** Dijkstra on the graph directly, no precomputed index. Slower per query than contraction-hierarchy tools, but edge-weight changes are immediate — what makes iterative calibration and scenario comparison practical.
|
|
147
|
+
|
|
148
|
+
What aperta is not:
|
|
149
|
+
|
|
150
|
+
- **No filesystem assumptions.** Algorithm functions take plain `networkx` graphs, `pandas` / `geopandas` frames, and `numpy` arrays — no file I/O.
|
|
151
|
+
- **No DAG engine, no global state.** No caching, no dependency tracking, no orchestration. Every function takes its inputs explicitly. For DAG features, layer [DVC](https://dvc.org/) or [Snakemake](https://snakemake.readthedocs.io/) on top.
|
|
152
|
+
|
|
153
|
+
## Interoperability with other accessibility tools
|
|
154
|
+
|
|
155
|
+
Aperta deliberately doesn't try to do everything in-house. Two interoperability patterns are worth flagging:
|
|
156
|
+
|
|
157
|
+
- **Public transit via R5.** Aperta has no native public-transit support right now (no GTFS reader, no RAPTOR-style time-dependent routing). Anything that can be expressed as a `networkx` graph with appropriate edge weights — including simplified transit-as-graph models — will route in aperta like any other network. For full GTFS-based transit routing (calendars, transfers, frequency-based services), the pragmatic pattern is to compute the transit OD cost matrix with [R5](https://github.com/conveyal/r5) (via [r5py](https://r5py.readthedocs.io/)), align its origins/destinations to the same cell layer aperta uses, and feed the resulting per-mode cost ODM into `od_pairs.aggregate_across_modes` alongside the walk / cycle / car ODMs computed by aperta. The cross-modal aggregation proceeds identically whether each per-mode ODM came from aperta's router or elsewhere.
|
|
158
|
+
- **Faster cost-only routing via Pandana/pandarm.** Aperta's live-graph routing is the right trade-off for path-first, iterative, and scenario-comparative workloads, but for one-shot cost-only accessibility on a large fixed network, contraction-hierarchy backends like [Pandana](https://udst.github.io/pandana/) (and its recent modernized fork pandarm) route faster per query. The calibrated edge weights produced by `calibration.calibrate_edge_weights` are plain per-edge attributes on the `networkx` graph and transfer cleanly to a Pandana/pandarm network built from the same OSM extract — i.e., you can calibrate edge weights in aperta and then route with them in Pandana/pandarm.
|
|
159
|
+
|
|
160
|
+
## Benchmark vs Pandana
|
|
161
|
+
|
|
162
|
+
Ultimate speed for the full accessibility stack was not aperta's goal. Nonetheless, aperta typically runs within 1–5× of Pandana on equivalent cumulative-opportunity workloads. When the area of interest (for which to calculate accessibilities) is substantially smaller than the buffer zone (destinations to consider), or when aiming to recalculate accessibilities for a select subset of locations after a graph topology or edge weight change, aperta can even be faster than Pandana. See the [benchmark](https://aperta.readthedocs.io/en/latest/benchmark.html) for the full setup and numbers, or run [`examples/extended/benchmark.py`](examples/extended/benchmark.py) to reproduce.
|
|
163
|
+
|
|
164
|
+
## Acknowledgments
|
|
165
|
+
|
|
166
|
+
Aperta was developed at the [Chair of Ecological Systems Design](https://esd.ifu.ethz.ch/) at [ETH Zurich](https://ethz.ch) in the context of the [BlueCity](https://www.epfl.ch/schools/enac/blue-city-project/) project and [LUMOS](https://csfm.ethz.ch/en/research/projects/lumos.html).
|
|
167
|
+
|
|
168
|
+
## Cite this
|
|
169
|
+
|
|
170
|
+
If you use aperta in a publication, please cite the archived release:
|
|
171
|
+
|
|
172
|
+
```bibtex
|
|
173
|
+
@software{miotti_aperta_2026,
|
|
174
|
+
author = {Miotti, Marco},
|
|
175
|
+
title = {{Aperta: Path-first, cross-modal accessibility analysis in Python}},
|
|
176
|
+
year = 2026,
|
|
177
|
+
publisher = {Zenodo},
|
|
178
|
+
doi = {10.5281/zenodo.XXXXXXX},
|
|
179
|
+
url = {https://doi.org/10.5281/zenodo.XXXXXXX}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
MIT. See [LICENSE](LICENSE).
|
aperta-0.1.0a0/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# aperta
|
|
2
|
+
|
|
3
|
+
[](https://github.com/mmiotti/aperta/actions/workflows/test.yml)
|
|
4
|
+
[](https://aperta.readthedocs.io/en/latest/)
|
|
5
|
+
[](https://github.com/astral-sh/ruff)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
A Python toolkit for **cross-modal accessibility analysis on transport networks** — routing, distance/time computation, utility-based travel costs, and gravity- and logsum-based accessibility metrics on `networkx` graphs (routed via `scipy.sparse.csgraph`).
|
|
9
|
+
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
The name is Latin/Italian for *open* — the condition that accessibility, at root, measures.
|
|
13
|
+
|
|
14
|
+
## Status
|
|
15
|
+
|
|
16
|
+
**Pre-1.0, alpha.** Published alongside a toolkit paper (in submission). APIs may change without notice until v1.0.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install aperta # algorithms only
|
|
22
|
+
pip install 'aperta[osm]' # + OSM ingestion (osmnx)
|
|
23
|
+
pip install 'aperta[examples]' # + everything needed to run the example notebooks
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Requires Python ≥ 3.11.
|
|
27
|
+
|
|
28
|
+
The `osm_helpers` and `topography` modules import their backing libraries
|
|
29
|
+
(`osmnx`, `rasterio`, `requests`) lazily — install the matching `[osm]` /
|
|
30
|
+
`[topo]` extra if you use them, otherwise an `ImportError` surfaces at first use.
|
|
31
|
+
|
|
32
|
+
For development:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
git clone git@github.com:mmiotti/aperta.git
|
|
36
|
+
cd aperta
|
|
37
|
+
pip install -e ".[osm,topo,h3]"
|
|
38
|
+
python -m unittest discover -s tests -t .
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
> If you plan to edit the example notebooks under `examples/`, run the
|
|
42
|
+
> [jupytext + nbstripout setup](CONTRIBUTING.md#editing-notebooks) once
|
|
43
|
+
> after cloning. Not needed if you're only using the library or
|
|
44
|
+
> modifying Python source.
|
|
45
|
+
|
|
46
|
+
## Workflow
|
|
47
|
+
|
|
48
|
+
Aperta is organized around a six-phase workflow. Phases 4 and 5's calibration sub-step are optional; the rest is the minimum end-to-end pipeline.
|
|
49
|
+
|
|
50
|
+
1. **Load and prepare data** — networks (one per mode), land use, topography, optional ground-truth data (traffic counters, travel-survey times).
|
|
51
|
+
2. **Map data to units** — aggregate source data into the `cells → zones` hierarchy; snap geo units to network nodes.
|
|
52
|
+
3. **Build sparse OD pairs** — the tiered OD structure (three distance tiers) with per-cell origins at near range and zone-aggregated destinations at far range, keeping per-origin compute bounded independently of network extent.
|
|
53
|
+
4. **(Optional) Estimate traffic flows** — sampled betweenness centrality; optionally calibrate against observed counter data.
|
|
54
|
+
5. **Estimate travel costs** — shortest paths on the routing graph plus per-cell trip overheads. Optionally: utility-based generalized costs and edge-weight calibration against observed travel times.
|
|
55
|
+
6. **Calculate accessibilities** — cumulative-opportunity, gravity, nearest-k, logsum (and cross-modal aggregation across per-mode results).
|
|
56
|
+
|
|
57
|
+
See the [API reference](https://aperta.readthedocs.io/en/latest/api/) for which module covers each phase and for the specific functions.
|
|
58
|
+
|
|
59
|
+
Runnable examples, in increasing depth:
|
|
60
|
+
|
|
61
|
+
- [examples/minimal/accessibility.ipynb](examples/minimal/accessibility.ipynb) — what aperta does in ~50 lines using only OpenStreetMap. Cambridge MA, ~10 s.
|
|
62
|
+
- [examples/walkthrough/accessibility.ipynb](examples/walkthrough/accessibility.ipynb) — guided tour of every primitive; walking + cycling, cross-modal logsum, path-first per-edge feature aggregation. Central Paris, ~1 min end-to-end.
|
|
63
|
+
- [examples/extended/](examples/extended/) — production-scale Bern + 40 km: prep pipeline, calibration against observed travel times, traffic-flow estimation, accessibility analysis. ~30 min.
|
|
64
|
+
|
|
65
|
+
The toy-world end-to-end test in [tests/test_workflow.py](tests/test_workflow.py) doubles as the smallest possible walk-through (~150 lines, runs in a second).
|
|
66
|
+
|
|
67
|
+
## Quick example
|
|
68
|
+
|
|
69
|
+
The three-line core of an accessibility analysis: build the tiered OD pairs, route shortest paths, count opportunities within a travel-time budget.
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from aperta import accessibility, od_pairs, routing
|
|
73
|
+
|
|
74
|
+
pairs = od_pairs.get_pairs(cells, r_cells=2000.0, node_column='node_id')
|
|
75
|
+
times = routing.tiered_path_costs(pairs, graph, weight='walk_time_s')
|
|
76
|
+
acc = accessibility.cumulative_opportunities(
|
|
77
|
+
times, {'supermarkets': weights}, {},
|
|
78
|
+
[accessibility.Bin('15min', 0, 15 * 60)],
|
|
79
|
+
)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
A complete, runnable version (OSM ingestion, plotting): [`examples/minimal/accessibility.ipynb`](examples/minimal/accessibility.ipynb).
|
|
83
|
+
|
|
84
|
+
## Modules
|
|
85
|
+
|
|
86
|
+
See the [API reference](https://aperta.readthedocs.io/en/latest/api/) for module-by-module documentation.
|
|
87
|
+
|
|
88
|
+
## Design
|
|
89
|
+
|
|
90
|
+
What aperta is:
|
|
91
|
+
|
|
92
|
+
- **Path-first.** Routing returns the realized route alongside the cost as a single primitive, so per-edge attributes (gradient, exposure, surface, perceived safety) aggregate along the path natively — the architectural prerequisite for utility-based and route-aware accessibility.
|
|
93
|
+
- **Cross-modal.** Mode and network are orthogonal: one network per mode, with `min` / `logsum` aggregation across modes as a first-class operation. Generalizes to any axis of network variation — time-of-day, congestion regime, infrastructure scenario.
|
|
94
|
+
- **Multi-scale.** A tiered cell / zone OD structure bounds per-origin computation independently of network extent — country-scale reach without country-scale destination counts.
|
|
95
|
+
- **Live-graph routing.** Dijkstra on the graph directly, no precomputed index. Slower per query than contraction-hierarchy tools, but edge-weight changes are immediate — what makes iterative calibration and scenario comparison practical.
|
|
96
|
+
|
|
97
|
+
What aperta is not:
|
|
98
|
+
|
|
99
|
+
- **No filesystem assumptions.** Algorithm functions take plain `networkx` graphs, `pandas` / `geopandas` frames, and `numpy` arrays — no file I/O.
|
|
100
|
+
- **No DAG engine, no global state.** No caching, no dependency tracking, no orchestration. Every function takes its inputs explicitly. For DAG features, layer [DVC](https://dvc.org/) or [Snakemake](https://snakemake.readthedocs.io/) on top.
|
|
101
|
+
|
|
102
|
+
## Interoperability with other accessibility tools
|
|
103
|
+
|
|
104
|
+
Aperta deliberately doesn't try to do everything in-house. Two interoperability patterns are worth flagging:
|
|
105
|
+
|
|
106
|
+
- **Public transit via R5.** Aperta has no native public-transit support right now (no GTFS reader, no RAPTOR-style time-dependent routing). Anything that can be expressed as a `networkx` graph with appropriate edge weights — including simplified transit-as-graph models — will route in aperta like any other network. For full GTFS-based transit routing (calendars, transfers, frequency-based services), the pragmatic pattern is to compute the transit OD cost matrix with [R5](https://github.com/conveyal/r5) (via [r5py](https://r5py.readthedocs.io/)), align its origins/destinations to the same cell layer aperta uses, and feed the resulting per-mode cost ODM into `od_pairs.aggregate_across_modes` alongside the walk / cycle / car ODMs computed by aperta. The cross-modal aggregation proceeds identically whether each per-mode ODM came from aperta's router or elsewhere.
|
|
107
|
+
- **Faster cost-only routing via Pandana/pandarm.** Aperta's live-graph routing is the right trade-off for path-first, iterative, and scenario-comparative workloads, but for one-shot cost-only accessibility on a large fixed network, contraction-hierarchy backends like [Pandana](https://udst.github.io/pandana/) (and its recent modernized fork pandarm) route faster per query. The calibrated edge weights produced by `calibration.calibrate_edge_weights` are plain per-edge attributes on the `networkx` graph and transfer cleanly to a Pandana/pandarm network built from the same OSM extract — i.e., you can calibrate edge weights in aperta and then route with them in Pandana/pandarm.
|
|
108
|
+
|
|
109
|
+
## Benchmark vs Pandana
|
|
110
|
+
|
|
111
|
+
Ultimate speed for the full accessibility stack was not aperta's goal. Nonetheless, aperta typically runs within 1–5× of Pandana on equivalent cumulative-opportunity workloads. When the area of interest (for which to calculate accessibilities) is substantially smaller than the buffer zone (destinations to consider), or when aiming to recalculate accessibilities for a select subset of locations after a graph topology or edge weight change, aperta can even be faster than Pandana. See the [benchmark](https://aperta.readthedocs.io/en/latest/benchmark.html) for the full setup and numbers, or run [`examples/extended/benchmark.py`](examples/extended/benchmark.py) to reproduce.
|
|
112
|
+
|
|
113
|
+
## Acknowledgments
|
|
114
|
+
|
|
115
|
+
Aperta was developed at the [Chair of Ecological Systems Design](https://esd.ifu.ethz.ch/) at [ETH Zurich](https://ethz.ch) in the context of the [BlueCity](https://www.epfl.ch/schools/enac/blue-city-project/) project and [LUMOS](https://csfm.ethz.ch/en/research/projects/lumos.html).
|
|
116
|
+
|
|
117
|
+
## Cite this
|
|
118
|
+
|
|
119
|
+
If you use aperta in a publication, please cite the archived release:
|
|
120
|
+
|
|
121
|
+
```bibtex
|
|
122
|
+
@software{miotti_aperta_2026,
|
|
123
|
+
author = {Miotti, Marco},
|
|
124
|
+
title = {{Aperta: Path-first, cross-modal accessibility analysis in Python}},
|
|
125
|
+
year = 2026,
|
|
126
|
+
publisher = {Zenodo},
|
|
127
|
+
doi = {10.5281/zenodo.XXXXXXX},
|
|
128
|
+
url = {https://doi.org/10.5281/zenodo.XXXXXXX}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "aperta"
|
|
7
|
+
version = "0.1.0a0"
|
|
8
|
+
description = "Python toolkit for cross-modal accessibility analysis on transport networks."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [
|
|
14
|
+
{name = "Marco Miotti", email = "marco@miotti.me"},
|
|
15
|
+
]
|
|
16
|
+
keywords = [
|
|
17
|
+
"accessibility",
|
|
18
|
+
"transport",
|
|
19
|
+
"mobility",
|
|
20
|
+
"urban",
|
|
21
|
+
"routing",
|
|
22
|
+
"networks",
|
|
23
|
+
"gis",
|
|
24
|
+
]
|
|
25
|
+
classifiers = [
|
|
26
|
+
"Development Status :: 3 - Alpha",
|
|
27
|
+
"Intended Audience :: Science/Research",
|
|
28
|
+
"Operating System :: OS Independent",
|
|
29
|
+
"Programming Language :: Python :: 3",
|
|
30
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
31
|
+
"Programming Language :: Python :: 3.11",
|
|
32
|
+
"Programming Language :: Python :: 3.12",
|
|
33
|
+
"Programming Language :: Python :: 3.13",
|
|
34
|
+
"Topic :: Scientific/Engineering :: GIS",
|
|
35
|
+
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
36
|
+
]
|
|
37
|
+
dependencies = [
|
|
38
|
+
"numpy>=2.0",
|
|
39
|
+
"pandas>=2.0",
|
|
40
|
+
"geopandas>=1.0",
|
|
41
|
+
"networkx>=3.0", # input graph type; routing converts to scipy CSR
|
|
42
|
+
"scipy>=1.10", # routing (csgraph.dijkstra), KDTree, sparse matrices
|
|
43
|
+
"statsmodels>=0.14", # calibration.calibrate_edge_weights (OLS)
|
|
44
|
+
"numba>=0.60", # @njit hot loops in od_pairs + traffic_flows
|
|
45
|
+
"matplotlib>=3.8", # visualization module
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
# Optional feature extras. Core routing / accessibility / OD-pairs work
|
|
49
|
+
# on any networkx graph without any of these.
|
|
50
|
+
#
|
|
51
|
+
# - `osm`: OSM-derived networks. `osmnx` covers all three uses in
|
|
52
|
+
# aperta core:
|
|
53
|
+
# * `osm_helpers.fetch_network` / `categorize_edges` — OSM downloads;
|
|
54
|
+
# * `network_processing.consolidate_intersections` — wraps
|
|
55
|
+
# `osmnx.consolidate_intersections` for OSMnx-output cleanup;
|
|
56
|
+
# * `network_processing.load_consolidated_graphml` — graphml load
|
|
57
|
+
# with custom-dtype casts.
|
|
58
|
+
# - `topo`: `topography.fetch_copernicus_dem` + `geo_processing.
|
|
59
|
+
# sample_raster_at_points` need `rasterio` (raster I/O) and `requests`
|
|
60
|
+
# (DEM tile download).
|
|
61
|
+
# - `h3`: `geo_processing.build_h3_grid` (and any other h3-based helper).
|
|
62
|
+
# - `examples`: everything needed to run the example notebooks. Rolls up
|
|
63
|
+
# the three domain extras + notebook tooling (`jupytext`), basemap
|
|
64
|
+
# tiles (`contextily`), and Swiss-specific BFS PC-Axis parsing
|
|
65
|
+
# (`pyaxis`).
|
|
66
|
+
#
|
|
67
|
+
# Install with `pip install 'aperta[osm]'` (or any other extra), or
|
|
68
|
+
# `pip install 'aperta[examples]'` for the full notebook environment.
|
|
69
|
+
[project.urls]
|
|
70
|
+
Homepage = "https://github.com/mmiotti/aperta"
|
|
71
|
+
Documentation = "https://aperta.readthedocs.io/"
|
|
72
|
+
Repository = "https://github.com/mmiotti/aperta"
|
|
73
|
+
Issues = "https://github.com/mmiotti/aperta/issues"
|
|
74
|
+
Changelog = "https://github.com/mmiotti/aperta/blob/main/CHANGELOG.md"
|
|
75
|
+
|
|
76
|
+
[project.optional-dependencies]
|
|
77
|
+
osm = ["osmnx>=2.0"]
|
|
78
|
+
topo = ["rasterio>=1.3", "requests>=2.28"]
|
|
79
|
+
h3 = ["h3>=4.0"]
|
|
80
|
+
examples = [
|
|
81
|
+
"aperta[osm]",
|
|
82
|
+
"aperta[h3]",
|
|
83
|
+
"jupytext>=1.16",
|
|
84
|
+
"contextily>=1.4",
|
|
85
|
+
]
|
|
86
|
+
# Building the Sphinx docs site (read by .readthedocs.yaml).
|
|
87
|
+
docs = [
|
|
88
|
+
"sphinx>=8.0",
|
|
89
|
+
"furo>=2024.0",
|
|
90
|
+
"myst-parser>=4.0",
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
# Src layout: package code lives under `src/aperta/`. Prevents accidental
|
|
94
|
+
# imports of the local source (Python won't find `aperta` unless it's
|
|
95
|
+
# actually `pip install`-ed), which catches "works on my machine but
|
|
96
|
+
# breaks when installed" bugs early.
|
|
97
|
+
[tool.setuptools.packages.find]
|
|
98
|
+
where = ["src"]
|
|
99
|
+
|
|
100
|
+
# mypy: static type checker. Loose initial config (non-strict mode) +
|
|
101
|
+
# escape hatches for libraries without type stubs. Tighten over time.
|
|
102
|
+
[tool.mypy]
|
|
103
|
+
python_version = "3.11"
|
|
104
|
+
files = ["src/aperta"]
|
|
105
|
+
# Common libraries without complete type stubs in this stack. Silences
|
|
106
|
+
# "no type stubs for X" errors without disabling type checking of our
|
|
107
|
+
# own code that uses them.
|
|
108
|
+
[[tool.mypy.overrides]]
|
|
109
|
+
module = [
|
|
110
|
+
"pandas.*",
|
|
111
|
+
"geopandas.*",
|
|
112
|
+
"shapely.*",
|
|
113
|
+
"networkx.*",
|
|
114
|
+
"scipy.*",
|
|
115
|
+
"rasterio.*",
|
|
116
|
+
"osmnx.*",
|
|
117
|
+
"h3.*",
|
|
118
|
+
"contextily.*",
|
|
119
|
+
"matplotlib.*",
|
|
120
|
+
"mpl_toolkits.*",
|
|
121
|
+
"statsmodels.*",
|
|
122
|
+
"numba.*",
|
|
123
|
+
"pyaxis.*",
|
|
124
|
+
"requests.*",
|
|
125
|
+
]
|
|
126
|
+
ignore_missing_imports = true
|
|
127
|
+
|
|
128
|
+
# Ruff: linter + formatter. Lenient initial config; extend rule sets
|
|
129
|
+
# (B, UP, SIM, ...) as the project matures.
|
|
130
|
+
[tool.ruff]
|
|
131
|
+
line-length = 100
|
|
132
|
+
target-version = "py311"
|
|
133
|
+
extend-exclude = ["examples/", "build/", "dist/"]
|
|
134
|
+
|
|
135
|
+
[tool.ruff.lint]
|
|
136
|
+
select = [
|
|
137
|
+
"E", # pycodestyle errors
|
|
138
|
+
"F", # pyflakes (unused imports, undefined names, etc.)
|
|
139
|
+
"W", # pycodestyle warnings
|
|
140
|
+
"I", # isort (import ordering)
|
|
141
|
+
]
|
|
142
|
+
# E501 (line too long) is handled by the formatter; allowing it here
|
|
143
|
+
# means the linter doesn't complain about strings/comments that the
|
|
144
|
+
# formatter chose not to break.
|
|
145
|
+
ignore = ["E501"]
|
aperta-0.1.0a0/setup.cfg
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
aperta — cross-modal accessibility analysis on transport networks.
|
|
3
|
+
|
|
4
|
+
The library is organized around a six-phase workflow:
|
|
5
|
+
|
|
6
|
+
1. Load and prepare data — networks (per mode), land use, topography.
|
|
7
|
+
2. Map data to units — `cells → zones` aggregation hierarchy
|
|
8
|
+
(`geo_mapping`, `network_processing.snap_to_network_nodes`,
|
|
9
|
+
`network_processing.assign_to_eligible_centroid`).
|
|
10
|
+
3. Build sparse OD pairs — `od_pairs.get_pairs` returns a `TieredODNodePairs`
|
|
11
|
+
(three distance tiers: cells_to_cells,
|
|
12
|
+
cells_to_zones, zones_to_zones). Lift to
|
|
13
|
+
`TieredODGeoPairs` via
|
|
14
|
+
`od_pairs.reindex_by_geo_unit` for cross-modal
|
|
15
|
+
alignment / geo-unit-keyed overheads.
|
|
16
|
+
4. Estimate traffic flows — `traffic_flows.nested_node_sample` +
|
|
17
|
+
`network_processing.get_*_betweenness*`.
|
|
18
|
+
5. Estimate travel costs — `routing.tiered_path_costs` /
|
|
19
|
+
`routing.tiered_path_aggregate` (Dijkstra on any
|
|
20
|
+
networkx graph; the latter also aggregates
|
|
21
|
+
per-edge / per-node features along realised
|
|
22
|
+
routes via `PathAggregation` / `NodeAggregation`)
|
|
23
|
+
+ the `overhead` module
|
|
24
|
+
(`add_node_overheads` for node-keyed,
|
|
25
|
+
`add_geo_overheads` / `add_origin_cell_overhead`
|
|
26
|
+
for geo-keyed). `utility.route_utility` +
|
|
27
|
+
`add_endpoint_utility` for utility-based costs.
|
|
28
|
+
`routing.aggregate_along_paths` is the path-walker
|
|
29
|
+
primitive when you have a pre-computed list of
|
|
30
|
+
paths rather than a `TieredODPairs`.
|
|
31
|
+
6. Calculate accessibility — `accessibility.cumulative_opportunities` (cumulative),
|
|
32
|
+
`accessibility.gravity`, `accessibility.nearest_k`.
|
|
33
|
+
Cross-modal: combine per-mode `TieredODGeoPairs`
|
|
34
|
+
with `od_pairs.aggregate_across_modes` first.
|
|
35
|
+
|
|
36
|
+
All algorithm modules (`od_pairs`, `routing`, `overhead`, `accessibility`,
|
|
37
|
+
`utility`, `traffic_flows`, `geo_processing`, `geo_mapping`,
|
|
38
|
+
`network_processing`, `visualization`, `osm_helpers`, `calibration`,
|
|
39
|
+
`topography`, `errors`) operate on plain numpy / pandas / networkx inputs — no
|
|
40
|
+
filesystem assumptions, no opinionated project structure.
|
|
41
|
+
See `tests/test_workflow.py` for the ~150-line end-to-end toy-world
|
|
42
|
+
example, `examples/minimal/accessibility.ipynb` for a ~50-line OSM
|
|
43
|
+
quickstart, `examples/walkthrough/accessibility.ipynb` for the full
|
|
44
|
+
guided tour, and `examples/extended/` for a multi-notebook showcase
|
|
45
|
+
with published-paper calibration (Bern + 40 km).
|
|
46
|
+
|
|
47
|
+
Key types:
|
|
48
|
+
- `od_pairs.TieredODNodePairs` — three-tier OD dict-of-arrays keyed by network
|
|
49
|
+
node IDs. Output of routing.
|
|
50
|
+
- `od_pairs.TieredODGeoPairs` — three-tier OD dict-of-arrays keyed by
|
|
51
|
+
geo-unit IDs (cells / zones).
|
|
52
|
+
Mode-agnostic; required for cross-modal
|
|
53
|
+
accessibility and geo-unit-keyed overhead.
|
|
54
|
+
- `od_pairs.TieredODPairs` — abstract base of the two above; use as a
|
|
55
|
+
type hint when key space doesn't matter.
|
|
56
|
+
- `accessibility.Bin` — half-open cost bin for `cumulative_opportunities`.
|
|
57
|
+
- `accessibility.Decay` — named cost-decay callable for `gravity`.
|
|
58
|
+
- `utility.Utility` — linear utility spec (constant + cost + route
|
|
59
|
+
+ origin + destination feature coefficients).
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
from importlib.metadata import PackageNotFoundError
|
|
63
|
+
from importlib.metadata import version as _pkg_version
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
__version__ = _pkg_version("aperta")
|
|
67
|
+
except PackageNotFoundError: # running from a source tree without install
|
|
68
|
+
__version__ = "0.0.0+unknown"
|
|
69
|
+
|
|
70
|
+
__all__ = ["__version__"]
|