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.
Files changed (30) hide show
  1. {objectnat-2.0.0 → objectnat-2.1.0}/PKG-INFO +28 -13
  2. {objectnat-2.0.0 → objectnat-2.1.0}/README.rst +17 -5
  3. {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/__init__.py +1 -1
  4. objectnat-2.1.0/objectnat/_version.py +1 -0
  5. {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/accessibility/_utils.py +30 -15
  6. {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/accessibility/coverage.py +7 -0
  7. {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/accessibility/isochrones.py +8 -0
  8. {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/noise/noise_simulation.py +9 -11
  9. {objectnat-2.0.0 → objectnat-2.1.0}/pyproject.toml +22 -12
  10. objectnat-2.0.0/objectnat/_version.py +0 -1
  11. {objectnat-2.0.0 → objectnat-2.1.0}/.gitignore +0 -0
  12. {objectnat-2.0.0 → objectnat-2.1.0}/LICENSE.txt +0 -0
  13. {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/_api.py +0 -0
  14. {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/_config.py +0 -0
  15. {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/__init__.py +0 -0
  16. {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/accessibility/__init__.py +0 -0
  17. {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/accessibility/radius.py +0 -0
  18. {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/noise/__init__.py +0 -0
  19. {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/noise/noise_init_data.py +0 -0
  20. {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/noise/noise_reduce.py +0 -0
  21. {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/noise/noise_simulation_simplified.py +0 -0
  22. {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/provision/__init__.py +0 -0
  23. {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/provision/provision.py +0 -0
  24. {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/provision/provision_calculating.py +0 -0
  25. {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/provision/provision_exceptions.py +0 -0
  26. {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/provision/provision_result.py +0 -0
  27. {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/utils/__init__.py +0 -0
  28. {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/utils/geom_utils.py +0 -0
  29. {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/visibility/__init__.py +0 -0
  30. {objectnat-2.0.0 → objectnat-2.1.0}/objectnat/methods/visibility/visibility_analysis.py +0 -0
@@ -1,17 +1,20 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: ObjectNat
3
- Version: 2.0.0
4
- Summary: ObjectNat is an open-source library created for geospatial analysis created by IDU team
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.13,>=3.11
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.0.0
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.4.0
13
- Requires-Dist: pandas<4.0.0,>=3.0.0
14
- Requires-Dist: scipy<2.0.0,>=1.17.0
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-codecov| image:: https://codecov.io/gh/IDUclub/ObjectNat/graph/badge.svg?token=K6JFSJ02GU
37
- :target: https://codecov.io/gh/IDUclub/ObjectNat
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-codecov| |badge-license| |badge-docs|
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/DDonnyy/ObjectNat/wiki/Noise-simulation>`__
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 based on OpenStreetMap data.
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-codecov| image:: https://codecov.io/gh/IDUclub/ObjectNat/graph/badge.svg?token=K6JFSJ02GU
20
- :target: https://codecov.io/gh/IDUclub/ObjectNat
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-codecov| |badge-license| |badge-docs|
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/DDonnyy/ObjectNat/wiki/Noise-simulation>`__
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 based on OpenStreetMap data.
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
 
@@ -1,7 +1,7 @@
1
1
  """
2
2
  ObjectNat is an open-source library created for geospatial analysis created by IDU team.
3
3
 
4
- Homepage https://github.com/DDonnyy/ObjectNat.
4
+ Homepage https://github.com/IDUclub/ObjectNat.
5
5
  """
6
6
 
7
7
  from ._config import config
@@ -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
- def road_edges_for_ways(urban_graph: UrbanGraph) -> gpd.GeoDataFrame:
22
- """Return the edge subset used to build ``geometry_type="ways"`` geometry.
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 _WALK_ONLY_WAYS_GRAPH_TYPES and "type" in edges.columns:
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
- clip_mask = urban_graph.nodes_gdf.geometry.intersects(clip_geom)
53
- voronoi_node_index = urban_graph.nodes_gdf.index[clip_mask].append(reachable_node_index).unique()
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 = road_edges_for_ways(urban_graph)
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 = multiprocessing.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.put((_noise_from_point_task, args, kwargs))
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.qsize()
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 not task_queue.empty() and len(future_to_task) < max_workers:
563
- func, task, kwargs = task_queue.get_nowait()
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.put((func, new_task, new_kwargs))
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.empty():
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 created for geospatial analysis created by IDU team"
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.13"
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.0.0,<3.0.0",
14
+ "iduedu>=2.2.0,<3.0.0",
15
15
  "loguru>=0.7.3,<0.8.0",
16
- "numpy>=2.4.0,<3.0.0",
17
- "pandas>=3.0.0,<4.0.0",
18
- "scipy>=1.17.0,<2.0.0",
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.10.1,<4.0.0",
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
- # The release workflow runs after CI succeeds on master, bumps
141
- # objectnat/_version.py, updates CHANGELOG.md, commits, tags vX.Y.Z, and
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