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
@@ -1,6 +1,11 @@
1
1
  import numbers
2
2
  import re
3
- from collections.abc import Collection, Iterator, Mapping, Sized
3
+ from collections.abc import Callable
4
+ from collections.abc import Collection
5
+ from collections.abc import Iterable
6
+ from collections.abc import Iterator
7
+ from collections.abc import Mapping
8
+ from collections.abc import Sized
4
9
  from typing import Any
5
10
 
6
11
  import geopandas as gpd
@@ -10,38 +15,44 @@ import pyproj
10
15
  import rasterio
11
16
  import shapely
12
17
  from affine import Affine
13
- from geopandas import GeoDataFrame, GeoSeries
14
- from pandas.api.types import is_array_like, is_dict_like, is_list_like
18
+ from geopandas import GeoDataFrame
19
+ from geopandas import GeoSeries
20
+ from numpy.typing import NDArray
21
+ from pandas.api.types import is_array_like
22
+ from pandas.api.types import is_dict_like
23
+ from pandas.api.types import is_list_like
15
24
  from pyproj import CRS
16
25
  from rasterio import features
17
- from shapely import Geometry, box, wkb, wkt
26
+ from shapely import Geometry
27
+ from shapely import box
28
+ from shapely import wkb
29
+ from shapely import wkt
18
30
  from shapely.errors import GEOSException
19
- from shapely.geometry import Point, shape
31
+ from shapely.geometry import Point
32
+ from shapely.geometry import shape
20
33
  from shapely.ops import unary_union
21
34
 
22
-
23
35
  try:
24
36
  from torchgeo.datasets.geo import RasterDataset
25
37
  except ImportError:
26
38
 
27
- class RasterDataset:
28
- """Placeholder"""
39
+ class RasterDataset: # type: ignore
40
+ """Placeholder."""
29
41
 
30
42
 
31
- @staticmethod
32
- def crs_to_string(crs):
43
+ def crs_to_string(crs: Any) -> str:
44
+ """Extract the string of a CRS-like object."""
33
45
  if crs is None:
34
46
  return "None"
35
47
  crs = pyproj.CRS(crs)
36
48
  crs_str = str(crs.to_json_dict()["name"])
37
49
  pattern = r"\d{4,5}"
38
- try:
39
- return re.search(pattern, crs_str).group()
40
- except AttributeError:
41
- return crs_str
50
+ match = re.search(pattern, crs_str)
51
+ return match.group() if match else crs_str
42
52
 
43
53
 
44
54
  def to_geoseries(obj: Any, crs: Any | None = None) -> GeoSeries:
55
+ """Convert an object to GeoSeries."""
45
56
  if crs is None:
46
57
  try:
47
58
  crs = obj.crs
@@ -73,19 +84,33 @@ def to_geoseries(obj: Any, crs: Any | None = None) -> GeoSeries:
73
84
  return GeoSeries(obj, index=index, crs=crs)
74
85
 
75
86
 
76
- def to_shapely(obj) -> Geometry:
87
+ def to_shapely(obj: Any) -> Geometry:
88
+ """Convert a geometry object or bounding box to a shapely Geometry."""
77
89
  if isinstance(obj, Geometry):
78
90
  return obj
79
91
  if not hasattr(obj, "__iter__"):
80
92
  raise TypeError(type(obj))
81
- if hasattr(obj, "unary_union"):
93
+ try:
82
94
  return obj.unary_union
83
- # if is_bbox_like(obj):
84
- # return box(*obj)
95
+ except AttributeError:
96
+ pass
85
97
  try:
86
98
  return Point(*obj)
87
99
  except TypeError:
100
+ pass
101
+ try:
88
102
  return box(*to_bbox(obj))
103
+ except TypeError:
104
+ pass
105
+ try:
106
+ return shapely.wkt.loads(obj)
107
+ except TypeError:
108
+ pass
109
+ try:
110
+ return shapely.wkb.loads(obj)
111
+ except TypeError:
112
+ pass
113
+ raise TypeError(type(obj))
89
114
 
90
115
 
91
116
  def to_bbox(
@@ -100,29 +125,35 @@ def to_bbox(
100
125
  "xmin", "ymin", "xmax", "ymax".
101
126
  """
102
127
  if isinstance(obj, (GeoDataFrame, GeoSeries)):
103
- return tuple(obj.total_bounds)
104
- if isinstance(obj, Geometry):
105
- return tuple(obj.bounds)
128
+ bounds = tuple(obj.total_bounds)
129
+ assert isinstance(bounds, tuple)
130
+ return bounds
131
+ try:
132
+ bounds = tuple(obj.bounds)
133
+ assert isinstance(bounds, tuple)
134
+ return bounds
135
+ except Exception:
136
+ pass
106
137
 
107
138
  try:
108
- minx = int(np.min(obj["minx"]))
109
- miny = int(np.min(obj["miny"]))
110
- maxx = int(np.max(obj["maxx"]))
111
- maxy = int(np.max(obj["maxy"]))
139
+ minx = int(np.min(obj["minx"])) # type: ignore [index]
140
+ miny = int(np.min(obj["miny"])) # type: ignore [index]
141
+ maxx = int(np.max(obj["maxx"])) # type: ignore [index]
142
+ maxy = int(np.max(obj["maxy"])) # type: ignore [index]
112
143
  return minx, miny, maxx, maxy
113
144
  except Exception:
114
145
  try:
115
- minx = int(np.min(obj.minx))
116
- miny = int(np.min(obj.miny))
117
- maxx = int(np.max(obj.maxx))
118
- maxy = int(np.max(obj.maxy))
146
+ minx = int(np.min(obj.minx)) # type: ignore [union-attr]
147
+ miny = int(np.min(obj.miny)) # type: ignore [union-attr]
148
+ maxx = int(np.max(obj.maxx)) # type: ignore [union-attr]
149
+ maxy = int(np.max(obj.maxy)) # type: ignore [union-attr]
119
150
  return minx, miny, maxx, maxy
120
151
  except Exception:
121
152
  pass
122
153
 
123
154
  if hasattr(obj, "geometry"):
124
155
  try:
125
- return tuple(GeoSeries(obj["geometry"]).total_bounds)
156
+ return tuple(GeoSeries(obj["geometry"]).total_bounds) # type: ignore [index]
126
157
  except Exception:
127
158
  return tuple(GeoSeries(obj.geometry).total_bounds)
128
159
 
@@ -140,7 +171,7 @@ def to_bbox(
140
171
  raise TypeError(f"Cannot convert type {obj.__class__.__name__}{of_length} to bbox")
141
172
 
142
173
 
143
- def from_4326(lon: float, lat: float, crs=25833):
174
+ def from_4326(lon: float, lat: float, crs=25833) -> tuple[float, float]:
144
175
  """Get utm 33 N coordinates from lonlat (4326)."""
145
176
  transformer = pyproj.Transformer.from_crs(
146
177
  "EPSG:4326", f"EPSG:{crs}", always_xy=True
@@ -148,7 +179,7 @@ def from_4326(lon: float, lat: float, crs=25833):
148
179
  return transformer.transform(lon, lat)
149
180
 
150
181
 
151
- def to_4326(lon: float, lat: float, crs=25833):
182
+ def to_4326(lon: float, lat: float, crs=25833) -> tuple[float, float]:
152
183
  """Get degree coordinates 33 N coordinates from lonlat (4326)."""
153
184
  transformer = pyproj.Transformer.from_crs(
154
185
  f"EPSG:{crs}", "EPSG:4326", always_xy=True
@@ -158,20 +189,37 @@ def to_4326(lon: float, lat: float, crs=25833):
158
189
 
159
190
  def coordinate_array(
160
191
  gdf: GeoDataFrame | GeoSeries,
161
- strict=False,
162
- ) -> np.ndarray[np.ndarray[float], np.ndarray[float]]:
192
+ strict: bool = False,
193
+ include_z: bool = False,
194
+ ) -> NDArray[np.float64]:
163
195
  """Creates a 2d ndarray of coordinates from point geometries.
164
196
 
165
197
  Args:
166
198
  gdf: GeoDataFrame or GeoSeries of point geometries.
199
+ strict: If False (default), geometries without coordinates
200
+ are given the value None.
201
+ include_z: Whether to include z-coordinates. Defaults to False.
167
202
 
168
203
  Returns:
169
204
  np.ndarray of np.ndarrays of coordinates.
170
205
 
171
- Examples
172
- --------
173
- >>> from sgis import coordinate_array, random_points
174
- >>> points = random_points(5)
206
+ Examples:
207
+ ---------
208
+ >>> import sgis as sg
209
+ >>> points = sg.to_gdf(
210
+ ... [
211
+ ... (0, 1),
212
+ ... (1, 0),
213
+ ... (1, 1),
214
+ ... (0, 0),
215
+ ... (0.5, 0.5),
216
+ ... (0.5, 0.25),
217
+ ... (0.25, 0.25),
218
+ ... (0.75, 0.75),
219
+ ... (0.25, 0.75),
220
+ ... (0.75, 0.25),
221
+ ... ]
222
+ ... )
175
223
  >>> points
176
224
  geometry
177
225
  0 POINT (0.59376 0.92577)
@@ -179,26 +227,23 @@ def coordinate_array(
179
227
  2 POINT (0.74841 0.10627)
180
228
  3 POINT (0.00966 0.87868)
181
229
  4 POINT (0.38046 0.87879)
182
- >>> coordinate_array(points)
230
+ >>> sg.coordinate_array(points)
183
231
  array([[0.59376221, 0.92577159],
184
232
  [0.34074678, 0.91650446],
185
233
  [0.74840912, 0.10626954],
186
234
  [0.00965935, 0.87867915],
187
235
  [0.38045827, 0.87878816]])
188
- >>> coordinate_array(points.geometry)
236
+ >>> sg.coordinate_array(points.geometry)
189
237
  array([[0.59376221, 0.92577159],
190
238
  [0.34074678, 0.91650446],
191
239
  [0.74840912, 0.10626954],
192
240
  [0.00965935, 0.87867915],
193
241
  [0.38045827, 0.87878816]])
194
242
  """
195
- if isinstance(gdf, GeoDataFrame):
196
- gdf = gdf.geometry
197
- if strict:
198
- return np.array([(geom.x, geom.y) for geom in gdf])
199
- return np.array(
200
- [(geom.x, geom.y) if hasattr(geom, "x") else (None, None) for geom in gdf]
201
- )
243
+ try:
244
+ return shapely.get_coordinates(gdf.geometry.values, include_z=include_z)
245
+ except AttributeError:
246
+ return shapely.get_coordinates(gdf, include_z=include_z)
202
247
 
203
248
 
204
249
  def to_gdf(
@@ -242,18 +287,18 @@ def to_gdf(
242
287
  Returns:
243
288
  A GeoDataFrame with one column, the geometry column.
244
289
 
245
- Examples
246
- --------
247
- >>> from sgis import to_gdf
290
+ Examples:
291
+ ---------
292
+ >>> import sgis as sg
248
293
  >>> coords = (10, 60)
249
- >>> to_gdf(coords, crs=4326)
294
+ >>> sg.to_gdf(coords, crs=4326)
250
295
  geometry
251
296
  0 POINT (10.00000 60.00000)
252
297
 
253
298
  From wkt.
254
299
 
255
300
  >>> wkt = "POINT (10 60)"
256
- >>> to_gdf(wkt, crs=4326)
301
+ >>> sg.to_gdf(wkt, crs=4326)
257
302
  geometry
258
303
  0 POINT (10.00000 60.00000)
259
304
 
@@ -264,7 +309,7 @@ def to_gdf(
264
309
  x y
265
310
  1 10 60
266
311
  3 11 59
267
- >>> gdf = to_gdf(df, geometry=["x", "y"], crs=4326)
312
+ >>> gdf = sg.to_gdf(df, geometry=["x", "y"], crs=4326)
268
313
  >>> gdf
269
314
  x y geometry
270
315
  1 10 60 POINT (10.00000 60.00000)
@@ -278,7 +323,7 @@ def to_gdf(
278
323
  col geometry
279
324
  0 1 point (10 60)
280
325
  1 2 (11, 59)
281
- >>> gdf = to_gdf(df, crs=4326)
326
+ >>> gdf = sg.to_gdf(df, crs=4326)
282
327
  >>> gdf
283
328
  col geometry
284
329
  0 1 POINT (10.00000 60.00000)
@@ -287,7 +332,7 @@ def to_gdf(
287
332
  From Series.
288
333
 
289
334
  >>> series = Series({1: (10, 60), 3: (11, 59)})
290
- >>> to_gdf(series)
335
+ >>> sg.to_gdf(series)
291
336
  geometry
292
337
  1 POINT (10.00000 60.00000)
293
338
  3 POINT (11.00000 59.00000)
@@ -296,20 +341,20 @@ def to_gdf(
296
341
  is constructed beforehand.
297
342
 
298
343
  >>> coordslist = [(10, 60), (11, 59)]
299
- >>> to_gdf(coordslist, crs=4326)
344
+ >>> sg.to_gdf(coordslist, crs=4326)
300
345
  geometry
301
346
  0 POINT (10.00000 60.00000)
302
347
  1 POINT (11.00000 59.00000)
303
348
 
304
349
  >>> from shapely.geometry import LineString
305
- >>> to_gdf(LineString(coordslist), crs=4326)
350
+ >>> sg.to_gdf(LineString(coordslist), crs=4326)
306
351
  geometry
307
352
  0 LINESTRING (10.00000 60.00000, 11.00000 59.00000)
308
353
 
309
354
  From 2 or 3 dimensional array.
310
355
 
311
356
  >>> arr = np.random.randint(100, size=(5, 3))
312
- >>> to_gdf(arr)
357
+ >>> sg.to_gdf(arr)
313
358
  geometry
314
359
  0 POINT Z (82.000 88.000 82.000)
315
360
  1 POINT Z (70.000 92.000 20.000)
@@ -333,17 +378,25 @@ def to_gdf(
333
378
 
334
379
  if crs is None:
335
380
  try:
336
- crs = obj.crs
381
+ crs = obj.crs # type: ignore
337
382
  except AttributeError:
338
383
  try:
339
- matches = re.search(r"SRID=(\d+);", obj)
384
+ matches = re.search(r"SRID=(\d+);", obj) # type: ignore
340
385
  except TypeError:
341
386
  try:
342
- matches = re.search(r"SRID=(\d+);", obj[0])
387
+ matches = re.search(r"SRID=(\d+);", obj[0]) # type: ignore
343
388
  except Exception:
344
389
  pass
345
390
  try:
346
- crs = CRS(int("".join(x for x in matches.group(0) if x.isnumeric())))
391
+ crs = CRS(
392
+ int(
393
+ "".join(
394
+ x
395
+ for x in matches.group(0) # type:ignore
396
+ if x.isnumeric()
397
+ )
398
+ )
399
+ )
347
400
  except Exception:
348
401
  pass
349
402
 
@@ -362,13 +415,13 @@ def to_gdf(
362
415
  crs=crs,
363
416
  )
364
417
 
365
- if is_array_like(geometry) and len(geometry) == len(obj):
418
+ if is_array_like(geometry) and len(geometry) == len(obj): # type: ignore
366
419
  geometry = GeoSeries(
367
- _make_one_shapely_geom(g) for g in geometry if g is not None
420
+ _make_one_shapely_geom(g) for g in geometry if g is not None # type: ignore
368
421
  )
369
422
  return GeoDataFrame(obj, geometry=geometry, crs=crs, **kwargs)
370
423
 
371
- geom_col: str = find_geometry_column(obj, geometry)
424
+ geom_col: str = _find_geometry_column(obj, geometry) # type: ignore[no-redef]
372
425
  index = kwargs.pop("index", None)
373
426
 
374
427
  # get done with iterators that get consumed by 'all'
@@ -393,7 +446,7 @@ def to_gdf(
393
446
  if is_nested_geojson(obj):
394
447
  # crs = crs or get_crs_from_dict(obj)
395
448
  obj = pd.concat(
396
- (GeoSeries(_from_json(g)) for g in obj if g is not None),
449
+ (GeoSeries(_from_json(g)) for g in obj if g is not None), # type: ignore
397
450
  ignore_index=True,
398
451
  )
399
452
  if index is not None:
@@ -401,14 +454,14 @@ def to_gdf(
401
454
  return GeoDataFrame({geom_col: obj}, geometry=geom_col, crs=crs, **kwargs)
402
455
  # list etc.
403
456
  else:
404
- obj = GeoSeries(make_shapely_geoms(obj), index=index)
457
+ obj = GeoSeries(_make_shapely_geoms(obj), index=index)
405
458
  return GeoDataFrame(
406
459
  {geom_col: obj}, geometry=geom_col, index=index, crs=crs, **kwargs
407
460
  )
408
461
 
409
462
  # now we have dict, Series or DataFrame
410
463
 
411
- obj = obj.copy()
464
+ obj = obj.copy() # type: ignore [union-attr]
412
465
 
413
466
  # preserve Series/DataFrame index
414
467
  index = obj.index if hasattr(obj, "index") and index is None else index
@@ -417,7 +470,7 @@ def to_gdf(
417
470
  if isinstance(obj, pd.DataFrame):
418
471
  notna = obj[geom_col].notna()
419
472
  obj.loc[notna, geom_col] = list(
420
- make_shapely_geoms(obj.loc[notna, geom_col])
473
+ _make_shapely_geoms(obj.loc[notna, geom_col])
421
474
  )
422
475
  obj[geom_col] = GeoSeries(obj[geom_col])
423
476
  return GeoDataFrame(obj, geometry=geom_col, crs=crs, **kwargs)
@@ -426,27 +479,27 @@ def to_gdf(
426
479
  dict(obj), geometry=geom_col, crs=crs, index=[0], **kwargs
427
480
  )
428
481
  if not hasattr(obj[geom_col], "__iter__") or len(obj[geom_col]) == 1:
429
- obj[geom_col] = make_shapely_geoms(obj[geom_col])
482
+ obj[geom_col] = _make_shapely_geoms(obj[geom_col])
430
483
  return GeoDataFrame(
431
484
  dict(obj), geometry=geom_col, crs=crs, index=index, **kwargs
432
485
  )
433
- obj[geom_col] = GeoSeries(make_shapely_geoms(obj[geom_col]), index=index)
486
+ obj[geom_col] = GeoSeries(_make_shapely_geoms(obj[geom_col]), index=index)
434
487
  return GeoDataFrame(dict(obj), geometry=geom_col, crs=crs, **kwargs)
435
488
 
436
- if geometry and all(g in obj for g in geometry):
489
+ if geometry and all(g in obj for g in geometry): # type: ignore [union-attr]
437
490
  obj[geom_col] = _geoseries_from_xyz(obj, geometry, index=index)
438
491
  return GeoDataFrame(obj, geometry=geom_col, crs=crs, **kwargs)
439
492
 
440
493
  if len(obj.keys()) == 1:
441
- key = list(obj.keys())[0]
494
+ key = next(iter(obj.keys()))
442
495
  if isinstance(obj, dict):
443
496
  geoseries = GeoSeries(
444
- make_shapely_geoms(list(obj.values())[0]), index=index
497
+ _make_shapely_geoms(next(iter(obj.values()))), index=index
445
498
  )
446
499
  elif isinstance(obj, pd.Series):
447
- geoseries = GeoSeries(make_shapely_geoms(obj), index=index)
500
+ geoseries = GeoSeries(_make_shapely_geoms(obj), index=index)
448
501
  else:
449
- geoseries = GeoSeries(make_shapely_geoms(obj.iloc[:, 0]), index=index)
502
+ geoseries = GeoSeries(_make_shapely_geoms(obj.iloc[:, 0]), index=index)
450
503
  return GeoDataFrame({key: geoseries}, geometry=key, crs=crs, **kwargs)
451
504
 
452
505
  if geometry and geom_col not in obj or isinstance(obj, pd.DataFrame):
@@ -471,7 +524,9 @@ def to_gdf(
471
524
  return GeoDataFrame(geometry=geoseries, crs=crs, **kwargs)
472
525
 
473
526
 
474
- def _array_to_geojson(array: np.ndarray, transform: Affine):
527
+ def _array_to_geojson(
528
+ array: np.ndarray, transform: Affine
529
+ ) -> list[tuple[dict, Geometry]]:
475
530
  try:
476
531
  return [
477
532
  (value, shape(geom))
@@ -498,7 +553,7 @@ def get_transform_from_bounds(
498
553
  return rasterio.transform.from_bounds(minx, miny, maxx, maxy, width, height)
499
554
 
500
555
 
501
- def make_shapely_geoms(obj):
556
+ def _make_shapely_geoms(obj: Any) -> Geometry | Any:
502
557
  if _is_one_geometry(obj):
503
558
  return _make_one_shapely_geom(obj)
504
559
  if isinstance(obj, dict) and "coordinates" in obj:
@@ -506,21 +561,12 @@ def make_shapely_geoms(obj):
506
561
  return (_make_one_shapely_geom(g) for g in obj)
507
562
 
508
563
 
509
- """def is_boundingbox(obj) -> bool:
510
- if not hasattr(obj, "__iter__"):
511
- return False
512
-
513
- classname = obj.__class__.__name__.lower()
514
- if "bounding" not in classname and "box" not in classname:
515
- return False
516
-
517
- if len(obj) == 4 and all(isinstance(x, numbers.Number) for x in obj):
518
- return True
519
-
520
- return False"""
521
-
564
+ def is_bbox_like(obj: Any) -> bool:
565
+ """Returns True if the object is an iterable of 4 numbers.
522
566
 
523
- def is_bbox_like(obj) -> bool:
567
+ Args:
568
+ obj: Any object.
569
+ """
524
570
  if (
525
571
  hasattr(obj, "__len__")
526
572
  and len(obj) == 4
@@ -531,13 +577,19 @@ def is_bbox_like(obj) -> bool:
531
577
  return False
532
578
 
533
579
 
534
- def is_nested_geojson(obj) -> bool:
580
+ def is_nested_geojson(obj: Any) -> bool:
581
+ """Returns True if the object is an iterable of all dicts.
582
+
583
+ Args:
584
+ obj: Any object.
585
+ """
535
586
  if hasattr(obj, "__iter__") and all(isinstance(g, dict) for g in obj):
536
587
  return True
537
588
  return False
538
589
 
539
590
 
540
- def get_crs_from_dict(obj):
591
+ def get_crs_from_dict(obj: Any) -> CRS | None | Any:
592
+ """Try to extract the 'crs' attribute of the object or an object in the object."""
541
593
  if (
542
594
  not hasattr(obj, "__iter__")
543
595
  or not is_dict_like(obj)
@@ -566,7 +618,7 @@ def get_crs_from_dict(obj):
566
618
  return None
567
619
 
568
620
 
569
- def _from_json(obj: dict):
621
+ def _from_json(obj: dict | list[dict]) -> Geometry:
570
622
  if not isinstance(obj, dict) and isinstance(obj[0], dict):
571
623
  return [_from_json(g) for g in obj]
572
624
  if "geometry" in obj:
@@ -574,7 +626,7 @@ def _from_json(obj: dict):
574
626
  if "features" in obj:
575
627
  return _from_json(obj["features"])
576
628
  coords = obj["coordinates"]
577
- constructor = eval("shapely.geometry." + obj.get("type", Point))
629
+ constructor: Callable = eval("shapely.geometry." + obj.get("type", Point))
578
630
  try:
579
631
  return constructor(coords)
580
632
  except TypeError:
@@ -583,16 +635,18 @@ def _from_json(obj: dict):
583
635
  return constructor(coords)
584
636
 
585
637
 
586
- def _series_like_to_geoseries(obj, index):
638
+ def _series_like_to_geoseries(obj: Iterable, index: Iterable) -> GeoSeries:
587
639
  if index is None:
588
640
  index = obj.keys()
589
641
  if isinstance(obj, dict):
590
- return GeoSeries(make_shapely_geoms(list(obj.values())), index=index)
642
+ return GeoSeries(_make_shapely_geoms(list(obj.values())), index=index)
591
643
  else:
592
- return GeoSeries(make_shapely_geoms(obj.values), index=index)
644
+ return GeoSeries(_make_shapely_geoms(obj.values), index=index)
593
645
 
594
646
 
595
- def _geoseries_to_gdf(obj: GeoSeries, geometry, crs, **kwargs) -> GeoDataFrame:
647
+ def _geoseries_to_gdf(
648
+ obj: GeoSeries, geometry: str | GeoSeries | Iterable, crs: CRS | Any, **kwargs
649
+ ) -> GeoDataFrame:
596
650
  if not crs:
597
651
  crs = obj.crs
598
652
  else:
@@ -601,7 +655,7 @@ def _geoseries_to_gdf(obj: GeoSeries, geometry, crs, **kwargs) -> GeoDataFrame:
601
655
  return GeoDataFrame({geometry: obj}, geometry=geometry, crs=crs, **kwargs)
602
656
 
603
657
 
604
- def find_geometry_column(obj, geometry) -> str:
658
+ def _find_geometry_column(obj: Any, geometry: GeoSeries | Iterable | None) -> str:
605
659
  if geometry is None:
606
660
  return "geometry"
607
661
 
@@ -621,9 +675,12 @@ def find_geometry_column(obj, geometry) -> str:
621
675
  )
622
676
 
623
677
 
624
- def _geoseries_from_xyz(obj, geometry, index) -> GeoSeries:
678
+ def _geoseries_from_xyz(
679
+ obj: Any,
680
+ geometry: Iterable[float, float] | Iterable[float, float, float],
681
+ index: Iterable | None,
682
+ ) -> GeoSeries:
625
683
  """Make geoseries from the geometry column or columns (x y (z))."""
626
-
627
684
  if len(geometry) == 2:
628
685
  x, y = geometry
629
686
  z = None
@@ -640,17 +697,17 @@ def _geoseries_from_xyz(obj, geometry, index) -> GeoSeries:
640
697
  return gpd.GeoSeries.from_xy(x=obj[x], y=obj[y], z=z, index=index)
641
698
 
642
699
 
643
- def _is_one_geometry(obj) -> bool:
700
+ def _is_one_geometry(obj: Any) -> bool:
644
701
  if (
645
- isinstance(obj, (str, bytes, Geometry))
702
+ isinstance(obj, (str, bytes, Geometry)) # type: ignore [unreachable]
646
703
  or all(isinstance(i, numbers.Number) for i in obj)
647
704
  or not hasattr(obj, "__iter__")
648
705
  ):
649
706
  return True
650
- return False
707
+ return False # type: ignore [unreachable]
651
708
 
652
709
 
653
- def _make_one_shapely_geom(obj):
710
+ def _make_one_shapely_geom(obj: Any) -> Geometry:
654
711
  """Create shapely geometry from wkt, wkb or coordinate tuple.
655
712
 
656
713
  Works recursively if the object is a nested iterable.
@@ -2,20 +2,26 @@ from collections.abc import Iterable
2
2
 
3
3
  import networkx as nx
4
4
  import pandas as pd
5
- from geopandas import GeoDataFrame, GeoSeries
6
- from shapely import STRtree, difference, make_valid, simplify, unary_union
5
+ from geopandas import GeoDataFrame
6
+ from geopandas import GeoSeries
7
+ from shapely import STRtree
8
+ from shapely import difference
9
+ from shapely import make_valid
10
+ from shapely import simplify
11
+ from shapely import unary_union
7
12
  from shapely.errors import GEOSException
8
13
 
9
- from .general import (
10
- _determine_geom_type_args,
11
- _push_geom_col,
12
- clean_geoms,
13
- parallel_unary_union_geoseries,
14
- )
15
- from .geometry_types import get_geom_type, make_all_singlepart, to_single_geom_type
16
- from .overlay import _run_overlay_dask, clean_overlay, make_valid_and_keep_geom_type
17
- from .sfilter import sfilter_inverse, sfilter_split
18
-
14
+ from .general import _determine_geom_type_args
15
+ from .general import _parallel_unary_union_geoseries
16
+ from .general import _push_geom_col
17
+ from .general import clean_geoms
18
+ from .geometry_types import get_geom_type
19
+ from .geometry_types import make_all_singlepart
20
+ from .geometry_types import to_single_geom_type
21
+ from .overlay import _run_overlay_dask
22
+ from .overlay import clean_overlay
23
+ from .overlay import make_valid_and_keep_geom_type
24
+ from .sfilter import sfilter_inverse
19
25
 
20
26
  PRECISION = 1e-3
21
27
 
@@ -44,9 +50,11 @@ def update_geometries(
44
50
  "line" or "point".
45
51
  grid_size: Precision grid size to round the geometries. Will use the highest
46
52
  precision of the inputs by default.
53
+ n_jobs: Number of threads.
54
+ predicate: Spatial predicate for the spatial tree.
47
55
 
48
- Example
49
- ------
56
+ Example:
57
+ --------
50
58
  Create two circles and get the overlap.
51
59
 
52
60
  >>> import sgis as sg
@@ -107,7 +115,7 @@ def update_geometries(
107
115
  # select geometries from 'right', index from 'left', dissolve by 'left'
108
116
  erasers = pd.Series(copied.geometry.loc[indices.values].values, index=indices.index)
109
117
  if n_jobs > 1:
110
- erasers = parallel_unary_union_geoseries(
118
+ erasers = _parallel_unary_union_geoseries(
111
119
  erasers,
112
120
  level=0,
113
121
  n_jobs=n_jobs,
@@ -198,12 +206,14 @@ def get_intersections(
198
206
  keep_geom_type: Whether to keep the original geometry type.
199
207
  If mixed geometry types and keep_geom_type=True,
200
208
  an exception is raised.
209
+ n_jobs: Number of threads.
210
+ predicate: Spatial predicate for the spatial tree.
201
211
 
202
212
  Returns:
203
213
  A GeoDataFrame of the overlapping polygons.
204
214
 
205
- Examples
206
- --------
215
+ Examples:
216
+ ---------
207
217
  Create three partially overlapping polygons.
208
218
 
209
219
  >>> import sgis as sg
@@ -284,7 +294,11 @@ def get_intersections(
284
294
 
285
295
 
286
296
  def _get_intersecting_geometries(
287
- gdf: GeoDataFrame, geom_type, keep_geom_type, n_jobs, predicate
297
+ gdf: GeoDataFrame,
298
+ geom_type: str | None,
299
+ keep_geom_type: bool,
300
+ n_jobs: int,
301
+ predicate: str | None,
288
302
  ) -> GeoDataFrame:
289
303
  right = gdf[[gdf._geometry_column_name]]
290
304
  right["idx_right"] = right.index
@@ -376,7 +390,7 @@ def _get_duplicate_geometry_groups(
376
390
  tree = STRtree(gdf.geometry.values)
377
391
  left, right = tree.query(gdf.geometry.values, predicate="within")
378
392
 
379
- edges = list(zip(left, right))
393
+ edges = list(zip(left, right, strict=False))
380
394
 
381
395
  graph = nx.Graph()
382
396
  graph.add_edges_from(edges)