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.
- sgis/__init__.py +107 -121
- sgis/exceptions.py +5 -3
- sgis/geopandas_tools/__init__.py +1 -0
- sgis/geopandas_tools/bounds.py +86 -47
- sgis/geopandas_tools/buffer_dissolve_explode.py +62 -39
- sgis/geopandas_tools/centerlines.py +53 -44
- sgis/geopandas_tools/cleaning.py +87 -104
- sgis/geopandas_tools/conversion.py +164 -107
- sgis/geopandas_tools/duplicates.py +33 -19
- sgis/geopandas_tools/general.py +84 -52
- sgis/geopandas_tools/geometry_types.py +24 -10
- sgis/geopandas_tools/neighbors.py +23 -11
- sgis/geopandas_tools/overlay.py +136 -53
- sgis/geopandas_tools/point_operations.py +11 -10
- sgis/geopandas_tools/polygon_operations.py +53 -61
- sgis/geopandas_tools/polygons_as_rings.py +121 -78
- sgis/geopandas_tools/sfilter.py +17 -17
- sgis/helpers.py +116 -58
- sgis/io/dapla_functions.py +32 -23
- sgis/io/opener.py +13 -6
- sgis/io/read_parquet.py +2 -2
- sgis/maps/examine.py +55 -28
- sgis/maps/explore.py +471 -112
- sgis/maps/httpserver.py +12 -12
- sgis/maps/legend.py +285 -134
- sgis/maps/map.py +248 -129
- sgis/maps/maps.py +123 -119
- sgis/maps/thematicmap.py +260 -94
- sgis/maps/tilesources.py +3 -8
- sgis/networkanalysis/_get_route.py +5 -4
- sgis/networkanalysis/_od_cost_matrix.py +44 -1
- sgis/networkanalysis/_points.py +10 -4
- sgis/networkanalysis/_service_area.py +5 -2
- sgis/networkanalysis/closing_network_holes.py +22 -64
- sgis/networkanalysis/cutting_lines.py +58 -46
- sgis/networkanalysis/directednetwork.py +16 -8
- sgis/networkanalysis/finding_isolated_networks.py +6 -5
- sgis/networkanalysis/network.py +15 -13
- sgis/networkanalysis/networkanalysis.py +79 -61
- sgis/networkanalysis/networkanalysisrules.py +21 -17
- sgis/networkanalysis/nodes.py +2 -3
- sgis/networkanalysis/traveling_salesman.py +6 -3
- sgis/parallel/parallel.py +372 -142
- sgis/raster/base.py +9 -3
- sgis/raster/cube.py +331 -213
- sgis/raster/cubebase.py +15 -29
- sgis/raster/image_collection.py +2560 -0
- sgis/raster/indices.py +17 -12
- sgis/raster/raster.py +356 -275
- sgis/raster/sentinel_config.py +104 -0
- sgis/raster/zonal.py +38 -14
- {ssb_sgis-1.0.1.dist-info → ssb_sgis-1.0.3.dist-info}/LICENSE +1 -1
- {ssb_sgis-1.0.1.dist-info → ssb_sgis-1.0.3.dist-info}/METADATA +87 -16
- ssb_sgis-1.0.3.dist-info/RECORD +61 -0
- {ssb_sgis-1.0.1.dist-info → ssb_sgis-1.0.3.dist-info}/WHEEL +1 -1
- sgis/raster/bands.py +0 -48
- sgis/raster/gradient.py +0 -78
- sgis/raster/methods_as_functions.py +0 -124
- sgis/raster/torchgeo.py +0 -150
- 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
|
|
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
|
|
14
|
-
from
|
|
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
|
|
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
|
|
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
|
-
|
|
32
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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
|
-
|
|
93
|
+
try:
|
|
82
94
|
return obj.unary_union
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
return
|
|
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
|
-
|
|
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
|
-
>>>
|
|
174
|
-
>>> points =
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
return
|
|
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
|
-
>>>
|
|
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(
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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] =
|
|
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(
|
|
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 =
|
|
494
|
+
key = next(iter(obj.keys()))
|
|
442
495
|
if isinstance(obj, dict):
|
|
443
496
|
geoseries = GeoSeries(
|
|
444
|
-
|
|
497
|
+
_make_shapely_geoms(next(iter(obj.values()))), index=index
|
|
445
498
|
)
|
|
446
499
|
elif isinstance(obj, pd.Series):
|
|
447
|
-
geoseries = GeoSeries(
|
|
500
|
+
geoseries = GeoSeries(_make_shapely_geoms(obj), index=index)
|
|
448
501
|
else:
|
|
449
|
-
geoseries = GeoSeries(
|
|
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(
|
|
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
|
|
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
|
-
|
|
510
|
-
if
|
|
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
|
-
|
|
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(
|
|
642
|
+
return GeoSeries(_make_shapely_geoms(list(obj.values())), index=index)
|
|
591
643
|
else:
|
|
592
|
-
return GeoSeries(
|
|
644
|
+
return GeoSeries(_make_shapely_geoms(obj.values), index=index)
|
|
593
645
|
|
|
594
646
|
|
|
595
|
-
def _geoseries_to_gdf(
|
|
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
|
|
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(
|
|
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
|
|
6
|
-
from
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
from .geometry_types import
|
|
16
|
-
from .overlay import _run_overlay_dask
|
|
17
|
-
from .
|
|
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 =
|
|
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,
|
|
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)
|