ObjectNat 1.0.1__tar.gz → 1.2.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.

Potentially problematic release.


This version of ObjectNat might be problematic. Click here for more details.

Files changed (39) hide show
  1. {objectnat-1.0.1 → objectnat-1.2.0}/PKG-INFO +22 -16
  2. {objectnat-1.0.1 → objectnat-1.2.0}/README.md +20 -15
  3. {objectnat-1.0.1 → objectnat-1.2.0}/pyproject.toml +3 -5
  4. {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/_api.py +3 -2
  5. objectnat-1.2.0/src/objectnat/_version.py +1 -0
  6. objectnat-1.2.0/src/objectnat/methods/coverage_zones/__init__.py +3 -0
  7. {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/coverage_zones/graph_coverage.py +8 -18
  8. objectnat-1.2.0/src/objectnat/methods/coverage_zones/stepped_coverage.py +142 -0
  9. {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/isochrones/isochrone_utils.py +37 -0
  10. {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/isochrones/isochrones.py +3 -29
  11. objectnat-1.2.0/src/objectnat/methods/noise/__init__.py +4 -0
  12. objectnat-1.0.1/src/objectnat/methods/noise/noise_sim.py → objectnat-1.2.0/src/objectnat/methods/noise/noise_simulation.py +103 -86
  13. objectnat-1.2.0/src/objectnat/methods/noise/noise_simulation_simplified.py +135 -0
  14. objectnat-1.2.0/src/objectnat/methods/utils/__init__.py +1 -0
  15. {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/utils/geom_utils.py +45 -2
  16. objectnat-1.2.0/src/objectnat/methods/utils/graph_utils.py +320 -0
  17. {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/visibility/visibility_analysis.py +2 -2
  18. objectnat-1.0.1/src/objectnat/_version.py +0 -1
  19. objectnat-1.0.1/src/objectnat/methods/coverage_zones/__init__.py +0 -2
  20. objectnat-1.0.1/src/objectnat/methods/noise/__init__.py +0 -3
  21. objectnat-1.0.1/src/objectnat/methods/utils/__init__.py +0 -0
  22. objectnat-1.0.1/src/objectnat/methods/utils/graph_utils.py +0 -127
  23. {objectnat-1.0.1 → objectnat-1.2.0}/LICENSE.txt +0 -0
  24. {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/__init__.py +0 -0
  25. {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/_config.py +0 -0
  26. {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/__init__.py +0 -0
  27. /objectnat-1.0.1/src/objectnat/methods/coverage_zones/radius_voronoi.py → /objectnat-1.2.0/src/objectnat/methods/coverage_zones/radius_voronoi_coverage.py +0 -0
  28. {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/isochrones/__init__.py +0 -0
  29. {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/noise/noise_exceptions.py +0 -0
  30. {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/noise/noise_init_data.py +0 -0
  31. {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/noise/noise_reduce.py +0 -0
  32. {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/point_clustering/__init__.py +0 -0
  33. {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/point_clustering/cluster_points_in_polygons.py +0 -0
  34. {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/provision/__init__.py +0 -0
  35. {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/provision/provision.py +0 -0
  36. {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/provision/provision_exceptions.py +0 -0
  37. {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/provision/provision_model.py +0 -0
  38. {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/utils/math_utils.py +0 -0
  39. {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/visibility/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ObjectNat
3
- Version: 1.0.1
3
+ Version: 1.2.0
4
4
  Summary: ObjectNat is an open-source library created for geospatial analysis created by IDU team
5
5
  License: BSD-3-Clause
6
6
  Author: DDonnyy
@@ -12,6 +12,7 @@ Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
14
  Requires-Dist: geopandas (>=1.0.1,<2.0.0)
15
+ Requires-Dist: loguru (>=0.7.3,<0.8.0)
15
16
  Requires-Dist: networkx (>=3.4.2,<4.0.0)
16
17
  Requires-Dist: numpy (>=2.1.3,<3.0.0)
17
18
  Requires-Dist: pandarallel (>=1.6.5,<2.0.0)
@@ -44,21 +45,26 @@ Description-Content-Type: text/markdown
44
45
  - **Stepped isochrones**: show accessibility ranges divided into time intervals (e.g., 5, 10, 15 minutes).
45
46
 
46
47
  <p align="center">
47
- <img src="https://github.com/user-attachments/assets/b1787430-63e1-4907-9198-a6171d546599" alt="isochrone_ways_15_min" width="300">
48
- <img src="https://github.com/user-attachments/assets/64fce6bf-6509-490c-928c-dbd8daf9f570" alt="isochrone_radius_15_min" width="300">
48
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/isochrone_ways_15_min.png" alt="isochrone_ways_15_min" width="300">
49
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/isochrone_radius_15_min.png" alt="isochrone_radius_15_min" width="300">
50
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/isochrone_3points_radius_8_min.png" alt="isochrone_3points_radius_8_min" width="300">
49
51
  </p>
50
52
  <p align="center">
51
- <img src="https://github.com/user-attachments/assets/ac9f8840-a867-4eb5-aec8-91a411d4e545" alt="stepped_isochrone_stepped_ways_15_min" width="300">
52
- <img src="https://github.com/user-attachments/assets/b5429aa1-4625-44d1-982f-8bd4264148fb" alt="stepped_isochrone_stepped_radius_15_min" width="300">
53
- <img src="https://github.com/user-attachments/assets/042c7362-70e1-45df-b2e1-02fc76bf638c" alt="stepped_isochrone_stepped_separate_15_min" width="300">
53
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/stepped_isochrone_ways_15_min.png" alt="stepped_isochrone_ways_15_min" width="300">
54
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/stepped_isochrone_radius_15_min.png" alt="stepped_isochrone_radius_15_min" width="300">
55
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/stepped_isochrone_separate_15_min.png" alt="stepped_isochrone_separate_15_min" width="300">
54
56
  </p>
55
57
 
58
+ 2. **[Coverage Zones](./examples/coverage_zones.ipynb)** — Function for generating **coverage zones** from a set of source points using a transport network. It calculates the area each point can reach based on **travel time** or **distance**, then builds polygons via **Voronoi diagrams** and clips them to a custom boundary if provided.
56
59
 
57
- 2. **[Coverage Zones](./examples/graph_coverage.ipynb)** — Function for generating **coverage zones** from a set of source points using a transport network. It calculates the area each point can reach based on **travel time** or **distance**, then builds polygons via **Voronoi diagrams** and clips them to a custom boundary if provided.
58
-
59
60
  <p align="center">
60
- <img src="https://github.com/user-attachments/assets/fa8057d7-77aa-48a2-aa10-ea3e292a918d" alt="coverage_zones_time_10min" width="350">
61
- <img src="https://github.com/user-attachments/assets/44362dde-c3b0-4321-9a0a-aa547f0f2e04" alt="coverage_zones_distance_600m" width="350">
61
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/coverage_zones_time_10min.png" alt="coverage_zones_time_10min" width="350">
62
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/coverage_zones_distance_600m.png" alt="coverage_zones_distance_600m" width="350">
63
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/coverage_zones_radius_distance_800m.png" alt="coverage_zones_distance_radius_voronoi" width="350">
64
+ </p>
65
+ <p align="center">
66
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/stepped_coverage_zones_separate.png" alt="stepped_coverage_zones_separate" width="350">
67
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/stepped_coverage_zones_voronoi.png" alt="stepped_coverage_zones_voronoi" width="350">
62
68
  </p>
63
69
 
64
70
  3. **[Service Provision Analysis](./examples/calculate_provision.ipynb)** — Function for evaluating the provision of residential buildings and their population with services (e.g., schools, clinics)
@@ -69,9 +75,9 @@ Description-Content-Type: text/markdown
69
75
  - **Clipping** of provision results to a custom analysis area (e.g., administrative boundaries).
70
76
 
71
77
  <p align="center">
72
- <img src="https://github.com/user-attachments/assets/ff1ed08d-9a35-4035-9e1f-9a7fdae5b0e0" alt="service_provision_initial" width="300">
73
- <img src="https://github.com/user-attachments/assets/a0c0a6b0-f83f-4982-bfb3-4a476b2153ea" alt="service_provision_recalculated" width="300">
74
- <img src="https://github.com/user-attachments/assets/f57dc1c6-21a0-458d-85f4-fe1b17c77695" alt="service_provision_clipped" width="300">
78
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/service_provision_initial.png" alt="service_provision_initial" width="300">
79
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/service_provision_recalculated.png" alt="service_provision_recalculated" width="300">
80
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/service_provision_clipped.png" alt="service_provision_clipped" width="300">
75
81
  </p>
76
82
 
77
83
  4. **[Visibility Analysis](./examples/visibility_analysis.ipynb)** — Function for estimating visibility from a given point or multiple points to nearby buildings within a certain distance.
@@ -84,7 +90,7 @@ Description-Content-Type: text/markdown
84
90
  - A **accurate method** for detailed local analysis.
85
91
 
86
92
  <p align="center">
87
- <img src="https://github.com/user-attachments/assets/aa139d29-07d4-4560-b835-9646c8802fe1" alt="visibility_comparison_methods" height="250">
93
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/visibility_comparison_methods.png" alt="visibility_comparison_methods" height="250">
88
94
  <img src="https://github.com/user-attachments/assets/b5b0d4b3-a02f-4ade-8772-475703cd6435" alt="visibility-catchment-area" height="250">
89
95
  </p>
90
96
 
@@ -93,7 +99,7 @@ Description-Content-Type: text/markdown
93
99
  🔗 **[See detailed explanation in the Wiki](https://github.com/DDonnyy/ObjectNat/wiki/Noise-simulation)**
94
100
 
95
101
  <p align="center">
96
- <img src="https://github.com/user-attachments/assets/b3a41962-6220-49c4-90d4-2e756f9706cf" alt="noise_simulation_test_result" width="400">
102
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/noise_simulation_1point.png" alt="noise_simulation_1point" width="400">
97
103
  </p>
98
104
 
99
105
 
@@ -104,7 +110,7 @@ Description-Content-Type: text/markdown
104
110
  Additionally, the function can calculate the **relative ratio** of different service types within each cluster, enabling spatial analysis of service composition.
105
111
 
106
112
  <p align="center">
107
- <img src="https://github.com/user-attachments/assets/f86aac61-497a-4330-b4cf-68f4fc47fd34" alt="building_clusters" width="400">
113
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/building_clusters.png" alt="building_clusters" width="400">
108
114
  </p>
109
115
 
110
116
  ## City graphs
@@ -22,21 +22,26 @@
22
22
  - **Stepped isochrones**: show accessibility ranges divided into time intervals (e.g., 5, 10, 15 minutes).
23
23
 
24
24
  <p align="center">
25
- <img src="https://github.com/user-attachments/assets/b1787430-63e1-4907-9198-a6171d546599" alt="isochrone_ways_15_min" width="300">
26
- <img src="https://github.com/user-attachments/assets/64fce6bf-6509-490c-928c-dbd8daf9f570" alt="isochrone_radius_15_min" width="300">
25
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/isochrone_ways_15_min.png" alt="isochrone_ways_15_min" width="300">
26
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/isochrone_radius_15_min.png" alt="isochrone_radius_15_min" width="300">
27
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/isochrone_3points_radius_8_min.png" alt="isochrone_3points_radius_8_min" width="300">
27
28
  </p>
28
29
  <p align="center">
29
- <img src="https://github.com/user-attachments/assets/ac9f8840-a867-4eb5-aec8-91a411d4e545" alt="stepped_isochrone_stepped_ways_15_min" width="300">
30
- <img src="https://github.com/user-attachments/assets/b5429aa1-4625-44d1-982f-8bd4264148fb" alt="stepped_isochrone_stepped_radius_15_min" width="300">
31
- <img src="https://github.com/user-attachments/assets/042c7362-70e1-45df-b2e1-02fc76bf638c" alt="stepped_isochrone_stepped_separate_15_min" width="300">
30
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/stepped_isochrone_ways_15_min.png" alt="stepped_isochrone_ways_15_min" width="300">
31
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/stepped_isochrone_radius_15_min.png" alt="stepped_isochrone_radius_15_min" width="300">
32
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/stepped_isochrone_separate_15_min.png" alt="stepped_isochrone_separate_15_min" width="300">
32
33
  </p>
33
34
 
35
+ 2. **[Coverage Zones](./examples/coverage_zones.ipynb)** — Function for generating **coverage zones** from a set of source points using a transport network. It calculates the area each point can reach based on **travel time** or **distance**, then builds polygons via **Voronoi diagrams** and clips them to a custom boundary if provided.
34
36
 
35
- 2. **[Coverage Zones](./examples/graph_coverage.ipynb)** — Function for generating **coverage zones** from a set of source points using a transport network. It calculates the area each point can reach based on **travel time** or **distance**, then builds polygons via **Voronoi diagrams** and clips them to a custom boundary if provided.
36
-
37
37
  <p align="center">
38
- <img src="https://github.com/user-attachments/assets/fa8057d7-77aa-48a2-aa10-ea3e292a918d" alt="coverage_zones_time_10min" width="350">
39
- <img src="https://github.com/user-attachments/assets/44362dde-c3b0-4321-9a0a-aa547f0f2e04" alt="coverage_zones_distance_600m" width="350">
38
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/coverage_zones_time_10min.png" alt="coverage_zones_time_10min" width="350">
39
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/coverage_zones_distance_600m.png" alt="coverage_zones_distance_600m" width="350">
40
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/coverage_zones_radius_distance_800m.png" alt="coverage_zones_distance_radius_voronoi" width="350">
41
+ </p>
42
+ <p align="center">
43
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/stepped_coverage_zones_separate.png" alt="stepped_coverage_zones_separate" width="350">
44
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/stepped_coverage_zones_voronoi.png" alt="stepped_coverage_zones_voronoi" width="350">
40
45
  </p>
41
46
 
42
47
  3. **[Service Provision Analysis](./examples/calculate_provision.ipynb)** — Function for evaluating the provision of residential buildings and their population with services (e.g., schools, clinics)
@@ -47,9 +52,9 @@
47
52
  - **Clipping** of provision results to a custom analysis area (e.g., administrative boundaries).
48
53
 
49
54
  <p align="center">
50
- <img src="https://github.com/user-attachments/assets/ff1ed08d-9a35-4035-9e1f-9a7fdae5b0e0" alt="service_provision_initial" width="300">
51
- <img src="https://github.com/user-attachments/assets/a0c0a6b0-f83f-4982-bfb3-4a476b2153ea" alt="service_provision_recalculated" width="300">
52
- <img src="https://github.com/user-attachments/assets/f57dc1c6-21a0-458d-85f4-fe1b17c77695" alt="service_provision_clipped" width="300">
55
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/service_provision_initial.png" alt="service_provision_initial" width="300">
56
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/service_provision_recalculated.png" alt="service_provision_recalculated" width="300">
57
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/service_provision_clipped.png" alt="service_provision_clipped" width="300">
53
58
  </p>
54
59
 
55
60
  4. **[Visibility Analysis](./examples/visibility_analysis.ipynb)** — Function for estimating visibility from a given point or multiple points to nearby buildings within a certain distance.
@@ -62,7 +67,7 @@
62
67
  - A **accurate method** for detailed local analysis.
63
68
 
64
69
  <p align="center">
65
- <img src="https://github.com/user-attachments/assets/aa139d29-07d4-4560-b835-9646c8802fe1" alt="visibility_comparison_methods" height="250">
70
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/visibility_comparison_methods.png" alt="visibility_comparison_methods" height="250">
66
71
  <img src="https://github.com/user-attachments/assets/b5b0d4b3-a02f-4ade-8772-475703cd6435" alt="visibility-catchment-area" height="250">
67
72
  </p>
68
73
 
@@ -71,7 +76,7 @@
71
76
  🔗 **[See detailed explanation in the Wiki](https://github.com/DDonnyy/ObjectNat/wiki/Noise-simulation)**
72
77
 
73
78
  <p align="center">
74
- <img src="https://github.com/user-attachments/assets/b3a41962-6220-49c4-90d4-2e756f9706cf" alt="noise_simulation_test_result" width="400">
79
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/noise_simulation_1point.png" alt="noise_simulation_1point" width="400">
75
80
  </p>
76
81
 
77
82
 
@@ -82,7 +87,7 @@
82
87
  Additionally, the function can calculate the **relative ratio** of different service types within each cluster, enabling spatial analysis of service composition.
83
88
 
84
89
  <p align="center">
85
- <img src="https://github.com/user-attachments/assets/f86aac61-497a-4330-b4cf-68f4fc47fd34" alt="building_clusters" width="400">
90
+ <img src="https://raw.githubusercontent.com/DDonnyy/ObjectNat/assets/building_clusters.png" alt="building_clusters" width="400">
86
91
  </p>
87
92
 
88
93
  ## City graphs
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "ObjectNat"
3
- version = "1.0.1"
3
+ version = "1.2.0"
4
4
  description = "ObjectNat is an open-source library created for geospatial analysis created by IDU team"
5
5
  license = "BSD-3-Clause"
6
6
  authors = ["DDonnyy <63115678+DDonnyy@users.noreply.github.com>"]
@@ -17,7 +17,7 @@ tqdm = "^4.66.2"
17
17
  pandarallel = "^1.6.5"
18
18
  networkx = "^3.4.2"
19
19
  scikit-learn = "^1.4.0"
20
-
20
+ loguru = "^0.7.3"
21
21
 
22
22
  [tool.poetry.group.dev.dependencies]
23
23
  iduedu = "^0.5.0"
@@ -28,6 +28,7 @@ isort = "^5.13.2"
28
28
  jupyter = "^1.0.0"
29
29
  pytest = "^8.3.5"
30
30
  pytest-cov = "^6.0.0"
31
+ pre-commit = "^4.2.0"
31
32
  folium = "^0.19.5"
32
33
  matplotlib = "^3.10.1"
33
34
  mapclassify = "^2.8.1"
@@ -56,9 +57,6 @@ disable = [
56
57
  "too-many-arguments",
57
58
  "cyclic-import"
58
59
  ]
59
- good-names = [
60
-
61
- ]
62
60
 
63
61
  [tool.isort]
64
62
  multi_line_output = 3
@@ -1,10 +1,11 @@
1
1
  # pylint: disable=unused-import,wildcard-import,unused-wildcard-import
2
2
 
3
- from .methods.coverage_zones import get_graph_coverage, get_radius_coverage
3
+ from .methods.coverage_zones import get_graph_coverage, get_radius_coverage, get_stepped_graph_coverage
4
4
  from .methods.isochrones import get_accessibility_isochrone_stepped, get_accessibility_isochrones
5
- from .methods.noise import simulate_noise
5
+ from .methods.noise import calculate_simplified_noise_frame, simulate_noise
6
6
  from .methods.point_clustering import get_clusters_polygon
7
7
  from .methods.provision import clip_provision, get_service_provision, recalculate_links
8
+ from .methods.utils import gdf_to_graph, graph_to_gdf
8
9
  from .methods.visibility import (
9
10
  calculate_visibility_catchment_area,
10
11
  get_visibilities_from_points,
@@ -0,0 +1 @@
1
+ VERSION = "1.2.0"
@@ -0,0 +1,3 @@
1
+ from .graph_coverage import get_graph_coverage
2
+ from .radius_voronoi_coverage import get_radius_coverage
3
+ from .stepped_coverage import get_stepped_graph_coverage
@@ -6,11 +6,11 @@ import pandas as pd
6
6
  from pyproj.exceptions import CRSError
7
7
  from shapely import Point, concave_hull
8
8
 
9
- from objectnat.methods.utils.graph_utils import get_closest_nodes_from_gdf, remove_weakly_connected_nodes
9
+ from objectnat.methods.utils.graph_utils import get_closest_nodes_from_gdf, reverse_graph
10
10
 
11
11
 
12
12
  def get_graph_coverage(
13
- gdf_from: gpd.GeoDataFrame,
13
+ gdf_to: gpd.GeoDataFrame,
14
14
  nx_graph: nx.Graph,
15
15
  weight_type: Literal["time_min", "length_meter"],
16
16
  weight_value_cutoff: float = None,
@@ -29,8 +29,8 @@ def get_graph_coverage(
29
29
 
30
30
  Parameters
31
31
  ----------
32
- gdf_from : gpd.GeoDataFrame
33
- Source points from which coverage is calculated.
32
+ gdf_to : gpd.GeoDataFrame
33
+ Source points to which coverage is calculated.
34
34
  nx_graph : nx.Graph
35
35
  NetworkX graph representing the transportation network.
36
36
  weight_type : Literal["time_min", "length_meter"]
@@ -58,29 +58,19 @@ def get_graph_coverage(
58
58
  >>> graph = get_intermodal_graph(osm_id=1114252)
59
59
  >>> coverage = get_graph_coverage(points, graph, "time_min", 15)
60
60
  """
61
- original_crs = gdf_from.crs
61
+ original_crs = gdf_to.crs
62
62
  try:
63
63
  local_crs = nx_graph.graph["crs"]
64
64
  except KeyError as exc:
65
65
  raise ValueError("Graph does not have crs attribute") from exc
66
66
 
67
67
  try:
68
- points = gdf_from.copy()
68
+ points = gdf_to.copy()
69
69
  points.to_crs(local_crs, inplace=True)
70
70
  except CRSError as e:
71
71
  raise CRSError(f"Graph crs ({local_crs}) has invalid format.") from e
72
72
 
73
- if nx_graph.is_multigraph():
74
- if nx_graph.is_directed():
75
- nx_graph = nx.DiGraph(nx_graph)
76
- else:
77
- nx_graph = nx.Graph(nx_graph)
78
- nx_graph = remove_weakly_connected_nodes(nx_graph)
79
- sparse_matrix = nx.to_scipy_sparse_array(nx_graph, weight=weight_type)
80
- transposed_matrix = sparse_matrix.transpose()
81
- reversed_graph = nx.from_scipy_sparse_array(
82
- transposed_matrix, edge_attribute=weight_type, create_using=type(nx_graph)
83
- )
73
+ nx_graph, reversed_graph = reverse_graph(nx_graph, weight_type)
84
74
 
85
75
  points.geometry = points.representative_point()
86
76
 
@@ -88,7 +78,7 @@ def get_graph_coverage(
88
78
 
89
79
  points["nearest_node"] = nearest_nodes
90
80
 
91
- _, nearest_paths = nx.multi_source_dijkstra(
81
+ nearest_paths = nx.multi_source_dijkstra_path(
92
82
  reversed_graph, nearest_nodes, weight=weight_type, cutoff=weight_value_cutoff
93
83
  )
94
84
  reachable_nodes = list(nearest_paths.keys())
@@ -0,0 +1,142 @@
1
+ from typing import Literal
2
+
3
+ import geopandas as gpd
4
+ import networkx as nx
5
+ import numpy as np
6
+ import pandas as pd
7
+ from pyproj.exceptions import CRSError
8
+ from shapely import Point, concave_hull
9
+
10
+ from objectnat.methods.isochrones.isochrone_utils import create_separated_dist_polygons
11
+ from objectnat.methods.utils.graph_utils import get_closest_nodes_from_gdf, reverse_graph
12
+
13
+
14
+ def get_stepped_graph_coverage(
15
+ gdf_to: gpd.GeoDataFrame,
16
+ nx_graph: nx.Graph,
17
+ weight_type: Literal["time_min", "length_meter"],
18
+ step_type: Literal["voronoi", "separate"],
19
+ weight_value_cutoff: float = None,
20
+ zone: gpd.GeoDataFrame = None,
21
+ step: float = None,
22
+ ):
23
+ """
24
+ Calculate stepped coverage zones from source points through a graph network using Dijkstra's algorithm
25
+ and Voronoi-based or buffer-based isochrone steps.
26
+
27
+ This function combines graph-based accessibility with stepped isochrone logic. It:
28
+ 1. Finds nearest graph nodes for each input point
29
+ 2. Computes reachability for increasing weights (e.g. time or distance) in defined steps
30
+ 3. Generates Voronoi-based or separate buffer zones around network nodes
31
+ 4. Aggregates zones into stepped coverage layers
32
+ 5. Optionally clips results to a boundary zone
33
+
34
+ Parameters
35
+ ----------
36
+ gdf_to : gpd.GeoDataFrame
37
+ Source points from which stepped coverage is calculated.
38
+ nx_graph : nx.Graph
39
+ NetworkX graph representing the transportation network.
40
+ weight_type : Literal["time_min", "length_meter"]
41
+ Type of edge weight to use for path calculation:
42
+ - "time_min": Edge travel time in minutes
43
+ - "length_meter": Edge length in meters
44
+ step_type : Literal["voronoi", "separate"]
45
+ Method for generating stepped zones:
46
+ - "voronoi": Stepped zones based on Voronoi polygons around graph nodes
47
+ - "separate": Independent buffer zones per step
48
+ weight_value_cutoff : float, optional
49
+ Maximum weight value (e.g., max travel time or distance) to limit the coverage extent.
50
+ zone : gpd.GeoDataFrame, optional
51
+ Optional boundary polygon to clip resulting stepped zones. If None, concave hull of reachable area is used.
52
+ step : float, optional
53
+ Step interval for coverage zone construction. Defaults to:
54
+ - 100 meters for distance-based weight
55
+ - 1 minute for time-based weight
56
+
57
+ Returns
58
+ -------
59
+ gpd.GeoDataFrame
60
+ GeoDataFrame with polygons representing stepped coverage zones for each input point, annotated by step range.
61
+
62
+ Notes
63
+ -----
64
+ - Input graph must have a valid CRS defined.
65
+ - MultiGraph or MultiDiGraph inputs will be simplified.
66
+ - Designed for accessibility and spatial equity analyses over multimodal networks.
67
+
68
+ Examples
69
+ --------
70
+ >>> from iduedu import get_intermodal_graph
71
+ >>> points = gpd.read_file('destinations.geojson')
72
+ >>> graph = get_intermodal_graph(osm_id=1114252)
73
+ >>> stepped_coverage = get_stepped_graph_coverage(
74
+ ... points, graph, "time_min", step_type="voronoi", weight_value_cutoff=30, step=5
75
+ ... )
76
+ >>> # Using buffer-style zones
77
+ >>> stepped_separate = get_stepped_graph_coverage(
78
+ ... points, graph, "length_meter", step_type="separate", weight_value_cutoff=1000, step=200
79
+ ... )
80
+ """
81
+ if step is None:
82
+ if weight_type == "length_meter":
83
+ step = 100
84
+ else:
85
+ step = 1
86
+ original_crs = gdf_to.crs
87
+ try:
88
+ local_crs = nx_graph.graph["crs"]
89
+ except KeyError as exc:
90
+ raise ValueError("Graph does not have crs attribute") from exc
91
+
92
+ try:
93
+ points = gdf_to.copy()
94
+ points.to_crs(local_crs, inplace=True)
95
+ except CRSError as e:
96
+ raise CRSError(f"Graph crs ({local_crs}) has invalid format.") from e
97
+
98
+ nx_graph, reversed_graph = reverse_graph(nx_graph, weight_type)
99
+
100
+ points.geometry = points.representative_point()
101
+
102
+ distances, nearest_nodes = get_closest_nodes_from_gdf(points, nx_graph)
103
+
104
+ points["nearest_node"] = nearest_nodes
105
+ points["distance"] = distances
106
+
107
+ dist = nx.multi_source_dijkstra_path_length(
108
+ reversed_graph, nearest_nodes, weight=weight_type, cutoff=weight_value_cutoff
109
+ )
110
+
111
+ graph_points = pd.DataFrame(
112
+ data=[{"node": node, "geometry": Point(data["x"], data["y"])} for node, data in nx_graph.nodes(data=True)]
113
+ )
114
+
115
+ nearest_nodes = pd.DataFrame.from_dict(dist, orient="index", columns=["dist"]).reset_index()
116
+
117
+ graph_nodes_gdf = gpd.GeoDataFrame(
118
+ graph_points.merge(nearest_nodes, left_on="node", right_on="index", how="left").reset_index(drop=True),
119
+ geometry="geometry",
120
+ crs=local_crs,
121
+ )
122
+ graph_nodes_gdf.drop(columns=["index", "node"], inplace=True)
123
+ if weight_value_cutoff is None:
124
+ weight_value_cutoff = max(nearest_nodes["dist"])
125
+ if step_type == "voronoi":
126
+ graph_nodes_gdf["dist"] = np.minimum(np.ceil(graph_nodes_gdf["dist"] / step) * step, weight_value_cutoff)
127
+ voronois = gpd.GeoDataFrame(geometry=graph_nodes_gdf.voronoi_polygons(), crs=local_crs)
128
+ zone_coverages = voronois.sjoin(graph_nodes_gdf).dissolve(by="dist", as_index=False, dropna=False)
129
+ zone_coverages = zone_coverages[["dist", "geometry"]].explode(ignore_index=True)
130
+ if zone is None:
131
+ zone = concave_hull(graph_nodes_gdf[~graph_nodes_gdf["node_to"].isna()].union_all(), ratio=0.5)
132
+ else:
133
+ zone = zone.to_crs(local_crs)
134
+ zone_coverages = zone_coverages.clip(zone).to_crs(original_crs)
135
+ else: # step_type == 'separate':
136
+ speed = 83.33 # TODO HARDCODED WALK SPEED
137
+ weight_value = weight_value_cutoff
138
+ zone_coverages = create_separated_dist_polygons(graph_nodes_gdf, weight_value, weight_type, step, speed)
139
+ if zone is not None:
140
+ zone = zone.to_crs(local_crs)
141
+ zone_coverages = zone_coverages.clip(zone).to_crs(original_crs)
142
+ return zone_coverages
@@ -5,8 +5,10 @@ import networkx as nx
5
5
  import numpy as np
6
6
  import pandas as pd
7
7
  from pyproj.exceptions import CRSError
8
+ from shapely.ops import polygonize
8
9
 
9
10
  from objectnat import config
11
+ from objectnat.methods.utils.geom_utils import polygons_to_multilinestring
10
12
  from objectnat.methods.utils.graph_utils import get_closest_nodes_from_gdf, remove_weakly_connected_nodes
11
13
 
12
14
  logger = config.logger
@@ -128,3 +130,38 @@ def _create_isochrones_gdf(
128
130
  isochrones["weight_type"] = weight_type
129
131
  isochrones["weight_value"] = weight_value
130
132
  return isochrones
133
+
134
+
135
+ def create_separated_dist_polygons(
136
+ points: gpd.GeoDataFrame, weight_value, weight_type, step, speed
137
+ ) -> gpd.GeoDataFrame:
138
+ points["dist"] = points["dist"].clip(lower=0.1)
139
+ steps = np.arange(0, weight_value + step, step)
140
+ if steps[-1] > weight_value:
141
+ steps[-1] = weight_value # Ensure last step doesn't exceed weight_value
142
+ for i in range(len(steps) - 1):
143
+ min_dist = steps[i]
144
+ max_dist = steps[i + 1]
145
+ nodes_in_step = points["dist"].between(min_dist, max_dist, inclusive="left")
146
+ nodes_in_step = nodes_in_step[nodes_in_step].index
147
+ if not nodes_in_step.empty:
148
+ buffer_size = (max_dist - points.loc[nodes_in_step, "dist"]) * 0.7
149
+ if weight_type == "time_min":
150
+ buffer_size = buffer_size * speed
151
+ points.loc[nodes_in_step, "buffer_size"] = buffer_size
152
+ points.geometry = points.geometry.buffer(points["buffer_size"])
153
+ points["dist"] = np.minimum(np.ceil(points["dist"] / step) * step, weight_value)
154
+ points = points.dissolve(by="dist", as_index=False)
155
+ polygons = gpd.GeoDataFrame(
156
+ geometry=list(polygonize(points.geometry.apply(polygons_to_multilinestring).union_all())),
157
+ crs=points.crs,
158
+ )
159
+ polygons_points = polygons.copy()
160
+ polygons_points.geometry = polygons.representative_point()
161
+ stepped_polygons = polygons_points.sjoin(points, predicate="within").reset_index()
162
+ stepped_polygons = stepped_polygons.groupby("index").agg({"dist": "mean"})
163
+ stepped_polygons["dist"] = np.minimum(np.floor(stepped_polygons["dist"] / step) * step, weight_value)
164
+ stepped_polygons["geometry"] = polygons
165
+ stepped_polygons = gpd.GeoDataFrame(stepped_polygons, geometry="geometry", crs=points.crs).reset_index(drop=True)
166
+ stepped_polygons = stepped_polygons.dissolve(by="dist", as_index=False).explode(ignore_index=True)
167
+ return stepped_polygons
@@ -3,7 +3,6 @@ from typing import Literal
3
3
  import geopandas as gpd
4
4
  import networkx as nx
5
5
  import numpy as np
6
- from shapely.ops import polygonize
7
6
 
8
7
  from objectnat import config
9
8
  from objectnat.methods.isochrones.isochrone_utils import (
@@ -12,8 +11,9 @@ from objectnat.methods.isochrones.isochrone_utils import (
12
11
  _prepare_graph_and_nodes,
13
12
  _process_pt_data,
14
13
  _validate_inputs,
14
+ create_separated_dist_polygons,
15
15
  )
16
- from objectnat.methods.utils.geom_utils import polygons_to_multilinestring, remove_inner_geom
16
+ from objectnat.methods.utils.geom_utils import remove_inner_geom
17
17
  from objectnat.methods.utils.graph_utils import graph_to_gdf
18
18
 
19
19
  logger = config.logger
@@ -116,35 +116,9 @@ def get_accessibility_isochrone_stepped(
116
116
  logger.info("Building isochrones geometry...")
117
117
  nodes, edges = graph_to_gdf(subgraph)
118
118
  nodes.loc[dist_matrix.columns, "dist"] = dist_matrix.iloc[0]
119
- steps = np.arange(0, weight_value + step, step)
120
- if steps[-1] > weight_value:
121
- steps[-1] = weight_value # Ensure last step doesn't exceed weight_value
122
119
 
123
120
  if isochrone_type == "separate":
124
- for i in range(len(steps) - 1):
125
- min_dist = steps[i]
126
- max_dist = steps[i + 1]
127
- nodes_in_step = nodes["dist"].between(min_dist, max_dist, inclusive="left")
128
- nodes_in_step = nodes_in_step[nodes_in_step].index
129
- if not nodes_in_step.empty:
130
- buffer_size = (max_dist - nodes.loc[nodes_in_step, "dist"]) * 0.7
131
- if weight_type == "time_min":
132
- buffer_size = buffer_size * speed
133
- nodes.loc[nodes_in_step, "buffer_size"] = buffer_size
134
- nodes.geometry = nodes.geometry.buffer(nodes["buffer_size"])
135
- nodes["dist"] = np.round(nodes["dist"], 0)
136
- nodes = nodes.dissolve(by="dist", as_index=False)
137
- polygons = gpd.GeoDataFrame(
138
- geometry=list(polygonize(nodes.geometry.apply(polygons_to_multilinestring).union_all())),
139
- crs=local_crs,
140
- )
141
- polygons_points = polygons.copy()
142
- polygons_points.geometry = polygons.representative_point()
143
-
144
- stepped_iso = polygons_points.sjoin(nodes, predicate="within").reset_index()
145
- stepped_iso = stepped_iso.groupby("index").agg({"dist": "mean"})
146
- stepped_iso["geometry"] = polygons
147
- stepped_iso = gpd.GeoDataFrame(stepped_iso, geometry="geometry", crs=local_crs).reset_index(drop=True)
121
+ stepped_iso = create_separated_dist_polygons(nodes, weight_value, weight_type, step, speed)
148
122
  else:
149
123
  if isochrone_type == "radius":
150
124
  isochrone_geoms = _build_radius_isochrones(
@@ -0,0 +1,4 @@
1
+ from .noise_simulation import simulate_noise
2
+ from .noise_reduce import dist_to_target_db, green_noise_reduce_db
3
+ from .noise_exceptions import InvalidStepError
4
+ from .noise_simulation_simplified import calculate_simplified_noise_frame