ssb-sgis 1.1.17__tar.gz → 1.2.1__tar.gz

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 (67) hide show
  1. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/PKG-INFO +1 -1
  2. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/pyproject.toml +1 -1
  3. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/__init__.py +5 -0
  4. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/conf.py +18 -0
  5. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/geopandas_tools/buffer_dissolve_explode.py +25 -47
  6. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/geopandas_tools/conversion.py +18 -25
  7. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/geopandas_tools/duplicates.py +45 -60
  8. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/geopandas_tools/general.py +69 -114
  9. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/geopandas_tools/neighbors.py +25 -4
  10. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/geopandas_tools/overlay.py +178 -256
  11. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/geopandas_tools/polygon_operations.py +68 -88
  12. ssb_sgis-1.2.1/src/sgis/geopandas_tools/runners.py +326 -0
  13. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/geopandas_tools/sfilter.py +42 -24
  14. ssb_sgis-1.2.1/src/sgis/geopandas_tools/utils.py +37 -0
  15. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/helpers.py +1 -1
  16. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/io/dapla_functions.py +96 -107
  17. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/maps/map.py +3 -1
  18. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/parallel/parallel.py +32 -24
  19. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/raster/image_collection.py +184 -162
  20. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/raster/indices.py +0 -1
  21. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/LICENSE +0 -0
  22. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/README.md +0 -0
  23. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/debug_config.py +0 -0
  24. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/exceptions.py +0 -0
  25. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/geopandas_tools/__init__.py +0 -0
  26. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/geopandas_tools/bounds.py +0 -0
  27. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/geopandas_tools/centerlines.py +0 -0
  28. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/geopandas_tools/cleaning.py +0 -0
  29. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/geopandas_tools/geocoding.py +0 -0
  30. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/geopandas_tools/geometry_types.py +0 -0
  31. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/geopandas_tools/point_operations.py +0 -0
  32. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/geopandas_tools/polygons_as_rings.py +0 -0
  33. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/io/__init__.py +0 -0
  34. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/io/_is_dapla.py +0 -0
  35. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/io/opener.py +0 -0
  36. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/io/read_parquet.py +0 -0
  37. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/maps/__init__.py +0 -0
  38. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/maps/examine.py +0 -0
  39. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/maps/explore.py +0 -0
  40. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/maps/httpserver.py +0 -0
  41. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/maps/legend.py +0 -0
  42. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/maps/maps.py +0 -0
  43. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/maps/norge_i_bilder.json +0 -0
  44. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/maps/thematicmap.py +0 -0
  45. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/maps/tilesources.py +0 -0
  46. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/maps/wms.py +0 -0
  47. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/networkanalysis/__init__.py +0 -0
  48. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/networkanalysis/_get_route.py +0 -0
  49. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/networkanalysis/_od_cost_matrix.py +0 -0
  50. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/networkanalysis/_points.py +0 -0
  51. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/networkanalysis/_service_area.py +0 -0
  52. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/networkanalysis/closing_network_holes.py +0 -0
  53. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/networkanalysis/cutting_lines.py +0 -0
  54. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/networkanalysis/directednetwork.py +0 -0
  55. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/networkanalysis/finding_isolated_networks.py +0 -0
  56. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/networkanalysis/network.py +0 -0
  57. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/networkanalysis/networkanalysis.py +0 -0
  58. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/networkanalysis/networkanalysisrules.py +0 -0
  59. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/networkanalysis/nodes.py +0 -0
  60. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/networkanalysis/traveling_salesman.py +0 -0
  61. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/parallel/__init__.py +0 -0
  62. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/py.typed +0 -0
  63. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/raster/__init__.py +0 -0
  64. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/raster/base.py +0 -0
  65. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/raster/regex.py +0 -0
  66. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/raster/sentinel_config.py +0 -0
  67. {ssb_sgis-1.1.17 → ssb_sgis-1.2.1}/src/sgis/raster/zonal.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ssb-sgis
3
- Version: 1.1.17
3
+ Version: 1.2.1
4
4
  Summary: GIS functions used at Statistics Norway.
5
5
  Home-page: https://github.com/statisticsnorway/ssb-sgis
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "ssb-sgis"
3
- version = "1.1.17"
3
+ version = "1.2.1"
4
4
  description = "GIS functions used at Statistics Norway."
5
5
  authors = ["Morten Letnes <morten.letnes@ssb.no>"]
6
6
  license = "MIT"
@@ -42,6 +42,7 @@ from .geopandas_tools.general import make_lines_between_points
42
42
  from .geopandas_tools.general import points_in_bounds
43
43
  from .geopandas_tools.general import random_points
44
44
  from .geopandas_tools.general import random_points_in_polygons
45
+ from .geopandas_tools.general import random_points_norway
45
46
  from .geopandas_tools.general import sort_large_first
46
47
  from .geopandas_tools.general import sort_long_first
47
48
  from .geopandas_tools.general import sort_short_first
@@ -76,6 +77,10 @@ from .geopandas_tools.polygon_operations import get_holes
76
77
  from .geopandas_tools.polygon_operations import get_polygon_clusters
77
78
  from .geopandas_tools.polygon_operations import split_polygons_by_lines
78
79
  from .geopandas_tools.polygons_as_rings import PolygonsAsRings
80
+ from .geopandas_tools.runners import GridSizeOverlayRunner
81
+ from .geopandas_tools.runners import OverlayRunner
82
+ from .geopandas_tools.runners import RTreeQueryRunner
83
+ from .geopandas_tools.runners import UnionRunner
79
84
  from .geopandas_tools.sfilter import sfilter
80
85
  from .geopandas_tools.sfilter import sfilter_inverse
81
86
  from .geopandas_tools.sfilter import sfilter_split
@@ -1,3 +1,5 @@
1
+ from typing import Any
2
+
1
3
  try:
2
4
  from gcsfs import GCSFileSystem
3
5
 
@@ -66,7 +68,23 @@ except ImportError:
66
68
 
67
69
  file_system = LocalFileSystem
68
70
 
71
+ from .geopandas_tools.runners import OverlayRunner
72
+ from .geopandas_tools.runners import RTreeQueryRunner
73
+ from .geopandas_tools.runners import UnionRunner
74
+
75
+
76
+ def _get_instance(data: dict, key: str, **kwargs) -> Any:
77
+ """Get the dict value and call it if callable."""
78
+ x = data[key]
79
+ if callable(x):
80
+ return x(**kwargs)
81
+ return x
82
+
83
+
69
84
  config = {
70
85
  "n_jobs": 1,
71
86
  "file_system": file_system,
87
+ "rtree_runner": RTreeQueryRunner,
88
+ "overlay_runner": OverlayRunner,
89
+ "union_runner": UnionRunner,
72
90
  }
@@ -23,12 +23,14 @@ from geopandas import GeoDataFrame
23
23
  from geopandas import GeoSeries
24
24
  from shapely import get_num_geometries
25
25
 
26
+ from ..conf import _get_instance
27
+ from ..conf import config
26
28
  from ..parallel.parallel import Parallel
27
- from .general import _parallel_unary_union
28
- from .general import _unary_union_for_notna
29
29
  from .geometry_types import make_all_singlepart
30
30
  from .polygon_operations import get_cluster_mapper
31
31
  from .polygon_operations import get_grouped_centroids
32
+ from .runners import UnionRunner
33
+ from .utils import _unary_union_for_notna
32
34
 
33
35
 
34
36
  def _decide_ignore_index(kwargs: dict) -> tuple[dict, bool]:
@@ -53,8 +55,8 @@ def buffdissexp(
53
55
  index_parts: bool = False,
54
56
  copy: bool = True,
55
57
  grid_size: float | int | None = None,
56
- n_jobs: int = 1,
57
58
  join_style: int | str = "round",
59
+ n_jobs: int = 1,
58
60
  **dissolve_kwargs,
59
61
  ) -> GeoDataFrame:
60
62
  """Buffers and dissolves overlapping geometries.
@@ -187,27 +189,27 @@ def _dissolve(
187
189
  gdf: GeoDataFrame,
188
190
  aggfunc: str = "first",
189
191
  grid_size: None | float = None,
190
- n_jobs: int = 1,
191
192
  as_index: bool = True,
193
+ n_jobs: int = 1,
194
+ union_runner: UnionRunner | None = None,
192
195
  **dissolve_kwargs,
193
196
  ) -> GeoDataFrame:
194
-
195
197
  if not len(gdf):
196
198
  return gdf
197
199
 
198
- geom_col = gdf._geometry_column_name
200
+ if union_runner is None:
201
+ union_runner = _get_instance(config, "union_runner", n_jobs=n_jobs)
199
202
 
200
- gdf[geom_col] = gdf[geom_col].make_valid()
203
+ geom_col = gdf.geometry.name
204
+ by = dissolve_kwargs.pop("by", None)
205
+ by_was_none = not bool(by)
201
206
 
207
+ # make sure geometries are dissolved rowwise to make dissolving simpler later
202
208
  more_than_one = get_num_geometries(gdf.geometry.values) > 1
203
209
  gdf.loc[more_than_one, geom_col] = gdf.loc[more_than_one, geom_col].apply(
204
210
  _unary_union_for_notna
205
211
  )
206
212
 
207
- by = dissolve_kwargs.pop("by", None)
208
-
209
- by_was_none = not bool(by)
210
-
211
213
  if by is None and dissolve_kwargs.get("level") is None:
212
214
  by = np.zeros(len(gdf), dtype="int64")
213
215
  other_cols = list(gdf.columns.difference({geom_col}))
@@ -215,30 +217,23 @@ def _dissolve(
215
217
  if isinstance(by, str):
216
218
  by = [by]
217
219
  other_cols = list(gdf.columns.difference({geom_col} | set(by or {})))
218
-
219
220
  try:
220
221
  is_one_hit = (
221
222
  gdf.groupby(by, as_index=True, **dissolve_kwargs).transform("size") == 1
222
223
  )
223
224
  except IndexError:
224
- # if no rows when dropna=True
225
+ # if no rows after dropping na if dropna=True
225
226
  original_by = [x for x in by]
226
227
  query = gdf[by.pop(0)].notna()
227
228
  for col in gdf[by]:
228
229
  query &= gdf[col].notna()
229
230
  gdf = gdf.loc[query]
230
231
  assert not len(gdf), gdf
231
- if not by_was_none and as_index:
232
- try:
233
- gdf = gdf.set_index(original_by)
234
- except Exception as e:
235
- print(gdf)
236
- print(original_by)
237
- raise e
238
-
232
+ if as_index and not by_was_none:
233
+ gdf = gdf.set_index(original_by)
239
234
  return gdf
240
235
 
241
- if not by_was_none and as_index:
236
+ if as_index and not by_was_none:
242
237
  one_hit = gdf[is_one_hit].set_index(by)
243
238
  else:
244
239
  one_hit = gdf[is_one_hit]
@@ -250,38 +245,21 @@ def _dissolve(
250
245
  dissolved = many_hits.groupby(by, as_index=True, **dissolve_kwargs)[other_cols].agg(
251
246
  aggfunc
252
247
  )
253
-
254
- if n_jobs > 1:
255
- try:
256
- agged = _parallel_unary_union(
257
- many_hits,
258
- n_jobs=n_jobs,
259
- by=by,
260
- grid_size=grid_size,
261
- as_index=True,
262
- **dissolve_kwargs,
263
- )
264
- dissolved[geom_col] = agged
265
- return GeoDataFrame(dissolved, geometry=geom_col, crs=gdf.crs)
266
- except Exception as e:
267
- print(e, dissolved, agged, many_hits)
268
- raise e
269
-
270
- geoms_agged = many_hits.groupby(by, **dissolve_kwargs)[geom_col].agg(
271
- lambda x: _unary_union_for_notna(x, grid_size=grid_size)
248
+ dissolved[geom_col] = union_runner.run(
249
+ many_hits,
250
+ by=by,
251
+ grid_size=grid_size,
252
+ as_index=True,
253
+ **dissolve_kwargs,
272
254
  )
273
-
274
- dissolved[geom_col] = geoms_agged
275
-
276
255
  if not as_index:
277
256
  dissolved = dissolved.reset_index()
278
-
279
257
  try:
280
258
  return GeoDataFrame(
281
259
  pd.concat([dissolved, one_hit]).sort_index(), geometry=geom_col, crs=gdf.crs
282
260
  )
283
261
  except TypeError as e:
284
- raise e.__class__(e, dissolved.index, one_hit.index) from e
262
+ raise e.__class__(f"{e}. {dissolved.index}. {one_hit.index}") from e
285
263
 
286
264
 
287
265
  def diss(
@@ -582,7 +560,7 @@ def buff(
582
560
  if copy:
583
561
  gdf = gdf.copy()
584
562
 
585
- gdf[gdf._geometry_column_name] = gdf.buffer(
563
+ gdf[gdf.geometry.name] = gdf.buffer(
586
564
  distance, resolution=resolution, join_style=join_style, **buffer_kwargs
587
565
  ).make_valid()
588
566
 
@@ -146,31 +146,24 @@ def to_bbox(
146
146
  except Exception:
147
147
  pass
148
148
 
149
- try:
150
- minx = float(np.min(obj["minx"])) # type: ignore [index]
151
- miny = float(np.min(obj["miny"])) # type: ignore [index]
152
- maxx = float(np.max(obj["maxx"])) # type: ignore [index]
153
- maxy = float(np.max(obj["maxy"])) # type: ignore [index]
154
- return minx, miny, maxx, maxy
155
- except Exception:
156
- pass
157
- try:
158
- minx = float(np.min(obj.minx)) # type: ignore [union-attr]
159
- miny = float(np.min(obj.miny)) # type: ignore [union-attr]
160
- maxx = float(np.max(obj.maxx)) # type: ignore [union-attr]
161
- maxy = float(np.max(obj.maxy)) # type: ignore [union-attr]
162
- return minx, miny, maxx, maxy
163
- except Exception:
164
- pass
165
-
166
- try:
167
- minx = float(np.min(obj["west_longitude"])) # type: ignore [index]
168
- miny = float(np.min(obj["south_latitude"])) # type: ignore [index]
169
- maxx = float(np.max(obj["east_longitude"])) # type: ignore [index]
170
- maxy = float(np.max(obj["north_latitude"])) # type: ignore [index]
171
- return minx, miny, maxx, maxy
172
- except Exception:
173
- pass
149
+ def to_int_if_possible(x):
150
+ if isinstance(x, int) or float(x).is_integer():
151
+ return int(x)
152
+ return float(x)
153
+
154
+ for attrs in [
155
+ ("minx", "miny", "maxx", "maxy"),
156
+ ("xmin", "ymin", "xmax", "xmax"),
157
+ ("west_longitude", "south_latitude", "east_longitude", "north_latitude"),
158
+ ]:
159
+ try:
160
+ return tuple(to_int_if_possible(obj[attr]) for attr in attrs)
161
+ except Exception:
162
+ pass
163
+ try:
164
+ return tuple(to_int_if_possible(getattr(obj, attr)) for attr in attrs)
165
+ except Exception:
166
+ pass
174
167
 
175
168
  if hasattr(obj, "geometry"):
176
169
  try:
@@ -6,21 +6,20 @@ from geopandas import GeoDataFrame
6
6
  from geopandas import GeoSeries
7
7
  from shapely import STRtree
8
8
  from shapely import difference
9
- from shapely import make_valid
10
9
  from shapely import simplify
11
- from shapely.errors import GEOSException
12
10
 
11
+ from ..conf import _get_instance
12
+ from ..conf import config
13
13
  from .general import _determine_geom_type_args
14
- from .general import _grouped_unary_union
15
- from .general import _parallel_unary_union_geoseries
16
14
  from .general import _push_geom_col
17
15
  from .general import clean_geoms
18
16
  from .geometry_types import get_geom_type
19
17
  from .geometry_types import make_all_singlepart
20
18
  from .geometry_types import to_single_geom_type
21
- from .overlay import _run_overlay_dask
22
19
  from .overlay import clean_overlay
23
- from .overlay import make_valid_and_keep_geom_type
20
+ from .runners import OverlayRunner
21
+ from .runners import RTreeQueryRunner
22
+ from .runners import UnionRunner
24
23
  from .sfilter import sfilter_inverse
25
24
 
26
25
  PRECISION = 1e-3
@@ -31,8 +30,11 @@ def update_geometries(
31
30
  geom_type: str | None = None,
32
31
  keep_geom_type: bool | None = None,
33
32
  grid_size: int | None = None,
34
- n_jobs: int = 1,
35
33
  predicate: str | None = "intersects",
34
+ n_jobs: int = 1,
35
+ union_runner: UnionRunner | None = None,
36
+ rtree_runner: RTreeQueryRunner | None = None,
37
+ overlay_runner: OverlayRunner | None = None,
36
38
  ) -> GeoDataFrame:
37
39
  """Puts geometries on top of each other rowwise.
38
40
 
@@ -50,8 +52,14 @@ def update_geometries(
50
52
  "line" or "point".
51
53
  grid_size: Precision grid size to round the geometries. Will use the highest
52
54
  precision of the inputs by default.
53
- n_jobs: Number of threads.
54
55
  predicate: Spatial predicate for the spatial tree.
56
+ n_jobs: Number of workers.
57
+ union_runner: Optionally debug/manipulate the spatial union operations.
58
+ See the 'runners' module for example implementations.
59
+ rtree_runner: Optionally debug/manipulate the spatial indexing operations.
60
+ See the 'runners' module for example implementations.
61
+ overlay_runner: Optionally debug/manipulate the spatial overlay operations.
62
+ See the 'runners' module for example implementations.
55
63
 
56
64
  Example:
57
65
  --------
@@ -98,6 +106,13 @@ def update_geometries(
98
106
  if len(gdf) <= 1:
99
107
  return gdf
100
108
 
109
+ if rtree_runner is None:
110
+ rtree_runner = _get_instance(config, "rtree_runner", n_jobs=n_jobs)
111
+ if union_runner is None:
112
+ union_runner = _get_instance(config, "union_runner", n_jobs=n_jobs)
113
+ if overlay_runner is None:
114
+ overlay_runner = _get_instance(config, "overlay_runner", n_jobs=n_jobs)
115
+
101
116
  if geom_type == "polygon" or get_geom_type(gdf) == "polygon":
102
117
  gdf.geometry = gdf.buffer(0)
103
118
 
@@ -111,66 +126,35 @@ def update_geometries(
111
126
  index_mapper = {i: idx for i, idx in enumerate(copied.index)}
112
127
  copied = copied.reset_index(drop=True)
113
128
 
114
- tree = STRtree(copied.geometry.values)
115
- left, right = tree.query(copied.geometry.values, predicate=predicate)
129
+ left, right = rtree_runner.run(
130
+ copied.geometry.values, copied.geometry.values, predicate=predicate
131
+ )
116
132
  indices = pd.Series(right, index=left).loc[lambda x: x.index > x.values]
117
133
 
118
134
  # select geometries from 'right', index from 'left', dissolve by 'left'
119
135
  erasers = pd.Series(copied.geometry.loc[indices.values].values, index=indices.index)
120
- if n_jobs > 1:
121
- erasers = _parallel_unary_union_geoseries(
122
- erasers,
123
- level=0,
124
- n_jobs=n_jobs,
125
- grid_size=grid_size,
126
- )
127
- erasers = pd.Series(erasers, index=indices.index.unique())
128
- else:
129
- only_one = erasers.groupby(level=0).transform("size") == 1
130
- one_hit = erasers[only_one]
131
- many_hits = _grouped_unary_union(
132
- erasers[~only_one], level=0, grid_size=grid_size
133
- )
134
- erasers = pd.concat([one_hit, many_hits]).sort_index()
136
+ only_one = erasers.groupby(level=0).transform("size") == 1
137
+ one_hit = erasers[only_one]
138
+ many_hits = union_runner.run(erasers[~only_one], level=0, grid_size=grid_size)
139
+ erasers = pd.concat([one_hit, many_hits]).sort_index()
135
140
 
136
141
  # match up the aggregated erasers by index
137
- if n_jobs > 1:
138
- arr1 = copied.geometry.loc[erasers.index].to_numpy()
139
- arr2 = erasers.to_numpy()
140
- try:
141
- erased = _run_overlay_dask(
142
- arr1, arr2, func=difference, n_jobs=n_jobs, grid_size=grid_size
143
- )
144
- except GEOSException:
145
- arr1 = make_valid_and_keep_geom_type(
146
- arr1, geom_type=geom_type, n_jobs=n_jobs
147
- )
148
- arr2 = make_valid_and_keep_geom_type(
149
- arr2, geom_type=geom_type, n_jobs=n_jobs
150
- )
151
- erased = _run_overlay_dask(
152
- arr1, arr2, func=difference, n_jobs=n_jobs, grid_size=grid_size
153
- )
154
- erased = GeoSeries(erased, index=erasers.index)
155
- else:
156
- erased = make_valid(
157
- difference(
158
- copied.geometry.loc[erasers.index],
159
- erasers,
160
- grid_size=grid_size,
161
- )
162
- )
142
+ arr1 = copied.geometry.loc[erasers.index].to_numpy()
143
+ arr2 = erasers.to_numpy()
144
+ erased = overlay_runner.run(
145
+ difference, arr1, arr2, grid_size=grid_size, geom_type=geom_type
146
+ )
163
147
 
148
+ erased = GeoSeries(erased, index=erasers.index)
164
149
  copied.loc[erased.index, geom_col] = erased
165
-
166
150
  copied = copied.loc[~copied.is_empty]
167
-
168
151
  copied.index = copied.index.map(index_mapper)
169
-
170
152
  copied = make_all_singlepart(copied)
171
153
 
172
154
  # TODO check why polygons dissappear in rare cases. For now, just add back the missing
173
- dissapeared = sfilter_inverse(gdf, copied.buffer(-PRECISION))
155
+ dissapeared = sfilter_inverse(
156
+ gdf, copied.buffer(-PRECISION), rtree_runner=rtree_runner
157
+ )
174
158
  copied = pd.concat([copied, dissapeared])
175
159
 
176
160
  # TODO fix dupliates again with dissolve?
@@ -191,7 +175,7 @@ def get_intersections(
191
175
  keep_geom_type: bool | None = None,
192
176
  predicate: str | None = "intersects",
193
177
  grid_size: float | None = None,
194
- n_jobs: int = 1,
178
+ **kwargs,
195
179
  ) -> GeoDataFrame:
196
180
  """Find geometries that intersect in a GeoDataFrame.
197
181
 
@@ -214,6 +198,7 @@ def get_intersections(
214
198
  precision of the inputs by default.
215
199
  n_jobs: Number of threads.
216
200
  predicate: Spatial predicate for the spatial tree.
201
+ **kwargs: Keyword arguments passed to clean_overlay.
217
202
 
218
203
  Returns:
219
204
  A GeoDataFrame of the overlapping polygons.
@@ -286,9 +271,9 @@ def get_intersections(
286
271
  gdf,
287
272
  geom_type,
288
273
  keep_geom_type,
289
- n_jobs=n_jobs,
290
274
  grid_size=grid_size,
291
275
  predicate=predicate,
276
+ **kwargs,
292
277
  ).pipe(clean_geoms)
293
278
 
294
279
  duplicated_geoms.index = duplicated_geoms["orig_idx"].values
@@ -304,9 +289,9 @@ def _get_intersecting_geometries(
304
289
  gdf: GeoDataFrame,
305
290
  geom_type: str | None,
306
291
  keep_geom_type: bool,
307
- n_jobs: int,
308
292
  grid_size: float | None = None,
309
293
  predicate: str | None = None,
294
+ **kwargs,
310
295
  ) -> GeoDataFrame:
311
296
  right = gdf[[gdf._geometry_column_name]]
312
297
  right["idx_right"] = right.index
@@ -330,7 +315,7 @@ def _get_intersecting_geometries(
330
315
  grid_size=grid_size,
331
316
  geom_type=geom_type,
332
317
  keep_geom_type=keep_geom_type,
333
- n_jobs=n_jobs,
318
+ **kwargs,
334
319
  ).loc[are_not_identical]
335
320
  else:
336
321
  if keep_geom_type:
@@ -350,7 +335,7 @@ def _get_intersecting_geometries(
350
335
  grid_size=grid_size,
351
336
  predicate=predicate,
352
337
  geom_type=geom_type,
353
- n_jobs=n_jobs,
338
+ **kwargs,
354
339
  )
355
340
  ]
356
341
  intersected = pd.concat(intersected, ignore_index=True).loc[are_not_identical]