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,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,30 @@ 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
93
  if hasattr(obj, "unary_union"):
82
94
  return obj.unary_union
83
- # if is_bbox_like(obj):
84
- # return box(*obj)
85
95
  try:
86
96
  return Point(*obj)
87
97
  except TypeError:
98
+ pass
99
+ try:
88
100
  return box(*to_bbox(obj))
101
+ except TypeError:
102
+ pass
103
+ try:
104
+ return shapely.wkt.loads(obj)
105
+ except TypeError:
106
+ pass
107
+ try:
108
+ return shapely.wkb.loads(obj)
109
+ except TypeError:
110
+ pass
89
111
 
90
112
 
91
113
  def to_bbox(
@@ -105,24 +127,24 @@ def to_bbox(
105
127
  return tuple(obj.bounds)
106
128
 
107
129
  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"]))
130
+ minx = int(np.min(obj["minx"])) # type: ignore [index]
131
+ miny = int(np.min(obj["miny"])) # type: ignore [index]
132
+ maxx = int(np.max(obj["maxx"])) # type: ignore [index]
133
+ maxy = int(np.max(obj["maxy"])) # type: ignore [index]
112
134
  return minx, miny, maxx, maxy
113
135
  except Exception:
114
136
  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))
137
+ minx = int(np.min(obj.minx)) # type: ignore [union-attr]
138
+ miny = int(np.min(obj.miny)) # type: ignore [union-attr]
139
+ maxx = int(np.max(obj.maxx)) # type: ignore [union-attr]
140
+ maxy = int(np.max(obj.maxy)) # type: ignore [union-attr]
119
141
  return minx, miny, maxx, maxy
120
142
  except Exception:
121
143
  pass
122
144
 
123
145
  if hasattr(obj, "geometry"):
124
146
  try:
125
- return tuple(GeoSeries(obj["geometry"]).total_bounds)
147
+ return tuple(GeoSeries(obj["geometry"]).total_bounds) # type: ignore [index]
126
148
  except Exception:
127
149
  return tuple(GeoSeries(obj.geometry).total_bounds)
128
150
 
@@ -140,7 +162,7 @@ def to_bbox(
140
162
  raise TypeError(f"Cannot convert type {obj.__class__.__name__}{of_length} to bbox")
141
163
 
142
164
 
143
- def from_4326(lon: float, lat: float, crs=25833):
165
+ def from_4326(lon: float, lat: float, crs=25833) -> tuple[float, float]:
144
166
  """Get utm 33 N coordinates from lonlat (4326)."""
145
167
  transformer = pyproj.Transformer.from_crs(
146
168
  "EPSG:4326", f"EPSG:{crs}", always_xy=True
@@ -148,7 +170,7 @@ def from_4326(lon: float, lat: float, crs=25833):
148
170
  return transformer.transform(lon, lat)
149
171
 
150
172
 
151
- def to_4326(lon: float, lat: float, crs=25833):
173
+ def to_4326(lon: float, lat: float, crs=25833) -> tuple[float, float]:
152
174
  """Get degree coordinates 33 N coordinates from lonlat (4326)."""
153
175
  transformer = pyproj.Transformer.from_crs(
154
176
  f"EPSG:{crs}", "EPSG:4326", always_xy=True
@@ -158,20 +180,37 @@ def to_4326(lon: float, lat: float, crs=25833):
158
180
 
159
181
  def coordinate_array(
160
182
  gdf: GeoDataFrame | GeoSeries,
161
- strict=False,
162
- ) -> np.ndarray[np.ndarray[float], np.ndarray[float]]:
183
+ strict: bool = False,
184
+ include_z: bool = False,
185
+ ) -> NDArray[np.float64]:
163
186
  """Creates a 2d ndarray of coordinates from point geometries.
164
187
 
165
188
  Args:
166
189
  gdf: GeoDataFrame or GeoSeries of point geometries.
190
+ strict: If False (default), geometries without coordinates
191
+ are given the value None.
192
+ include_z: Whether to include z-coordinates. Defaults to False.
167
193
 
168
194
  Returns:
169
195
  np.ndarray of np.ndarrays of coordinates.
170
196
 
171
- Examples
197
+ Examples:
172
198
  --------
173
- >>> from sgis import coordinate_array, random_points
174
- >>> points = random_points(5)
199
+ >>> import sgis as sg
200
+ >>> points = sg.to_gdf(
201
+ ... [
202
+ ... (0, 1),
203
+ ... (1, 0),
204
+ ... (1, 1),
205
+ ... (0, 0),
206
+ ... (0.5, 0.5),
207
+ ... (0.5, 0.25),
208
+ ... (0.25, 0.25),
209
+ ... (0.75, 0.75),
210
+ ... (0.25, 0.75),
211
+ ... (0.75, 0.25),
212
+ ... ]
213
+ ... )
175
214
  >>> points
176
215
  geometry
177
216
  0 POINT (0.59376 0.92577)
@@ -179,26 +218,23 @@ def coordinate_array(
179
218
  2 POINT (0.74841 0.10627)
180
219
  3 POINT (0.00966 0.87868)
181
220
  4 POINT (0.38046 0.87879)
182
- >>> coordinate_array(points)
221
+ >>> sg.coordinate_array(points)
183
222
  array([[0.59376221, 0.92577159],
184
223
  [0.34074678, 0.91650446],
185
224
  [0.74840912, 0.10626954],
186
225
  [0.00965935, 0.87867915],
187
226
  [0.38045827, 0.87878816]])
188
- >>> coordinate_array(points.geometry)
227
+ >>> sg.coordinate_array(points.geometry)
189
228
  array([[0.59376221, 0.92577159],
190
229
  [0.34074678, 0.91650446],
191
230
  [0.74840912, 0.10626954],
192
231
  [0.00965935, 0.87867915],
193
232
  [0.38045827, 0.87878816]])
194
233
  """
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
- )
234
+ try:
235
+ return shapely.get_coordinates(gdf.geometry.values, include_z=include_z)
236
+ except AttributeError:
237
+ return shapely.get_coordinates(gdf, include_z=include_z)
202
238
 
203
239
 
204
240
  def to_gdf(
@@ -242,18 +278,18 @@ def to_gdf(
242
278
  Returns:
243
279
  A GeoDataFrame with one column, the geometry column.
244
280
 
245
- Examples
281
+ Examples:
246
282
  --------
247
- >>> from sgis import to_gdf
283
+ >>> import sgis as sg
248
284
  >>> coords = (10, 60)
249
- >>> to_gdf(coords, crs=4326)
285
+ >>> sg.to_gdf(coords, crs=4326)
250
286
  geometry
251
287
  0 POINT (10.00000 60.00000)
252
288
 
253
289
  From wkt.
254
290
 
255
291
  >>> wkt = "POINT (10 60)"
256
- >>> to_gdf(wkt, crs=4326)
292
+ >>> sg.to_gdf(wkt, crs=4326)
257
293
  geometry
258
294
  0 POINT (10.00000 60.00000)
259
295
 
@@ -264,7 +300,7 @@ def to_gdf(
264
300
  x y
265
301
  1 10 60
266
302
  3 11 59
267
- >>> gdf = to_gdf(df, geometry=["x", "y"], crs=4326)
303
+ >>> gdf = sg.to_gdf(df, geometry=["x", "y"], crs=4326)
268
304
  >>> gdf
269
305
  x y geometry
270
306
  1 10 60 POINT (10.00000 60.00000)
@@ -278,7 +314,7 @@ def to_gdf(
278
314
  col geometry
279
315
  0 1 point (10 60)
280
316
  1 2 (11, 59)
281
- >>> gdf = to_gdf(df, crs=4326)
317
+ >>> gdf = sg.to_gdf(df, crs=4326)
282
318
  >>> gdf
283
319
  col geometry
284
320
  0 1 POINT (10.00000 60.00000)
@@ -287,7 +323,7 @@ def to_gdf(
287
323
  From Series.
288
324
 
289
325
  >>> series = Series({1: (10, 60), 3: (11, 59)})
290
- >>> to_gdf(series)
326
+ >>> sg.to_gdf(series)
291
327
  geometry
292
328
  1 POINT (10.00000 60.00000)
293
329
  3 POINT (11.00000 59.00000)
@@ -296,20 +332,20 @@ def to_gdf(
296
332
  is constructed beforehand.
297
333
 
298
334
  >>> coordslist = [(10, 60), (11, 59)]
299
- >>> to_gdf(coordslist, crs=4326)
335
+ >>> sg.to_gdf(coordslist, crs=4326)
300
336
  geometry
301
337
  0 POINT (10.00000 60.00000)
302
338
  1 POINT (11.00000 59.00000)
303
339
 
304
340
  >>> from shapely.geometry import LineString
305
- >>> to_gdf(LineString(coordslist), crs=4326)
341
+ >>> sg.to_gdf(LineString(coordslist), crs=4326)
306
342
  geometry
307
343
  0 LINESTRING (10.00000 60.00000, 11.00000 59.00000)
308
344
 
309
345
  From 2 or 3 dimensional array.
310
346
 
311
347
  >>> arr = np.random.randint(100, size=(5, 3))
312
- >>> to_gdf(arr)
348
+ >>> sg.to_gdf(arr)
313
349
  geometry
314
350
  0 POINT Z (82.000 88.000 82.000)
315
351
  1 POINT Z (70.000 92.000 20.000)
@@ -333,17 +369,25 @@ def to_gdf(
333
369
 
334
370
  if crs is None:
335
371
  try:
336
- crs = obj.crs
372
+ crs = obj.crs # type: ignore
337
373
  except AttributeError:
338
374
  try:
339
- matches = re.search(r"SRID=(\d+);", obj)
375
+ matches = re.search(r"SRID=(\d+);", obj) # type: ignore
340
376
  except TypeError:
341
377
  try:
342
- matches = re.search(r"SRID=(\d+);", obj[0])
378
+ matches = re.search(r"SRID=(\d+);", obj[0]) # type: ignore
343
379
  except Exception:
344
380
  pass
345
381
  try:
346
- crs = CRS(int("".join(x for x in matches.group(0) if x.isnumeric())))
382
+ crs = CRS(
383
+ int(
384
+ "".join(
385
+ x
386
+ for x in matches.group(0) # type:ignore
387
+ if x.isnumeric()
388
+ )
389
+ )
390
+ )
347
391
  except Exception:
348
392
  pass
349
393
 
@@ -362,13 +406,13 @@ def to_gdf(
362
406
  crs=crs,
363
407
  )
364
408
 
365
- if is_array_like(geometry) and len(geometry) == len(obj):
409
+ if is_array_like(geometry) and len(geometry) == len(obj): # type: ignore
366
410
  geometry = GeoSeries(
367
- _make_one_shapely_geom(g) for g in geometry if g is not None
411
+ _make_one_shapely_geom(g) for g in geometry if g is not None # type: ignore
368
412
  )
369
413
  return GeoDataFrame(obj, geometry=geometry, crs=crs, **kwargs)
370
414
 
371
- geom_col: str = find_geometry_column(obj, geometry)
415
+ geom_col: str = _find_geometry_column(obj, geometry) # type: ignore[no-redef]
372
416
  index = kwargs.pop("index", None)
373
417
 
374
418
  # get done with iterators that get consumed by 'all'
@@ -393,7 +437,7 @@ def to_gdf(
393
437
  if is_nested_geojson(obj):
394
438
  # crs = crs or get_crs_from_dict(obj)
395
439
  obj = pd.concat(
396
- (GeoSeries(_from_json(g)) for g in obj if g is not None),
440
+ (GeoSeries(_from_json(g)) for g in obj if g is not None), # type: ignore
397
441
  ignore_index=True,
398
442
  )
399
443
  if index is not None:
@@ -401,14 +445,14 @@ def to_gdf(
401
445
  return GeoDataFrame({geom_col: obj}, geometry=geom_col, crs=crs, **kwargs)
402
446
  # list etc.
403
447
  else:
404
- obj = GeoSeries(make_shapely_geoms(obj), index=index)
448
+ obj = GeoSeries(_make_shapely_geoms(obj), index=index)
405
449
  return GeoDataFrame(
406
450
  {geom_col: obj}, geometry=geom_col, index=index, crs=crs, **kwargs
407
451
  )
408
452
 
409
453
  # now we have dict, Series or DataFrame
410
454
 
411
- obj = obj.copy()
455
+ obj = obj.copy() # type: ignore [union-attr]
412
456
 
413
457
  # preserve Series/DataFrame index
414
458
  index = obj.index if hasattr(obj, "index") and index is None else index
@@ -417,7 +461,7 @@ def to_gdf(
417
461
  if isinstance(obj, pd.DataFrame):
418
462
  notna = obj[geom_col].notna()
419
463
  obj.loc[notna, geom_col] = list(
420
- make_shapely_geoms(obj.loc[notna, geom_col])
464
+ _make_shapely_geoms(obj.loc[notna, geom_col])
421
465
  )
422
466
  obj[geom_col] = GeoSeries(obj[geom_col])
423
467
  return GeoDataFrame(obj, geometry=geom_col, crs=crs, **kwargs)
@@ -426,27 +470,27 @@ def to_gdf(
426
470
  dict(obj), geometry=geom_col, crs=crs, index=[0], **kwargs
427
471
  )
428
472
  if not hasattr(obj[geom_col], "__iter__") or len(obj[geom_col]) == 1:
429
- obj[geom_col] = make_shapely_geoms(obj[geom_col])
473
+ obj[geom_col] = _make_shapely_geoms(obj[geom_col])
430
474
  return GeoDataFrame(
431
475
  dict(obj), geometry=geom_col, crs=crs, index=index, **kwargs
432
476
  )
433
- obj[geom_col] = GeoSeries(make_shapely_geoms(obj[geom_col]), index=index)
477
+ obj[geom_col] = GeoSeries(_make_shapely_geoms(obj[geom_col]), index=index)
434
478
  return GeoDataFrame(dict(obj), geometry=geom_col, crs=crs, **kwargs)
435
479
 
436
- if geometry and all(g in obj for g in geometry):
480
+ if geometry and all(g in obj for g in geometry): # type: ignore [union-attr]
437
481
  obj[geom_col] = _geoseries_from_xyz(obj, geometry, index=index)
438
482
  return GeoDataFrame(obj, geometry=geom_col, crs=crs, **kwargs)
439
483
 
440
484
  if len(obj.keys()) == 1:
441
- key = list(obj.keys())[0]
485
+ key = next(iter(obj.keys()))
442
486
  if isinstance(obj, dict):
443
487
  geoseries = GeoSeries(
444
- make_shapely_geoms(list(obj.values())[0]), index=index
488
+ _make_shapely_geoms(next(iter(obj.values()))), index=index
445
489
  )
446
490
  elif isinstance(obj, pd.Series):
447
- geoseries = GeoSeries(make_shapely_geoms(obj), index=index)
491
+ geoseries = GeoSeries(_make_shapely_geoms(obj), index=index)
448
492
  else:
449
- geoseries = GeoSeries(make_shapely_geoms(obj.iloc[:, 0]), index=index)
493
+ geoseries = GeoSeries(_make_shapely_geoms(obj.iloc[:, 0]), index=index)
450
494
  return GeoDataFrame({key: geoseries}, geometry=key, crs=crs, **kwargs)
451
495
 
452
496
  if geometry and geom_col not in obj or isinstance(obj, pd.DataFrame):
@@ -471,7 +515,9 @@ def to_gdf(
471
515
  return GeoDataFrame(geometry=geoseries, crs=crs, **kwargs)
472
516
 
473
517
 
474
- def _array_to_geojson(array: np.ndarray, transform: Affine):
518
+ def _array_to_geojson(
519
+ array: np.ndarray, transform: Affine
520
+ ) -> list[tuple[dict, Geometry]]:
475
521
  try:
476
522
  return [
477
523
  (value, shape(geom))
@@ -498,7 +544,7 @@ def get_transform_from_bounds(
498
544
  return rasterio.transform.from_bounds(minx, miny, maxx, maxy, width, height)
499
545
 
500
546
 
501
- def make_shapely_geoms(obj):
547
+ def _make_shapely_geoms(obj: Any) -> Geometry | Any:
502
548
  if _is_one_geometry(obj):
503
549
  return _make_one_shapely_geom(obj)
504
550
  if isinstance(obj, dict) and "coordinates" in obj:
@@ -506,21 +552,12 @@ def make_shapely_geoms(obj):
506
552
  return (_make_one_shapely_geom(g) for g in obj)
507
553
 
508
554
 
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
555
+ def is_bbox_like(obj: Any) -> bool:
556
+ """Returns True if the object is an iterable of 4 numbers.
519
557
 
520
- return False"""
521
-
522
-
523
- def is_bbox_like(obj) -> bool:
558
+ Args:
559
+ obj: Any object.
560
+ """
524
561
  if (
525
562
  hasattr(obj, "__len__")
526
563
  and len(obj) == 4
@@ -531,13 +568,19 @@ def is_bbox_like(obj) -> bool:
531
568
  return False
532
569
 
533
570
 
534
- def is_nested_geojson(obj) -> bool:
571
+ def is_nested_geojson(obj: Any) -> bool:
572
+ """Returns True if the object is an iterable of all dicts.
573
+
574
+ Args:
575
+ obj: Any object.
576
+ """
535
577
  if hasattr(obj, "__iter__") and all(isinstance(g, dict) for g in obj):
536
578
  return True
537
579
  return False
538
580
 
539
581
 
540
- def get_crs_from_dict(obj):
582
+ def get_crs_from_dict(obj: Any) -> CRS | None | Any:
583
+ """Try to extract the 'crs' attribute of the object or an object in the object."""
541
584
  if (
542
585
  not hasattr(obj, "__iter__")
543
586
  or not is_dict_like(obj)
@@ -566,7 +609,7 @@ def get_crs_from_dict(obj):
566
609
  return None
567
610
 
568
611
 
569
- def _from_json(obj: dict):
612
+ def _from_json(obj: dict | list[dict]) -> Geometry:
570
613
  if not isinstance(obj, dict) and isinstance(obj[0], dict):
571
614
  return [_from_json(g) for g in obj]
572
615
  if "geometry" in obj:
@@ -574,7 +617,7 @@ def _from_json(obj: dict):
574
617
  if "features" in obj:
575
618
  return _from_json(obj["features"])
576
619
  coords = obj["coordinates"]
577
- constructor = eval("shapely.geometry." + obj.get("type", Point))
620
+ constructor: Callable = eval("shapely.geometry." + obj.get("type", Point))
578
621
  try:
579
622
  return constructor(coords)
580
623
  except TypeError:
@@ -583,16 +626,18 @@ def _from_json(obj: dict):
583
626
  return constructor(coords)
584
627
 
585
628
 
586
- def _series_like_to_geoseries(obj, index):
629
+ def _series_like_to_geoseries(obj: Iterable, index: Iterable) -> GeoSeries:
587
630
  if index is None:
588
631
  index = obj.keys()
589
632
  if isinstance(obj, dict):
590
- return GeoSeries(make_shapely_geoms(list(obj.values())), index=index)
633
+ return GeoSeries(_make_shapely_geoms(list(obj.values())), index=index)
591
634
  else:
592
- return GeoSeries(make_shapely_geoms(obj.values), index=index)
635
+ return GeoSeries(_make_shapely_geoms(obj.values), index=index)
593
636
 
594
637
 
595
- def _geoseries_to_gdf(obj: GeoSeries, geometry, crs, **kwargs) -> GeoDataFrame:
638
+ def _geoseries_to_gdf(
639
+ obj: GeoSeries, geometry: str | GeoSeries | Iterable, crs: CRS | Any, **kwargs
640
+ ) -> GeoDataFrame:
596
641
  if not crs:
597
642
  crs = obj.crs
598
643
  else:
@@ -601,7 +646,7 @@ def _geoseries_to_gdf(obj: GeoSeries, geometry, crs, **kwargs) -> GeoDataFrame:
601
646
  return GeoDataFrame({geometry: obj}, geometry=geometry, crs=crs, **kwargs)
602
647
 
603
648
 
604
- def find_geometry_column(obj, geometry) -> str:
649
+ def _find_geometry_column(obj: Any, geometry: GeoSeries | Iterable | None) -> str:
605
650
  if geometry is None:
606
651
  return "geometry"
607
652
 
@@ -621,9 +666,12 @@ def find_geometry_column(obj, geometry) -> str:
621
666
  )
622
667
 
623
668
 
624
- def _geoseries_from_xyz(obj, geometry, index) -> GeoSeries:
669
+ def _geoseries_from_xyz(
670
+ obj: Any,
671
+ geometry: Iterable[float, float] | Iterable[float, float, float],
672
+ index: Iterable | None,
673
+ ) -> GeoSeries:
625
674
  """Make geoseries from the geometry column or columns (x y (z))."""
626
-
627
675
  if len(geometry) == 2:
628
676
  x, y = geometry
629
677
  z = None
@@ -640,17 +688,17 @@ def _geoseries_from_xyz(obj, geometry, index) -> GeoSeries:
640
688
  return gpd.GeoSeries.from_xy(x=obj[x], y=obj[y], z=z, index=index)
641
689
 
642
690
 
643
- def _is_one_geometry(obj) -> bool:
691
+ def _is_one_geometry(obj: Any) -> bool:
644
692
  if (
645
- isinstance(obj, (str, bytes, Geometry))
693
+ isinstance(obj, (str, bytes, Geometry)) # type: ignore [unreachable]
646
694
  or all(isinstance(i, numbers.Number) for i in obj)
647
695
  or not hasattr(obj, "__iter__")
648
696
  ):
649
697
  return True
650
- return False
698
+ return False # type: ignore [unreachable]
651
699
 
652
700
 
653
- def _make_one_shapely_geom(obj):
701
+ def _make_one_shapely_geom(obj: Any) -> Geometry:
654
702
  """Create shapely geometry from wkt, wkb or coordinate tuple.
655
703
 
656
704
  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,8 +50,10 @@ 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
56
+ Example:
49
57
  ------
50
58
  Create two circles and get the overlap.
51
59
 
@@ -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,11 +206,13 @@ 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
215
+ Examples:
206
216
  --------
207
217
  Create three partially overlapping polygons.
208
218
 
@@ -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)