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.
- {objectnat-1.0.1 → objectnat-1.2.0}/PKG-INFO +22 -16
- {objectnat-1.0.1 → objectnat-1.2.0}/README.md +20 -15
- {objectnat-1.0.1 → objectnat-1.2.0}/pyproject.toml +3 -5
- {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/_api.py +3 -2
- objectnat-1.2.0/src/objectnat/_version.py +1 -0
- objectnat-1.2.0/src/objectnat/methods/coverage_zones/__init__.py +3 -0
- {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/coverage_zones/graph_coverage.py +8 -18
- objectnat-1.2.0/src/objectnat/methods/coverage_zones/stepped_coverage.py +142 -0
- {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/isochrones/isochrone_utils.py +37 -0
- {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/isochrones/isochrones.py +3 -29
- objectnat-1.2.0/src/objectnat/methods/noise/__init__.py +4 -0
- objectnat-1.0.1/src/objectnat/methods/noise/noise_sim.py → objectnat-1.2.0/src/objectnat/methods/noise/noise_simulation.py +103 -86
- objectnat-1.2.0/src/objectnat/methods/noise/noise_simulation_simplified.py +135 -0
- objectnat-1.2.0/src/objectnat/methods/utils/__init__.py +1 -0
- {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/utils/geom_utils.py +45 -2
- objectnat-1.2.0/src/objectnat/methods/utils/graph_utils.py +320 -0
- {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/visibility/visibility_analysis.py +2 -2
- objectnat-1.0.1/src/objectnat/_version.py +0 -1
- objectnat-1.0.1/src/objectnat/methods/coverage_zones/__init__.py +0 -2
- objectnat-1.0.1/src/objectnat/methods/noise/__init__.py +0 -3
- objectnat-1.0.1/src/objectnat/methods/utils/__init__.py +0 -0
- objectnat-1.0.1/src/objectnat/methods/utils/graph_utils.py +0 -127
- {objectnat-1.0.1 → objectnat-1.2.0}/LICENSE.txt +0 -0
- {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/__init__.py +0 -0
- {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/_config.py +0 -0
- {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/__init__.py +0 -0
- /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
- {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/isochrones/__init__.py +0 -0
- {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/noise/noise_exceptions.py +0 -0
- {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/noise/noise_init_data.py +0 -0
- {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/noise/noise_reduce.py +0 -0
- {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/point_clustering/__init__.py +0 -0
- {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/point_clustering/cluster_points_in_polygons.py +0 -0
- {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/provision/__init__.py +0 -0
- {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/provision/provision.py +0 -0
- {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/provision/provision_exceptions.py +0 -0
- {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/provision/provision_model.py +0 -0
- {objectnat-1.0.1 → objectnat-1.2.0}/src/objectnat/methods/utils/math_utils.py +0 -0
- {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
|
|
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
|
-
|
|
48
|
-
|
|
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://
|
|
52
|
-
<img src="https://
|
|
53
|
-
<img src="https://
|
|
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
|
-
|
|
61
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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://
|
|
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
|
-
|
|
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://
|
|
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
|
-
|
|
26
|
-
|
|
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://
|
|
30
|
-
<img src="https://
|
|
31
|
-
<img src="https://
|
|
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
|
-
|
|
39
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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://
|
|
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
|
-
|
|
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://
|
|
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
|
|
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"
|
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
33
|
-
Source points
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|