ssb-sgis 1.0.1__py3-none-any.whl → 1.0.2__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.
- sgis/__init__.py +97 -115
- sgis/exceptions.py +3 -1
- sgis/geopandas_tools/__init__.py +1 -0
- sgis/geopandas_tools/bounds.py +75 -38
- sgis/geopandas_tools/buffer_dissolve_explode.py +38 -34
- sgis/geopandas_tools/centerlines.py +53 -44
- sgis/geopandas_tools/cleaning.py +87 -104
- sgis/geopandas_tools/conversion.py +149 -101
- sgis/geopandas_tools/duplicates.py +31 -17
- sgis/geopandas_tools/general.py +76 -48
- sgis/geopandas_tools/geometry_types.py +21 -7
- sgis/geopandas_tools/neighbors.py +20 -8
- sgis/geopandas_tools/overlay.py +136 -53
- sgis/geopandas_tools/point_operations.py +9 -8
- sgis/geopandas_tools/polygon_operations.py +48 -56
- sgis/geopandas_tools/polygons_as_rings.py +121 -78
- sgis/geopandas_tools/sfilter.py +14 -14
- sgis/helpers.py +114 -56
- sgis/io/dapla_functions.py +32 -23
- sgis/io/opener.py +13 -6
- sgis/io/read_parquet.py +1 -1
- sgis/maps/examine.py +39 -26
- sgis/maps/explore.py +112 -66
- sgis/maps/httpserver.py +12 -12
- sgis/maps/legend.py +124 -65
- sgis/maps/map.py +66 -41
- sgis/maps/maps.py +31 -29
- sgis/maps/thematicmap.py +46 -33
- sgis/maps/tilesources.py +3 -8
- sgis/networkanalysis/_get_route.py +5 -4
- sgis/networkanalysis/_od_cost_matrix.py +44 -1
- sgis/networkanalysis/_points.py +10 -4
- sgis/networkanalysis/_service_area.py +5 -2
- sgis/networkanalysis/closing_network_holes.py +20 -62
- sgis/networkanalysis/cutting_lines.py +55 -43
- sgis/networkanalysis/directednetwork.py +15 -7
- sgis/networkanalysis/finding_isolated_networks.py +4 -3
- sgis/networkanalysis/network.py +15 -13
- sgis/networkanalysis/networkanalysis.py +72 -54
- sgis/networkanalysis/networkanalysisrules.py +20 -16
- sgis/networkanalysis/nodes.py +2 -3
- sgis/networkanalysis/traveling_salesman.py +5 -2
- sgis/parallel/parallel.py +337 -127
- sgis/raster/__init__.py +6 -0
- sgis/raster/base.py +9 -3
- sgis/raster/cube.py +280 -208
- sgis/raster/cubebase.py +15 -29
- sgis/raster/indices.py +3 -7
- sgis/raster/methods_as_functions.py +0 -124
- sgis/raster/raster.py +313 -127
- sgis/raster/torchgeo.py +58 -37
- sgis/raster/zonal.py +38 -13
- {ssb_sgis-1.0.1.dist-info → ssb_sgis-1.0.2.dist-info}/LICENSE +1 -1
- {ssb_sgis-1.0.1.dist-info → ssb_sgis-1.0.2.dist-info}/METADATA +87 -16
- ssb_sgis-1.0.2.dist-info/RECORD +61 -0
- {ssb_sgis-1.0.1.dist-info → ssb_sgis-1.0.2.dist-info}/WHEEL +1 -1
- sgis/raster/bands.py +0 -48
- sgis/raster/gradient.py +0 -78
- ssb_sgis-1.0.1.dist-info/RECORD +0 -63
|
@@ -3,61 +3,15 @@
|
|
|
3
3
|
import geopandas as gpd
|
|
4
4
|
import numpy as np
|
|
5
5
|
import pandas as pd
|
|
6
|
-
from geopandas import GeoDataFrame
|
|
6
|
+
from geopandas import GeoDataFrame
|
|
7
|
+
from geopandas import GeoSeries
|
|
7
8
|
from pandas import DataFrame
|
|
8
9
|
from shapely import shortest_line
|
|
9
10
|
|
|
10
|
-
from ..geopandas_tools.conversion import coordinate_array
|
|
11
|
-
from ..geopandas_tools.geometry_types import get_geom_type
|
|
11
|
+
from ..geopandas_tools.conversion import coordinate_array
|
|
12
12
|
from ..geopandas_tools.neighbors import k_nearest_neighbors
|
|
13
|
-
from .nodes import make_edge_wkt_cols
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def close_network_holes_to(
|
|
17
|
-
lines: GeoDataFrame | GeoSeries,
|
|
18
|
-
extend_to: GeoDataFrame | GeoSeries,
|
|
19
|
-
max_distance: int | float,
|
|
20
|
-
max_angle: int | float,
|
|
21
|
-
) -> GeoDataFrame | GeoSeries:
|
|
22
|
-
if isinstance(lines, GeoSeries):
|
|
23
|
-
lines = lines.to_frame("geometry")
|
|
24
|
-
was_geoseries = True
|
|
25
|
-
else:
|
|
26
|
-
was_geoseries = False
|
|
27
|
-
|
|
28
|
-
lines, _ = make_node_ids(lines)
|
|
29
|
-
|
|
30
|
-
if isinstance(extend_to, GeoSeries):
|
|
31
|
-
extend_to = extend_to.to_frame("geometry")
|
|
32
|
-
|
|
33
|
-
if not (extend_to.geom_type == "Point").all():
|
|
34
|
-
raise ValueError("'extend_to' must be singlepart point geometries")
|
|
35
|
-
|
|
36
|
-
extend_to["wkt"] = extend_to.geometry.to_wkt()
|
|
37
|
-
extend_to = extend_to.drop_duplicates("wkt")
|
|
38
|
-
extend_to["node_id"] = range(len(extend_to))
|
|
39
|
-
|
|
40
|
-
new_lines: GeoSeries = _close_holes_all_lines(
|
|
41
|
-
lines, extend_to, max_distance=max_distance, max_angle=max_angle, idx_start=0
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
if was_geoseries:
|
|
45
|
-
return pd.concat([lines.geometry, new_lines])
|
|
46
|
-
|
|
47
|
-
new_lines = gpd.GeoDataFrame(
|
|
48
|
-
{"geometry": new_lines}, geometry="geometry", crs=lines.crs
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
return pd.concat([lines, new_lines], ignore_index=True).drop(
|
|
52
|
-
columns=[
|
|
53
|
-
"source_wkt",
|
|
54
|
-
"target_wkt",
|
|
55
|
-
"source",
|
|
56
|
-
"target",
|
|
57
|
-
"n_source",
|
|
58
|
-
"n_target",
|
|
59
|
-
]
|
|
60
|
-
)
|
|
13
|
+
from .nodes import make_edge_wkt_cols
|
|
14
|
+
from .nodes import make_node_ids
|
|
61
15
|
|
|
62
16
|
|
|
63
17
|
def close_network_holes(
|
|
@@ -65,7 +19,7 @@ def close_network_holes(
|
|
|
65
19
|
max_distance: int | float,
|
|
66
20
|
max_angle: int,
|
|
67
21
|
hole_col: str | None = "hole",
|
|
68
|
-
):
|
|
22
|
+
) -> GeoDataFrame:
|
|
69
23
|
"""Fills network gaps with straigt lines.
|
|
70
24
|
|
|
71
25
|
Fills holes in the network by connecting deadends with the nodes that are
|
|
@@ -89,7 +43,7 @@ def close_network_holes(
|
|
|
89
43
|
The holes will have missing values in the weight column used in
|
|
90
44
|
NetworkAnalysis. These values must be filled before analysis.
|
|
91
45
|
|
|
92
|
-
Examples
|
|
46
|
+
Examples:
|
|
93
47
|
--------
|
|
94
48
|
Read road data with small gaps.
|
|
95
49
|
|
|
@@ -105,10 +59,10 @@ def close_network_holes(
|
|
|
105
59
|
|
|
106
60
|
>>> filled = sg.close_network_holes(roads, max_distance=1.1, max_angle=180)
|
|
107
61
|
>>> filled.hole.value_counts()
|
|
108
|
-
|
|
62
|
+
hole
|
|
109
63
|
0 93395
|
|
110
64
|
1 7102
|
|
111
|
-
Name:
|
|
65
|
+
Name: count, dtype: int64
|
|
112
66
|
|
|
113
67
|
Compare the number of isolated lines before and after.
|
|
114
68
|
|
|
@@ -128,15 +82,15 @@ def close_network_holes(
|
|
|
128
82
|
|
|
129
83
|
>>> filled = sg.close_network_holes(roads, max_distance=1.1, max_angle=30)
|
|
130
84
|
>>> filled.hole.value_counts()
|
|
85
|
+
hole
|
|
131
86
|
0 93395
|
|
132
87
|
1 7092
|
|
133
|
-
Name:
|
|
88
|
+
Name: count, dtype: int64
|
|
134
89
|
|
|
135
90
|
It's not always wise to fill gaps. In the case of this data, these small gaps are
|
|
136
91
|
intentional. They are road blocks where most cars aren't allowed to pass. Fill the
|
|
137
92
|
holes only if it makes the travel times/routes more realistic.
|
|
138
93
|
"""
|
|
139
|
-
|
|
140
94
|
lines, nodes = make_node_ids(gdf)
|
|
141
95
|
|
|
142
96
|
# remove duplicates of lines going both directions
|
|
@@ -174,7 +128,7 @@ def close_network_holes(
|
|
|
174
128
|
return pd.concat([lines, new_lines], ignore_index=True)
|
|
175
129
|
|
|
176
130
|
|
|
177
|
-
def get_angle(array_a, array_b):
|
|
131
|
+
def get_angle(array_a: np.ndarray, array_b: np.ndarray) -> np.ndarray:
|
|
178
132
|
dx = array_b[:, 0] - array_a[:, 0]
|
|
179
133
|
dy = array_b[:, 1] - array_a[:, 1]
|
|
180
134
|
|
|
@@ -187,7 +141,7 @@ def close_network_holes_to_deadends(
|
|
|
187
141
|
gdf: GeoDataFrame,
|
|
188
142
|
max_distance: int | float,
|
|
189
143
|
hole_col: str | None = "hole",
|
|
190
|
-
):
|
|
144
|
+
) -> GeoDataFrame:
|
|
191
145
|
"""Fills gaps between two deadends if the distance is less than 'max_distance'.
|
|
192
146
|
|
|
193
147
|
Fills holes between deadends in the network with straight lines if the distance is
|
|
@@ -202,7 +156,7 @@ def close_network_holes_to_deadends(
|
|
|
202
156
|
Returns:
|
|
203
157
|
The input GeoDataFrame with new lines added.
|
|
204
158
|
|
|
205
|
-
Examples
|
|
159
|
+
Examples:
|
|
206
160
|
--------
|
|
207
161
|
Read road data with small gaps.
|
|
208
162
|
|
|
@@ -246,7 +200,7 @@ def close_network_holes_to_deadends(
|
|
|
246
200
|
new_lines = make_edge_wkt_cols(new_lines)
|
|
247
201
|
|
|
248
202
|
wkt_id_dict = {
|
|
249
|
-
wkt:
|
|
203
|
+
wkt: id_ for wkt, id_ in zip(nodes["wkt"], nodes["node_id"], strict=True)
|
|
250
204
|
}
|
|
251
205
|
new_lines["source"] = new_lines["source_wkt"].map(wkt_id_dict)
|
|
252
206
|
new_lines["target"] = new_lines["target_wkt"].map(wkt_id_dict)
|
|
@@ -259,7 +213,11 @@ def close_network_holes_to_deadends(
|
|
|
259
213
|
|
|
260
214
|
|
|
261
215
|
def _close_holes_all_lines(
|
|
262
|
-
lines
|
|
216
|
+
lines: GeoDataFrame,
|
|
217
|
+
nodes: GeoDataFrame,
|
|
218
|
+
max_distance: int | None,
|
|
219
|
+
max_angle: int | None,
|
|
220
|
+
idx_start: int,
|
|
263
221
|
) -> GeoSeries:
|
|
264
222
|
k = min(len(nodes), 50)
|
|
265
223
|
|
|
@@ -1,31 +1,23 @@
|
|
|
1
1
|
"""Cutting and splitting line geometries."""
|
|
2
|
+
|
|
2
3
|
import warnings
|
|
3
4
|
|
|
4
5
|
import numpy as np
|
|
5
6
|
import pandas as pd
|
|
6
|
-
from geopandas import GeoDataFrame
|
|
7
|
-
from pandas import DataFrame
|
|
8
|
-
from
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
get_parts,
|
|
14
|
-
linestrings,
|
|
15
|
-
touches,
|
|
16
|
-
unary_union,
|
|
17
|
-
)
|
|
18
|
-
from shapely.geometry import LineString, Point
|
|
7
|
+
from geopandas import GeoDataFrame
|
|
8
|
+
from pandas import DataFrame
|
|
9
|
+
from pandas import Series
|
|
10
|
+
from shapely import extract_unique_points
|
|
11
|
+
from shapely import force_2d
|
|
12
|
+
from shapely.geometry import LineString
|
|
13
|
+
from shapely.geometry import Point
|
|
19
14
|
|
|
20
15
|
from ..geopandas_tools.buffer_dissolve_explode import buff
|
|
21
16
|
from ..geopandas_tools.conversion import to_gdf
|
|
22
17
|
from ..geopandas_tools.geometry_types import get_geom_type
|
|
23
18
|
from ..geopandas_tools.neighbors import get_k_nearest_neighbors
|
|
24
|
-
from ..geopandas_tools.point_operations import
|
|
25
|
-
|
|
26
|
-
snap_all,
|
|
27
|
-
snap_within_distance,
|
|
28
|
-
)
|
|
19
|
+
from ..geopandas_tools.point_operations import snap_all
|
|
20
|
+
from ..geopandas_tools.point_operations import snap_within_distance
|
|
29
21
|
from ..geopandas_tools.sfilter import sfilter_split
|
|
30
22
|
from .nodes import make_edge_coords_cols
|
|
31
23
|
|
|
@@ -59,7 +51,7 @@ def split_lines_by_nearest_point(
|
|
|
59
51
|
Raises:
|
|
60
52
|
ValueError: If the crs of the input data differs.
|
|
61
53
|
|
|
62
|
-
Examples
|
|
54
|
+
Examples:
|
|
63
55
|
--------
|
|
64
56
|
>>> from sgis import read_parquet_url, split_lines_by_nearest_point
|
|
65
57
|
>>> roads = read_parquet_url("https://media.githubusercontent.com/media/statisticsnorway/ssb-sgis/main/tests/testdata/roads_oslo_2022.parquet")
|
|
@@ -148,8 +140,8 @@ def split_lines_by_nearest_point(
|
|
|
148
140
|
splitted_source = to_gdf(splitted["source_coords"], crs=gdf.crs)
|
|
149
141
|
splitted_target = to_gdf(splitted["target_coords"], crs=gdf.crs)
|
|
150
142
|
|
|
151
|
-
def get_nearest(splitted, snapped) -> pd.DataFrame:
|
|
152
|
-
"""
|
|
143
|
+
def get_nearest(splitted: GeoDataFrame, snapped: GeoDataFrame) -> pd.DataFrame:
|
|
144
|
+
"""Find the nearest snapped point for each source and target of the lines."""
|
|
153
145
|
return get_k_nearest_neighbors(splitted, snapped, k=1).loc[
|
|
154
146
|
lambda x: x["distance"] <= PRECISION * 2
|
|
155
147
|
]
|
|
@@ -166,7 +158,7 @@ def split_lines_by_nearest_point(
|
|
|
166
158
|
# now, we can replace the source/target coordinate with the coordinates of
|
|
167
159
|
# the snapped points.
|
|
168
160
|
|
|
169
|
-
splitted =
|
|
161
|
+
splitted = _change_line_endpoint(
|
|
170
162
|
splitted,
|
|
171
163
|
indices=dists_source.index,
|
|
172
164
|
pointmapper=pointmapper_source,
|
|
@@ -174,7 +166,7 @@ def split_lines_by_nearest_point(
|
|
|
174
166
|
) # i=0)
|
|
175
167
|
|
|
176
168
|
# same for the lines where the target was split, but change the last coordinate
|
|
177
|
-
splitted =
|
|
169
|
+
splitted = _change_line_endpoint(
|
|
178
170
|
splitted,
|
|
179
171
|
indices=dists_target.index,
|
|
180
172
|
pointmapper=pointmapper_target,
|
|
@@ -189,22 +181,38 @@ def split_lines_by_nearest_point(
|
|
|
189
181
|
)
|
|
190
182
|
|
|
191
183
|
|
|
192
|
-
def
|
|
184
|
+
def _change_line_endpoint(
|
|
193
185
|
gdf: GeoDataFrame,
|
|
194
186
|
indices: pd.Index,
|
|
195
187
|
pointmapper: pd.Series,
|
|
196
188
|
change_what: str | int,
|
|
197
189
|
) -> GeoDataFrame:
|
|
198
|
-
"""
|
|
199
|
-
|
|
200
|
-
|
|
190
|
+
"""Modify the endpoints of selected lines in a GeoDataFrame based on an index mapping.
|
|
191
|
+
|
|
192
|
+
This function updates the geometry of specified line features within a GeoDataFrame,
|
|
193
|
+
changing either the first or last point of each line to new coordinates provided by a mapping.
|
|
194
|
+
It is typically used in scenarios where line endpoints need to be adjusted to new locations,
|
|
195
|
+
such as in network adjustments or data corrections.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
gdf: A GeoDataFrame containing line geometries.
|
|
199
|
+
indices: An Index object identifying the rows in the GeoDataFrame whose endpoints will be changed.
|
|
200
|
+
pointmapper: A Series mapping from the index of lines to new point geometries.
|
|
201
|
+
change_what: Specifies which endpoint of the line to change. Accepts 'first' or 0 for the
|
|
202
|
+
starting point, and 'last' or -1 for the ending point.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
A GeoDataFrame with the specified line endpoints updated according to the pointmapper.
|
|
206
|
+
|
|
207
|
+
Raises:
|
|
208
|
+
ValueError: If `change_what` is not one of the accepted values ('first', 'last', 0, -1).
|
|
201
209
|
"""
|
|
202
210
|
assert gdf.index.is_unique
|
|
203
211
|
|
|
204
212
|
if change_what == "first" or change_what == 0:
|
|
205
|
-
|
|
213
|
+
keep = "first"
|
|
206
214
|
elif change_what == "last" or change_what == -1:
|
|
207
|
-
|
|
215
|
+
keep = "last"
|
|
208
216
|
else:
|
|
209
217
|
raise ValueError(
|
|
210
218
|
f"change_what should be 'first' or 'last' or 0 or -1. Got {change_what}"
|
|
@@ -216,8 +224,10 @@ def change_line_endpoint(
|
|
|
216
224
|
relevant_lines.geometry = extract_unique_points(relevant_lines.geometry)
|
|
217
225
|
relevant_lines = relevant_lines.explode(index_parts=False)
|
|
218
226
|
|
|
219
|
-
relevant_lines.loc[
|
|
220
|
-
relevant_lines.loc[
|
|
227
|
+
relevant_lines.loc[lambda x: ~x.index.duplicated(keep=keep), "geometry"] = (
|
|
228
|
+
relevant_lines.loc[lambda x: ~x.index.duplicated(keep=keep)]
|
|
229
|
+
.index.map(pointmapper)
|
|
230
|
+
.values
|
|
221
231
|
)
|
|
222
232
|
|
|
223
233
|
relevant_lines_mapped = relevant_lines.groupby(level=0)["geometry"].agg(LineString)
|
|
@@ -227,7 +237,9 @@ def change_line_endpoint(
|
|
|
227
237
|
return gdf
|
|
228
238
|
|
|
229
239
|
|
|
230
|
-
def cut_lines(
|
|
240
|
+
def cut_lines(
|
|
241
|
+
gdf: GeoDataFrame, max_length: int, ignore_index: bool = False
|
|
242
|
+
) -> GeoDataFrame:
|
|
231
243
|
"""Cuts lines of a GeoDataFrame into pieces of a given length.
|
|
232
244
|
|
|
233
245
|
Args:
|
|
@@ -242,7 +254,7 @@ def cut_lines(gdf: GeoDataFrame, max_length: int, ignore_index=False) -> GeoData
|
|
|
242
254
|
Note:
|
|
243
255
|
This method is time consuming for large networks and low 'max_length'.
|
|
244
256
|
|
|
245
|
-
Examples
|
|
257
|
+
Examples:
|
|
246
258
|
--------
|
|
247
259
|
>>> from sgis import read_parquet_url, cut_lines
|
|
248
260
|
>>> roads = read_parquet_url("https://media.githubusercontent.com/media/statisticsnorway/ssb-sgis/main/tests/testdata/roads_oslo_2022.parquet")
|
|
@@ -314,7 +326,7 @@ def cut_lines_once(
|
|
|
314
326
|
ignore_index: If True, the resulting axis will be labeled 0, 1, …, n - 1.
|
|
315
327
|
Defaults to False.
|
|
316
328
|
|
|
317
|
-
Examples
|
|
329
|
+
Examples:
|
|
318
330
|
--------
|
|
319
331
|
>>> from sgis import cut_lines_once, to_gdf
|
|
320
332
|
>>> import pandas as pd
|
|
@@ -344,24 +356,24 @@ def cut_lines_once(
|
|
|
344
356
|
>>> cut_lines_once(gdf, "dist")
|
|
345
357
|
geometry dist
|
|
346
358
|
0 LINESTRING (0.00000 0.00000, 0.70711 0.70711) 1
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
359
|
+
0 LINESTRING (0.70711 0.70711, 1.00000 1.00000, ... 1
|
|
360
|
+
0 LINESTRING (0.00000 0.00000, 1.00000 1.00000, ... 2
|
|
361
|
+
0 LINESTRING (1.41421 1.41421, 2.00000 2.00000) 2
|
|
362
|
+
0 LINESTRING (0.00000 0.00000, 1.00000 1.00000, ... 3
|
|
351
363
|
|
|
352
364
|
Cut distance as list (same result as above).
|
|
353
365
|
|
|
354
366
|
>>> cut_lines_once(gdf, [1, 2, 3])
|
|
355
367
|
geometry dist
|
|
356
368
|
0 LINESTRING (0.00000 0.00000, 0.70711 0.70711) 1
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
369
|
+
0 LINESTRING (0.70711 0.70711, 1.00000 1.00000, ... 1
|
|
370
|
+
0 LINESTRING (0.00000 0.00000, 1.00000 1.00000, ... 2
|
|
371
|
+
0 LINESTRING (1.41421 1.41421, 2.00000 2.00000) 2
|
|
372
|
+
0 LINESTRING (0.00000 0.00000, 1.00000 1.00000, ... 3
|
|
361
373
|
"""
|
|
362
374
|
|
|
363
375
|
def _cut(line: LineString, distance: int | float) -> list[LineString]:
|
|
364
|
-
"""From the shapely docs"""
|
|
376
|
+
"""From the shapely docs."""
|
|
365
377
|
if distance <= 0.0 or distance >= line.length:
|
|
366
378
|
return line
|
|
367
379
|
coords = list(line.coords)
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"""Prepare a GeoDataFrame of line geometries for directed network analysis."""
|
|
2
2
|
|
|
3
3
|
import warnings
|
|
4
|
+
from collections.abc import Sequence
|
|
4
5
|
|
|
5
6
|
import pandas as pd
|
|
6
7
|
from geopandas import GeoDataFrame
|
|
7
8
|
from shapely.constructive import reverse
|
|
8
9
|
|
|
9
|
-
from ..helpers import return_two_vals
|
|
10
|
+
from ..helpers import return_two_vals
|
|
11
|
+
from ..helpers import unit_is_meters
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
def make_directed_network_norway(gdf: GeoDataFrame, dropnegative: bool) -> GeoDataFrame:
|
|
@@ -26,7 +28,7 @@ def make_directed_network_norway(gdf: GeoDataFrame, dropnegative: bool) -> GeoDa
|
|
|
26
28
|
network graph. Recode these rows to a non-negative values if you want
|
|
27
29
|
to keep them.
|
|
28
30
|
|
|
29
|
-
Examples
|
|
31
|
+
Examples:
|
|
30
32
|
--------
|
|
31
33
|
2022 data for the municipalities of Oslo and Eidskog can be read directly like this:
|
|
32
34
|
|
|
@@ -47,11 +49,12 @@ def make_directed_network_norway(gdf: GeoDataFrame, dropnegative: bool) -> GeoDa
|
|
|
47
49
|
1944398 B 0.068239 0.068239 MULTILINESTRING Z ((258292.600 6648313.440 18....
|
|
48
50
|
1944409 B 0.023629 0.023629 MULTILINESTRING Z ((258291.452 6648289.258 19....
|
|
49
51
|
1944415 B 0.175876 0.175876 MULTILINESTRING Z ((260762.830 6650240.620 43....
|
|
50
|
-
|
|
52
|
+
<BLANKLINE>
|
|
53
|
+
[93395 rows x 4 columns]
|
|
51
54
|
|
|
52
55
|
And converted to a directed network like this:
|
|
53
56
|
|
|
54
|
-
>>> roads_directed = sg.make_directed_network_norway(roads)
|
|
57
|
+
>>> roads_directed = sg.make_directed_network_norway(roads, dropnegative=True)
|
|
55
58
|
>>> roads_directed[["minutes", "geometry"]]
|
|
56
59
|
minutes geometry
|
|
57
60
|
0 0.216611 MULTILINESTRING Z ((258028.440 6674249.890 413...
|
|
@@ -65,7 +68,8 @@ def make_directed_network_norway(gdf: GeoDataFrame, dropnegative: bool) -> GeoDa
|
|
|
65
68
|
175622 0.036810 MULTILINESTRING Z ((268681.757 6651886.457 110...
|
|
66
69
|
175623 0.003019 MULTILINESTRING Z ((268682.748 6651886.162 110...
|
|
67
70
|
175624 0.036975 MULTILINESTRING Z ((268694.594 6651881.688 111...
|
|
68
|
-
|
|
71
|
+
<BLANKLINE>
|
|
72
|
+
[175541 rows x 2 columns]
|
|
69
73
|
"""
|
|
70
74
|
if gdf["drivetime_fw"].isna().any():
|
|
71
75
|
raise ValueError("Missing values in the columns 'drivetime_fw'")
|
|
@@ -202,7 +206,9 @@ def make_directed_network(
|
|
|
202
206
|
return gdf
|
|
203
207
|
|
|
204
208
|
|
|
205
|
-
def _validate_minute_args(
|
|
209
|
+
def _validate_minute_args(
|
|
210
|
+
minute_cols: Sequence[str], speed_col_kmh: str, flat_speed_kmh: str
|
|
211
|
+
) -> None:
|
|
206
212
|
if not minute_cols and not speed_col_kmh and not flat_speed_kmh:
|
|
207
213
|
warnings.warn(
|
|
208
214
|
"Minute column will not be calculated when both 'minute_cols', "
|
|
@@ -217,7 +223,9 @@ def _validate_minute_args(minute_cols, speed_col_kmh, flat_speed_kmh):
|
|
|
217
223
|
)
|
|
218
224
|
|
|
219
225
|
|
|
220
|
-
def _validate_direction_args(
|
|
226
|
+
def _validate_direction_args(
|
|
227
|
+
gdf: GeoDataFrame, direction_col: str, direction_vals_bft: Sequence[str]
|
|
228
|
+
) -> None:
|
|
221
229
|
if len(direction_vals_bft) != 3:
|
|
222
230
|
raise ValueError(
|
|
223
231
|
"'direction_vals_bft' should be tuple/list with values of directions "
|
|
@@ -22,7 +22,7 @@ def get_connected_components(gdf: GeoDataFrame) -> GeoDataFrame:
|
|
|
22
22
|
Returns:
|
|
23
23
|
The GeoDataFrame with a new column "connected".
|
|
24
24
|
|
|
25
|
-
Examples
|
|
25
|
+
Examples:
|
|
26
26
|
--------
|
|
27
27
|
>>> from sgis import read_parquet_url, get_connected_components
|
|
28
28
|
>>> roads = read_parquet_url("https://media.githubusercontent.com/media/statisticsnorway/ssb-sgis/main/tests/testdata/roads_oslo_2022.parquet")
|
|
@@ -75,19 +75,20 @@ def get_component_size(gdf: GeoDataFrame) -> GeoDataFrame:
|
|
|
75
75
|
Returns:
|
|
76
76
|
A GeoDataFrame with a new column "component_size".
|
|
77
77
|
|
|
78
|
-
Examples
|
|
78
|
+
Examples:
|
|
79
79
|
--------
|
|
80
80
|
>>> from sgis import read_parquet_url, get_component_size
|
|
81
81
|
>>> roads = read_parquet_url("https://media.githubusercontent.com/media/statisticsnorway/ssb-sgis/main/tests/testdata/roads_oslo_2022.parquet")
|
|
82
82
|
|
|
83
83
|
>>> roads = get_component_size(roads)
|
|
84
84
|
>>> roads.component_size.value_counts().head()
|
|
85
|
+
component_size
|
|
85
86
|
79180 85638
|
|
86
87
|
2 1601
|
|
87
88
|
4 688
|
|
88
89
|
6 406
|
|
89
90
|
3 346
|
|
90
|
-
Name:
|
|
91
|
+
Name: count, dtype: int64
|
|
91
92
|
"""
|
|
92
93
|
gdf, _ = make_node_ids(gdf)
|
|
93
94
|
|
sgis/networkanalysis/network.py
CHANGED
|
@@ -5,7 +5,8 @@ network, finding and removing isolated network islands and creating unique node
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import warnings
|
|
8
|
-
from copy import copy
|
|
8
|
+
from copy import copy
|
|
9
|
+
from copy import deepcopy
|
|
9
10
|
|
|
10
11
|
import numpy as np
|
|
11
12
|
from geopandas import GeoDataFrame
|
|
@@ -21,9 +22,12 @@ from .nodes import make_node_ids
|
|
|
21
22
|
class Network:
|
|
22
23
|
"""Class used in NetworkAnalysis."""
|
|
23
24
|
|
|
24
|
-
def __init__(self, gdf: GeoDataFrame):
|
|
25
|
+
def __init__(self, gdf: GeoDataFrame) -> None:
|
|
25
26
|
"""The lines are fixed, welded together rowwise and exploded. Creates node-ids.
|
|
26
27
|
|
|
28
|
+
Args:
|
|
29
|
+
gdf: GeoDataFrame of line geometries to make up the network.
|
|
30
|
+
|
|
27
31
|
Raises:
|
|
28
32
|
TypeError: If 'gdf' is not of type GeoDataFrame.
|
|
29
33
|
ZeroLinesError: If 'gdf' has zero rows.
|
|
@@ -152,6 +156,7 @@ class Network:
|
|
|
152
156
|
return True
|
|
153
157
|
|
|
154
158
|
def get_edges(self) -> list[tuple[str, str]]:
|
|
159
|
+
"""Get a list of edges in the network."""
|
|
155
160
|
return [
|
|
156
161
|
(str(source), str(target))
|
|
157
162
|
for source, target in zip(
|
|
@@ -166,12 +171,12 @@ class Network:
|
|
|
166
171
|
"""Edge identifiers represented with source and target ids and the weight."""
|
|
167
172
|
return [f"{s}_{t}_{w}" for (s, t), w in zip(edges, weights, strict=True)]
|
|
168
173
|
|
|
169
|
-
def _update_nodes_if(self):
|
|
174
|
+
def _update_nodes_if(self) -> None:
|
|
170
175
|
if not self._nodes_are_up_to_date():
|
|
171
176
|
self._make_node_ids()
|
|
172
177
|
|
|
173
178
|
@property
|
|
174
|
-
def nodes(self):
|
|
179
|
+
def nodes(self) -> GeoDataFrame:
|
|
175
180
|
"""GeoDataFrame with the network nodes (line endpoints).
|
|
176
181
|
|
|
177
182
|
Upon instantiation of the class, a GeoDataFrame of points is created from the
|
|
@@ -182,7 +187,7 @@ class Network:
|
|
|
182
187
|
"""
|
|
183
188
|
return self._nodes
|
|
184
189
|
|
|
185
|
-
def _warn_if_undirected(self):
|
|
190
|
+
def _warn_if_undirected(self) -> None:
|
|
186
191
|
"""Road data often have to be duplicated and flipped to make it directed."""
|
|
187
192
|
if self.percent_bidirectional > 5:
|
|
188
193
|
return
|
|
@@ -202,15 +207,15 @@ class Network:
|
|
|
202
207
|
warnings.warn(mess, stacklevel=2)
|
|
203
208
|
|
|
204
209
|
@property
|
|
205
|
-
def percent_bidirectional(self):
|
|
210
|
+
def percent_bidirectional(self) -> float:
|
|
206
211
|
"""The percentage of lines that appear in both directions."""
|
|
207
212
|
return self._percent_bidirectional
|
|
208
213
|
|
|
209
|
-
def copy(self):
|
|
214
|
+
def copy(self) -> "Network":
|
|
210
215
|
"""Returns a shallow copy of the class instance."""
|
|
211
216
|
return copy(self)
|
|
212
217
|
|
|
213
|
-
def deepcopy(self):
|
|
218
|
+
def deepcopy(self) -> "Network":
|
|
214
219
|
"""Returns a deep copy of the class instance."""
|
|
215
220
|
return deepcopy(self)
|
|
216
221
|
|
|
@@ -220,9 +225,6 @@ class Network:
|
|
|
220
225
|
km = int(sum(self.gdf.length) / 1000)
|
|
221
226
|
return f"{cl}({km} km, percent_bidirectional={self._percent_bidirectional})"
|
|
222
227
|
|
|
223
|
-
def
|
|
224
|
-
"""
|
|
225
|
-
return iter(self.__dict__.items())
|
|
226
|
-
|
|
227
|
-
def __len__(self):
|
|
228
|
+
def __len__(self) -> int:
|
|
229
|
+
"""Number og rows in the GeoDataFrame."""
|
|
228
230
|
return len(self.gdf)
|