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.
Files changed (38) hide show
  1. aperta-0.1.0a0/LICENSE +21 -0
  2. aperta-0.1.0a0/PKG-INFO +185 -0
  3. aperta-0.1.0a0/README.md +134 -0
  4. aperta-0.1.0a0/pyproject.toml +145 -0
  5. aperta-0.1.0a0/setup.cfg +4 -0
  6. aperta-0.1.0a0/src/aperta/__init__.py +70 -0
  7. aperta-0.1.0a0/src/aperta/accessibility.py +481 -0
  8. aperta-0.1.0a0/src/aperta/calibration.py +758 -0
  9. aperta-0.1.0a0/src/aperta/errors.py +19 -0
  10. aperta-0.1.0a0/src/aperta/geo_mapping.py +336 -0
  11. aperta-0.1.0a0/src/aperta/geo_processing.py +452 -0
  12. aperta-0.1.0a0/src/aperta/network_processing.py +1016 -0
  13. aperta-0.1.0a0/src/aperta/od_pairs.py +1503 -0
  14. aperta-0.1.0a0/src/aperta/osm_helpers.py +254 -0
  15. aperta-0.1.0a0/src/aperta/overhead.py +647 -0
  16. aperta-0.1.0a0/src/aperta/routing.py +1195 -0
  17. aperta-0.1.0a0/src/aperta/topography.py +211 -0
  18. aperta-0.1.0a0/src/aperta/traffic_flows.py +260 -0
  19. aperta-0.1.0a0/src/aperta/utility.py +371 -0
  20. aperta-0.1.0a0/src/aperta/visualization.py +517 -0
  21. aperta-0.1.0a0/src/aperta.egg-info/PKG-INFO +185 -0
  22. aperta-0.1.0a0/src/aperta.egg-info/SOURCES.txt +36 -0
  23. aperta-0.1.0a0/src/aperta.egg-info/dependency_links.txt +1 -0
  24. aperta-0.1.0a0/src/aperta.egg-info/requires.txt +29 -0
  25. aperta-0.1.0a0/src/aperta.egg-info/top_level.txt +1 -0
  26. aperta-0.1.0a0/tests/test_accessibility.py +589 -0
  27. aperta-0.1.0a0/tests/test_calibration.py +327 -0
  28. aperta-0.1.0a0/tests/test_geo_pairs.py +692 -0
  29. aperta-0.1.0a0/tests/test_geo_processing.py +149 -0
  30. aperta-0.1.0a0/tests/test_network_processing.py +530 -0
  31. aperta-0.1.0a0/tests/test_od_pairs.py +1169 -0
  32. aperta-0.1.0a0/tests/test_osm_helpers.py +202 -0
  33. aperta-0.1.0a0/tests/test_overhead.py +449 -0
  34. aperta-0.1.0a0/tests/test_routing.py +596 -0
  35. aperta-0.1.0a0/tests/test_traffic_flows.py +430 -0
  36. aperta-0.1.0a0/tests/test_utility.py +305 -0
  37. aperta-0.1.0a0/tests/test_visualization.py +307 -0
  38. 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.
@@ -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
+ [![tests](https://github.com/mmiotti/aperta/actions/workflows/test.yml/badge.svg)](https://github.com/mmiotti/aperta/actions/workflows/test.yml)
55
+ [![docs](https://readthedocs.org/projects/aperta/badge/?version=latest)](https://aperta.readthedocs.io/en/latest/)
56
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
57
+ [![license](https://img.shields.io/github/license/mmiotti/aperta.svg)](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
+ ![Three families of aperta capabilities, illustrated on the Bern region: network preparation (estimated traffic volumes and calibrated edge speeds), path feature collection (bike-comfort scores along realized routes and aggregated per origin cell), and accessibility analysis (time-based access to hiking opportunities and cross-modal utility-based access to groceries).](docs/assets/hero.jpg)
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).
@@ -0,0 +1,134 @@
1
+ # aperta
2
+
3
+ [![tests](https://github.com/mmiotti/aperta/actions/workflows/test.yml/badge.svg)](https://github.com/mmiotti/aperta/actions/workflows/test.yml)
4
+ [![docs](https://readthedocs.org/projects/aperta/badge/?version=latest)](https://aperta.readthedocs.io/en/latest/)
5
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
6
+ [![license](https://img.shields.io/github/license/mmiotti/aperta.svg)](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
+ ![Three families of aperta capabilities, illustrated on the Bern region: network preparation (estimated traffic volumes and calibrated edge speeds), path feature collection (bike-comfort scores along realized routes and aggregated per origin cell), and accessibility analysis (time-based access to hiking opportunities and cross-modal utility-based access to groceries).](docs/assets/hero.jpg)
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"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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__"]