ObjectNat 2.0.0__tar.gz → 2.0.1__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.0.1}/PKG-INFO +17 -8
  2. {objectnat-2.0.0 → objectnat-2.0.1}/README.rst +14 -5
  3. {objectnat-2.0.0 → objectnat-2.0.1}/objectnat/__init__.py +1 -1
  4. objectnat-2.0.1/objectnat/_version.py +1 -0
  5. {objectnat-2.0.0 → objectnat-2.0.1}/objectnat/methods/accessibility/_utils.py +30 -15
  6. {objectnat-2.0.0 → objectnat-2.0.1}/objectnat/methods/accessibility/coverage.py +7 -0
  7. {objectnat-2.0.0 → objectnat-2.0.1}/objectnat/methods/accessibility/isochrones.py +8 -0
  8. {objectnat-2.0.0 → objectnat-2.0.1}/pyproject.toml +7 -5
  9. objectnat-2.0.0/objectnat/_version.py +0 -1
  10. {objectnat-2.0.0 → objectnat-2.0.1}/.gitignore +0 -0
  11. {objectnat-2.0.0 → objectnat-2.0.1}/LICENSE.txt +0 -0
  12. {objectnat-2.0.0 → objectnat-2.0.1}/objectnat/_api.py +0 -0
  13. {objectnat-2.0.0 → objectnat-2.0.1}/objectnat/_config.py +0 -0
  14. {objectnat-2.0.0 → objectnat-2.0.1}/objectnat/methods/__init__.py +0 -0
  15. {objectnat-2.0.0 → objectnat-2.0.1}/objectnat/methods/accessibility/__init__.py +0 -0
  16. {objectnat-2.0.0 → objectnat-2.0.1}/objectnat/methods/accessibility/radius.py +0 -0
  17. {objectnat-2.0.0 → objectnat-2.0.1}/objectnat/methods/noise/__init__.py +0 -0
  18. {objectnat-2.0.0 → objectnat-2.0.1}/objectnat/methods/noise/noise_init_data.py +0 -0
  19. {objectnat-2.0.0 → objectnat-2.0.1}/objectnat/methods/noise/noise_reduce.py +0 -0
  20. {objectnat-2.0.0 → objectnat-2.0.1}/objectnat/methods/noise/noise_simulation.py +0 -0
  21. {objectnat-2.0.0 → objectnat-2.0.1}/objectnat/methods/noise/noise_simulation_simplified.py +0 -0
  22. {objectnat-2.0.0 → objectnat-2.0.1}/objectnat/methods/provision/__init__.py +0 -0
  23. {objectnat-2.0.0 → objectnat-2.0.1}/objectnat/methods/provision/provision.py +0 -0
  24. {objectnat-2.0.0 → objectnat-2.0.1}/objectnat/methods/provision/provision_calculating.py +0 -0
  25. {objectnat-2.0.0 → objectnat-2.0.1}/objectnat/methods/provision/provision_exceptions.py +0 -0
  26. {objectnat-2.0.0 → objectnat-2.0.1}/objectnat/methods/provision/provision_result.py +0 -0
  27. {objectnat-2.0.0 → objectnat-2.0.1}/objectnat/methods/utils/__init__.py +0 -0
  28. {objectnat-2.0.0 → objectnat-2.0.1}/objectnat/methods/utils/geom_utils.py +0 -0
  29. {objectnat-2.0.0 → objectnat-2.0.1}/objectnat/methods/visibility/__init__.py +0 -0
  30. {objectnat-2.0.0 → objectnat-2.0.1}/objectnat/methods/visibility/visibility_analysis.py +0 -0
@@ -1,7 +1,7 @@
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.0.1
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
@@ -33,8 +33,8 @@ Object-oriented Network Analysis Tools
33
33
  :target: https://github.com/IDUclub/ObjectNat/actions/workflows/quality.yml
34
34
  :alt: Tests and coverage
35
35
 
36
- .. |badge-codecov| image:: https://codecov.io/gh/IDUclub/ObjectNat/graph/badge.svg?token=K6JFSJ02GU
37
- :target: https://codecov.io/gh/IDUclub/ObjectNat
36
+ .. |badge-coverage| image:: https://raw.githubusercontent.com/IDUclub/ObjectNat/python-coverage-comment-action-data/badge.svg
37
+ :target: https://github.com/IDUclub/ObjectNat/tree/python-coverage-comment-action-data
38
38
  :alt: Coverage
39
39
 
40
40
  .. |badge-license| image:: https://img.shields.io/badge/license-BSD--3--Clause-blue.svg
@@ -45,7 +45,7 @@ Object-oriented Network Analysis Tools
45
45
  :target: https://iduclub.github.io/ObjectNat/
46
46
  :alt: Docs
47
47
 
48
- |badge-black| |badge-pypi| |badge-ci| |badge-codecov| |badge-license| |badge-docs|
48
+ |badge-black| |badge-pypi| |badge-ci| |badge-coverage| |badge-license| |badge-docs|
49
49
 
50
50
  `РИДМИ (Russian) <https://github.com/IDUclub/ObjectNat/blob/master/README_RU.rst>`__
51
51
 
@@ -59,6 +59,7 @@ Object-oriented Network Analysis Tools
59
59
  for spatial and network analysis in urban studies.
60
60
  The library provides tools for analyzing **accessibility**, **visibility**,
61
61
  **noise propagation**, and **service provision**.
62
+
62
63
  ----
63
64
 
64
65
  Key Features
@@ -120,7 +121,7 @@ Each feature includes a **Jupyter Notebook example** and **full documentation**.
120
121
 
121
122
  📘 `Example <https://iduclub.github.io/ObjectNat/methods/examples/noise.html>`__
122
123
  🔗 `Documentation <https://iduclub.github.io/ObjectNat/methods/noise.html>`__
123
- 🧠 `Detailed theory <https://github.com/DDonnyy/ObjectNat/wiki/Noise-simulation>`__
124
+ 🧠 `Detailed theory <https://github.com/IDUclub/ObjectNat/wiki/Noise-simulation>`__
124
125
 
125
126
  ----
126
127
 
@@ -133,7 +134,7 @@ Graph-based ObjectNat methods consume ``iduedu.UrbanGraph`` directly; ObjectNat
133
134
  does not build NetworkX graphs internally.
134
135
 
135
136
  **IduEdu** is an open-source Python library designed for building and processing
136
- complex urban networks based on OpenStreetMap data.
137
+ complex urban networks from OpenStreetMap and GTFS Schedule data.
137
138
 
138
139
 
139
140
  **IduEdu** can be installed via ``pip``::
@@ -188,6 +189,14 @@ Contacts
188
189
 
189
190
  ----
190
191
 
192
+ Citing ObjectNat
193
+ ----------------
194
+
195
+ If you use ObjectNat in research, please cite it. Citation metadata is kept in
196
+ `CITATION.cff <https://github.com/IDUclub/ObjectNat/blob/master/CITATION.cff>`__; on GitHub,
197
+ **Cite this repository** in the repository sidebar copies it as APA or BibTeX.
198
+ Publications describing ObjectNat will be listed below.
199
+
191
200
  Publications
192
201
  ------------
193
202
 
@@ -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``::
@@ -171,6 +172,14 @@ Contacts
171
172
 
172
173
  ----
173
174
 
175
+ Citing ObjectNat
176
+ ----------------
177
+
178
+ If you use ObjectNat in research, please cite it. Citation metadata is kept in
179
+ `CITATION.cff <https://github.com/IDUclub/ObjectNat/blob/master/CITATION.cff>`__; on GitHub,
180
+ **Cite this repository** in the repository sidebar copies it as APA or BibTeX.
181
+ Publications describing ObjectNat will be listed below.
182
+
174
183
  Publications
175
184
  ------------
176
185
 
@@ -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.0.1"
@@ -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
  [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
6
  requires-python = ">=3.11,<3.13"
7
7
  license = "BSD-3-Clause"
@@ -119,6 +119,7 @@ skip = ["__init__.py"]
119
119
  [tool.coverage.run]
120
120
  branch = true
121
121
  source = ["objectnat"]
122
+ relative_files = true
122
123
 
123
124
  [tool.coverage.report]
124
125
  # Floor set below the ~85% baseline (2026-07-10) so runs stay green despite
@@ -137,12 +138,12 @@ testpaths = ["tests"]
137
138
  #
138
139
  # The next version is derived from Conventional Commit messages since the last
139
140
  # 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.
141
+ # In the official repository, the release workflow runs after Tests and Coverage
142
+ # succeeds on master, bumps objectnat/_version.py, updates CHANGELOG.md, commits,
143
+ # tags vX.Y.Z, and publishes. See CONTRIBUTING.md.
143
144
  # ---------------------------------------------------------------------------
144
145
  [tool.semantic_release]
145
- version_variables = ["objectnat/_version.py:VERSION"]
146
+ version_variables = ["objectnat/_version.py:VERSION", "CITATION.cff:version"]
146
147
  tag_format = "v{version}"
147
148
  commit_message = "chore(release): v{version} [skip ci]\n\nAutomated release, see CHANGELOG.md."
148
149
  build_command = "python -m pip install uv && uv build"
@@ -171,4 +172,5 @@ minor_tags = ["feat"]
171
172
  patch_tags = ["fix", "perf"]
172
173
 
173
174
  [tool.semantic_release.changelog]
175
+ mode = "update"
174
176
  exclude_commit_patterns = ['''^chore\(release\):.*''']
@@ -1 +0,0 @@
1
- VERSION = "2.0.0"
File without changes
File without changes
File without changes