ObjectNat 2.0.0__tar.gz → 2.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.
- {objectnat-2.0.0 → objectnat-2.1.0}/PKG-INFO +28 -13
- {objectnat-2.0.0 → objectnat-2.1.0}/README.rst +17 -5
- {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/__init__.py +1 -1
- objectnat-2.1.0/objectnat/_version.py +1 -0
- {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/accessibility/_utils.py +30 -15
- {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/accessibility/coverage.py +7 -0
- {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/accessibility/isochrones.py +8 -0
- {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/noise/noise_simulation.py +9 -11
- {objectnat-2.0.0 → objectnat-2.1.0}/pyproject.toml +22 -12
- objectnat-2.0.0/objectnat/_version.py +0 -1
- {objectnat-2.0.0 → objectnat-2.1.0}/.gitignore +0 -0
- {objectnat-2.0.0 → objectnat-2.1.0}/LICENSE.txt +0 -0
- {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/_api.py +0 -0
- {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/_config.py +0 -0
- {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/__init__.py +0 -0
- {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/accessibility/__init__.py +0 -0
- {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/accessibility/radius.py +0 -0
- {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/noise/__init__.py +0 -0
- {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/noise/noise_init_data.py +0 -0
- {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/noise/noise_reduce.py +0 -0
- {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/noise/noise_simulation_simplified.py +0 -0
- {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/provision/__init__.py +0 -0
- {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/provision/provision.py +0 -0
- {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/provision/provision_calculating.py +0 -0
- {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/provision/provision_exceptions.py +0 -0
- {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/provision/provision_result.py +0 -0
- {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/utils/__init__.py +0 -0
- {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/utils/geom_utils.py +0 -0
- {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/visibility/__init__.py +0 -0
- {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/visibility/visibility_analysis.py +0 -0
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: ObjectNat
|
|
3
|
-
Version: 2.
|
|
4
|
-
Summary: ObjectNat is an open-source library
|
|
3
|
+
Version: 2.1.0
|
|
4
|
+
Summary: ObjectNat is an open-source library for object-level geospatial analysis on city networks, developed by the IDU team.
|
|
5
5
|
Author-email: DDonnyy <63115678+DDonnyy@users.noreply.github.com>
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
7
7
|
License-File: LICENSE.txt
|
|
8
|
-
Requires-Python: <3.
|
|
8
|
+
Requires-Python: <3.15,>=3.11
|
|
9
9
|
Requires-Dist: geopandas<2.0.0,>=1.1.0
|
|
10
|
-
Requires-Dist: iduedu<3.0.0,>=2.
|
|
10
|
+
Requires-Dist: iduedu<3.0.0,>=2.2.0
|
|
11
11
|
Requires-Dist: loguru<0.8.0,>=0.7.3
|
|
12
|
-
Requires-Dist: numpy<3.0.0,>=2.
|
|
13
|
-
Requires-Dist:
|
|
14
|
-
Requires-Dist:
|
|
12
|
+
Requires-Dist: numpy<3.0.0,>=2.0.0; python_version < '3.14'
|
|
13
|
+
Requires-Dist: numpy<3.0.0,>=2.4.0; python_version >= '3.14'
|
|
14
|
+
Requires-Dist: pandas<4.0.0,>=2.2.0; python_version < '3.14'
|
|
15
|
+
Requires-Dist: pandas<4.0.0,>=3.0.0; python_version >= '3.14'
|
|
16
|
+
Requires-Dist: scipy<2.0.0,>=1.16.0; python_version < '3.14'
|
|
17
|
+
Requires-Dist: scipy<2.0.0,>=1.17.0; python_version >= '3.14'
|
|
15
18
|
Requires-Dist: tqdm<5.0.0,>=4.66.2
|
|
16
19
|
Description-Content-Type: text/x-rst
|
|
17
20
|
|
|
@@ -33,8 +36,8 @@ Object-oriented Network Analysis Tools
|
|
|
33
36
|
:target: https://github.com/IDUclub/ObjectNat/actions/workflows/quality.yml
|
|
34
37
|
:alt: Tests and coverage
|
|
35
38
|
|
|
36
|
-
.. |badge-
|
|
37
|
-
:target: https://
|
|
39
|
+
.. |badge-coverage| image:: https://raw.githubusercontent.com/IDUclub/ObjectNat/python-coverage-comment-action-data/badge.svg
|
|
40
|
+
:target: https://github.com/IDUclub/ObjectNat/tree/python-coverage-comment-action-data
|
|
38
41
|
:alt: Coverage
|
|
39
42
|
|
|
40
43
|
.. |badge-license| image:: https://img.shields.io/badge/license-BSD--3--Clause-blue.svg
|
|
@@ -45,7 +48,7 @@ Object-oriented Network Analysis Tools
|
|
|
45
48
|
:target: https://iduclub.github.io/ObjectNat/
|
|
46
49
|
:alt: Docs
|
|
47
50
|
|
|
48
|
-
|badge-black| |badge-pypi| |badge-ci| |badge-
|
|
51
|
+
|badge-black| |badge-pypi| |badge-ci| |badge-coverage| |badge-license| |badge-docs|
|
|
49
52
|
|
|
50
53
|
`РИДМИ (Russian) <https://github.com/IDUclub/ObjectNat/blob/master/README_RU.rst>`__
|
|
51
54
|
|
|
@@ -59,6 +62,7 @@ Object-oriented Network Analysis Tools
|
|
|
59
62
|
for spatial and network analysis in urban studies.
|
|
60
63
|
The library provides tools for analyzing **accessibility**, **visibility**,
|
|
61
64
|
**noise propagation**, and **service provision**.
|
|
65
|
+
|
|
62
66
|
----
|
|
63
67
|
|
|
64
68
|
Key Features
|
|
@@ -120,7 +124,7 @@ Each feature includes a **Jupyter Notebook example** and **full documentation**.
|
|
|
120
124
|
|
|
121
125
|
📘 `Example <https://iduclub.github.io/ObjectNat/methods/examples/noise.html>`__
|
|
122
126
|
🔗 `Documentation <https://iduclub.github.io/ObjectNat/methods/noise.html>`__
|
|
123
|
-
🧠 `Detailed theory <https://github.com/
|
|
127
|
+
🧠 `Detailed theory <https://github.com/IDUclub/ObjectNat/wiki/Noise-simulation>`__
|
|
124
128
|
|
|
125
129
|
----
|
|
126
130
|
|
|
@@ -133,7 +137,7 @@ Graph-based ObjectNat methods consume ``iduedu.UrbanGraph`` directly; ObjectNat
|
|
|
133
137
|
does not build NetworkX graphs internally.
|
|
134
138
|
|
|
135
139
|
**IduEdu** is an open-source Python library designed for building and processing
|
|
136
|
-
complex urban networks
|
|
140
|
+
complex urban networks from OpenStreetMap and GTFS Schedule data.
|
|
137
141
|
|
|
138
142
|
|
|
139
143
|
**IduEdu** can be installed via ``pip``::
|
|
@@ -152,6 +156,9 @@ Example usage::
|
|
|
152
156
|
Installation
|
|
153
157
|
------------
|
|
154
158
|
|
|
159
|
+
ObjectNat supports Python 3.11–3.14 on Windows, macOS, and Linux. It installs
|
|
160
|
+
IduEdu 2.2.0 or newer within the 2.x series.
|
|
161
|
+
|
|
155
162
|
**ObjectNat** can be installed via ``pip``::
|
|
156
163
|
|
|
157
164
|
pip install ObjectNat
|
|
@@ -188,6 +195,14 @@ Contacts
|
|
|
188
195
|
|
|
189
196
|
----
|
|
190
197
|
|
|
198
|
+
Citing ObjectNat
|
|
199
|
+
----------------
|
|
200
|
+
|
|
201
|
+
If you use ObjectNat in research, please cite it. Citation metadata is kept in
|
|
202
|
+
`CITATION.cff <https://github.com/IDUclub/ObjectNat/blob/master/CITATION.cff>`__; on GitHub,
|
|
203
|
+
**Cite this repository** in the repository sidebar copies it as APA or BibTeX.
|
|
204
|
+
Publications describing ObjectNat will be listed below.
|
|
205
|
+
|
|
191
206
|
Publications
|
|
192
207
|
------------
|
|
193
208
|
|
|
@@ -16,8 +16,8 @@ Object-oriented Network Analysis Tools
|
|
|
16
16
|
:target: https://github.com/IDUclub/ObjectNat/actions/workflows/quality.yml
|
|
17
17
|
:alt: Tests and coverage
|
|
18
18
|
|
|
19
|
-
.. |badge-
|
|
20
|
-
:target: https://
|
|
19
|
+
.. |badge-coverage| image:: https://raw.githubusercontent.com/IDUclub/ObjectNat/python-coverage-comment-action-data/badge.svg
|
|
20
|
+
:target: https://github.com/IDUclub/ObjectNat/tree/python-coverage-comment-action-data
|
|
21
21
|
:alt: Coverage
|
|
22
22
|
|
|
23
23
|
.. |badge-license| image:: https://img.shields.io/badge/license-BSD--3--Clause-blue.svg
|
|
@@ -28,7 +28,7 @@ Object-oriented Network Analysis Tools
|
|
|
28
28
|
:target: https://iduclub.github.io/ObjectNat/
|
|
29
29
|
:alt: Docs
|
|
30
30
|
|
|
31
|
-
|badge-black| |badge-pypi| |badge-ci| |badge-
|
|
31
|
+
|badge-black| |badge-pypi| |badge-ci| |badge-coverage| |badge-license| |badge-docs|
|
|
32
32
|
|
|
33
33
|
`РИДМИ (Russian) <https://github.com/IDUclub/ObjectNat/blob/master/README_RU.rst>`__
|
|
34
34
|
|
|
@@ -42,6 +42,7 @@ Object-oriented Network Analysis Tools
|
|
|
42
42
|
for spatial and network analysis in urban studies.
|
|
43
43
|
The library provides tools for analyzing **accessibility**, **visibility**,
|
|
44
44
|
**noise propagation**, and **service provision**.
|
|
45
|
+
|
|
45
46
|
----
|
|
46
47
|
|
|
47
48
|
Key Features
|
|
@@ -103,7 +104,7 @@ Each feature includes a **Jupyter Notebook example** and **full documentation**.
|
|
|
103
104
|
|
|
104
105
|
📘 `Example <https://iduclub.github.io/ObjectNat/methods/examples/noise.html>`__
|
|
105
106
|
🔗 `Documentation <https://iduclub.github.io/ObjectNat/methods/noise.html>`__
|
|
106
|
-
🧠 `Detailed theory <https://github.com/
|
|
107
|
+
🧠 `Detailed theory <https://github.com/IDUclub/ObjectNat/wiki/Noise-simulation>`__
|
|
107
108
|
|
|
108
109
|
----
|
|
109
110
|
|
|
@@ -116,7 +117,7 @@ Graph-based ObjectNat methods consume ``iduedu.UrbanGraph`` directly; ObjectNat
|
|
|
116
117
|
does not build NetworkX graphs internally.
|
|
117
118
|
|
|
118
119
|
**IduEdu** is an open-source Python library designed for building and processing
|
|
119
|
-
complex urban networks
|
|
120
|
+
complex urban networks from OpenStreetMap and GTFS Schedule data.
|
|
120
121
|
|
|
121
122
|
|
|
122
123
|
**IduEdu** can be installed via ``pip``::
|
|
@@ -135,6 +136,9 @@ Example usage::
|
|
|
135
136
|
Installation
|
|
136
137
|
------------
|
|
137
138
|
|
|
139
|
+
ObjectNat supports Python 3.11–3.14 on Windows, macOS, and Linux. It installs
|
|
140
|
+
IduEdu 2.2.0 or newer within the 2.x series.
|
|
141
|
+
|
|
138
142
|
**ObjectNat** can be installed via ``pip``::
|
|
139
143
|
|
|
140
144
|
pip install ObjectNat
|
|
@@ -171,6 +175,14 @@ Contacts
|
|
|
171
175
|
|
|
172
176
|
----
|
|
173
177
|
|
|
178
|
+
Citing ObjectNat
|
|
179
|
+
----------------
|
|
180
|
+
|
|
181
|
+
If you use ObjectNat in research, please cite it. Citation metadata is kept in
|
|
182
|
+
`CITATION.cff <https://github.com/IDUclub/ObjectNat/blob/master/CITATION.cff>`__; on GitHub,
|
|
183
|
+
**Cite this repository** in the repository sidebar copies it as APA or BibTeX.
|
|
184
|
+
Publications describing ObjectNat will be listed below.
|
|
185
|
+
|
|
174
186
|
Publications
|
|
175
187
|
------------
|
|
176
188
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION = "2.1.0"
|
|
@@ -11,28 +11,41 @@ from shapely.ops import polygonize, unary_union
|
|
|
11
11
|
from objectnat.methods.utils.geom_utils import polygons_to_multilinestring
|
|
12
12
|
|
|
13
13
|
SteppedGeometryType = Literal["radius", "ways", "separate"] | None
|
|
14
|
+
_WALK_ONLY_GRAPH_TYPES = {"intermodal", "walk"}
|
|
14
15
|
|
|
15
|
-
# Graph types whose "ways" geometry must be built from pedestrian edges only.
|
|
16
|
-
# On these graphs non-walk edges (transit legs) are straight lines between
|
|
17
|
-
# distant stops; buffering them would create spurious corridors.
|
|
18
|
-
_WALK_ONLY_WAYS_GRAPH_TYPES = {"intermodal", "walk"}
|
|
19
16
|
|
|
17
|
+
def geometry_edges(urban_graph: UrbanGraph) -> gpd.GeoDataFrame:
|
|
18
|
+
"""Return the edges that shape accessibility geometry.
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
For intermodal/walk graphs only pedestrian (``type == "walk"``) edges are
|
|
25
|
-
kept, so transit legs do not distort the resulting shape. Other graph types
|
|
26
|
-
(or graphs without a ``type`` column) use every edge.
|
|
20
|
+
Walk and intermodal graphs use their pedestrian (``type == "walk"``) edges; other
|
|
21
|
+
graphs, or graphs without a ``type`` column, use every edge.
|
|
27
22
|
"""
|
|
28
23
|
edges = urban_graph.edges_gdf
|
|
29
|
-
if urban_graph.type in
|
|
24
|
+
if urban_graph.type in _WALK_ONLY_GRAPH_TYPES and "type" in edges.columns:
|
|
30
25
|
walk_edges = edges[edges["type"] == "walk"]
|
|
31
26
|
if not walk_edges.empty:
|
|
32
27
|
return walk_edges
|
|
33
28
|
return edges
|
|
34
29
|
|
|
35
30
|
|
|
31
|
+
def select_geometry_nodes(urban_graph: UrbanGraph, reachable_graph_nodes_gdf: gpd.GeoDataFrame) -> gpd.GeoDataFrame:
|
|
32
|
+
"""Keep the reachable nodes that shape geometry, one per location.
|
|
33
|
+
|
|
34
|
+
Only endpoints of :func:`geometry_edges` are kept, unless none of them is reachable,
|
|
35
|
+
and nodes sharing a location are collapsed onto the smallest ``dist``. Several route
|
|
36
|
+
nodes can sit on one stop; a Voronoi cell shared by them would otherwise be assigned
|
|
37
|
+
to every band they fall in.
|
|
38
|
+
"""
|
|
39
|
+
edges = geometry_edges(urban_graph)
|
|
40
|
+
nodes = reachable_graph_nodes_gdf
|
|
41
|
+
on_geometry_edges = nodes.index.isin(edges["u"]) | nodes.index.isin(edges["v"])
|
|
42
|
+
if on_geometry_edges.any():
|
|
43
|
+
nodes = nodes.loc[on_geometry_edges]
|
|
44
|
+
ordered = nodes.iloc[np.argsort(nodes["dist"].to_numpy(dtype=float), kind="stable")]
|
|
45
|
+
location = pd.MultiIndex.from_arrays([ordered.geometry.x.to_numpy(), ordered.geometry.y.to_numpy()])
|
|
46
|
+
return ordered.iloc[np.flatnonzero(~location.duplicated())]
|
|
47
|
+
|
|
48
|
+
|
|
36
49
|
def build_voronoi_cells(
|
|
37
50
|
urban_graph: UrbanGraph,
|
|
38
51
|
reachable_graph_nodes_gdf: gpd.GeoDataFrame,
|
|
@@ -42,15 +55,16 @@ def build_voronoi_cells(
|
|
|
42
55
|
clip_geom=None,
|
|
43
56
|
) -> gpd.GeoDataFrame:
|
|
44
57
|
reachable_node_index = pd.Index(reachable_graph_nodes_gdf.index)
|
|
58
|
+
edges = geometry_edges(urban_graph)
|
|
45
59
|
if clip_geom is None:
|
|
46
|
-
edges = urban_graph.edges_gdf[["u", "v"]]
|
|
47
60
|
edges_near_reachable = edges[edges["u"].isin(reachable_node_index) | edges["v"].isin(reachable_node_index)]
|
|
48
61
|
neighbor_nodes = pd.Index(edges_near_reachable["u"]).append(pd.Index(edges_near_reachable["v"])).unique()
|
|
49
62
|
boundary_nodes = neighbor_nodes.difference(reachable_node_index)
|
|
50
63
|
voronoi_node_index = reachable_node_index.append(boundary_nodes).unique()
|
|
51
64
|
else:
|
|
52
|
-
|
|
53
|
-
|
|
65
|
+
nodes = urban_graph.nodes_gdf
|
|
66
|
+
site_mask = (nodes.index.isin(edges["u"]) | nodes.index.isin(edges["v"])) & nodes.geometry.intersects(clip_geom)
|
|
67
|
+
voronoi_node_index = nodes.index[site_mask].append(reachable_node_index).unique()
|
|
54
68
|
voronoi_nodes = urban_graph.nodes_gdf.loc[voronoi_node_index, ["geometry"]]
|
|
55
69
|
voronois = gpd.GeoDataFrame(geometry=list(voronoi_nodes.geometry.voronoi_polygons()), crs=local_crs)
|
|
56
70
|
reachable_cell_index = reachable_graph_nodes_gdf.reset_index(names="node").sjoin(voronois, how="inner")
|
|
@@ -119,7 +133,7 @@ def build_ways_clip_geometry(
|
|
|
119
133
|
speed_m_per_min=speed_m_per_min,
|
|
120
134
|
)
|
|
121
135
|
reachable_node_index = pd.Index(reachable_graph_nodes_gdf.index)
|
|
122
|
-
edges =
|
|
136
|
+
edges = geometry_edges(urban_graph)
|
|
123
137
|
edge_mask = edges["u"].isin(reachable_node_index) | edges["v"].isin(reachable_node_index)
|
|
124
138
|
if not edge_mask.any():
|
|
125
139
|
return GeometryCollection()
|
|
@@ -226,6 +240,7 @@ def build_stepped_accessibility_geometry(
|
|
|
226
240
|
if reachable_graph_nodes_gdf.empty:
|
|
227
241
|
return gpd.GeoDataFrame()
|
|
228
242
|
|
|
243
|
+
reachable_graph_nodes_gdf = select_geometry_nodes(urban_graph, reachable_graph_nodes_gdf)
|
|
229
244
|
speed_m_per_min = edge_speed_m_per_min(urban_graph) if weight_type == "time_min" else None
|
|
230
245
|
|
|
231
246
|
if geometry_type == "separate":
|
|
@@ -13,6 +13,7 @@ from objectnat.methods.accessibility._utils import (
|
|
|
13
13
|
build_voronoi_cells,
|
|
14
14
|
build_ways_clip_geometry,
|
|
15
15
|
edge_speed_m_per_min,
|
|
16
|
+
select_geometry_nodes,
|
|
16
17
|
)
|
|
17
18
|
|
|
18
19
|
|
|
@@ -104,6 +105,8 @@ def get_graph_coverage(
|
|
|
104
105
|
Notes:
|
|
105
106
|
- For a directed graph the search runs on the reversed edges, so a zone
|
|
106
107
|
describes the area from which its source can be reached.
|
|
108
|
+
- On walk and intermodal graphs the zones are built from nodes reachable on foot,
|
|
109
|
+
so route nodes sharing a stop cannot give one place to several sources.
|
|
107
110
|
- An empty ``GeoDataFrame`` is returned when nothing is reachable.
|
|
108
111
|
"""
|
|
109
112
|
|
|
@@ -149,6 +152,8 @@ def get_graph_coverage(
|
|
|
149
152
|
if reachable_graph_nodes_gdf.empty:
|
|
150
153
|
return gpd.GeoDataFrame()
|
|
151
154
|
|
|
155
|
+
reachable_graph_nodes_gdf = select_geometry_nodes(urban_graph, reachable_graph_nodes_gdf)
|
|
156
|
+
|
|
152
157
|
if geometry_type == "radius":
|
|
153
158
|
speed_m_per_min = edge_speed_m_per_min(urban_graph) if weight_type == "time_min" else None
|
|
154
159
|
clip_zone = build_radius_clip_geometry(
|
|
@@ -285,6 +290,8 @@ def get_stepped_graph_coverage(
|
|
|
285
290
|
Notes:
|
|
286
291
|
- For a directed graph the search runs on the reversed edges; for an
|
|
287
292
|
undirected graph on the original symmetric adjacency.
|
|
293
|
+
- On walk and intermodal graphs the bands are built from nodes reachable on foot,
|
|
294
|
+
so transit route nodes on a stop do not paint it with a later band.
|
|
288
295
|
- An empty ``GeoDataFrame`` is returned when nothing is reachable.
|
|
289
296
|
"""
|
|
290
297
|
|
|
@@ -12,6 +12,7 @@ from objectnat.methods.accessibility._utils import (
|
|
|
12
12
|
build_stepped_accessibility_geometry,
|
|
13
13
|
build_ways_clip_geometry,
|
|
14
14
|
edge_speed_m_per_min,
|
|
15
|
+
select_geometry_nodes,
|
|
15
16
|
)
|
|
16
17
|
|
|
17
18
|
|
|
@@ -98,6 +99,9 @@ def get_graph_isochrones(
|
|
|
98
99
|
- For ``geometry_type="ways"`` the search budget is slightly extended so
|
|
99
100
|
road edges are not clipped short of the boundary.
|
|
100
101
|
- Origins with no reachable nodes are dropped from the result.
|
|
102
|
+
- On walk and intermodal graphs the geometry is built from nodes reachable on
|
|
103
|
+
foot; transit route nodes, whose distance includes the wait to board, only
|
|
104
|
+
carry the search.
|
|
101
105
|
"""
|
|
102
106
|
|
|
103
107
|
if geometry_type not in {"radius", "ways", "separate"}:
|
|
@@ -144,6 +148,7 @@ def get_graph_isochrones(
|
|
|
144
148
|
reachable_graph_nodes_gdf = urban_graph.nodes_gdf.loc[pd.Index(reachable_nodes.index), ["geometry"]].join(
|
|
145
149
|
reachable_nodes, how="left"
|
|
146
150
|
)
|
|
151
|
+
reachable_graph_nodes_gdf = select_geometry_nodes(urban_graph, reachable_graph_nodes_gdf)
|
|
147
152
|
if geometry_type == "radius":
|
|
148
153
|
geom = build_radius_clip_geometry(
|
|
149
154
|
reachable_graph_nodes_gdf,
|
|
@@ -271,6 +276,9 @@ def get_stepped_graph_isochrones(
|
|
|
271
276
|
- Multiple origins are processed together: the bands describe the
|
|
272
277
|
combined reachability of all origins (each node keeps the distance to
|
|
273
278
|
its nearest origin).
|
|
279
|
+
- On walk and intermodal graphs the bands are built from nodes reachable on
|
|
280
|
+
foot, so a stop is drawn in the band it is walked to, not in the bands of the
|
|
281
|
+
waits for the vehicles that call there.
|
|
274
282
|
- An empty ``GeoDataFrame`` is returned when nothing is reachable.
|
|
275
283
|
"""
|
|
276
284
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import concurrent.futures
|
|
2
2
|
import math
|
|
3
|
-
import multiprocessing
|
|
4
3
|
import time
|
|
4
|
+
from collections import deque
|
|
5
5
|
|
|
6
6
|
import geopandas as gpd
|
|
7
7
|
import pandas as pd
|
|
@@ -178,7 +178,7 @@ def simulate_noise(
|
|
|
178
178
|
obstacles = obstacles[["absorb_ratio", "geometry"]]
|
|
179
179
|
|
|
180
180
|
# creating initial task and simulating for each point
|
|
181
|
-
task_queue =
|
|
181
|
+
task_queue = deque()
|
|
182
182
|
dead_area_dict = {}
|
|
183
183
|
for ind, row in source_points.iterrows():
|
|
184
184
|
source_point = row.geometry
|
|
@@ -208,7 +208,7 @@ def simulate_noise(
|
|
|
208
208
|
"min_db": target_noise_db,
|
|
209
209
|
"simulation_ind": ind,
|
|
210
210
|
}
|
|
211
|
-
task_queue.
|
|
211
|
+
task_queue.append((_noise_from_point_task, args, kwargs))
|
|
212
212
|
dead_area_dict[ind] = source_point.buffer(dead_area_r, quad_segs=2)
|
|
213
213
|
|
|
214
214
|
noise_gdf = _recursive_simulation_queue(
|
|
@@ -544,11 +544,9 @@ def _noise_from_point_task(task, **kwargs) -> tuple[gpd.GeoDataFrame, list[tuple
|
|
|
544
544
|
return noise_from_point, new_tasks
|
|
545
545
|
|
|
546
546
|
|
|
547
|
-
def _recursive_simulation_queue(
|
|
548
|
-
task_queue: multiprocessing.Queue, dead_area_dict: dict, dead_area_r: int, use_parallel: bool
|
|
549
|
-
):
|
|
547
|
+
def _recursive_simulation_queue(task_queue: deque, dead_area_dict: dict, dead_area_r: int, use_parallel: bool):
|
|
550
548
|
results = []
|
|
551
|
-
total_tasks = task_queue
|
|
549
|
+
total_tasks = len(task_queue)
|
|
552
550
|
|
|
553
551
|
with tqdm(total=total_tasks, desc="Simulating noise") as pbar:
|
|
554
552
|
if use_parallel:
|
|
@@ -559,8 +557,8 @@ def _recursive_simulation_queue(
|
|
|
559
557
|
max_workers = executor._max_workers # pylint: disable=protected-access
|
|
560
558
|
future_to_task = {}
|
|
561
559
|
while True:
|
|
562
|
-
while
|
|
563
|
-
func, task, kwargs = task_queue.
|
|
560
|
+
while task_queue and len(future_to_task) < max_workers:
|
|
561
|
+
func, task, kwargs = task_queue.popleft()
|
|
564
562
|
future = executor.submit(func, task, **kwargs)
|
|
565
563
|
future_to_task[future] = kwargs["simulation_ind"]
|
|
566
564
|
done, _ = concurrent.futures.wait(future_to_task.keys(), return_when=concurrent.futures.FIRST_COMPLETED)
|
|
@@ -574,7 +572,7 @@ def _recursive_simulation_queue(
|
|
|
574
572
|
for func, new_task, new_kwargs in new_tasks:
|
|
575
573
|
new_point = new_task[0]
|
|
576
574
|
if not local_dead_area.covers(new_point):
|
|
577
|
-
task_queue.
|
|
575
|
+
task_queue.append((func, new_task, new_kwargs))
|
|
578
576
|
new_dead_area_points.append(new_point.buffer(dead_area_r, quad_segs=2))
|
|
579
577
|
new_tasks_n += 1
|
|
580
578
|
dead_area_dict[simulation_ind] = unary_union(new_dead_area_points)
|
|
@@ -584,6 +582,6 @@ def _recursive_simulation_queue(
|
|
|
584
582
|
results.append(result)
|
|
585
583
|
pbar.update(1)
|
|
586
584
|
time.sleep(0.01)
|
|
587
|
-
if not future_to_task and task_queue
|
|
585
|
+
if not future_to_task and not task_queue:
|
|
588
586
|
break
|
|
589
587
|
return results
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ObjectNat"
|
|
3
3
|
dynamic = ["version"]
|
|
4
|
-
description = "ObjectNat is an open-source library
|
|
4
|
+
description = "ObjectNat is an open-source library for object-level geospatial analysis on city networks, developed by the IDU team."
|
|
5
5
|
readme = "README.rst"
|
|
6
|
-
requires-python = ">=3.11,<3.
|
|
6
|
+
requires-python = ">=3.11,<3.15"
|
|
7
7
|
license = "BSD-3-Clause"
|
|
8
8
|
license-files = ["LICENSE.txt"]
|
|
9
9
|
authors = [
|
|
@@ -11,11 +11,14 @@ authors = [
|
|
|
11
11
|
]
|
|
12
12
|
dependencies = [
|
|
13
13
|
"geopandas>=1.1.0,<2.0.0",
|
|
14
|
-
"iduedu>=2.
|
|
14
|
+
"iduedu>=2.2.0,<3.0.0",
|
|
15
15
|
"loguru>=0.7.3,<0.8.0",
|
|
16
|
-
"numpy>=2.
|
|
17
|
-
"
|
|
18
|
-
"
|
|
16
|
+
"numpy>=2.0.0,<3.0.0; python_version < '3.14'",
|
|
17
|
+
"numpy>=2.4.0,<3.0.0; python_version >= '3.14'",
|
|
18
|
+
"pandas>=2.2.0,<4.0.0; python_version < '3.14'",
|
|
19
|
+
"pandas>=3.0.0,<4.0.0; python_version >= '3.14'",
|
|
20
|
+
"scipy>=1.16.0,<2.0.0; python_version < '3.14'",
|
|
21
|
+
"scipy>=1.17.0,<2.0.0; python_version >= '3.14'",
|
|
19
22
|
"tqdm>=4.66.2,<5.0.0",
|
|
20
23
|
]
|
|
21
24
|
|
|
@@ -28,7 +31,6 @@ dev = [
|
|
|
28
31
|
{ include-group = "test" },
|
|
29
32
|
{ include-group = "viz" },
|
|
30
33
|
"pre-commit>=4.2.0,<5.0.0",
|
|
31
|
-
"pyarrow>=19.0.1,<20.0.0",
|
|
32
34
|
]
|
|
33
35
|
docs = [
|
|
34
36
|
"furo>=2025.9.25,<2026.0.0",
|
|
@@ -52,13 +54,18 @@ release = [
|
|
|
52
54
|
"python-semantic-release>=9.15.0,<10.0.0",
|
|
53
55
|
]
|
|
54
56
|
test = [
|
|
57
|
+
"matplotlib>=3.8.0,<4.0.0; python_version < '3.14'",
|
|
58
|
+
"matplotlib>=3.10.7,<4.0.0; python_version >= '3.14'",
|
|
59
|
+
"pyarrow>=18.1.0,<26.0.0; python_version < '3.14'",
|
|
60
|
+
"pyarrow>=22.0.0,<26.0.0; python_version >= '3.14'",
|
|
55
61
|
"pytest>=8.3.5,<9.0.0",
|
|
56
62
|
"pytest-cov>=6.0.0,<7.0.0",
|
|
57
63
|
]
|
|
58
64
|
viz = [
|
|
59
65
|
"folium>=0.19.5,<0.20.0",
|
|
60
66
|
"mapclassify>=2.8.1,<3.0.0",
|
|
61
|
-
"matplotlib>=3.
|
|
67
|
+
"matplotlib>=3.8.0,<4.0.0; python_version < '3.14'",
|
|
68
|
+
"matplotlib>=3.10.7,<4.0.0; python_version >= '3.14'",
|
|
62
69
|
]
|
|
63
70
|
|
|
64
71
|
|
|
@@ -119,6 +126,7 @@ skip = ["__init__.py"]
|
|
|
119
126
|
[tool.coverage.run]
|
|
120
127
|
branch = true
|
|
121
128
|
source = ["objectnat"]
|
|
129
|
+
relative_files = true
|
|
122
130
|
|
|
123
131
|
[tool.coverage.report]
|
|
124
132
|
# Floor set below the ~85% baseline (2026-07-10) so runs stay green despite
|
|
@@ -131,18 +139,19 @@ exclude_lines = [
|
|
|
131
139
|
|
|
132
140
|
[tool.pytest.ini_options]
|
|
133
141
|
testpaths = ["tests"]
|
|
142
|
+
markers = ["network: requires a live Overpass request to build an OSM graph"]
|
|
134
143
|
|
|
135
144
|
# ---------------------------------------------------------------------------
|
|
136
145
|
# Automated releases (python-semantic-release)
|
|
137
146
|
#
|
|
138
147
|
# The next version is derived from Conventional Commit messages since the last
|
|
139
148
|
# tag: feat -> minor, fix/perf -> patch, feat!/BREAKING CHANGE -> major.
|
|
140
|
-
#
|
|
141
|
-
# objectnat/_version.py, updates CHANGELOG.md, commits,
|
|
142
|
-
# publishes. See CONTRIBUTING.md.
|
|
149
|
+
# In the official repository, the release workflow runs after Tests and Coverage
|
|
150
|
+
# succeeds on master, bumps objectnat/_version.py, updates CHANGELOG.md, commits,
|
|
151
|
+
# tags vX.Y.Z, and publishes. See CONTRIBUTING.md.
|
|
143
152
|
# ---------------------------------------------------------------------------
|
|
144
153
|
[tool.semantic_release]
|
|
145
|
-
version_variables = ["objectnat/_version.py:VERSION"]
|
|
154
|
+
version_variables = ["objectnat/_version.py:VERSION", "CITATION.cff:version"]
|
|
146
155
|
tag_format = "v{version}"
|
|
147
156
|
commit_message = "chore(release): v{version} [skip ci]\n\nAutomated release, see CHANGELOG.md."
|
|
148
157
|
build_command = "python -m pip install uv && uv build"
|
|
@@ -171,4 +180,5 @@ minor_tags = ["feat"]
|
|
|
171
180
|
patch_tags = ["fix", "perf"]
|
|
172
181
|
|
|
173
182
|
[tool.semantic_release.changelog]
|
|
183
|
+
mode = "update"
|
|
174
184
|
exclude_commit_patterns = ['''^chore\(release\):.*''']
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
VERSION = "2.0.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|