ObjectNat 1.2.0__py3-none-any.whl → 1.2.1__py3-none-any.whl

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 (35) hide show
  1. objectnat/__init__.py +9 -13
  2. objectnat/_api.py +14 -14
  3. objectnat/_config.py +47 -47
  4. objectnat/_version.py +1 -1
  5. objectnat/methods/coverage_zones/__init__.py +3 -3
  6. objectnat/methods/coverage_zones/graph_coverage.py +98 -108
  7. objectnat/methods/coverage_zones/radius_voronoi_coverage.py +37 -45
  8. objectnat/methods/coverage_zones/stepped_coverage.py +126 -142
  9. objectnat/methods/isochrones/__init__.py +1 -1
  10. objectnat/methods/isochrones/isochrone_utils.py +167 -167
  11. objectnat/methods/isochrones/isochrones.py +262 -299
  12. objectnat/methods/noise/__init__.py +3 -4
  13. objectnat/methods/noise/noise_init_data.py +10 -10
  14. objectnat/methods/noise/noise_reduce.py +155 -155
  15. objectnat/methods/noise/noise_simulation.py +452 -440
  16. objectnat/methods/noise/noise_simulation_simplified.py +209 -135
  17. objectnat/methods/point_clustering/__init__.py +1 -1
  18. objectnat/methods/point_clustering/cluster_points_in_polygons.py +115 -116
  19. objectnat/methods/provision/__init__.py +1 -1
  20. objectnat/methods/provision/provision.py +117 -110
  21. objectnat/methods/provision/provision_exceptions.py +59 -59
  22. objectnat/methods/provision/provision_model.py +337 -337
  23. objectnat/methods/utils/__init__.py +1 -1
  24. objectnat/methods/utils/geom_utils.py +173 -173
  25. objectnat/methods/utils/graph_utils.py +306 -320
  26. objectnat/methods/utils/math_utils.py +32 -32
  27. objectnat/methods/visibility/__init__.py +6 -6
  28. objectnat/methods/visibility/visibility_analysis.py +470 -511
  29. {objectnat-1.2.0.dist-info → objectnat-1.2.1.dist-info}/LICENSE.txt +28 -28
  30. objectnat-1.2.1.dist-info/METADATA +115 -0
  31. objectnat-1.2.1.dist-info/RECORD +33 -0
  32. objectnat/methods/noise/noise_exceptions.py +0 -14
  33. objectnat-1.2.0.dist-info/METADATA +0 -148
  34. objectnat-1.2.0.dist-info/RECORD +0 -34
  35. {objectnat-1.2.0.dist-info → objectnat-1.2.1.dist-info}/WHEEL +0 -0
@@ -1,299 +1,262 @@
1
- from typing import Literal
2
-
3
- import geopandas as gpd
4
- import networkx as nx
5
- import numpy as np
6
-
7
- from objectnat import config
8
- from objectnat.methods.isochrones.isochrone_utils import (
9
- _calculate_distance_matrix,
10
- _create_isochrones_gdf,
11
- _prepare_graph_and_nodes,
12
- _process_pt_data,
13
- _validate_inputs,
14
- create_separated_dist_polygons,
15
- )
16
- from objectnat.methods.utils.geom_utils import remove_inner_geom
17
- from objectnat.methods.utils.graph_utils import graph_to_gdf
18
-
19
- logger = config.logger
20
-
21
-
22
- def get_accessibility_isochrone_stepped(
23
- isochrone_type: Literal["radius", "ways", "separate"],
24
- point: gpd.GeoDataFrame,
25
- weight_value: float,
26
- weight_type: Literal["time_min", "length_meter"],
27
- nx_graph: nx.Graph,
28
- step: float = None,
29
- **kwargs,
30
- ) -> tuple[gpd.GeoDataFrame, gpd.GeoDataFrame | None, gpd.GeoDataFrame | None]:
31
- """
32
- Calculate stepped accessibility isochrones for a single point with specified intervals.
33
-
34
- Parameters
35
- ----------
36
- isochrone_type : Literal["radius", "ways", "separate"]
37
- Visualization method for stepped isochrones:
38
- - "radius": Voronoi-based in circular buffers
39
- - "ways": Voronoi-based in road network polygons
40
- - "separate": Circular buffers for each step
41
- point : gpd.GeoDataFrame
42
- Single source point for isochrone calculation (uses first geometry if multiple provided).
43
- weight_value : float
44
- Maximum travel time (minutes) or distance (meters) threshold.
45
- weight_type : Literal["time_min", "length_meter"]
46
- Type of weight calculation:
47
- - "time_min": Time-based in minutes
48
- - "length_meter": Distance-based in meters
49
- nx_graph : nx.Graph
50
- NetworkX graph representing the transportation network.
51
- step : float, optional
52
- Interval between isochrone steps. Defaults to:
53
- - 100 meters for distance-based
54
- - 1 minute for time-based
55
- **kwargs
56
- Additional buffer parameters:
57
- - buffer_factor: Size multiplier for buffers (default: 0.7)
58
- - road_buffer_size: Buffer size for road edges in meters (default: 5)
59
-
60
- Returns
61
- -------
62
- tuple[gpd.GeoDataFrame, gpd.GeoDataFrame | None, gpd.GeoDataFrame | None]
63
- Tuple containing:
64
- - stepped_isochrones: GeoDataFrame with stepped polygons and distance/time attributes
65
- - pt_stops: Public transport stops within isochrones (if available)
66
- - pt_routes: Public transport routes within isochrones (if available)
67
-
68
- Examples
69
- --------
70
- >>> from iduedu import get_intermodal_graph # pip install iduedu to get OSM city network graph
71
- >>> graph = get_intermodal_graph(polygon=my_territory_polygon)
72
- >>> point = gpd.GeoDataFrame(geometry=[Point(30.33, 59.95)], crs=4326)
73
- >>> # Stepped radius isochrones with 5-minute intervals
74
- >>> radius_stepped, stops, _ = get_accessibility_isochrone_stepped(
75
- ... "radius", point, 30, "time_min", graph, step=5
76
- ... )
77
- >>> # Stepped road isochrones with 200m intervals
78
- >>> ways_stepped, _, routes = get_accessibility_isochrone_stepped(
79
- ... "ways", point, 1000, "length_meter", graph, step=200
80
- ... )
81
- >>> # Voronoi-based stepped isochrones
82
- >>> separate_stepped, stops, _ = get_accessibility_isochrone_stepped(
83
- ... "separate", point, 15, "time_min", graph
84
- ... )
85
- """
86
- buffer_params = {
87
- "buffer_factor": 0.7,
88
- "road_buffer_size": 5,
89
- }
90
-
91
- buffer_params.update(kwargs)
92
- original_crs = point.crs
93
- point = point.copy()
94
- if len(point) > 1:
95
- logger.warning(
96
- f"This method processes only single point. The GeoDataFrame contains {len(point)} points - "
97
- "only the first geometry will be used for isochrone calculation. "
98
- )
99
- point = point.iloc[[0]]
100
-
101
- local_crs, graph_type = _validate_inputs(point, weight_value, weight_type, nx_graph)
102
-
103
- if step is None:
104
- if weight_type == "length_meter":
105
- step = 100
106
- else:
107
- step = 1
108
- nx_graph, points, dist_nearest, speed = _prepare_graph_and_nodes(
109
- point, nx_graph, graph_type, weight_type, weight_value
110
- )
111
-
112
- dist_matrix, subgraph = _calculate_distance_matrix(
113
- nx_graph, points["nearest_node"].values, weight_type, weight_value, dist_nearest
114
- )
115
-
116
- logger.info("Building isochrones geometry...")
117
- nodes, edges = graph_to_gdf(subgraph)
118
- nodes.loc[dist_matrix.columns, "dist"] = dist_matrix.iloc[0]
119
-
120
- if isochrone_type == "separate":
121
- stepped_iso = create_separated_dist_polygons(nodes, weight_value, weight_type, step, speed)
122
- else:
123
- if isochrone_type == "radius":
124
- isochrone_geoms = _build_radius_isochrones(
125
- dist_matrix, weight_value, weight_type, speed, nodes, buffer_params["buffer_factor"]
126
- )
127
- else: # isochrone_type == 'ways':
128
- if graph_type in ["intermodal", "walk"]:
129
- isochrone_edges = edges[edges["type"] == "walk"]
130
- else:
131
- isochrone_edges = edges.copy()
132
- all_isochrones_edges = isochrone_edges.buffer(buffer_params["road_buffer_size"], resolution=1).union_all()
133
- all_isochrones_edges = gpd.GeoDataFrame(geometry=[all_isochrones_edges], crs=local_crs)
134
- isochrone_geoms = _build_ways_isochrones(
135
- dist_matrix=dist_matrix,
136
- weight_value=weight_value,
137
- weight_type=weight_type,
138
- speed=speed,
139
- nodes=nodes,
140
- all_isochrones_edges=all_isochrones_edges,
141
- buffer_factor=buffer_params["buffer_factor"],
142
- )
143
- nodes = nodes.clip(isochrone_geoms[0], keep_geom_type=True)
144
- nodes["dist"] = np.minimum(np.ceil(nodes["dist"] / step) * step, weight_value)
145
- voronois = gpd.GeoDataFrame(geometry=nodes.voronoi_polygons(), crs=local_crs)
146
- stepped_iso = (
147
- voronois.sjoin(nodes[["dist", "geometry"]]).dissolve(by="dist", as_index=False).drop(columns="index_right")
148
- )
149
- stepped_iso = stepped_iso.clip(isochrone_geoms[0], keep_geom_type=True)
150
-
151
- pt_nodes, pt_edges = _process_pt_data(nodes, edges, graph_type)
152
- if pt_nodes is not None:
153
- pt_nodes.to_crs(original_crs, inplace=True)
154
- if pt_edges is not None:
155
- pt_edges.to_crs(original_crs, inplace=True)
156
- return stepped_iso.to_crs(original_crs), pt_nodes, pt_edges
157
-
158
-
159
- def get_accessibility_isochrones(
160
- isochrone_type: Literal["radius", "ways"],
161
- points: gpd.GeoDataFrame,
162
- weight_value: float,
163
- weight_type: Literal["time_min", "length_meter"],
164
- nx_graph: nx.Graph,
165
- **kwargs,
166
- ) -> tuple[gpd.GeoDataFrame, gpd.GeoDataFrame | None, gpd.GeoDataFrame | None]:
167
- """
168
- Calculate accessibility isochrones from input points based on the provided city graph.
169
-
170
- Supports two types of isochrones:
171
- - 'radius': Circular buffer-based isochrones
172
- - 'ways': Road network-based isochrones
173
-
174
- Parameters
175
- ----------
176
- isochrone_type : Literal["radius", "ways"]
177
- Type of isochrone to calculate:
178
- - "radius": Creates circular buffers around reachable nodes
179
- - "ways": Creates polygons based on reachable road network
180
- points : gpd.GeoDataFrame
181
- GeoDataFrame containing source points for isochrone calculation.
182
- weight_value : float
183
- Maximum travel time (minutes) or distance (meters) threshold.
184
- weight_type : Literal["time_min", "length_meter"]
185
- Type of weight calculation:
186
- - "time_min": Time-based accessibility in minutes
187
- - "length_meter": Distance-based accessibility in meters
188
- nx_graph : nx.Graph
189
- NetworkX graph representing the transportation network.
190
- Must contain CRS and speed attributes for time calculations.
191
- **kwargs
192
- Additional buffer parameters:
193
- - buffer_factor: Size multiplier for buffers (default: 0.7)
194
- - road_buffer_size: Buffer size for road edges in meters (default: 5)
195
-
196
- Returns
197
- -------
198
- tuple[gpd.GeoDataFrame, gpd.GeoDataFrame | None, gpd.GeoDataFrame | None]
199
- Tuple containing:
200
- - isochrones: GeoDataFrame with calculated isochrone polygons
201
- - pt_stops: Public transport stops within isochrones (if available)
202
- - pt_routes: Public transport routes within isochrones (if available)
203
-
204
- Examples
205
- --------
206
- >>> from iduedu import get_intermodal_graph # pip install iduedu to get OSM city network graph
207
- >>> graph = get_intermodal_graph(polygon=my_territory_polygon)
208
- >>> points = gpd.GeoDataFrame(geometry=[Point(30.33, 59.95)], crs=4326)
209
- >>> # Radius isochrones
210
- >>> radius_iso, stops, routes = get_accessibility_isochrones(
211
- ... "radius", points, 15, "time_min", graph, buffer_factor=0.8
212
- ... )
213
- >>> # Road network isochrones
214
- >>> ways_iso, stops, routes = get_accessibility_isochrones(
215
- ... "ways", points, 1000, "length_meter", graph, road_buffer_size=7
216
- ... )
217
- """
218
-
219
- buffer_params = {
220
- "buffer_factor": 0.7,
221
- "road_buffer_size": 5,
222
- }
223
- original_crs = points.crs
224
- buffer_params.update(kwargs)
225
-
226
- points = points.copy()
227
- local_crs, graph_type = _validate_inputs(points, weight_value, weight_type, nx_graph)
228
-
229
- nx_graph, points, dist_nearest, speed = _prepare_graph_and_nodes(
230
- points, nx_graph, graph_type, weight_type, weight_value
231
- )
232
-
233
- weight_cutoff = (
234
- weight_value + (100 if weight_type == "length_meter" else 1) if isochrone_type == "ways" else weight_value
235
- )
236
-
237
- dist_matrix, subgraph = _calculate_distance_matrix(
238
- nx_graph, points["nearest_node"].values, weight_type, weight_cutoff, dist_nearest
239
- )
240
-
241
- logger.info("Building isochrones geometry...")
242
- nodes, edges = graph_to_gdf(subgraph)
243
- if isochrone_type == "radius":
244
- isochrone_geoms = _build_radius_isochrones(
245
- dist_matrix, weight_value, weight_type, speed, nodes, buffer_params["buffer_factor"]
246
- )
247
- else: # isochrone_type == 'ways':
248
- if graph_type in ["intermodal", "walk"]:
249
- isochrone_edges = edges[edges["type"] == "walk"]
250
- else:
251
- isochrone_edges = edges.copy()
252
- all_isochrones_edges = isochrone_edges.buffer(buffer_params["road_buffer_size"], resolution=1).union_all()
253
- all_isochrones_edges = gpd.GeoDataFrame(geometry=[all_isochrones_edges], crs=local_crs)
254
- isochrone_geoms = _build_ways_isochrones(
255
- dist_matrix=dist_matrix,
256
- weight_value=weight_value,
257
- weight_type=weight_type,
258
- speed=speed,
259
- nodes=nodes,
260
- all_isochrones_edges=all_isochrones_edges,
261
- buffer_factor=buffer_params["buffer_factor"],
262
- )
263
- isochrones = _create_isochrones_gdf(points, isochrone_geoms, dist_matrix, local_crs, weight_type, weight_value)
264
- pt_nodes, pt_edges = _process_pt_data(nodes, edges, graph_type)
265
- if pt_nodes is not None:
266
- pt_nodes.to_crs(original_crs, inplace=True)
267
- if pt_edges is not None:
268
- pt_edges.to_crs(original_crs, inplace=True)
269
- return isochrones.to_crs(original_crs), pt_nodes, pt_edges
270
-
271
-
272
- def _build_radius_isochrones(dist_matrix, weight_value, weight_type, speed, nodes, buffer_factor):
273
- results = []
274
- for source in dist_matrix.index:
275
- buffers = (weight_value - dist_matrix.loc[source]) * buffer_factor
276
- if weight_type == "time_min":
277
- buffers = buffers * speed
278
- buffers = nodes.merge(buffers, left_index=True, right_index=True)
279
- buffers.geometry = buffers.geometry.buffer(buffers[source], resolution=8)
280
- results.append(buffers.union_all())
281
- return results
282
-
283
-
284
- def _build_ways_isochrones(dist_matrix, weight_value, weight_type, speed, nodes, all_isochrones_edges, buffer_factor):
285
- results = []
286
- for source in dist_matrix.index:
287
- reachable_nodes = dist_matrix.loc[source]
288
- reachable_nodes = reachable_nodes[reachable_nodes <= weight_value]
289
- reachable_nodes = (weight_value - reachable_nodes) * buffer_factor
290
- if weight_type == "time_min":
291
- reachable_nodes = reachable_nodes * speed
292
- reachable_nodes = nodes.merge(reachable_nodes, left_index=True, right_index=True)
293
- clip_zone = reachable_nodes.buffer(reachable_nodes[source], resolution=4).union_all()
294
-
295
- isochrone_edges = all_isochrones_edges.clip(clip_zone, keep_geom_type=True).explode(ignore_index=True)
296
- geom_to_keep = isochrone_edges.sjoin(reachable_nodes, how="inner").index.unique()
297
- isochrone = remove_inner_geom(isochrone_edges.loc[geom_to_keep].union_all())
298
- results.append(isochrone)
299
- return results
1
+ from typing import Any, Literal
2
+
3
+ import geopandas as gpd
4
+ import networkx as nx
5
+ import numpy as np
6
+
7
+ from objectnat import config
8
+ from objectnat.methods.isochrones.isochrone_utils import (
9
+ _calculate_distance_matrix,
10
+ _create_isochrones_gdf,
11
+ _prepare_graph_and_nodes,
12
+ _process_pt_data,
13
+ _validate_inputs,
14
+ create_separated_dist_polygons,
15
+ )
16
+ from objectnat.methods.utils.geom_utils import remove_inner_geom
17
+ from objectnat.methods.utils.graph_utils import graph_to_gdf
18
+
19
+ logger = config.logger
20
+
21
+
22
+ def get_accessibility_isochrone_stepped(
23
+ isochrone_type: Literal["radius", "ways", "separate"],
24
+ point: gpd.GeoDataFrame,
25
+ weight_value: float,
26
+ weight_type: Literal["time_min", "length_meter"],
27
+ nx_graph: nx.Graph,
28
+ step: float = None,
29
+ **kwargs: Any,
30
+ ) -> tuple[gpd.GeoDataFrame, gpd.GeoDataFrame | None, gpd.GeoDataFrame | None]:
31
+ """
32
+ Calculate stepped accessibility isochrones for a single point with specified intervals.
33
+
34
+ Parameters:
35
+ isochrone_type (Literal["radius", "ways", "separate"]):
36
+ Visualization method for stepped isochrones:
37
+ - "radius": Voronoi-based in circular buffers
38
+ - "ways": Voronoi-based in road network polygons
39
+ - "separate": Circular buffers for each step
40
+ point (gpd.GeoDataFrame):
41
+ Single source point for isochrone calculation (uses first geometry if multiple provided).
42
+ weight_value (float):
43
+ Maximum travel time (minutes) or distance (meters) threshold.
44
+ weight_type (Literal["time_min", "length_meter"]):
45
+ Type of weight calculation:
46
+ - "time_min": Time-based in minutes
47
+ - "length_meter": Distance-based in meters
48
+ nx_graph (nx.Graph):
49
+ NetworkX graph representing the transportation network.
50
+ step (float, optional):
51
+ Interval between isochrone steps. Defaults to:
52
+ - 100 meters for distance-based
53
+ - 1 minute for time-based
54
+ **kwargs: Additional parameters:
55
+ - buffer_factor: Size multiplier for buffers (default: 0.7)
56
+ - road_buffer_size: Buffer size for road edges in meters (default: 5)
57
+
58
+ Returns:
59
+ (tuple[gpd.GeoDataFrame, gpd.GeoDataFrame | None, gpd.GeoDataFrame | None]):
60
+ Tuple containing:
61
+ - stepped_isochrones: GeoDataFrame with stepped polygons and distance/time attributes
62
+ - pt_stops: Public transport stops within isochrones (if available)
63
+ - pt_routes: Public transport routes within isochrones (if available)
64
+ """
65
+ buffer_params = {
66
+ "buffer_factor": 0.7,
67
+ "road_buffer_size": 5,
68
+ }
69
+
70
+ buffer_params.update(kwargs)
71
+ original_crs = point.crs
72
+ point = point.copy()
73
+ if len(point) > 1:
74
+ logger.warning(
75
+ f"This method processes only single point. The GeoDataFrame contains {len(point)} points - "
76
+ "only the first geometry will be used for isochrone calculation. "
77
+ )
78
+ point = point.iloc[[0]]
79
+
80
+ local_crs, graph_type = _validate_inputs(point, weight_value, weight_type, nx_graph)
81
+
82
+ if step is None:
83
+ if weight_type == "length_meter":
84
+ step = 100
85
+ else:
86
+ step = 1
87
+ nx_graph, points, dist_nearest, speed = _prepare_graph_and_nodes(
88
+ point, nx_graph, graph_type, weight_type, weight_value
89
+ )
90
+
91
+ dist_matrix, subgraph = _calculate_distance_matrix(
92
+ nx_graph, points["nearest_node"].values, weight_type, weight_value, dist_nearest
93
+ )
94
+
95
+ logger.info("Building isochrones geometry...")
96
+ nodes, edges = graph_to_gdf(subgraph)
97
+ nodes.loc[dist_matrix.columns, "dist"] = dist_matrix.iloc[0]
98
+
99
+ if isochrone_type == "separate":
100
+ stepped_iso = create_separated_dist_polygons(nodes, weight_value, weight_type, step, speed)
101
+ else:
102
+ if isochrone_type == "radius":
103
+ isochrone_geoms = _build_radius_isochrones(
104
+ dist_matrix, weight_value, weight_type, speed, nodes, buffer_params["buffer_factor"]
105
+ )
106
+ else: # isochrone_type == 'ways':
107
+ if graph_type in ["intermodal", "walk"]:
108
+ isochrone_edges = edges[edges["type"] == "walk"]
109
+ else:
110
+ isochrone_edges = edges.copy()
111
+ all_isochrones_edges = isochrone_edges.buffer(buffer_params["road_buffer_size"], resolution=1).union_all()
112
+ all_isochrones_edges = gpd.GeoDataFrame(geometry=[all_isochrones_edges], crs=local_crs)
113
+ isochrone_geoms = _build_ways_isochrones(
114
+ dist_matrix=dist_matrix,
115
+ weight_value=weight_value,
116
+ weight_type=weight_type,
117
+ speed=speed,
118
+ nodes=nodes,
119
+ all_isochrones_edges=all_isochrones_edges,
120
+ buffer_factor=buffer_params["buffer_factor"],
121
+ )
122
+ nodes = nodes.clip(isochrone_geoms[0], keep_geom_type=True)
123
+ nodes["dist"] = np.minimum(np.ceil(nodes["dist"] / step) * step, weight_value)
124
+ voronois = gpd.GeoDataFrame(geometry=nodes.voronoi_polygons(), crs=local_crs)
125
+ stepped_iso = (
126
+ voronois.sjoin(nodes[["dist", "geometry"]]).dissolve(by="dist", as_index=False).drop(columns="index_right")
127
+ )
128
+ stepped_iso = stepped_iso.clip(isochrone_geoms[0], keep_geom_type=True)
129
+
130
+ pt_nodes, pt_edges = _process_pt_data(nodes, edges, graph_type)
131
+ if pt_nodes is not None:
132
+ pt_nodes.to_crs(original_crs, inplace=True)
133
+ if pt_edges is not None:
134
+ pt_edges.to_crs(original_crs, inplace=True)
135
+ return stepped_iso.to_crs(original_crs), pt_nodes, pt_edges
136
+
137
+
138
+ def get_accessibility_isochrones(
139
+ isochrone_type: Literal["radius", "ways"],
140
+ points: gpd.GeoDataFrame,
141
+ weight_value: float,
142
+ weight_type: Literal["time_min", "length_meter"],
143
+ nx_graph: nx.Graph,
144
+ **kwargs: Any,
145
+ ) -> tuple[gpd.GeoDataFrame, gpd.GeoDataFrame | None, gpd.GeoDataFrame | None]:
146
+ """
147
+ Calculate accessibility isochrones from input points based on the provided city graph.
148
+
149
+ Supports two types of isochrones:
150
+ - 'radius': Circular buffer-based isochrones
151
+ - 'ways': Road network-based isochrones
152
+
153
+ Parameters:
154
+ isochrone_type (Literal["radius", "ways"]):
155
+ Type of isochrone to calculate:
156
+ - "radius": Creates circular buffers around reachable nodes
157
+ - "ways": Creates polygons based on reachable road network
158
+ points (gpd.GeoDataFrame):
159
+ GeoDataFrame containing source points for isochrone calculation.
160
+ weight_value (float):
161
+ Maximum travel time (minutes) or distance (meters) threshold.
162
+ weight_type (Literal["time_min", "length_meter"]):
163
+ Type of weight calculation:
164
+ - "time_min": Time-based accessibility in minutes
165
+ - "length_meter": Distance-based accessibility in meters
166
+ nx_graph (nx.Graph):
167
+ NetworkX graph representing the transportation network.
168
+ Must contain CRS and speed attributes for time calculations.
169
+ **kwargs: Additional parameters:
170
+ - buffer_factor: Size multiplier for buffers (default: 0.7)
171
+ - road_buffer_size: Buffer size for road edges in meters (default: 5)
172
+
173
+ Returns:
174
+ (tuple[gpd.GeoDataFrame, gpd.GeoDataFrame | None, gpd.GeoDataFrame | None]):
175
+ Tuple containing:
176
+ - isochrones: GeoDataFrame with calculated isochrone polygons
177
+ - pt_stops: Public transport stops within isochrones (if available)
178
+ - pt_routes: Public transport routes within isochrones (if available)
179
+
180
+ """
181
+
182
+ buffer_params = {
183
+ "buffer_factor": 0.7,
184
+ "road_buffer_size": 5,
185
+ }
186
+ original_crs = points.crs
187
+ buffer_params.update(kwargs)
188
+
189
+ points = points.copy()
190
+ local_crs, graph_type = _validate_inputs(points, weight_value, weight_type, nx_graph)
191
+
192
+ nx_graph, points, dist_nearest, speed = _prepare_graph_and_nodes(
193
+ points, nx_graph, graph_type, weight_type, weight_value
194
+ )
195
+
196
+ weight_cutoff = (
197
+ weight_value + (100 if weight_type == "length_meter" else 1) if isochrone_type == "ways" else weight_value
198
+ )
199
+
200
+ dist_matrix, subgraph = _calculate_distance_matrix(
201
+ nx_graph, points["nearest_node"].values, weight_type, weight_cutoff, dist_nearest
202
+ )
203
+
204
+ logger.info("Building isochrones geometry...")
205
+ nodes, edges = graph_to_gdf(subgraph)
206
+ if isochrone_type == "radius":
207
+ isochrone_geoms = _build_radius_isochrones(
208
+ dist_matrix, weight_value, weight_type, speed, nodes, buffer_params["buffer_factor"]
209
+ )
210
+ else: # isochrone_type == 'ways':
211
+ if graph_type in ["intermodal", "walk"]:
212
+ isochrone_edges = edges[edges["type"] == "walk"]
213
+ else:
214
+ isochrone_edges = edges.copy()
215
+ all_isochrones_edges = isochrone_edges.buffer(buffer_params["road_buffer_size"], resolution=1).union_all()
216
+ all_isochrones_edges = gpd.GeoDataFrame(geometry=[all_isochrones_edges], crs=local_crs)
217
+ isochrone_geoms = _build_ways_isochrones(
218
+ dist_matrix=dist_matrix,
219
+ weight_value=weight_value,
220
+ weight_type=weight_type,
221
+ speed=speed,
222
+ nodes=nodes,
223
+ all_isochrones_edges=all_isochrones_edges,
224
+ buffer_factor=buffer_params["buffer_factor"],
225
+ )
226
+ isochrones = _create_isochrones_gdf(points, isochrone_geoms, dist_matrix, local_crs, weight_type, weight_value)
227
+ pt_nodes, pt_edges = _process_pt_data(nodes, edges, graph_type)
228
+ if pt_nodes is not None:
229
+ pt_nodes.to_crs(original_crs, inplace=True)
230
+ if pt_edges is not None:
231
+ pt_edges.to_crs(original_crs, inplace=True)
232
+ return isochrones.to_crs(original_crs), pt_nodes, pt_edges
233
+
234
+
235
+ def _build_radius_isochrones(dist_matrix, weight_value, weight_type, speed, nodes, buffer_factor):
236
+ results = []
237
+ for source in dist_matrix.index:
238
+ buffers = (weight_value - dist_matrix.loc[source]) * buffer_factor
239
+ if weight_type == "time_min":
240
+ buffers = buffers * speed
241
+ buffers = nodes.merge(buffers, left_index=True, right_index=True)
242
+ buffers.geometry = buffers.geometry.buffer(buffers[source], resolution=8)
243
+ results.append(buffers.union_all())
244
+ return results
245
+
246
+
247
+ def _build_ways_isochrones(dist_matrix, weight_value, weight_type, speed, nodes, all_isochrones_edges, buffer_factor):
248
+ results = []
249
+ for source in dist_matrix.index:
250
+ reachable_nodes = dist_matrix.loc[source]
251
+ reachable_nodes = reachable_nodes[reachable_nodes <= weight_value]
252
+ reachable_nodes = (weight_value - reachable_nodes) * buffer_factor
253
+ if weight_type == "time_min":
254
+ reachable_nodes = reachable_nodes * speed
255
+ reachable_nodes = nodes.merge(reachable_nodes, left_index=True, right_index=True)
256
+ clip_zone = reachable_nodes.buffer(reachable_nodes[source], resolution=4).union_all()
257
+
258
+ isochrone_edges = all_isochrones_edges.clip(clip_zone, keep_geom_type=True).explode(ignore_index=True)
259
+ geom_to_keep = isochrone_edges.sjoin(reachable_nodes, how="inner").index.unique()
260
+ isochrone = remove_inner_geom(isochrone_edges.loc[geom_to_keep].union_all())
261
+ results.append(isochrone)
262
+ return results
@@ -1,4 +1,3 @@
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
1
+ from .noise_simulation import simulate_noise
2
+ from .noise_reduce import dist_to_target_db, green_noise_reduce_db
3
+ from .noise_simulation_simplified import calculate_simplified_noise_frame
@@ -1,10 +1,10 @@
1
- import pandas as pd
2
-
3
- data = {
4
- 30: {63: 0, 125: 0.0002, 250: 0.0009, 500: 0.003, 1000: 0.0075, 2000: 0.014, 4000: 0.025, 8000: 0.064},
5
- 20: {63: 0, 125: 0.0003, 250: 0.0011, 500: 0.0028, 1000: 0.0052, 2000: 0.0096, 4000: 0.025, 8000: 0.083},
6
- 10: {63: 0, 125: 0.0004, 250: 0.001, 500: 0.002, 1000: 0.0039, 2000: 0.01, 4000: 0.035, 8000: 0.125},
7
- 0: {63: 0, 125: 0.0004, 250: 0.0008, 500: 0.0017, 1000: 0.0049, 2000: 0.017, 4000: 0.058, 8000: 0.156},
8
- }
9
-
10
- air_resist_ratio = pd.DataFrame(data)
1
+ import pandas as pd
2
+
3
+ data = {
4
+ 30: {63: 0, 125: 0.0002, 250: 0.0009, 500: 0.003, 1000: 0.0075, 2000: 0.014, 4000: 0.025, 8000: 0.064},
5
+ 20: {63: 0, 125: 0.0003, 250: 0.0011, 500: 0.0028, 1000: 0.0052, 2000: 0.0096, 4000: 0.025, 8000: 0.083},
6
+ 10: {63: 0, 125: 0.0004, 250: 0.001, 500: 0.002, 1000: 0.0039, 2000: 0.01, 4000: 0.035, 8000: 0.125},
7
+ 0: {63: 0, 125: 0.0004, 250: 0.0008, 500: 0.0017, 1000: 0.0049, 2000: 0.017, 4000: 0.058, 8000: 0.156},
8
+ }
9
+
10
+ air_resist_ratio = pd.DataFrame(data)