ssb-sgis 1.0.2__py3-none-any.whl → 1.0.4__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 +20 -9
- sgis/debug_config.py +24 -0
- sgis/exceptions.py +2 -2
- sgis/geopandas_tools/bounds.py +33 -36
- sgis/geopandas_tools/buffer_dissolve_explode.py +136 -35
- sgis/geopandas_tools/centerlines.py +4 -91
- sgis/geopandas_tools/cleaning.py +1576 -583
- sgis/geopandas_tools/conversion.py +38 -19
- sgis/geopandas_tools/duplicates.py +29 -8
- sgis/geopandas_tools/general.py +263 -100
- sgis/geopandas_tools/geometry_types.py +4 -4
- sgis/geopandas_tools/neighbors.py +19 -15
- sgis/geopandas_tools/overlay.py +2 -2
- sgis/geopandas_tools/point_operations.py +5 -5
- sgis/geopandas_tools/polygon_operations.py +510 -105
- sgis/geopandas_tools/polygons_as_rings.py +40 -8
- sgis/geopandas_tools/sfilter.py +29 -12
- sgis/helpers.py +3 -3
- sgis/io/dapla_functions.py +238 -19
- sgis/io/read_parquet.py +1 -1
- sgis/maps/examine.py +27 -12
- sgis/maps/explore.py +450 -65
- sgis/maps/legend.py +177 -76
- sgis/maps/map.py +206 -103
- sgis/maps/maps.py +178 -105
- sgis/maps/thematicmap.py +243 -83
- sgis/networkanalysis/_service_area.py +6 -1
- sgis/networkanalysis/closing_network_holes.py +2 -2
- sgis/networkanalysis/cutting_lines.py +15 -8
- sgis/networkanalysis/directednetwork.py +1 -1
- sgis/networkanalysis/finding_isolated_networks.py +15 -8
- sgis/networkanalysis/networkanalysis.py +17 -19
- sgis/networkanalysis/networkanalysisrules.py +1 -1
- sgis/networkanalysis/traveling_salesman.py +1 -1
- sgis/parallel/parallel.py +64 -27
- sgis/raster/__init__.py +0 -6
- sgis/raster/base.py +208 -0
- sgis/raster/cube.py +54 -8
- sgis/raster/image_collection.py +3257 -0
- sgis/raster/indices.py +17 -5
- sgis/raster/raster.py +138 -243
- sgis/raster/sentinel_config.py +120 -0
- sgis/raster/zonal.py +0 -1
- {ssb_sgis-1.0.2.dist-info → ssb_sgis-1.0.4.dist-info}/METADATA +6 -7
- ssb_sgis-1.0.4.dist-info/RECORD +62 -0
- sgis/raster/methods_as_functions.py +0 -0
- sgis/raster/torchgeo.py +0 -171
- ssb_sgis-1.0.2.dist-info/RECORD +0 -61
- {ssb_sgis-1.0.2.dist-info → ssb_sgis-1.0.4.dist-info}/LICENSE +0 -0
- {ssb_sgis-1.0.2.dist-info → ssb_sgis-1.0.4.dist-info}/WHEEL +0 -0
sgis/maps/maps.py
CHANGED
|
@@ -8,18 +8,22 @@ The 'qtm' function shows a simple static map of one or more GeoDataFrames.
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
import inspect
|
|
11
|
+
import random
|
|
11
12
|
from numbers import Number
|
|
12
13
|
from typing import Any
|
|
13
14
|
|
|
15
|
+
import pyproj
|
|
14
16
|
from geopandas import GeoDataFrame
|
|
15
17
|
from geopandas import GeoSeries
|
|
16
|
-
from pyproj import CRS
|
|
17
18
|
from shapely import Geometry
|
|
18
19
|
from shapely import box
|
|
19
20
|
from shapely.geometry import Polygon
|
|
20
21
|
|
|
22
|
+
from ..debug_config import _NoExplore
|
|
21
23
|
from ..geopandas_tools.bounds import get_total_bounds
|
|
22
|
-
from ..geopandas_tools.conversion import
|
|
24
|
+
from ..geopandas_tools.conversion import to_bbox
|
|
25
|
+
from ..geopandas_tools.conversion import to_gdf
|
|
26
|
+
from ..geopandas_tools.conversion import to_shapely
|
|
23
27
|
from ..geopandas_tools.general import clean_geoms
|
|
24
28
|
from ..geopandas_tools.general import get_common_crs
|
|
25
29
|
from ..geopandas_tools.general import is_wkt
|
|
@@ -40,10 +44,10 @@ except ImportError:
|
|
|
40
44
|
def _get_location_mask(kwargs: dict, gdfs) -> tuple[GeoDataFrame | None, dict]:
|
|
41
45
|
try:
|
|
42
46
|
crs = get_common_crs(gdfs)
|
|
43
|
-
except IndexError:
|
|
47
|
+
except (IndexError, pyproj.exceptions.CRSError):
|
|
44
48
|
for x in kwargs.values():
|
|
45
49
|
try:
|
|
46
|
-
crs = CRS(x.crs) if hasattr(x, "crs") else CRS(x["crs"])
|
|
50
|
+
crs = pyproj.CRS(x.crs) if hasattr(x, "crs") else pyproj.CRS(x["crs"])
|
|
47
51
|
break
|
|
48
52
|
except Exception:
|
|
49
53
|
crs = None
|
|
@@ -67,7 +71,7 @@ def _get_location_mask(kwargs: dict, gdfs) -> tuple[GeoDataFrame | None, dict]:
|
|
|
67
71
|
kwargs.pop(key)
|
|
68
72
|
if isinstance(value, Number) and value > 1:
|
|
69
73
|
size = value
|
|
70
|
-
the_mask =
|
|
74
|
+
the_mask = to_gdf([mask], crs=4326).to_crs(crs).buffer(size)
|
|
71
75
|
return the_mask, kwargs
|
|
72
76
|
|
|
73
77
|
return None, kwargs
|
|
@@ -77,13 +81,14 @@ def explore(
|
|
|
77
81
|
*gdfs: GeoDataFrame | dict[str, GeoDataFrame],
|
|
78
82
|
column: str | None = None,
|
|
79
83
|
center: Any | None = None,
|
|
80
|
-
labels: tuple[str] | None = None,
|
|
81
84
|
max_zoom: int = 40,
|
|
82
85
|
browser: bool = False,
|
|
83
86
|
smooth_factor: int | float = 1.5,
|
|
84
87
|
size: int | None = None,
|
|
88
|
+
max_images: int = 10,
|
|
89
|
+
images_to_gdf: bool = False,
|
|
85
90
|
**kwargs,
|
|
86
|
-
) ->
|
|
91
|
+
) -> Explore:
|
|
87
92
|
"""Interactive map of GeoDataFrames with layers that can be toggled on/off.
|
|
88
93
|
|
|
89
94
|
It takes all the given GeoDataFrames and displays them together in an
|
|
@@ -101,9 +106,6 @@ def explore(
|
|
|
101
106
|
center: Geometry-like object to center the map on. If a three-length tuple
|
|
102
107
|
is given, the first two should be x and y coordinates and the third
|
|
103
108
|
should be a number of meters to buffer the centerpoint by.
|
|
104
|
-
labels: By default, the GeoDataFrames will be labeled by their object names.
|
|
105
|
-
Alternatively, labels can be specified as a tuple of strings with the same
|
|
106
|
-
length as the number of gdfs.
|
|
107
109
|
max_zoom: The maximum allowed level of zoom. Higher number means more zoom
|
|
108
110
|
allowed. Defaults to 30, which is higher than the geopandas default.
|
|
109
111
|
browser: If False (default), the maps will be shown in Jupyter.
|
|
@@ -112,17 +114,21 @@ def explore(
|
|
|
112
114
|
5 is quite a lot of simplification.
|
|
113
115
|
size: The buffer distance. Only used when center is given. It then defaults to
|
|
114
116
|
1000.
|
|
117
|
+
max_images: Maximum number of images (Image, ImageCollection, Band) to show per
|
|
118
|
+
map. Defaults to 10.
|
|
119
|
+
images_to_gdf: If True (not default), images (Image, ImageCollection, Band)
|
|
120
|
+
will be converted to GeoDataFrame and added to the map.
|
|
115
121
|
**kwargs: Keyword arguments to pass to geopandas.GeoDataFrame.explore, for
|
|
116
122
|
instance 'cmap' to change the colors, 'scheme' to change how the data
|
|
117
123
|
is grouped. This defaults to 'fisherjenkssampled' for numeric data.
|
|
118
124
|
|
|
119
125
|
See Also:
|
|
120
|
-
|
|
126
|
+
---------
|
|
121
127
|
samplemap: same functionality, but shows only a random area of a given size.
|
|
122
128
|
clipmap: same functionality, but shows only the areas clipped by a given mask.
|
|
123
129
|
|
|
124
130
|
Examples:
|
|
125
|
-
|
|
131
|
+
---------
|
|
126
132
|
>>> import sgis as sg
|
|
127
133
|
>>> roads = sg.read_parquet_url("https://media.githubusercontent.com/media/statisticsnorway/ssb-sgis/main/tests/testdata/roads_oslo_2022.parquet")
|
|
128
134
|
>>> points = sg.read_parquet_url("https://media.githubusercontent.com/media/statisticsnorway/ssb-sgis/main/tests/testdata/points_oslo.parquet")
|
|
@@ -141,6 +147,9 @@ def explore(
|
|
|
141
147
|
>>> points["meters"] = points.length
|
|
142
148
|
>>> sg.explore(roads, points, column="meters", cmap="plasma", max_zoom=60, center_4326=(10.7463, 59.92, 500))
|
|
143
149
|
"""
|
|
150
|
+
if isinstance(center, _NoExplore):
|
|
151
|
+
return
|
|
152
|
+
|
|
144
153
|
gdfs, column, kwargs = Map._separate_args(gdfs, column, kwargs)
|
|
145
154
|
|
|
146
155
|
loc_mask, kwargs = _get_location_mask(kwargs | {"size": size}, gdfs)
|
|
@@ -154,7 +163,6 @@ def explore(
|
|
|
154
163
|
*gdfs,
|
|
155
164
|
column=column,
|
|
156
165
|
mask=mask,
|
|
157
|
-
labels=labels,
|
|
158
166
|
browser=browser,
|
|
159
167
|
max_zoom=max_zoom,
|
|
160
168
|
**kwargs,
|
|
@@ -162,10 +170,10 @@ def explore(
|
|
|
162
170
|
|
|
163
171
|
try:
|
|
164
172
|
to_crs = gdfs[0].crs
|
|
165
|
-
except IndexError:
|
|
173
|
+
except (IndexError, AttributeError):
|
|
166
174
|
try:
|
|
167
175
|
to_crs = next(x for x in kwargs.values() if hasattr(x, "crs")).crs
|
|
168
|
-
except IndexError:
|
|
176
|
+
except (IndexError, StopIteration):
|
|
169
177
|
to_crs = None
|
|
170
178
|
|
|
171
179
|
if "crs" in kwargs:
|
|
@@ -182,16 +190,66 @@ def explore(
|
|
|
182
190
|
else:
|
|
183
191
|
if isinstance(center, (tuple, list)) and len(center) == 3:
|
|
184
192
|
*center, size = center
|
|
185
|
-
mask =
|
|
193
|
+
mask = to_gdf(center, crs=from_crs)
|
|
186
194
|
|
|
187
195
|
bounds: Polygon = box(*get_total_bounds(*gdfs, *list(kwargs.values())))
|
|
188
|
-
if not mask.intersects(bounds).any():
|
|
189
|
-
mask = mask.set_crs(4326, allow_override=True)
|
|
190
196
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
197
|
+
any_intersections: bool = mask.intersects(bounds).any()
|
|
198
|
+
if not any_intersections and to_crs is None:
|
|
199
|
+
mask = to_gdf(Polygon(), to_crs)
|
|
200
|
+
elif not any_intersections:
|
|
201
|
+
bounds4326 = to_gdf(bounds, to_crs).to_crs(25833).geometry.iloc[0]
|
|
202
|
+
mask4326 = mask.set_crs(4326, allow_override=True).to_crs(25833)
|
|
203
|
+
|
|
204
|
+
if (mask4326.distance(bounds4326) > size).all():
|
|
205
|
+
# try flipping coordinates
|
|
206
|
+
x, y = next(iter(mask.geometry.iloc[0].coords))
|
|
207
|
+
mask4326 = to_gdf([y, x], 4326).to_crs(25833)
|
|
208
|
+
|
|
209
|
+
if (mask4326.distance(bounds4326) > size).all():
|
|
210
|
+
mask = to_gdf(Polygon(), to_crs)
|
|
211
|
+
else:
|
|
212
|
+
mask = mask4326.to_crs(to_crs)
|
|
213
|
+
|
|
214
|
+
# else:
|
|
215
|
+
# mask_flipped = mask
|
|
216
|
+
|
|
217
|
+
# # coords = mask.get_coordinates()
|
|
218
|
+
# if (
|
|
219
|
+
# (mask_flipped.distance(bounds) > size).all()
|
|
220
|
+
# # and coords["x"].max() < 180
|
|
221
|
+
# # and coords["y"].max() < 180
|
|
222
|
+
# # and coords["x"].min() > -180
|
|
223
|
+
# # and coords["y"].min() > -180
|
|
224
|
+
# ):
|
|
225
|
+
# try:
|
|
226
|
+
# bounds4326 = to_gdf(bounds, to_crs).to_crs(4326).geometry.iloc[0]
|
|
227
|
+
# except ValueError:
|
|
228
|
+
# bounds4326 = to_gdf(bounds, to_crs).set_crs(4326).geometry.iloc[0]
|
|
229
|
+
|
|
230
|
+
# mask4326 = mask.set_crs(4326, allow_override=True)
|
|
231
|
+
|
|
232
|
+
# if (mask4326.distance(bounds4326) > size).all():
|
|
233
|
+
# # try flipping coordinates
|
|
234
|
+
# x, y = list(mask4326.geometry.iloc[0].coords)[0]
|
|
235
|
+
# mask4326 = to_gdf([y, x], 4326)
|
|
236
|
+
|
|
237
|
+
# mask = mask4326
|
|
238
|
+
|
|
239
|
+
# # if mask4326.intersects(bounds4326).any():
|
|
240
|
+
# # mask = mask4326
|
|
241
|
+
# # else:
|
|
242
|
+
# # try:
|
|
243
|
+
# # mask = mask.to_crs(to_crs)
|
|
244
|
+
# # except ValueError:
|
|
245
|
+
# # pass
|
|
246
|
+
# else:
|
|
247
|
+
# mask = mask_flipped
|
|
248
|
+
|
|
249
|
+
# try:
|
|
250
|
+
# mask = mask.to_crs(to_crs)
|
|
251
|
+
# except ValueError:
|
|
252
|
+
# pass
|
|
195
253
|
|
|
196
254
|
if get_geom_type(mask) in ["point", "line"]:
|
|
197
255
|
mask = mask.buffer(size)
|
|
@@ -200,7 +258,6 @@ def explore(
|
|
|
200
258
|
*gdfs,
|
|
201
259
|
column=column,
|
|
202
260
|
mask=mask,
|
|
203
|
-
labels=labels,
|
|
204
261
|
browser=browser,
|
|
205
262
|
max_zoom=max_zoom,
|
|
206
263
|
**kwargs,
|
|
@@ -209,34 +266,36 @@ def explore(
|
|
|
209
266
|
m = Explore(
|
|
210
267
|
*gdfs,
|
|
211
268
|
column=column,
|
|
212
|
-
labels=labels,
|
|
213
269
|
browser=browser,
|
|
214
270
|
max_zoom=max_zoom,
|
|
215
271
|
smooth_factor=smooth_factor,
|
|
272
|
+
max_images=max_images,
|
|
216
273
|
**kwargs,
|
|
217
274
|
)
|
|
218
275
|
|
|
219
|
-
if m.gdfs is None and not len(m.
|
|
220
|
-
return
|
|
276
|
+
if m.gdfs is None and not len(m.rasters):
|
|
277
|
+
return m
|
|
221
278
|
|
|
222
279
|
if not kwargs.pop("explore", True):
|
|
223
280
|
return qtm(m._gdf, column=m.column, cmap=m._cmap, k=m.k)
|
|
224
281
|
|
|
225
282
|
m.explore()
|
|
226
283
|
|
|
284
|
+
return m
|
|
285
|
+
|
|
227
286
|
|
|
228
287
|
def samplemap(
|
|
229
288
|
*gdfs: GeoDataFrame,
|
|
230
289
|
column: str | None = None,
|
|
231
290
|
size: int = 1000,
|
|
232
291
|
sample_from_first: bool = True,
|
|
233
|
-
labels: tuple[str] | None = None,
|
|
234
292
|
max_zoom: int = 40,
|
|
235
293
|
smooth_factor: int = 1.5,
|
|
236
294
|
explore: bool = True,
|
|
237
295
|
browser: bool = False,
|
|
296
|
+
max_images: int = 10,
|
|
238
297
|
**kwargs,
|
|
239
|
-
) ->
|
|
298
|
+
) -> Explore:
|
|
240
299
|
"""Shows an interactive map of a random area of GeoDataFrames.
|
|
241
300
|
|
|
242
301
|
It takes all the GeoDataFrames specified, takes a random sample point from the
|
|
@@ -258,9 +317,6 @@ def samplemap(
|
|
|
258
317
|
Defaults to 1000 (meters).
|
|
259
318
|
sample_from_first: If True (default), the sample point is taken form the
|
|
260
319
|
first specified GeoDataFrame. If False, all GeoDataFrames are considered.
|
|
261
|
-
labels: By default, the GeoDataFrames will be labeled by their object names.
|
|
262
|
-
Alternatively, labels can be specified as a tuple of strings the same
|
|
263
|
-
length as the number of gdfs.
|
|
264
320
|
max_zoom: The maximum allowed level of zoom. Higher number means more zoom
|
|
265
321
|
allowed. Defaults to 30, which is higher than the geopandas default.
|
|
266
322
|
smooth_factor: How much to simplify the geometries. 1 is the minimum,
|
|
@@ -269,17 +325,19 @@ def samplemap(
|
|
|
269
325
|
or not in Jupyter, a static plot will be shown.
|
|
270
326
|
browser: If False (default), the maps will be shown in Jupyter.
|
|
271
327
|
If True the maps will be opened in a browser folder.
|
|
328
|
+
max_images: Maximum number of images (Image, ImageCollection, Band) to show per
|
|
329
|
+
map. Defaults to 10.
|
|
272
330
|
**kwargs: Keyword arguments to pass to geopandas.GeoDataFrame.explore, for
|
|
273
331
|
instance 'cmap' to change the colors, 'scheme' to change how the data
|
|
274
332
|
is grouped. This defaults to 'fisherjenkssampled' for numeric data.
|
|
275
333
|
|
|
276
334
|
See Also:
|
|
277
|
-
|
|
335
|
+
---------
|
|
278
336
|
explore: Same functionality, but shows the entire area of the geometries.
|
|
279
337
|
clipmap: Same functionality, but shows only the areas clipped by a given mask.
|
|
280
338
|
|
|
281
339
|
Examples:
|
|
282
|
-
|
|
340
|
+
---------
|
|
283
341
|
>>> from sgis import read_parquet_url, samplemap
|
|
284
342
|
>>> roads = read_parquet_url("https://media.githubusercontent.com/media/statisticsnorway/ssb-sgis/main/tests/testdata/roads_eidskog_2022.parquet")
|
|
285
343
|
>>> points = read_parquet_url("https://media.githubusercontent.com/media/statisticsnorway/ssb-sgis/main/tests/testdata/points_eidskog.parquet")
|
|
@@ -293,77 +351,80 @@ def samplemap(
|
|
|
293
351
|
>>> samplemap(roads, points, size=5_000, column="meters")
|
|
294
352
|
|
|
295
353
|
"""
|
|
296
|
-
if
|
|
354
|
+
if isinstance(kwargs.get("center", None), _NoExplore):
|
|
355
|
+
return
|
|
356
|
+
|
|
357
|
+
if gdfs and len(gdfs) > 1 and isinstance(gdfs[-1], (float, int)):
|
|
297
358
|
*gdfs, size = gdfs
|
|
298
359
|
|
|
299
360
|
gdfs, column, kwargs = Map._separate_args(gdfs, column, kwargs)
|
|
300
361
|
|
|
301
|
-
|
|
362
|
+
loc_mask, kwargs = _get_location_mask(kwargs | {"size": size}, gdfs)
|
|
302
363
|
kwargs.pop("size")
|
|
364
|
+
mask = kwargs.pop("mask", loc_mask)
|
|
303
365
|
|
|
304
|
-
if
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
browser=browser,
|
|
310
|
-
max_zoom=max_zoom,
|
|
311
|
-
smooth_factor=smooth_factor,
|
|
312
|
-
**kwargs,
|
|
313
|
-
)
|
|
314
|
-
if m.gdfs is None and not len(m.raster_datasets):
|
|
315
|
-
return
|
|
316
|
-
if mask is not None:
|
|
317
|
-
m._gdfs = [gdf.clip(mask) for gdf in m._gdfs]
|
|
318
|
-
m._gdf = m._gdf.clip(mask)
|
|
319
|
-
m._nan_idx = m._gdf[m._column].isna()
|
|
320
|
-
m._get_unique_values()
|
|
321
|
-
|
|
322
|
-
m.samplemap(size, sample_from_first=sample_from_first)
|
|
366
|
+
i = 0 if sample_from_first else random.choice(list(range(len(gdfs))))
|
|
367
|
+
try:
|
|
368
|
+
sample = gdfs[i]
|
|
369
|
+
except IndexError:
|
|
370
|
+
sample = list(kwargs.values())[i]
|
|
323
371
|
|
|
372
|
+
if mask is None:
|
|
373
|
+
try:
|
|
374
|
+
sample = sample.geometry.loc[lambda x: ~x.is_empty].sample(1)
|
|
375
|
+
except Exception:
|
|
376
|
+
try:
|
|
377
|
+
sample = sample.sample(1)
|
|
378
|
+
except Exception:
|
|
379
|
+
pass
|
|
380
|
+
try:
|
|
381
|
+
sample = to_gdf(to_shapely(sample)).explode(ignore_index=True)
|
|
382
|
+
except Exception:
|
|
383
|
+
sample = to_gdf(to_shapely(to_bbox(sample))).explode(ignore_index=True)
|
|
324
384
|
else:
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
**kwargs,
|
|
330
|
-
)
|
|
331
|
-
|
|
332
|
-
if sample_from_first:
|
|
333
|
-
sample = m._gdfs[0].sample(1)
|
|
334
|
-
else:
|
|
335
|
-
sample = m._gdf.sample(1)
|
|
336
|
-
|
|
337
|
-
# convert lines to polygons
|
|
338
|
-
if get_geom_type(sample) == "line":
|
|
339
|
-
sample["geometry"] = sample.buffer(1)
|
|
385
|
+
try:
|
|
386
|
+
sample = to_gdf(to_shapely(sample)).explode(ignore_index=True)
|
|
387
|
+
except Exception:
|
|
388
|
+
sample = to_gdf(to_shapely(to_bbox(sample))).explode(ignore_index=True)
|
|
340
389
|
|
|
341
|
-
|
|
342
|
-
random_point = sample.sample_points(size=1)
|
|
390
|
+
sample = sample.clip(mask).explode(ignore_index=True).sample(1)
|
|
343
391
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
random_point = sample.centroid
|
|
392
|
+
print(locals())
|
|
393
|
+
random_point = sample.sample_points(size=1)
|
|
347
394
|
|
|
395
|
+
try:
|
|
348
396
|
center = (random_point.geometry.iloc[0].x, random_point.geometry.iloc[0].y)
|
|
349
|
-
|
|
397
|
+
except AttributeError as e:
|
|
398
|
+
raise AttributeError(e, random_point.geometry.iloc[0]) from e
|
|
350
399
|
|
|
351
|
-
|
|
400
|
+
print(f"center={center}, size={size}")
|
|
352
401
|
|
|
353
|
-
|
|
402
|
+
mask = random_point.buffer(size)
|
|
403
|
+
|
|
404
|
+
return clipmap(
|
|
405
|
+
*gdfs,
|
|
406
|
+
column=column,
|
|
407
|
+
mask=mask,
|
|
408
|
+
browser=browser,
|
|
409
|
+
max_zoom=max_zoom,
|
|
410
|
+
explore=explore,
|
|
411
|
+
smooth_factor=smooth_factor,
|
|
412
|
+
max_images=max_images,
|
|
413
|
+
**kwargs,
|
|
414
|
+
)
|
|
354
415
|
|
|
355
416
|
|
|
356
417
|
def clipmap(
|
|
357
418
|
*gdfs: GeoDataFrame,
|
|
358
419
|
column: str | None = None,
|
|
359
420
|
mask: GeoDataFrame | GeoSeries | Geometry = None,
|
|
360
|
-
labels: tuple[str] | None = None,
|
|
361
421
|
explore: bool = True,
|
|
362
422
|
max_zoom: int = 40,
|
|
363
423
|
smooth_factor: int | float = 1.5,
|
|
364
424
|
browser: bool = False,
|
|
425
|
+
max_images: int = 10,
|
|
365
426
|
**kwargs,
|
|
366
|
-
) ->
|
|
427
|
+
) -> Explore | Map:
|
|
367
428
|
"""Shows an interactive map of a of GeoDataFrames clipped to the mask extent.
|
|
368
429
|
|
|
369
430
|
It takes all the GeoDataFrames specified, clips them to the extent of the mask,
|
|
@@ -380,9 +441,6 @@ def clipmap(
|
|
|
380
441
|
mask: the geometry to clip the data by.
|
|
381
442
|
column: The column to color the geometries by. Defaults to None, which means
|
|
382
443
|
each GeoDataFrame will get a unique color.
|
|
383
|
-
labels: By default, the GeoDataFrames will be labeled by their object names.
|
|
384
|
-
Alternatively, labels can be specified as a tuple of strings the same
|
|
385
|
-
length as the number of gdfs.
|
|
386
444
|
max_zoom: The maximum allowed level of zoom. Higher number means more zoom
|
|
387
445
|
allowed. Defaults to 30, which is higher than the geopandas default.
|
|
388
446
|
smooth_factor: How much to simplify the geometries. 1 is the minimum,
|
|
@@ -391,21 +449,31 @@ def clipmap(
|
|
|
391
449
|
or not in Jupyter, a static plot will be shown.
|
|
392
450
|
browser: If False (default), the maps will be shown in Jupyter.
|
|
393
451
|
If True the maps will be opened in a browser folder.
|
|
452
|
+
max_images: Maximum number of images (Image, ImageCollection, Band) to show per
|
|
453
|
+
map. Defaults to 10.
|
|
394
454
|
**kwargs: Keyword arguments to pass to geopandas.GeoDataFrame.explore, for
|
|
395
455
|
instance 'cmap' to change the colors, 'scheme' to change how the data
|
|
396
456
|
is grouped. This defaults to 'fisherjenkssampled' for numeric data.
|
|
397
457
|
|
|
398
458
|
See Also:
|
|
399
|
-
|
|
459
|
+
---------
|
|
400
460
|
explore: same functionality, but shows the entire area of the geometries.
|
|
401
461
|
samplemap: same functionality, but shows only a random area of a given size.
|
|
402
462
|
"""
|
|
403
|
-
|
|
463
|
+
if isinstance(kwargs.get("center", None), _NoExplore):
|
|
464
|
+
return
|
|
404
465
|
|
|
466
|
+
gdfs, column, kwargs = Map._separate_args(gdfs, column, kwargs)
|
|
405
467
|
if mask is None and len(gdfs) > 1:
|
|
406
468
|
mask = gdfs[-1]
|
|
407
469
|
gdfs = gdfs[:-1]
|
|
408
470
|
|
|
471
|
+
if not isinstance(mask, (GeoDataFrame | GeoSeries | Geometry | tuple)):
|
|
472
|
+
try:
|
|
473
|
+
mask = to_gdf(mask)
|
|
474
|
+
except Exception:
|
|
475
|
+
mask = to_shapely(to_bbox(mask))
|
|
476
|
+
|
|
409
477
|
center = kwargs.pop("center", None)
|
|
410
478
|
size = kwargs.pop("size", None)
|
|
411
479
|
|
|
@@ -413,29 +481,31 @@ def clipmap(
|
|
|
413
481
|
m = Explore(
|
|
414
482
|
*gdfs,
|
|
415
483
|
column=column,
|
|
416
|
-
labels=labels,
|
|
417
484
|
browser=browser,
|
|
418
485
|
max_zoom=max_zoom,
|
|
419
486
|
smooth_factor=smooth_factor,
|
|
487
|
+
max_images=max_images,
|
|
420
488
|
**kwargs,
|
|
421
489
|
)
|
|
422
|
-
|
|
423
|
-
|
|
490
|
+
m.mask = mask
|
|
491
|
+
|
|
492
|
+
if m.gdfs is None and not len(m.rasters):
|
|
493
|
+
return m
|
|
424
494
|
|
|
425
495
|
m._gdfs = [gdf.clip(mask) for gdf in m._gdfs]
|
|
426
496
|
m._gdf = m._gdf.clip(mask)
|
|
427
497
|
m._nan_idx = m._gdf[m._column].isna()
|
|
428
498
|
m._get_unique_values()
|
|
429
499
|
m.explore(center=center, size=size)
|
|
500
|
+
return m
|
|
430
501
|
else:
|
|
431
502
|
m = Map(
|
|
432
503
|
*gdfs,
|
|
433
504
|
column=column,
|
|
434
|
-
labels=labels,
|
|
435
505
|
**kwargs,
|
|
436
506
|
)
|
|
437
507
|
if m.gdfs is None:
|
|
438
|
-
return
|
|
508
|
+
return m
|
|
439
509
|
|
|
440
510
|
m._gdfs = [gdf.clip(mask) for gdf in m._gdfs]
|
|
441
511
|
m._gdf = m._gdf.clip(mask)
|
|
@@ -444,8 +514,12 @@ def clipmap(
|
|
|
444
514
|
|
|
445
515
|
qtm(m._gdf, column=m.column, cmap=m._cmap, k=m.k)
|
|
446
516
|
|
|
517
|
+
return m
|
|
447
518
|
|
|
448
|
-
|
|
519
|
+
|
|
520
|
+
def explore_locals(
|
|
521
|
+
*gdfs: GeoDataFrame, convert: bool = True, crs: Any | None = None, **kwargs
|
|
522
|
+
) -> None:
|
|
449
523
|
"""Displays all local variables with geometries (GeoDataFrame etc.).
|
|
450
524
|
|
|
451
525
|
Local means inside a function or file/notebook.
|
|
@@ -454,8 +528,11 @@ def explore_locals(*gdfs: GeoDataFrame, convert: bool = True, **kwargs) -> None:
|
|
|
454
528
|
*gdfs: Additional GeoDataFrames.
|
|
455
529
|
convert: If True (default), non-GeoDataFrames will be converted
|
|
456
530
|
to GeoDataFrames if possible.
|
|
531
|
+
crs: Optional crs if no objects have any crs.
|
|
457
532
|
**kwargs: keyword arguments passed to sg.explore.
|
|
458
533
|
"""
|
|
534
|
+
if isinstance(kwargs.get("center", None), _NoExplore):
|
|
535
|
+
return
|
|
459
536
|
|
|
460
537
|
def as_dict(obj):
|
|
461
538
|
if hasattr(obj, "__dict__"):
|
|
@@ -477,11 +554,19 @@ def explore_locals(*gdfs: GeoDataFrame, convert: bool = True, **kwargs) -> None:
|
|
|
477
554
|
if not convert:
|
|
478
555
|
continue
|
|
479
556
|
|
|
557
|
+
try:
|
|
558
|
+
gdf = clean_geoms(to_gdf(value, crs=crs))
|
|
559
|
+
if len(gdf):
|
|
560
|
+
local_gdfs[name] = gdf
|
|
561
|
+
continue
|
|
562
|
+
except Exception:
|
|
563
|
+
pass
|
|
564
|
+
|
|
480
565
|
if isinstance(value, dict) or hasattr(value, "__dict__"):
|
|
481
566
|
# add dicts or classes with GeoDataFrames to kwargs
|
|
482
567
|
for key, val in as_dict(value).items():
|
|
483
568
|
if isinstance(val, allowed_types):
|
|
484
|
-
gdf = clean_geoms(
|
|
569
|
+
gdf = clean_geoms(to_gdf(val, crs=crs))
|
|
485
570
|
if len(gdf):
|
|
486
571
|
local_gdfs[key] = gdf
|
|
487
572
|
|
|
@@ -489,22 +574,13 @@ def explore_locals(*gdfs: GeoDataFrame, convert: bool = True, **kwargs) -> None:
|
|
|
489
574
|
try:
|
|
490
575
|
for k, v in val.items():
|
|
491
576
|
if isinstance(v, allowed_types):
|
|
492
|
-
gdf = clean_geoms(
|
|
577
|
+
gdf = clean_geoms(to_gdf(v, crs=crs))
|
|
493
578
|
if len(gdf):
|
|
494
579
|
local_gdfs[k] = gdf
|
|
495
580
|
except Exception:
|
|
496
581
|
# no need to raise here
|
|
497
582
|
pass
|
|
498
583
|
|
|
499
|
-
continue
|
|
500
|
-
try:
|
|
501
|
-
gdf = clean_geoms(to_gdf_func(value))
|
|
502
|
-
if len(gdf):
|
|
503
|
-
local_gdfs[name] = gdf
|
|
504
|
-
continue
|
|
505
|
-
except Exception:
|
|
506
|
-
pass
|
|
507
|
-
|
|
508
584
|
if local_gdfs:
|
|
509
585
|
break
|
|
510
586
|
|
|
@@ -513,7 +589,7 @@ def explore_locals(*gdfs: GeoDataFrame, convert: bool = True, **kwargs) -> None:
|
|
|
513
589
|
if not frame:
|
|
514
590
|
break
|
|
515
591
|
|
|
516
|
-
explore(*gdfs, **local_gdfs
|
|
592
|
+
return explore(*gdfs, **(local_gdfs | kwargs))
|
|
517
593
|
|
|
518
594
|
|
|
519
595
|
def qtm(
|
|
@@ -563,9 +639,6 @@ def qtm(
|
|
|
563
639
|
else:
|
|
564
640
|
new_kwargs[key] = value
|
|
565
641
|
|
|
566
|
-
# self.labels.append(key)
|
|
567
|
-
# self.show.append(last_show)
|
|
568
|
-
|
|
569
642
|
m = ThematicMap(*gdfs, column=column, size=size, black=black)
|
|
570
643
|
|
|
571
644
|
if m._gdfs is None:
|