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.
Files changed (59) hide show
  1. sgis/__init__.py +97 -115
  2. sgis/exceptions.py +3 -1
  3. sgis/geopandas_tools/__init__.py +1 -0
  4. sgis/geopandas_tools/bounds.py +75 -38
  5. sgis/geopandas_tools/buffer_dissolve_explode.py +38 -34
  6. sgis/geopandas_tools/centerlines.py +53 -44
  7. sgis/geopandas_tools/cleaning.py +87 -104
  8. sgis/geopandas_tools/conversion.py +149 -101
  9. sgis/geopandas_tools/duplicates.py +31 -17
  10. sgis/geopandas_tools/general.py +76 -48
  11. sgis/geopandas_tools/geometry_types.py +21 -7
  12. sgis/geopandas_tools/neighbors.py +20 -8
  13. sgis/geopandas_tools/overlay.py +136 -53
  14. sgis/geopandas_tools/point_operations.py +9 -8
  15. sgis/geopandas_tools/polygon_operations.py +48 -56
  16. sgis/geopandas_tools/polygons_as_rings.py +121 -78
  17. sgis/geopandas_tools/sfilter.py +14 -14
  18. sgis/helpers.py +114 -56
  19. sgis/io/dapla_functions.py +32 -23
  20. sgis/io/opener.py +13 -6
  21. sgis/io/read_parquet.py +1 -1
  22. sgis/maps/examine.py +39 -26
  23. sgis/maps/explore.py +112 -66
  24. sgis/maps/httpserver.py +12 -12
  25. sgis/maps/legend.py +124 -65
  26. sgis/maps/map.py +66 -41
  27. sgis/maps/maps.py +31 -29
  28. sgis/maps/thematicmap.py +46 -33
  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 +20 -62
  35. sgis/networkanalysis/cutting_lines.py +55 -43
  36. sgis/networkanalysis/directednetwork.py +15 -7
  37. sgis/networkanalysis/finding_isolated_networks.py +4 -3
  38. sgis/networkanalysis/network.py +15 -13
  39. sgis/networkanalysis/networkanalysis.py +72 -54
  40. sgis/networkanalysis/networkanalysisrules.py +20 -16
  41. sgis/networkanalysis/nodes.py +2 -3
  42. sgis/networkanalysis/traveling_salesman.py +5 -2
  43. sgis/parallel/parallel.py +337 -127
  44. sgis/raster/__init__.py +6 -0
  45. sgis/raster/base.py +9 -3
  46. sgis/raster/cube.py +280 -208
  47. sgis/raster/cubebase.py +15 -29
  48. sgis/raster/indices.py +3 -7
  49. sgis/raster/methods_as_functions.py +0 -124
  50. sgis/raster/raster.py +313 -127
  51. sgis/raster/torchgeo.py +58 -37
  52. sgis/raster/zonal.py +38 -13
  53. {ssb_sgis-1.0.1.dist-info → ssb_sgis-1.0.2.dist-info}/LICENSE +1 -1
  54. {ssb_sgis-1.0.1.dist-info → ssb_sgis-1.0.2.dist-info}/METADATA +87 -16
  55. ssb_sgis-1.0.2.dist-info/RECORD +61 -0
  56. {ssb_sgis-1.0.1.dist-info → ssb_sgis-1.0.2.dist-info}/WHEEL +1 -1
  57. sgis/raster/bands.py +0 -48
  58. sgis/raster/gradient.py +0 -78
  59. ssb_sgis-1.0.1.dist-info/RECORD +0 -63
@@ -1,30 +1,35 @@
1
1
  import numbers
2
2
  import warnings
3
- from collections.abc import Hashable, Iterable
3
+ from collections.abc import Hashable
4
+ from collections.abc import Iterable
4
5
  from typing import Any
5
6
 
7
+ import dask_geopandas
6
8
  import joblib
7
9
  import numpy as np
8
10
  import pandas as pd
9
11
  import pyproj
10
- from geopandas import GeoDataFrame, GeoSeries
11
- from geopandas.array import GeometryArray, GeometryDtype
12
+ from geopandas import GeoDataFrame
13
+ from geopandas import GeoSeries
14
+ from geopandas.array import GeometryArray
15
+ from geopandas.array import GeometryDtype
12
16
  from numpy.typing import NDArray
13
- from shapely import (
14
- Geometry,
15
- get_coordinates,
16
- get_exterior_ring,
17
- get_interior_ring,
18
- get_num_interior_rings,
19
- get_parts,
20
- linestrings,
21
- make_valid,
22
- )
17
+ from shapely import Geometry
18
+ from shapely import get_coordinates
19
+ from shapely import get_exterior_ring
20
+ from shapely import get_interior_ring
21
+ from shapely import get_num_interior_rings
22
+ from shapely import get_parts
23
+ from shapely import linestrings
24
+ from shapely import make_valid
23
25
  from shapely import points as shapely_points
24
26
  from shapely import unary_union
25
- from shapely.geometry import LineString, Point
27
+ from shapely.geometry import LineString
28
+ from shapely.geometry import Point
26
29
 
27
- from .geometry_types import get_geom_type, make_all_singlepart, to_single_geom_type
30
+ from .geometry_types import get_geom_type
31
+ from .geometry_types import make_all_singlepart
32
+ from .geometry_types import to_single_geom_type
28
33
 
29
34
 
30
35
  def split_geom_types(gdf: GeoDataFrame | GeoSeries) -> tuple[GeoDataFrame | GeoSeries]:
@@ -79,13 +84,13 @@ def get_common_crs(
79
84
  actually_different.add(x)
80
85
 
81
86
  if len(actually_different) == 1:
82
- return list(actually_different)[0]
87
+ return next(iter(actually_different))
83
88
  raise ValueError("'crs' mismatch.", truthy_crs)
84
89
 
85
90
  return pyproj.CRS(truthy_crs[0])
86
91
 
87
92
 
88
- def is_bbox_like(obj) -> bool:
93
+ def is_bbox_like(obj: Any) -> bool:
89
94
  if (
90
95
  hasattr(obj, "__iter__")
91
96
  and len(obj) == 4
@@ -114,6 +119,7 @@ def _push_geom_col(gdf: GeoDataFrame) -> GeoDataFrame:
114
119
 
115
120
 
116
121
  def drop_inactive_geometry_columns(gdf: GeoDataFrame) -> GeoDataFrame:
122
+ """Removes geometry columns in a GeoDataFrame if they are not active."""
117
123
  for col in gdf.columns:
118
124
  if (
119
125
  isinstance(gdf[col].dtype, GeometryDtype)
@@ -123,7 +129,7 @@ def drop_inactive_geometry_columns(gdf: GeoDataFrame) -> GeoDataFrame:
123
129
  return gdf
124
130
 
125
131
 
126
- def rename_geometry_if(gdf: GeoDataFrame) -> GeoDataFrame:
132
+ def _rename_geometry_if(gdf: GeoDataFrame) -> GeoDataFrame:
127
133
  geom_col = gdf._geometry_column_name
128
134
  if geom_col == "geometry" and geom_col in gdf.columns:
129
135
  return gdf
@@ -157,7 +163,7 @@ def clean_geoms(
157
163
  GeoDataFrame or GeoSeries with fixed geometries and only the rows with valid,
158
164
  non-empty and not-NaN/-None geometries.
159
165
 
160
- Examples
166
+ Examples:
161
167
  --------
162
168
  >>> import sgis as sg
163
169
  >>> import pandas as pd
@@ -231,8 +237,20 @@ def clean_geoms(
231
237
 
232
238
 
233
239
  def get_grouped_centroids(
234
- gdf: GeoDataFrame, groupby: str, as_string: bool = True
240
+ gdf: GeoDataFrame, groupby: str | list[str], as_string: bool = True
235
241
  ) -> pd.Series:
242
+ """Get the centerpoint of the geometries within a group.
243
+
244
+ Args:
245
+ gdf: GeoDataFrame.
246
+ groupby: column to group by.
247
+ as_string: If True (default), coordinates are returned in
248
+ the format "{x}_{y}". If False, coordinates are returned
249
+ as Points.
250
+
251
+ Returns:
252
+ A pandas.Series of grouped centroids with the index of 'gdf'.
253
+ """
236
254
  centerpoints = gdf.assign(geometry=lambda x: x.centroid)
237
255
 
238
256
  grouped_centerpoints = centerpoints.dissolve(by=groupby).assign(
@@ -242,9 +260,13 @@ def get_grouped_centroids(
242
260
  ys = grouped_centerpoints.geometry.y
243
261
 
244
262
  if as_string:
245
- grouped_centerpoints["wkt"] = [f"{int(x)}_{int(y)}" for x, y in zip(xs, ys)]
263
+ grouped_centerpoints["wkt"] = [
264
+ f"{int(x)}_{int(y)}" for x, y in zip(xs, ys, strict=False)
265
+ ]
246
266
  else:
247
- grouped_centerpoints["wkt"] = [Point(x, y) for x, y in zip(xs, ys)]
267
+ grouped_centerpoints["wkt"] = [
268
+ Point(x, y) for x, y in zip(xs, ys, strict=False)
269
+ ]
248
270
 
249
271
  return gdf[groupby].map(grouped_centerpoints["wkt"])
250
272
 
@@ -258,12 +280,20 @@ def sort_large_first(gdf: GeoDataFrame | GeoSeries) -> GeoDataFrame | GeoSeries:
258
280
  Returns:
259
281
  A GeoDataFrame or GeoSeries sorted from large to small in area.
260
282
 
261
- Examples
283
+ Examples:
262
284
  --------
263
285
  Create GeoDataFrame with NaN values.
264
286
 
265
287
  >>> import sgis as sg
266
- >>> df = sg.random_points(5)
288
+ >>> df = sg.to_gdf(
289
+ ... [
290
+ ... (0, 1),
291
+ ... (1, 0),
292
+ ... (1, 1),
293
+ ... (0, 0),
294
+ ... (0.5, 0.5),
295
+ ... ]
296
+ ... )
267
297
  >>> df.geometry = df.buffer([4, 1, 2, 3, 5])
268
298
  >>> df["col"] = [None, 1, 2, None, 1]
269
299
  >>> df["col2"] = [None, 1, 2, 3, None]
@@ -299,16 +329,6 @@ def sort_large_first(gdf: GeoDataFrame | GeoSeries) -> GeoDataFrame | GeoSeries:
299
329
  return gdf.iloc[list(sorted_areas)]
300
330
 
301
331
 
302
- def sort_df(
303
- df: pd.DataFrame | GeoDataFrame, sort_col: pd.Series
304
- ) -> pd.DataFrame | GeoDataFrame:
305
- value_mapper: dict[int, Any] = dict(enumerate(sort_col.values))
306
- sorted_indices = dict(
307
- reversed(sorted(value_mapper.items(), key=lambda item: item[1]))
308
- )
309
- return df.iloc[list(sorted_indices)]
310
-
311
-
312
332
  def sort_long_first(gdf: GeoDataFrame | GeoSeries) -> GeoDataFrame | GeoSeries:
313
333
  """Sort GeoDataFrame by length in decending order.
314
334
 
@@ -402,7 +422,7 @@ def random_points(n: int, loc: float | int = 0.5) -> GeoDataFrame:
402
422
  Returns:
403
423
  A GeoDataFrame of points with n rows.
404
424
 
405
- Examples
425
+ Examples:
406
426
  --------
407
427
  >>> import sgis as sg
408
428
  >>> points = sg.random_points(10_000)
@@ -451,6 +471,16 @@ def random_points(n: int, loc: float | int = 0.5) -> GeoDataFrame:
451
471
 
452
472
 
453
473
  def random_points_in_polygons(gdf: GeoDataFrame, n: int, seed=None) -> GeoDataFrame:
474
+ """Creates a GeoDataFrame with n random points within the geometries of 'gdf'.
475
+
476
+ Args:
477
+ gdf: A GeoDataFrame.
478
+ n: Number of points/rows to create.
479
+ seed: Optional random seet.
480
+
481
+ Returns:
482
+ A GeoDataFrame of points with n rows.
483
+ """
454
484
  all_points = []
455
485
 
456
486
  rng = np.random.default_rng(seed)
@@ -492,7 +522,7 @@ def to_lines(*gdfs: GeoDataFrame, copy: bool = True) -> GeoDataFrame:
492
522
  ignored. This is because the union overlay used if multiple GeoDataFrames
493
523
  always ignores the index.
494
524
 
495
- Examples
525
+ Examples:
496
526
  --------
497
527
  Convert single polygon to linestring.
498
528
 
@@ -525,7 +555,6 @@ def to_lines(*gdfs: GeoDataFrame, copy: bool = True) -> GeoDataFrame:
525
555
  >>> lines["l"] = lines.length
526
556
  >>> sg.qtm(lines, "l")
527
557
  """
528
-
529
558
  if not all(isinstance(gdf, (GeoSeries, GeoDataFrame)) for gdf in gdfs):
530
559
  raise TypeError("gdf must be GeoDataFrame or GeoSeries")
531
560
 
@@ -534,7 +563,6 @@ def to_lines(*gdfs: GeoDataFrame, copy: bool = True) -> GeoDataFrame:
534
563
 
535
564
  def _shapely_geometry_to_lines(geom):
536
565
  """Get all lines from the exterior and interiors of a Polygon."""
537
-
538
566
  # if lines (points are not allowed in this function)
539
567
  if geom.area == 0:
540
568
  return geom
@@ -664,11 +692,11 @@ def _determine_geom_type_args(
664
692
  return gdf, geom_type, keep_geom_type
665
693
 
666
694
 
667
- def merge_geometries(geoms: GeoSeries, grid_size=None) -> Geometry:
695
+ def _merge_geometries(geoms: GeoSeries, grid_size=None) -> Geometry:
668
696
  return make_valid(unary_union(geoms, grid_size=grid_size))
669
697
 
670
698
 
671
- def parallel_unary_union(
699
+ def _parallel_unary_union(
672
700
  gdf: GeoDataFrame, n_jobs: int = 1, by=None, grid_size=None, **kwargs
673
701
  ) -> list[Geometry]:
674
702
  try:
@@ -702,7 +730,7 @@ def parallel_unary_union(
702
730
  return dissolved.geometry
703
731
 
704
732
 
705
- def parallel_unary_union_geoseries(
733
+ def _parallel_unary_union_geoseries(
706
734
  ser: GeoSeries, n_jobs: int = 1, grid_size=None, **kwargs
707
735
  ) -> list[Geometry]:
708
736
  if ser.crs is None:
@@ -725,7 +753,7 @@ def parallel_unary_union_geoseries(
725
753
  return dissolved.geometry
726
754
 
727
755
 
728
- def parallel_unary_union(
756
+ def _parallel_unary_union(
729
757
  gdf: GeoDataFrame, n_jobs: int = 1, by=None, grid_size=None, **kwargs
730
758
  ) -> list[Geometry]:
731
759
  try:
@@ -737,13 +765,13 @@ def parallel_unary_union(
737
765
  delayed_operations = []
738
766
  for _, geoms in gdf.groupby(by, **kwargs)[geom_col]:
739
767
  delayed_operations.append(
740
- joblib.delayed(merge_geometries)(geoms, grid_size=grid_size)
768
+ joblib.delayed(_merge_geometries)(geoms, grid_size=grid_size)
741
769
  )
742
770
 
743
771
  return parallel(delayed_operations)
744
772
 
745
773
 
746
- def parallel_unary_union_geoseries(
774
+ def _parallel_unary_union_geoseries(
747
775
  ser: GeoSeries, n_jobs: int = 1, grid_size=None, **kwargs
748
776
  ) -> list[Geometry]:
749
777
 
@@ -756,18 +784,18 @@ def parallel_unary_union_geoseries(
756
784
  delayed_operations = []
757
785
  for _, geoms in many_hits.groupby(**kwargs):
758
786
  delayed_operations.append(
759
- joblib.delayed(merge_geometries)(geoms, grid_size=grid_size)
787
+ joblib.delayed(_merge_geometries)(geoms, grid_size=grid_size)
760
788
  )
761
789
 
762
790
  dissolved = pd.Series(
763
791
  parallel(delayed_operations),
764
- index=is_one_hit[lambda x: x == False].index.unique(),
792
+ index=is_one_hit[lambda x: x is False].index.unique(),
765
793
  )
766
794
 
767
795
  return pd.concat([dissolved, one_hit]).sort_index().values
768
796
 
769
797
 
770
- def parallel_unary_union_geoseries(
798
+ def _parallel_unary_union_geoseries(
771
799
  ser: GeoSeries, n_jobs: int = 1, grid_size=None, **kwargs
772
800
  ) -> list[Geometry]:
773
801
 
@@ -775,7 +803,7 @@ def parallel_unary_union_geoseries(
775
803
  delayed_operations = []
776
804
  for _, geoms in ser.groupby(**kwargs):
777
805
  delayed_operations.append(
778
- joblib.delayed(merge_geometries)(geoms, grid_size=grid_size)
806
+ joblib.delayed(_merge_geometries)(geoms, grid_size=grid_size)
779
807
  )
780
808
 
781
809
  return parallel(delayed_operations)
@@ -1,8 +1,10 @@
1
1
  """Check and set geometry type."""
2
+
2
3
  import numpy as np
3
4
  import pandas as pd
4
5
  import shapely
5
- from geopandas import GeoDataFrame, GeoSeries
6
+ from geopandas import GeoDataFrame
7
+ from geopandas import GeoSeries
6
8
  from geopandas.array import GeometryArray
7
9
  from shapely import Geometry
8
10
 
@@ -10,6 +12,18 @@ from shapely import Geometry
10
12
  def make_all_singlepart(
11
13
  gdf: GeoDataFrame | GeoSeries, index_parts: bool = False, ignore_index: bool = False
12
14
  ) -> GeoDataFrame | GeoSeries:
15
+ """Make all geometries single part.
16
+
17
+ This means doing either 0, 1 or 2 calls to the explode method.
18
+
19
+ Args:
20
+ gdf: GeoDataFrame
21
+ index_parts: Defaults to False.
22
+ ignore_index: Defaults to False.
23
+
24
+ Returns:
25
+ A GeoDataFrame of singlepart geometries.
26
+ """
13
27
  # only explode if nessecary
14
28
  if (
15
29
  index_parts or ignore_index
@@ -25,7 +39,7 @@ def make_all_singlepart(
25
39
 
26
40
 
27
41
  def to_single_geom_type(
28
- gdf: GeoDataFrame | GeoSeries,
42
+ gdf: GeoDataFrame | GeoSeries | Geometry | GeometryArray | np.ndarray,
29
43
  geom_type: str,
30
44
  ignore_index: bool = False,
31
45
  ) -> GeoDataFrame | GeoSeries:
@@ -49,7 +63,7 @@ def to_single_geom_type(
49
63
  TypeError: If incorrect gdf type.
50
64
  ValueError: If 'geom_type' is neither 'polygon', 'line' or 'point'.
51
65
 
52
- Examples
66
+ Examples:
53
67
  --------
54
68
  First create a GeoDataFrame of mixed geometries.
55
69
 
@@ -96,7 +110,7 @@ def to_single_geom_type(
96
110
  arr = np.vectorize(_shapely_to_single_geom_type)(gdf, geom_type)
97
111
  return arr[~shapely.is_empty(arr)]
98
112
 
99
- if not isinstance(gdf, (GeoDataFrame, GeoSeries)):
113
+ if not isinstance(gdf, (GeoDataFrame, GeoSeries)): # type: ignore [unreachable]
100
114
  raise TypeError(f"'gdf' should be GeoDataFrame or GeoSeries, got {type(gdf)}")
101
115
 
102
116
  # explode collections to single-typed geometries
@@ -122,7 +136,7 @@ def to_single_geom_type(
122
136
  return gdf.reset_index(drop=True) if ignore_index else gdf
123
137
 
124
138
 
125
- def _shapely_to_single_geom_type(geom, geom_type):
139
+ def _shapely_to_single_geom_type(geom: Geometry, geom_type: str) -> Geometry:
126
140
  parts = shapely.get_parts(geom)
127
141
  return shapely.unary_union(
128
142
  [part for part in parts if geom_type.lower() in part.geom_type.lower()]
@@ -141,7 +155,7 @@ def get_geom_type(gdf: GeoDataFrame | GeoSeries) -> str:
141
155
  Raises:
142
156
  TypeError: If 'gdf' is not of type GeoDataFrame or GeoSeries.
143
157
 
144
- Examples
158
+ Examples:
145
159
  --------
146
160
  >>> from sgis import to_gdf, get_geom_type
147
161
  >>> gdf = to_gdf([0, 0])
@@ -189,7 +203,7 @@ def is_single_geom_type(gdf: GeoDataFrame | GeoSeries) -> bool:
189
203
  Raises:
190
204
  TypeError: If 'gdf' is not of type GeoDataFrame or GeoSeries.
191
205
 
192
- Examples
206
+ Examples:
193
207
  --------
194
208
  >>> from sgis import to_gdf, get_geom_type
195
209
  >>> gdf = to_gdf([0, 0])
@@ -10,9 +10,10 @@ types.
10
10
 
11
11
  import numpy as np
12
12
  import shapely
13
- from geopandas import GeoDataFrame, GeoSeries
14
- from pandas import DataFrame, Series, concat
15
- from shapely import STRtree
13
+ from geopandas import GeoDataFrame
14
+ from geopandas import GeoSeries
15
+ from pandas import DataFrame
16
+ from pandas import Series
16
17
  from sklearn.neighbors import NearestNeighbors
17
18
 
18
19
  from .conversion import coordinate_array
@@ -48,7 +49,7 @@ def get_neighbor_indices(
48
49
  ValueError: If gdf and neighbors do not have the same coordinate reference
49
50
  system.
50
51
 
51
- Examples
52
+ Examples:
52
53
  --------
53
54
  >>> from sgis import get_neighbor_indices, to_gdf
54
55
  >>> points = to_gdf([(0, 0), (0.5, 0.5)])
@@ -96,7 +97,6 @@ def get_neighbor_indices(
96
97
  ['a' 'a' 'b' 'b']
97
98
 
98
99
  """
99
-
100
100
  if gdf.crs != neighbors.crs:
101
101
  raise ValueError(f"'crs' mismatch. Got {gdf.crs} and {neighbors.crs}")
102
102
 
@@ -152,7 +152,7 @@ def get_all_distances(
152
152
  ValueError: If the coordinate reference system of 'gdf' and 'neighbors' are
153
153
  not the same.
154
154
 
155
- Examples
155
+ Examples:
156
156
  --------
157
157
  >>> from sgis import get_all_distances, random_points
158
158
  >>> points = random_points(100)
@@ -248,7 +248,6 @@ def sjoin_within_distance(
248
248
  **kwargs,
249
249
  ) -> GeoDataFrame:
250
250
  """Sjoin with a buffer on the right GeoDataFrame and adds a distance column."""
251
-
252
251
  new_neighbor_cols = {"__left_range_idx": range(len(neighbors))}
253
252
  if distance:
254
253
  new_neighbor_cols[neighbors._geometry_column_name] = lambda x: x.buffer(
@@ -297,7 +296,7 @@ def get_k_nearest_neighbors(
297
296
  ValueError: If the coordinate reference system of 'gdf' and 'neighbors' are
298
297
  not the same.
299
298
 
300
- Examples
299
+ Examples:
301
300
  --------
302
301
  Make some random points.
303
302
 
@@ -430,6 +429,19 @@ def k_nearest_neighbors(
430
429
  k: int | None = None,
431
430
  strict: bool = False,
432
431
  ) -> tuple[np.ndarray[float], np.ndarray[int]]:
432
+ """Finds nearest neighbors for an array of coordinates to another array of coordinates.
433
+
434
+ Args:
435
+ from_array: Numpy array of coordinates.
436
+ to_array: Numpy array of coordinates.
437
+ k: Number of neighbors to find.
438
+ strict: If True (not default), an exception is raised
439
+ if k is larger than the length of 'to_array'.
440
+
441
+ Returns a tuple of arrays, one with distances and one with indices
442
+ of the neighbors.
443
+
444
+ """
433
445
  if not len(to_array) or not len(from_array):
434
446
  return np.array([]), np.array([])
435
447