ssb-sgis 1.0.1__py3-none-any.whl → 1.0.3__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.
Files changed (60) hide show
  1. sgis/__init__.py +107 -121
  2. sgis/exceptions.py +5 -3
  3. sgis/geopandas_tools/__init__.py +1 -0
  4. sgis/geopandas_tools/bounds.py +86 -47
  5. sgis/geopandas_tools/buffer_dissolve_explode.py +62 -39
  6. sgis/geopandas_tools/centerlines.py +53 -44
  7. sgis/geopandas_tools/cleaning.py +87 -104
  8. sgis/geopandas_tools/conversion.py +164 -107
  9. sgis/geopandas_tools/duplicates.py +33 -19
  10. sgis/geopandas_tools/general.py +84 -52
  11. sgis/geopandas_tools/geometry_types.py +24 -10
  12. sgis/geopandas_tools/neighbors.py +23 -11
  13. sgis/geopandas_tools/overlay.py +136 -53
  14. sgis/geopandas_tools/point_operations.py +11 -10
  15. sgis/geopandas_tools/polygon_operations.py +53 -61
  16. sgis/geopandas_tools/polygons_as_rings.py +121 -78
  17. sgis/geopandas_tools/sfilter.py +17 -17
  18. sgis/helpers.py +116 -58
  19. sgis/io/dapla_functions.py +32 -23
  20. sgis/io/opener.py +13 -6
  21. sgis/io/read_parquet.py +2 -2
  22. sgis/maps/examine.py +55 -28
  23. sgis/maps/explore.py +471 -112
  24. sgis/maps/httpserver.py +12 -12
  25. sgis/maps/legend.py +285 -134
  26. sgis/maps/map.py +248 -129
  27. sgis/maps/maps.py +123 -119
  28. sgis/maps/thematicmap.py +260 -94
  29. sgis/maps/tilesources.py +3 -8
  30. sgis/networkanalysis/_get_route.py +5 -4
  31. sgis/networkanalysis/_od_cost_matrix.py +44 -1
  32. sgis/networkanalysis/_points.py +10 -4
  33. sgis/networkanalysis/_service_area.py +5 -2
  34. sgis/networkanalysis/closing_network_holes.py +22 -64
  35. sgis/networkanalysis/cutting_lines.py +58 -46
  36. sgis/networkanalysis/directednetwork.py +16 -8
  37. sgis/networkanalysis/finding_isolated_networks.py +6 -5
  38. sgis/networkanalysis/network.py +15 -13
  39. sgis/networkanalysis/networkanalysis.py +79 -61
  40. sgis/networkanalysis/networkanalysisrules.py +21 -17
  41. sgis/networkanalysis/nodes.py +2 -3
  42. sgis/networkanalysis/traveling_salesman.py +6 -3
  43. sgis/parallel/parallel.py +372 -142
  44. sgis/raster/base.py +9 -3
  45. sgis/raster/cube.py +331 -213
  46. sgis/raster/cubebase.py +15 -29
  47. sgis/raster/image_collection.py +2560 -0
  48. sgis/raster/indices.py +17 -12
  49. sgis/raster/raster.py +356 -275
  50. sgis/raster/sentinel_config.py +104 -0
  51. sgis/raster/zonal.py +38 -14
  52. {ssb_sgis-1.0.1.dist-info → ssb_sgis-1.0.3.dist-info}/LICENSE +1 -1
  53. {ssb_sgis-1.0.1.dist-info → ssb_sgis-1.0.3.dist-info}/METADATA +87 -16
  54. ssb_sgis-1.0.3.dist-info/RECORD +61 -0
  55. {ssb_sgis-1.0.1.dist-info → ssb_sgis-1.0.3.dist-info}/WHEEL +1 -1
  56. sgis/raster/bands.py +0 -48
  57. sgis/raster/gradient.py +0 -78
  58. sgis/raster/methods_as_functions.py +0 -124
  59. sgis/raster/torchgeo.py +0 -150
  60. ssb_sgis-1.0.1.dist-info/RECORD +0 -63
@@ -47,7 +47,9 @@ def _od_cost_matrix(
47
47
  return results.reset_index(drop=True)
48
48
 
49
49
 
50
- def _get_od_df(graph, origins, destinations, weight_col):
50
+ def _get_od_df(
51
+ graph: Graph, origins: GeoDataFrame, destinations: GeoDataFrame, weight_col: str
52
+ ) -> pd.DataFrame:
51
53
  distances: list[list[float]] = graph.distances(
52
54
  weights="weight",
53
55
  source=origins,
@@ -68,3 +70,44 @@ def _get_od_df(graph, origins, destinations, weight_col):
68
70
  .replace([np.inf, -np.inf], np.nan)
69
71
  .reset_index(drop=True)
70
72
  )
73
+
74
+
75
+ def _get_one_od_df(
76
+ graph: Graph, origins: GeoDataFrame, destinations: GeoDataFrame, weight_col: str
77
+ ) -> pd.DataFrame:
78
+ distances: list[list[float]] = graph.distances(
79
+ weights="weight",
80
+ source=origins,
81
+ target=destinations,
82
+ )
83
+
84
+ ori_idx, des_idx, costs = [], [], []
85
+ for i, f_idx in enumerate(origins):
86
+ for j, t_idx in enumerate(destinations):
87
+ ori_idx.append(f_idx)
88
+ des_idx.append(t_idx)
89
+ costs.append(distances[i][j])
90
+
91
+ return (
92
+ pd.DataFrame(
93
+ data={"origin": ori_idx, "destination": des_idx, weight_col: costs}
94
+ )
95
+ .replace([np.inf, -np.inf], np.nan)
96
+ .reset_index(drop=True)
97
+ )
98
+
99
+
100
+ # def _get_od_df(
101
+ # graph: Graph,
102
+ # origins: GeoDataFrame,
103
+ # destinations: GeoDataFrame,
104
+ # weight_col: str,
105
+ # ) -> pd.DataFrame:
106
+ # from ..parallel.parallel import Parallel
107
+
108
+ # results: list[pd.DataFrame] = Parallel(40, backend="loky").map(
109
+ # _get_one_od_df,
110
+ # [origins[origins.index == i] for i in origins.index.unique()],
111
+ # kwargs=dict(graph=graph, destinations=destinations, weight_col=weight_col),
112
+ # )
113
+ # return pd.concat(results, ignore_index=True)
@@ -1,10 +1,12 @@
1
+ from collections.abc import Sequence
2
+
1
3
  import numpy as np
4
+ import pandas as pd
2
5
  from geopandas import GeoDataFrame
3
6
 
4
7
  from ..geopandas_tools.neighbors import get_k_nearest_neighbors
5
8
  from .networkanalysisrules import NetworkAnalysisRules
6
9
 
7
-
8
10
  """
9
11
  These are internal classes used in the NetworkAnalysis class. The classes used in
10
12
  NetworkAnalysis are Origins and Destinations, which are subclasses of Points. The
@@ -26,7 +28,6 @@ class Points:
26
28
  The original indices are stored in a dict and mapped back to the results in the
27
29
  end.
28
30
  """
29
-
30
31
  self.gdf["temp_idx"] = np.arange(start=start, stop=start + len(self.gdf))
31
32
  self.gdf["temp_idx"] = self.gdf["temp_idx"].astype(str)
32
33
 
@@ -36,7 +37,9 @@ class Points:
36
37
  }
37
38
 
38
39
  @staticmethod
39
- def _convert_distance_to_weight(distances, rules):
40
+ def _convert_distance_to_weight(
41
+ distances: Sequence, rules: NetworkAnalysisRules
42
+ ) -> list[float]:
40
43
  """Meters to minutes based on 'weight_to_nodes_' attribute of the rules."""
41
44
  if not rules.nodedist_multiplier and not rules.nodedist_kmh:
42
45
  return [0 for _ in distances]
@@ -60,7 +63,9 @@ class Points:
60
63
 
61
64
  return [x / (16.666667 * rules.nodedist_kmh) for x in distances]
62
65
 
63
- def _make_edges(self, df, from_col, to_col):
66
+ def _make_edges(
67
+ self, df: GeoDataFrame | pd.DataFrame, from_col: str, to_col: str
68
+ ) -> list[tuple[int, int]]:
64
69
  return [(f, t) for f, t in zip(df[from_col], df[to_col], strict=True)]
65
70
 
66
71
  def _get_edges_and_weights(
@@ -71,6 +76,7 @@ class Points:
71
76
  from_col: str,
72
77
  to_col: str,
73
78
  ):
79
+ """Make edges and weights between points and the nodes of a network."""
74
80
  distances = get_k_nearest_neighbors(
75
81
  gdf=self.gdf.set_index("temp_idx"),
76
82
  neighbors=nodes.set_index("node_id"),
@@ -2,8 +2,11 @@ import numpy as np
2
2
  import pandas as pd
3
3
  from geopandas import GeoDataFrame
4
4
  from igraph import Graph
5
- from shapely import force_2d, reverse, unary_union
6
- from shapely.geometry import MultiLineString, MultiPoint, Point
5
+ from shapely import force_2d
6
+ from shapely import reverse
7
+ from shapely import unary_union
8
+ from shapely.geometry import MultiPoint
9
+ from shapely.geometry import Point
7
10
  from shapely.ops import nearest_points
8
11
  from shapely.wkt import loads
9
12
 
@@ -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, GeoSeries
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, to_geoseries
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, make_node_ids
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,8 +43,8 @@ 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
93
- --------
46
+ Examples:
47
+ ---------
94
48
  Read road data with small gaps.
95
49
 
96
50
  >>> import sgis as sg
@@ -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
- Name: connected, dtype: int64
62
+ hole
109
63
  0 93395
110
64
  1 7102
111
- Name: hole, dtype: int64
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: hole, dtype: int64
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,8 +156,8 @@ def close_network_holes_to_deadends(
202
156
  Returns:
203
157
  The input GeoDataFrame with new lines added.
204
158
 
205
- Examples
206
- --------
159
+ Examples:
160
+ ---------
207
161
  Read road data with small gaps.
208
162
 
209
163
  >>> import sgis as sg
@@ -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: id for wkt, id in zip(nodes["wkt"], nodes["node_id"], strict=True)
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, nodes, max_distance, max_angle, idx_start: int
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, GeoSeries
7
- from pandas import DataFrame, Series
8
- from shapely import (
9
- buffer,
10
- extract_unique_points,
11
- force_2d,
12
- get_coordinates,
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
- _shapely_snap,
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,8 +51,8 @@ def split_lines_by_nearest_point(
59
51
  Raises:
60
52
  ValueError: If the crs of the input data differs.
61
53
 
62
- Examples
63
- --------
54
+ Examples:
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")
66
58
  >>> points = read_parquet_url("https://media.githubusercontent.com/media/statisticsnorway/ssb-sgis/main/tests/testdata/points_oslo.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
- """find the nearest snapped point for each source and target of the lines"""
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 = change_line_endpoint(
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 = change_line_endpoint(
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 change_line_endpoint(
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
- Loop for each line where the source is the endpoint that was split
200
- change the first point of the line to the point it was split by
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
- to_be_changed = lambda x: ~x.index.duplicated(keep="first")
213
+ keep = "first"
206
214
  elif change_what == "last" or change_what == -1:
207
- to_be_changed = lambda x: ~x.index.duplicated(keep="last")
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[to_be_changed, "geometry"] = (
220
- relevant_lines.loc[to_be_changed].index.map(pointmapper).values
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(gdf: GeoDataFrame, max_length: int, ignore_index=False) -> GeoDataFrame:
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,8 +254,8 @@ 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
246
- --------
257
+ Examples:
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")
249
261
  >>> roads.length.describe().round(1)
@@ -314,8 +326,8 @@ 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
318
- --------
329
+ Examples:
330
+ ---------
319
331
  >>> from sgis import cut_lines_once, to_gdf
320
332
  >>> import pandas as pd
321
333
  >>> from shapely.geometry import LineString
@@ -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
- 1 LINESTRING (0.70711 0.70711, 1.00000 1.00000, ... 1
348
- 2 LINESTRING (0.00000 0.00000, 1.00000 1.00000, ... 2
349
- 3 LINESTRING (1.41421 1.41421, 2.00000 2.00000) 2
350
- 4 LINESTRING (0.00000 0.00000, 1.00000 1.00000, ... 3
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
- 1 LINESTRING (0.70711 0.70711, 1.00000 1.00000, ... 1
358
- 2 LINESTRING (0.00000 0.00000, 1.00000 1.00000, ... 2
359
- 3 LINESTRING (1.41421 1.41421, 2.00000 2.00000) 2
360
- 4 LINESTRING (0.00000 0.00000, 1.00000 1.00000, ... 3
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, unit_is_meters
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,8 +28,8 @@ 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
30
- --------
31
+ Examples:
32
+ ---------
31
33
  2022 data for the municipalities of Oslo and Eidskog can be read directly like this:
32
34
 
33
35
  >>> import sgis as sg
@@ -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
- [93395 rows x 46 columns]
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
- [175541 rows x 45 columns]
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(minute_cols, speed_col_kmh, flat_speed_kmh):
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(gdf, direction_col, direction_vals_bft):
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,8 +22,8 @@ def get_connected_components(gdf: GeoDataFrame) -> GeoDataFrame:
22
22
  Returns:
23
23
  The GeoDataFrame with a new column "connected".
24
24
 
25
- Examples
26
- --------
25
+ Examples:
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")
29
29
 
@@ -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
79
- --------
78
+ Examples:
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: component_size, dtype: int64
91
+ Name: count, dtype: int64
91
92
  """
92
93
  gdf, _ = make_node_ids(gdf)
93
94