ssb-sgis 1.0.0__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.0.dist-info → ssb_sgis-1.0.2.dist-info}/LICENSE +1 -1
  54. {ssb_sgis-1.0.0.dist-info → ssb_sgis-1.0.2.dist-info}/METADATA +89 -18
  55. ssb_sgis-1.0.2.dist-info/RECORD +61 -0
  56. {ssb_sgis-1.0.0.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.0.dist-info/RECORD +0 -63
@@ -1,65 +1,43 @@
1
1
  import re
2
2
  import warnings
3
- from typing import Callable
3
+ from collections.abc import Callable
4
+ from typing import Any
4
5
 
5
- import networkx as nx
6
6
  import numpy as np
7
7
  import pandas as pd
8
8
  import shapely
9
- from geopandas import GeoDataFrame, GeoSeries
9
+ from geopandas import GeoDataFrame
10
+ from geopandas import GeoSeries
10
11
  from geopandas.array import GeometryArray
11
12
  from numpy.typing import NDArray
12
- from shapely import (
13
- Geometry,
14
- STRtree,
15
- extract_unique_points,
16
- force_2d,
17
- get_coordinates,
18
- get_exterior_ring,
19
- get_parts,
20
- linearrings,
21
- linestrings,
22
- make_valid,
23
- multipoints,
24
- polygons,
25
- reverse,
26
- segmentize,
27
- simplify,
28
- unary_union,
29
- )
13
+ from shapely import extract_unique_points
14
+ from shapely import get_coordinates
15
+ from shapely import get_parts
16
+ from shapely import linestrings
30
17
  from shapely.errors import GEOSException
31
- from shapely.geometry import LinearRing, LineString, MultiLineString, MultiPoint, Point
32
- from shapely.ops import nearest_points
18
+ from shapely.geometry import LineString
19
+ from shapely.geometry import Point
33
20
 
34
- from ..networkanalysis.closing_network_holes import get_angle
35
- from ..networkanalysis.cutting_lines import split_lines_by_nearest_point
36
- from .buffer_dissolve_explode import buff, buffdissexp, dissexp, dissexp_by_cluster
37
- from .conversion import coordinate_array, to_gdf, to_geoseries
38
- from .duplicates import get_intersections, update_geometries
21
+ from .buffer_dissolve_explode import buff
22
+ from .buffer_dissolve_explode import dissexp
23
+ from .conversion import coordinate_array
24
+ from .conversion import to_gdf
25
+ from .duplicates import get_intersections
26
+ from .duplicates import update_geometries
39
27
 
40
28
  # from .general import sort_large_first as _sort_large_first
41
- from .general import (
42
- clean_clip,
43
- clean_geoms,
44
- sort_large_first,
45
- sort_long_first,
46
- sort_small_first,
47
- to_lines,
48
- )
49
- from .geometry_types import get_geom_type, make_all_singlepart, to_single_geom_type
50
- from .neighbors import get_k_nearest_neighbors, get_neighbor_indices
29
+ from .general import clean_geoms
30
+ from .general import sort_large_first
31
+ from .general import sort_small_first
32
+ from .general import to_lines
33
+ from .geometry_types import make_all_singlepart
34
+ from .geometry_types import to_single_geom_type
51
35
  from .overlay import clean_overlay
52
- from .polygon_operations import (
53
- close_all_holes,
54
- close_small_holes,
55
- close_thin_holes,
56
- eliminate_by_longest,
57
- get_cluster_mapper,
58
- get_gaps,
59
- )
60
- from .polygons_as_rings import PolygonsAsRings
61
- from .sfilter import sfilter, sfilter_inverse, sfilter_split
62
-
36
+ from .polygon_operations import eliminate_by_longest
37
+ from .polygon_operations import get_cluster_mapper
38
+ from .polygon_operations import get_gaps
39
+ from .sfilter import sfilter_inverse
40
+ from .sfilter import sfilter_split
63
41
 
64
42
  warnings.simplefilter(action="ignore", category=UserWarning)
65
43
  warnings.simplefilter(action="ignore", category=RuntimeWarning)
@@ -74,8 +52,6 @@ def coverage_clean(
74
52
  tolerance: int | float,
75
53
  duplicate_action: str = "fix",
76
54
  grid_sizes: tuple[None | int] = (None,),
77
- logger=None,
78
- mask=None,
79
55
  n_jobs: int = 1,
80
56
  ) -> GeoDataFrame:
81
57
  """Fix thin gaps, holes, slivers and double surfaces.
@@ -102,7 +78,7 @@ def coverage_clean(
102
78
  for polygons to be eliminated. Any gap, hole, sliver or double
103
79
  surface that are empty after a negative buffer of tolerance / 2
104
80
  are eliminated into the neighbor with the longest shared border.
105
- duplicate action: Either "fix", "error" or "ignore".
81
+ duplicate_action: Either "fix", "error" or "ignore".
106
82
  If "fix" (default), double surfaces thicker than the
107
83
  tolerance will be updated from top to bottom (function update_geometries)
108
84
  and then dissolved into the neighbor polygon with the longest shared border.
@@ -110,36 +86,11 @@ def coverage_clean(
110
86
  than the tolerance. If "ignore", double surfaces are kept as is.
111
87
  grid_sizes: One or more grid_sizes used in overlay and dissolve operations that
112
88
  might raise a GEOSException. Defaults to (None,), meaning no grid_sizes.
89
+ n_jobs: Number of threads.
113
90
 
114
91
  Returns:
115
92
  A GeoDataFrame with cleaned polygons.
116
-
117
- Examples
118
- --------
119
-
120
- >>> cleaned = coverage_clean(
121
- ... gdf,
122
- ... 0.1,
123
- ... grid_sizes=[None, 1e-6, 1e-5, 1e-4, 1e-3],
124
- ... )
125
-
126
- If you have a known mask for your coverage, e.g. municipality polygons,
127
- it might be a good idea to buffer the gaps, slivers and double surfaces
128
- before elimination to make sure the polygons are properly dissolved.
129
-
130
- >>> def _small_buffer(df):
131
- ... df.geometry = df.buffer(0.001)
132
- ... return df
133
- ...
134
- >>> cleaned = coverage_clean(
135
- ... gdf,
136
- ... 0.1,
137
- ... grid_sizes=[None, 1e-6, 1e-5, 1e-4, 1e-3],
138
- ... pre_dissolve_func=_small_buffer,
139
- ... ).pipe(sg.clean_clip, your_mask, geom_type="polygon")
140
-
141
93
  """
142
-
143
94
  if not len(gdf):
144
95
  return gdf
145
96
 
@@ -153,7 +104,7 @@ def coverage_clean(
153
104
  ]
154
105
 
155
106
  try:
156
- gdf = safe_simplify(gdf, PRECISION)
107
+ gdf = _safe_simplify(gdf, PRECISION)
157
108
  except GEOSException:
158
109
  pass
159
110
 
@@ -175,7 +126,7 @@ def coverage_clean(
175
126
  break
176
127
  except GEOSException as e:
177
128
  if i == len(grid_sizes) - 1:
178
- explore_geosexception(e, gdf, logger=logger)
129
+ explore_geosexception(e, gdf)
179
130
  raise e
180
131
 
181
132
  gaps["_was_gap"] = 1
@@ -262,7 +213,7 @@ def coverage_clean(
262
213
  break
263
214
  except GEOSException as e:
264
215
  if i == len(grid_sizes) - 1:
265
- explore_geosexception(e, gdf, intersecting, isolated, logger=logger)
216
+ explore_geosexception(e, gdf, intersecting, isolated)
266
217
  raise e
267
218
 
268
219
  not_really_isolated = isolated[["geometry", "_eliminate_idx", "_cluster"]].merge(
@@ -333,9 +284,7 @@ def coverage_clean(
333
284
  break
334
285
  except GEOSException as e:
335
286
  if i == len(grid_sizes) - 1:
336
- explore_geosexception(
337
- e, gdf, without_double, isolated, really_isolated, logger=logger
338
- )
287
+ explore_geosexception(e, gdf, without_double, isolated, really_isolated)
339
288
  raise e
340
289
 
341
290
  cleaned = pd.concat([many_hits, one_hit], ignore_index=True)
@@ -349,6 +298,7 @@ def coverage_clean(
349
298
  cleaned,
350
299
  how="update",
351
300
  geom_type="polygon",
301
+ grid_size=grid_size,
352
302
  n_jobs=n_jobs,
353
303
  )
354
304
  break
@@ -361,7 +311,6 @@ def coverage_clean(
361
311
  without_double,
362
312
  isolated,
363
313
  really_isolated,
364
- logger=logger,
365
314
  )
366
315
  raise e
367
316
 
@@ -388,11 +337,10 @@ def coverage_clean(
388
337
  without_double,
389
338
  isolated,
390
339
  really_isolated,
391
- logger=logger,
392
340
  )
393
341
  raise e
394
342
 
395
- # cleaned = safe_simplify(cleaned, PRECISION)
343
+ # cleaned = _safe_simplify(cleaned, PRECISION)
396
344
  # cleaned.geometry = shapely.make_valid(cleaned.geometry)
397
345
 
398
346
  # TODO check why polygons dissappear in rare cases. For now, just add back the missing
@@ -402,7 +350,7 @@ def coverage_clean(
402
350
  return to_single_geom_type(cleaned, "polygon")
403
351
 
404
352
 
405
- def safe_simplify(gdf, tolerance: float | int, **kwargs):
353
+ def _safe_simplify(gdf: GeoDataFrame, tolerance: float | int, **kwargs) -> GeoDataFrame:
406
354
  """Simplify only if the resulting area is no more than 1 percent larger.
407
355
 
408
356
  Because simplifying can result in holes being filled.
@@ -420,7 +368,7 @@ def safe_simplify(gdf, tolerance: float | int, **kwargs):
420
368
  return copied
421
369
 
422
370
 
423
- def remove_interior_slivers(gdf, tolerance):
371
+ def _remove_interior_slivers(gdf: GeoDataFrame, tolerance: int | float) -> GeoDataFrame:
424
372
  gdf, slivers = split_out_slivers(gdf, tolerance)
425
373
  slivers["_idx"] = range(len(slivers))
426
374
  without_thick = clean_overlay(
@@ -439,14 +387,29 @@ def remove_interior_slivers(gdf, tolerance):
439
387
  def remove_spikes(
440
388
  gdf: GeoDataFrame, tolerance: int | float, n_jobs: int = 1
441
389
  ) -> GeoDataFrame:
390
+ """Remove thin spikes from polygons.
391
+
392
+ Args:
393
+ gdf: A GeoDataFrame.
394
+ tolerance: Spike tolerance.
395
+ n_jobs: Number of threads.
396
+
397
+ Returns:
398
+ A GeoDataFrame.
399
+ """
442
400
  return clean_overlay(
443
401
  gdf, gdf[["geometry"]], how="intersection", grid_size=tolerance, n_jobs=n_jobs
444
402
  )
445
403
 
446
404
 
447
405
  def _properly_fix_duplicates(
448
- gdf, double, slivers, thin_gaps_and_double, tolerance, n_jobs
449
- ):
406
+ gdf: GeoDataFrame,
407
+ double: GeoDataFrame,
408
+ slivers: GeoDataFrame,
409
+ thin_gaps_and_double: GeoDataFrame,
410
+ tolerance: int | float,
411
+ n_jobs: int,
412
+ ) -> GeoDataFrame:
450
413
  gdf = _dissolve_thick_double_and_update(gdf, double, thin_gaps_and_double, n_jobs)
451
414
  gdf, more_slivers = split_out_slivers(gdf, tolerance)
452
415
  slivers = pd.concat([slivers, more_slivers], ignore_index=True)
@@ -462,7 +425,9 @@ def _properly_fix_duplicates(
462
425
  return gdf, thin_gaps_and_double, slivers
463
426
 
464
427
 
465
- def _dissolve_thick_double_and_update(gdf, double, thin_double, n_jobs):
428
+ def _dissolve_thick_double_and_update(
429
+ gdf: GeoDataFrame, double: GeoDataFrame, thin_double: GeoDataFrame, n_jobs: int
430
+ ) -> GeoDataFrame:
466
431
  large = (
467
432
  double.loc[~double["_double_idx"].isin(thin_double["_double_idx"])].drop(
468
433
  columns="_double_idx"
@@ -479,7 +444,9 @@ def _dissolve_thick_double_and_update(gdf, double, thin_double, n_jobs):
479
444
  )
480
445
 
481
446
 
482
- def _cleaning_checks(gdf, tolerance, duplicate_action): # , spike_action):
447
+ def _cleaning_checks(
448
+ gdf: GeoDataFrame, tolerance: int | float, duplicate_action: bool
449
+ ) -> GeoDataFrame: # , spike_action):
483
450
  if not len(gdf) or not tolerance:
484
451
  return gdf
485
452
  if tolerance < PRECISION:
@@ -503,11 +470,11 @@ def split_out_slivers(
503
470
 
504
471
 
505
472
  def try_for_grid_size(
506
- func,
473
+ func: Callable,
507
474
  grid_sizes: tuple[None, float | int],
508
475
  args: tuple | None = None,
509
476
  kwargs: dict | None = None,
510
- ):
477
+ ) -> Any:
511
478
  args = args or ()
512
479
  kwargs = kwargs or {}
513
480
  for i, grid_size in enumerate(grid_sizes):
@@ -523,7 +490,6 @@ def split_and_eliminate_by_longest(
523
490
  to_eliminate: GeoDataFrame,
524
491
  tolerance: int | float,
525
492
  grid_sizes: tuple[None | float | int] = (None,),
526
- logger=None,
527
493
  n_jobs: int = 1,
528
494
  **kwargs,
529
495
  ) -> GeoDataFrame | tuple[GeoDataFrame]:
@@ -585,7 +551,12 @@ def split_and_eliminate_by_longest(
585
551
  )
586
552
 
587
553
 
588
- def split_by_neighbors(df, split_by, tolerance, grid_size=None):
554
+ def split_by_neighbors(
555
+ df: GeoDataFrame,
556
+ split_by: GeoDataFrame,
557
+ tolerance: int | float,
558
+ grid_size: float | int | None = None,
559
+ ) -> GeoDataFrame:
589
560
  if not len(df):
590
561
  return df
591
562
 
@@ -621,7 +592,7 @@ def split_by_neighbors(df, split_by, tolerance, grid_size=None):
621
592
  return clean_overlay(df, buffered, how="identity", grid_size=grid_size)
622
593
 
623
594
 
624
- def extend_lines(arr1, arr2, distance):
595
+ def extend_lines(arr1, arr2, distance) -> NDArray[LineString]:
625
596
  if len(arr1) != len(arr2):
626
597
  raise ValueError
627
598
  if not len(arr1):
@@ -664,7 +635,7 @@ def make_lines_between_points(
664
635
  return linestrings(coords.values, indices=coords.index)
665
636
 
666
637
 
667
- def get_line_segments(lines) -> GeoDataFrame:
638
+ def get_line_segments(lines: GeoDataFrame | GeoSeries) -> GeoDataFrame:
668
639
  assert lines.index.is_unique
669
640
  if isinstance(lines, GeoDataFrame):
670
641
  geom_col = lines._geometry_column_name
@@ -711,7 +682,8 @@ def multipoints_to_line_segments(multipoints: GeoSeries) -> GeoDataFrame:
711
682
  assert point_df["next"].notna().all()
712
683
 
713
684
  point_df["geometry"] = [
714
- LineString([x1, x2]) for x1, x2 in zip(point_df["geometry"], point_df["next"])
685
+ LineString([x1, x2])
686
+ for x1, x2 in zip(point_df["geometry"], point_df["next"], strict=False)
715
687
  ]
716
688
  return GeoDataFrame(point_df.drop(columns=["next"]), geometry="geometry", crs=crs)
717
689
 
@@ -727,16 +699,27 @@ def points_to_line_segments(points: GeoDataFrame) -> GeoDataFrame:
727
699
  assert points["next"].notna().all()
728
700
 
729
701
  points["geometry"] = [
730
- LineString([x1, x2]) for x1, x2 in zip(points["geometry"], points["next"])
702
+ LineString([x1, x2])
703
+ for x1, x2 in zip(points["geometry"], points["next"], strict=False)
731
704
  ]
732
705
  return GeoDataFrame(
733
706
  points.drop(columns=["next"]), geometry="geometry", crs=points.crs
734
707
  )
735
708
 
736
709
 
737
- def explore_geosexception(e: GEOSException, *gdfs, logger=None):
738
- from ..maps.maps import Explore, explore
739
- from .conversion import to_gdf
710
+ def explore_geosexception(
711
+ e: GEOSException, *gdfs: GeoDataFrame, logger: Any | None = None
712
+ ) -> None:
713
+ """Extract the coordinates of a GEOSException and show in map.
714
+
715
+ Args:
716
+ e: The exception thrown by a GEOS operation, which potentially contains coordinates information.
717
+ *gdfs: One or more GeoDataFrames to display for context in the map.
718
+ logger: An optional logger to log the error with visualization. If None, uses standard output.
719
+
720
+ """
721
+ from ..maps.maps import Explore
722
+ from ..maps.maps import explore
740
723
 
741
724
  pattern = r"(\d+\.\d+)\s+(\d+\.\d+)"
742
725