ssb-sgis 1.0.3__py3-none-any.whl → 1.0.5__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 +10 -3
- sgis/debug_config.py +24 -0
- sgis/geopandas_tools/bounds.py +16 -21
- sgis/geopandas_tools/buffer_dissolve_explode.py +112 -30
- sgis/geopandas_tools/centerlines.py +4 -91
- sgis/geopandas_tools/cleaning.py +1576 -583
- sgis/geopandas_tools/conversion.py +24 -14
- sgis/geopandas_tools/duplicates.py +27 -6
- sgis/geopandas_tools/general.py +259 -100
- sgis/geopandas_tools/geometry_types.py +1 -1
- sgis/geopandas_tools/neighbors.py +16 -12
- sgis/geopandas_tools/overlay.py +7 -3
- sgis/geopandas_tools/point_operations.py +3 -3
- sgis/geopandas_tools/polygon_operations.py +505 -100
- sgis/geopandas_tools/polygons_as_rings.py +40 -8
- sgis/geopandas_tools/sfilter.py +26 -9
- sgis/io/dapla_functions.py +238 -19
- sgis/maps/examine.py +11 -10
- sgis/maps/explore.py +227 -155
- sgis/maps/legend.py +13 -4
- sgis/maps/map.py +22 -13
- sgis/maps/maps.py +100 -29
- sgis/maps/thematicmap.py +25 -18
- sgis/networkanalysis/_service_area.py +6 -1
- sgis/networkanalysis/cutting_lines.py +12 -5
- sgis/networkanalysis/finding_isolated_networks.py +13 -6
- sgis/networkanalysis/networkanalysis.py +10 -12
- sgis/parallel/parallel.py +27 -10
- sgis/raster/base.py +208 -0
- sgis/raster/cube.py +3 -3
- sgis/raster/image_collection.py +1421 -724
- sgis/raster/indices.py +10 -7
- sgis/raster/raster.py +7 -7
- sgis/raster/sentinel_config.py +33 -17
- {ssb_sgis-1.0.3.dist-info → ssb_sgis-1.0.5.dist-info}/METADATA +6 -7
- ssb_sgis-1.0.5.dist-info/RECORD +62 -0
- ssb_sgis-1.0.3.dist-info/RECORD +0 -61
- {ssb_sgis-1.0.3.dist-info → ssb_sgis-1.0.5.dist-info}/LICENSE +0 -0
- {ssb_sgis-1.0.3.dist-info → ssb_sgis-1.0.5.dist-info}/WHEEL +0 -0
sgis/maps/explore.py
CHANGED
|
@@ -31,6 +31,7 @@ from jinja2 import Template
|
|
|
31
31
|
from pandas.api.types import is_datetime64_any_dtype
|
|
32
32
|
from shapely import Geometry
|
|
33
33
|
from shapely import box
|
|
34
|
+
from shapely import union_all
|
|
34
35
|
from shapely.geometry import LineString
|
|
35
36
|
|
|
36
37
|
from ..geopandas_tools.bounds import get_total_bounds
|
|
@@ -188,13 +189,19 @@ def _single_band_to_arr(band, mask, name, raster_data_dict):
|
|
|
188
189
|
bounds: tuple = (
|
|
189
190
|
_any_to_bbox_crs4326(mask, band.crs)
|
|
190
191
|
if mask is not None
|
|
191
|
-
else
|
|
192
|
-
|
|
193
|
-
.
|
|
192
|
+
else union_all(
|
|
193
|
+
gpd.GeoSeries(box(*band.bounds), crs=band.crs).to_crs(4326).geometry.values
|
|
194
|
+
).bounds
|
|
194
195
|
)
|
|
195
196
|
# if np.max(arr) > 0:
|
|
196
197
|
# arr = arr / 255
|
|
197
|
-
|
|
198
|
+
try:
|
|
199
|
+
raster_data_dict["cmap"] = band.get_cmap(arr)
|
|
200
|
+
except Exception:
|
|
201
|
+
try:
|
|
202
|
+
raster_data_dict["cmap"] = plt.get_cmap(band.cmap)
|
|
203
|
+
except Exception:
|
|
204
|
+
raster_data_dict["cmap"] = band.cmap or "Grays"
|
|
198
205
|
raster_data_dict["arr"] = arr
|
|
199
206
|
raster_data_dict["bounds"] = bounds
|
|
200
207
|
raster_data_dict["label"] = name
|
|
@@ -204,152 +211,15 @@ def _any_to_bbox_crs4326(obj, crs):
|
|
|
204
211
|
return to_bbox(to_gdf(obj, crs).to_crs(4326))
|
|
205
212
|
|
|
206
213
|
|
|
207
|
-
def _image_collection_to_background_map(
|
|
208
|
-
image_collection: ImageCollection | Image | Band,
|
|
209
|
-
mask: Any | None,
|
|
210
|
-
name: str,
|
|
211
|
-
max_images: int,
|
|
212
|
-
n_added_images: int,
|
|
213
|
-
rbg_bands: list[str] = (["B02", "B03", "B04"], ["B2", "B3", "B4"]),
|
|
214
|
-
) -> list[dict]:
|
|
215
|
-
out = []
|
|
216
|
-
|
|
217
|
-
if all(isinstance(x, str) for x in rbg_bands):
|
|
218
|
-
rbg_bands = (rbg_bands,)
|
|
219
|
-
|
|
220
|
-
# red, blue, green = rbg_bands
|
|
221
|
-
if isinstance(image_collection, ImageCollection):
|
|
222
|
-
images = image_collection.images
|
|
223
|
-
name = None
|
|
224
|
-
elif isinstance(image_collection, Image):
|
|
225
|
-
img = image_collection
|
|
226
|
-
if mask is not None and not to_shapely(mask).intersects(to_shapely(img.bounds)):
|
|
227
|
-
return out
|
|
228
|
-
|
|
229
|
-
if len(img) == 1:
|
|
230
|
-
band = next(iter(img))
|
|
231
|
-
raster_data_dict = {}
|
|
232
|
-
out.append(raster_data_dict)
|
|
233
|
-
name = _determine_label(band, name, out, 0)
|
|
234
|
-
_single_band_to_arr(band, mask, name, raster_data_dict)
|
|
235
|
-
return out
|
|
236
|
-
elif len(img) < 3:
|
|
237
|
-
raster_data_dict = {}
|
|
238
|
-
out.append(raster_data_dict)
|
|
239
|
-
for i, band in enumerate(img):
|
|
240
|
-
name = _determine_label(band, None, out, i)
|
|
241
|
-
_single_band_to_arr(band, mask, name, raster_data_dict)
|
|
242
|
-
return out
|
|
243
|
-
else:
|
|
244
|
-
images = [image_collection]
|
|
245
|
-
|
|
246
|
-
elif isinstance(image_collection, Band):
|
|
247
|
-
band = image_collection
|
|
248
|
-
|
|
249
|
-
if mask is not None and not to_shapely(mask).intersects(
|
|
250
|
-
to_shapely(band.bounds)
|
|
251
|
-
):
|
|
252
|
-
return out
|
|
253
|
-
|
|
254
|
-
raster_data_dict = {}
|
|
255
|
-
out.append(raster_data_dict)
|
|
256
|
-
_single_band_to_arr(band, mask, name, raster_data_dict)
|
|
257
|
-
return out
|
|
258
|
-
|
|
259
|
-
else:
|
|
260
|
-
raise TypeError(type(image_collection))
|
|
261
|
-
|
|
262
|
-
if len(images) + n_added_images > max_images:
|
|
263
|
-
warnings.warn(
|
|
264
|
-
f"Showing only a sample of {max_images}. Set 'max_images.", stacklevel=1
|
|
265
|
-
)
|
|
266
|
-
random.shuffle(images)
|
|
267
|
-
images = images[: (max_images - n_added_images)]
|
|
268
|
-
try:
|
|
269
|
-
images = list(sorted(images))
|
|
270
|
-
except Exception:
|
|
271
|
-
pass
|
|
272
|
-
|
|
273
|
-
i = -1
|
|
274
|
-
for image in images:
|
|
275
|
-
i += 1
|
|
276
|
-
if mask is not None and not to_shapely(mask).intersects(
|
|
277
|
-
to_shapely(image.bounds)
|
|
278
|
-
):
|
|
279
|
-
continue
|
|
280
|
-
|
|
281
|
-
raster_data_dict = {}
|
|
282
|
-
out.append(raster_data_dict)
|
|
283
|
-
|
|
284
|
-
if len(image) < 3:
|
|
285
|
-
for band in image:
|
|
286
|
-
name = _determine_label(band, None, out, i)
|
|
287
|
-
_single_band_to_arr(band, mask, name, raster_data_dict)
|
|
288
|
-
i += 1
|
|
289
|
-
continue
|
|
290
|
-
|
|
291
|
-
for red, blue, green in rbg_bands:
|
|
292
|
-
try:
|
|
293
|
-
red_band = image[red].load(indexes=1, bounds=mask).values
|
|
294
|
-
except KeyError:
|
|
295
|
-
continue
|
|
296
|
-
try:
|
|
297
|
-
blue_band = image[blue].load(indexes=1, bounds=mask).values
|
|
298
|
-
except KeyError:
|
|
299
|
-
continue
|
|
300
|
-
try:
|
|
301
|
-
green_band = image[green].load(indexes=1, bounds=mask).values
|
|
302
|
-
except KeyError:
|
|
303
|
-
continue
|
|
304
|
-
break
|
|
305
|
-
|
|
306
|
-
if mask is not None:
|
|
307
|
-
print(mask)
|
|
308
|
-
print(to_gdf(mask).area.sum())
|
|
309
|
-
print(to_gdf(image.bounds).area.sum())
|
|
310
|
-
print(red_band.shape)
|
|
311
|
-
print(image.bounds)
|
|
312
|
-
|
|
313
|
-
if red_band.shape[0] == 0:
|
|
314
|
-
continue
|
|
315
|
-
if blue_band.shape[0] == 0:
|
|
316
|
-
continue
|
|
317
|
-
if green_band.shape[0] == 0:
|
|
318
|
-
continue
|
|
319
|
-
|
|
320
|
-
crs = image.crs
|
|
321
|
-
bounds: tuple = (
|
|
322
|
-
_any_to_bbox_crs4326(mask, crs)
|
|
323
|
-
if mask is not None
|
|
324
|
-
else (
|
|
325
|
-
gpd.GeoSeries(box(*image.bounds), crs=crs)
|
|
326
|
-
.to_crs(4326)
|
|
327
|
-
.unary_union.bounds
|
|
328
|
-
)
|
|
329
|
-
)
|
|
330
|
-
print(bounds)
|
|
331
|
-
print(to_gdf(bounds).area.sum())
|
|
332
|
-
|
|
333
|
-
# to 3d array in shape (x, y, 3)
|
|
334
|
-
rbg_image = np.stack([red_band, blue_band, green_band], axis=2)
|
|
335
|
-
|
|
336
|
-
raster_data_dict["arr"] = rbg_image
|
|
337
|
-
raster_data_dict["bounds"] = bounds
|
|
338
|
-
raster_data_dict["cmap"] = None
|
|
339
|
-
raster_data_dict["label"] = _determine_label(image, name, out, i)
|
|
340
|
-
|
|
341
|
-
return out
|
|
342
|
-
|
|
343
|
-
|
|
344
214
|
class Explore(Map):
|
|
345
215
|
"""Class for displaying and saving html maps of multiple GeoDataFrames."""
|
|
346
216
|
|
|
347
217
|
# class attribute that can be overridden locally
|
|
348
218
|
tiles: ClassVar[tuple[str]] = (
|
|
349
|
-
"grunnkart",
|
|
350
|
-
"norge_i_bilder",
|
|
351
|
-
"dark",
|
|
352
219
|
"OpenStreetMap",
|
|
220
|
+
"dark",
|
|
221
|
+
"norge_i_bilder",
|
|
222
|
+
"grunnkart",
|
|
353
223
|
)
|
|
354
224
|
|
|
355
225
|
def __init__(
|
|
@@ -368,7 +238,7 @@ class Explore(Map):
|
|
|
368
238
|
show: bool | Iterable[bool] | None = None,
|
|
369
239
|
text: str | None = None,
|
|
370
240
|
decimals: int = 6,
|
|
371
|
-
max_images: int =
|
|
241
|
+
max_images: int = 10,
|
|
372
242
|
**kwargs,
|
|
373
243
|
) -> None:
|
|
374
244
|
"""Initialiser.
|
|
@@ -418,10 +288,10 @@ class Explore(Map):
|
|
|
418
288
|
self.browser = kwargs.pop("in_browser")
|
|
419
289
|
|
|
420
290
|
if show is None:
|
|
421
|
-
|
|
291
|
+
self._show_was_none = True
|
|
422
292
|
show = True
|
|
423
293
|
else:
|
|
424
|
-
|
|
294
|
+
self._show_was_none = False
|
|
425
295
|
|
|
426
296
|
new_gdfs = {}
|
|
427
297
|
self.rasters = {}
|
|
@@ -446,7 +316,7 @@ class Explore(Map):
|
|
|
446
316
|
else:
|
|
447
317
|
new_kwargs[key] = value
|
|
448
318
|
|
|
449
|
-
super().__init__(column=column, show=show, **new_kwargs
|
|
319
|
+
super().__init__(column=column, show=show, **(new_kwargs | new_gdfs))
|
|
450
320
|
|
|
451
321
|
if self.gdfs is None:
|
|
452
322
|
return
|
|
@@ -492,8 +362,8 @@ class Explore(Map):
|
|
|
492
362
|
self._gdf = GeoDataFrame({"geometry": [], self._column: []})
|
|
493
363
|
self.show = show_new
|
|
494
364
|
|
|
495
|
-
if
|
|
496
|
-
|
|
365
|
+
# if self._show_was_none and len(self._gdfs) > 6:
|
|
366
|
+
# self.show = [False] * len(self._gdfs)
|
|
497
367
|
|
|
498
368
|
if self._is_categorical:
|
|
499
369
|
if len(self.gdfs) == 1:
|
|
@@ -511,7 +381,19 @@ class Explore(Map):
|
|
|
511
381
|
|
|
512
382
|
def __repr__(self) -> str:
|
|
513
383
|
"""Representation."""
|
|
514
|
-
return f"{self.__class__.__name__}()"
|
|
384
|
+
return f"{self.__class__.__name__}({len(self)})"
|
|
385
|
+
|
|
386
|
+
def __len__(self) -> int:
|
|
387
|
+
"""Number of gdfs that have rows plus number of raster images."""
|
|
388
|
+
try:
|
|
389
|
+
rasters = self.raster_data
|
|
390
|
+
except AttributeError:
|
|
391
|
+
rasters = self.rasters
|
|
392
|
+
return len([gdf for gdf in self._gdfs if len(gdf)]) + len(rasters)
|
|
393
|
+
|
|
394
|
+
def __bool__(self) -> bool:
|
|
395
|
+
"""True if any gdfs have rows or there are any raster images."""
|
|
396
|
+
return bool(len(self))
|
|
515
397
|
|
|
516
398
|
def explore(
|
|
517
399
|
self,
|
|
@@ -627,20 +509,36 @@ class Explore(Map):
|
|
|
627
509
|
def _load_rasters_as_images(self):
|
|
628
510
|
self.raster_data = []
|
|
629
511
|
n_added_images = 0
|
|
512
|
+
self._show_rasters = True
|
|
630
513
|
for name, value in self.rasters.items():
|
|
631
|
-
data = _image_collection_to_background_map(
|
|
514
|
+
data, n_added_images = self._image_collection_to_background_map(
|
|
632
515
|
value,
|
|
633
516
|
self.mask,
|
|
634
517
|
name,
|
|
635
518
|
max_images=self.max_images,
|
|
636
519
|
n_added_images=n_added_images,
|
|
637
520
|
)
|
|
638
|
-
n_added_images += len(data)
|
|
639
521
|
self.raster_data += data
|
|
640
522
|
|
|
641
523
|
def _rasters_to_background_maps(self):
|
|
642
524
|
for raster_data_dict in self.raster_data:
|
|
643
|
-
|
|
525
|
+
try:
|
|
526
|
+
arr = raster_data_dict["arr"]
|
|
527
|
+
except KeyError:
|
|
528
|
+
continue
|
|
529
|
+
if (arr.shape) == 1:
|
|
530
|
+
continue
|
|
531
|
+
if hasattr(arr, "mask"):
|
|
532
|
+
arr = arr.data
|
|
533
|
+
if "bool" in str(arr.dtype):
|
|
534
|
+
arr = np.where(arr, 1, 0)
|
|
535
|
+
# if np.max(arr[~np.isnan(arr)]) > 255:
|
|
536
|
+
# arr = (arr - np.min(arr)) / (np.max(arr) - np.min(arr))
|
|
537
|
+
try:
|
|
538
|
+
arr = (arr - np.min(arr)) / (np.max(arr) - np.min(arr))
|
|
539
|
+
except Exception:
|
|
540
|
+
pass
|
|
541
|
+
|
|
644
542
|
label = raster_data_dict["label"]
|
|
645
543
|
bounds = raster_data_dict["bounds"]
|
|
646
544
|
if raster_data_dict["cmap"] is not None:
|
|
@@ -649,7 +547,10 @@ class Explore(Map):
|
|
|
649
547
|
kwargs = {}
|
|
650
548
|
minx, miny, maxx, maxy = bounds
|
|
651
549
|
image_overlay = folium.raster_layers.ImageOverlay(
|
|
652
|
-
arr,
|
|
550
|
+
arr,
|
|
551
|
+
bounds=[[miny, minx], [maxy, maxx]],
|
|
552
|
+
show=self._show_rasters,
|
|
553
|
+
**kwargs,
|
|
653
554
|
)
|
|
654
555
|
image_overlay.layer_name = Path(label).stem
|
|
655
556
|
image_overlay.add_to(self.map)
|
|
@@ -662,6 +563,9 @@ class Explore(Map):
|
|
|
662
563
|
def _explore(self, **kwargs) -> None:
|
|
663
564
|
self.kwargs = self.kwargs | kwargs
|
|
664
565
|
|
|
566
|
+
if self._show_was_none and len([gdf for gdf in self._gdfs if len(gdf)]) > 6:
|
|
567
|
+
self.show = [False] * len(self._gdfs)
|
|
568
|
+
|
|
665
569
|
if self._is_categorical:
|
|
666
570
|
self._create_categorical_map()
|
|
667
571
|
else:
|
|
@@ -718,6 +622,8 @@ class Explore(Map):
|
|
|
718
622
|
return gdf
|
|
719
623
|
|
|
720
624
|
def _update_column(self) -> None:
|
|
625
|
+
if not self._gdfs:
|
|
626
|
+
return
|
|
721
627
|
self._is_categorical = self._check_if_categorical()
|
|
722
628
|
self._fillna_if_col_is_missing()
|
|
723
629
|
self._gdf = pd.concat(self._gdfs, ignore_index=True)
|
|
@@ -1146,6 +1052,163 @@ class Explore(Map):
|
|
|
1146
1052
|
**kwargs,
|
|
1147
1053
|
)
|
|
1148
1054
|
|
|
1055
|
+
def _image_collection_to_background_map(
|
|
1056
|
+
self,
|
|
1057
|
+
image_collection: ImageCollection | Image | Band,
|
|
1058
|
+
mask: Any | None,
|
|
1059
|
+
name: str,
|
|
1060
|
+
max_images: int,
|
|
1061
|
+
n_added_images: int,
|
|
1062
|
+
rbg_bands: list[str] = (["B02", "B03", "B04"], ["B2", "B3", "B4"]),
|
|
1063
|
+
) -> tuple[list[dict], int]:
|
|
1064
|
+
out = []
|
|
1065
|
+
|
|
1066
|
+
if all(isinstance(x, str) for x in rbg_bands):
|
|
1067
|
+
rbg_bands = (rbg_bands,)
|
|
1068
|
+
|
|
1069
|
+
if isinstance(image_collection, ImageCollection):
|
|
1070
|
+
images = image_collection.images
|
|
1071
|
+
name = None
|
|
1072
|
+
elif isinstance(image_collection, Image):
|
|
1073
|
+
img = image_collection
|
|
1074
|
+
if not _intersects_if_not_none_or_empty(
|
|
1075
|
+
mask, img.bounds
|
|
1076
|
+
): # is not None and not to_shapely(mask).intersects(
|
|
1077
|
+
# to_shapely(img.bounds)
|
|
1078
|
+
# ):
|
|
1079
|
+
return out, n_added_images
|
|
1080
|
+
|
|
1081
|
+
if len(img) == 1:
|
|
1082
|
+
band = next(iter(img))
|
|
1083
|
+
raster_data_dict = {}
|
|
1084
|
+
out.append(raster_data_dict)
|
|
1085
|
+
name = _determine_label(band, name, out, n_added_images)
|
|
1086
|
+
_single_band_to_arr(band, mask, name, raster_data_dict)
|
|
1087
|
+
n_added_images += 1
|
|
1088
|
+
return out, n_added_images
|
|
1089
|
+
elif len(img) < 3:
|
|
1090
|
+
raster_data_dict = {}
|
|
1091
|
+
out.append(raster_data_dict)
|
|
1092
|
+
for band in img:
|
|
1093
|
+
name = _determine_label(band, None, out, n_added_images)
|
|
1094
|
+
_single_band_to_arr(band, mask, name, raster_data_dict)
|
|
1095
|
+
n_added_images += 1
|
|
1096
|
+
return out, n_added_images
|
|
1097
|
+
else:
|
|
1098
|
+
images = [image_collection]
|
|
1099
|
+
|
|
1100
|
+
elif isinstance(image_collection, Band):
|
|
1101
|
+
band = image_collection
|
|
1102
|
+
|
|
1103
|
+
if not _intersects_if_not_none_or_empty(
|
|
1104
|
+
mask, band.bounds
|
|
1105
|
+
): # mask is not None and not to_shapely(mask).intersects(
|
|
1106
|
+
# to_shapely(band.bounds)
|
|
1107
|
+
# ):
|
|
1108
|
+
return out, n_added_images
|
|
1109
|
+
|
|
1110
|
+
raster_data_dict = {}
|
|
1111
|
+
out.append(raster_data_dict)
|
|
1112
|
+
_single_band_to_arr(band, mask, name, raster_data_dict)
|
|
1113
|
+
return out, n_added_images
|
|
1114
|
+
|
|
1115
|
+
else:
|
|
1116
|
+
raise TypeError(type(image_collection))
|
|
1117
|
+
|
|
1118
|
+
if max(len(out), len(images)) + n_added_images > max_images:
|
|
1119
|
+
warnings.warn(
|
|
1120
|
+
f"Showing only a sample of {max_images}. Set 'max_images.", stacklevel=1
|
|
1121
|
+
)
|
|
1122
|
+
self._show_rasters = False
|
|
1123
|
+
random.shuffle(images)
|
|
1124
|
+
|
|
1125
|
+
images = images[: (max_images - n_added_images)]
|
|
1126
|
+
images = (
|
|
1127
|
+
list(sorted([img for img in images if img.date is not None]))
|
|
1128
|
+
+ sorted(
|
|
1129
|
+
[
|
|
1130
|
+
img
|
|
1131
|
+
for img in images
|
|
1132
|
+
if img.date is None and img.path is not None
|
|
1133
|
+
],
|
|
1134
|
+
key=lambda x: x.path,
|
|
1135
|
+
)
|
|
1136
|
+
+ [img for img in images if img.date is None and img.path is None]
|
|
1137
|
+
)
|
|
1138
|
+
|
|
1139
|
+
for image in images:
|
|
1140
|
+
|
|
1141
|
+
if not _intersects_if_not_none_or_empty(
|
|
1142
|
+
mask, image.bounds
|
|
1143
|
+
): # mask is not None and not to_shapely(mask).intersects(
|
|
1144
|
+
# to_shapely(image.bounds)
|
|
1145
|
+
# ):
|
|
1146
|
+
continue
|
|
1147
|
+
|
|
1148
|
+
raster_data_dict = {}
|
|
1149
|
+
out.append(raster_data_dict)
|
|
1150
|
+
|
|
1151
|
+
if len(image) < 3:
|
|
1152
|
+
for band in image:
|
|
1153
|
+
name = _determine_label(band, None, out, n_added_images)
|
|
1154
|
+
_single_band_to_arr(band, mask, name, raster_data_dict)
|
|
1155
|
+
n_added_images += 1
|
|
1156
|
+
continue
|
|
1157
|
+
|
|
1158
|
+
for red, blue, green in rbg_bands:
|
|
1159
|
+
try:
|
|
1160
|
+
red_band = image[red].load(indexes=1, bounds=mask)
|
|
1161
|
+
except KeyError:
|
|
1162
|
+
continue
|
|
1163
|
+
try:
|
|
1164
|
+
blue_band = image[blue].load(indexes=1, bounds=mask)
|
|
1165
|
+
except KeyError:
|
|
1166
|
+
continue
|
|
1167
|
+
try:
|
|
1168
|
+
green_band = image[green].load(indexes=1, bounds=mask)
|
|
1169
|
+
except KeyError:
|
|
1170
|
+
continue
|
|
1171
|
+
break
|
|
1172
|
+
|
|
1173
|
+
crs = red_band.crs
|
|
1174
|
+
|
|
1175
|
+
bounds: tuple = (
|
|
1176
|
+
_any_to_bbox_crs4326(mask, crs)
|
|
1177
|
+
if mask is not None
|
|
1178
|
+
else (
|
|
1179
|
+
union_all(
|
|
1180
|
+
gpd.GeoSeries(box(*red_band.bounds), crs=crs)
|
|
1181
|
+
.to_crs(4326)
|
|
1182
|
+
.geometry.values
|
|
1183
|
+
).bounds
|
|
1184
|
+
)
|
|
1185
|
+
)
|
|
1186
|
+
|
|
1187
|
+
red_band = red_band.values
|
|
1188
|
+
blue_band = blue_band.values
|
|
1189
|
+
green_band = green_band.values
|
|
1190
|
+
|
|
1191
|
+
if red_band.shape[0] == 0:
|
|
1192
|
+
continue
|
|
1193
|
+
if blue_band.shape[0] == 0:
|
|
1194
|
+
continue
|
|
1195
|
+
if green_band.shape[0] == 0:
|
|
1196
|
+
continue
|
|
1197
|
+
|
|
1198
|
+
# to 3d array in shape (x, y, 3)
|
|
1199
|
+
rbg_image = np.stack([red_band, blue_band, green_band], axis=2)
|
|
1200
|
+
|
|
1201
|
+
raster_data_dict["arr"] = rbg_image
|
|
1202
|
+
raster_data_dict["bounds"] = bounds
|
|
1203
|
+
raster_data_dict["cmap"] = None
|
|
1204
|
+
raster_data_dict["label"] = _determine_label(
|
|
1205
|
+
image, name, out, n_added_images
|
|
1206
|
+
)
|
|
1207
|
+
|
|
1208
|
+
n_added_images += 1
|
|
1209
|
+
|
|
1210
|
+
return out, n_added_images
|
|
1211
|
+
|
|
1149
1212
|
|
|
1150
1213
|
def _tooltip_popup(
|
|
1151
1214
|
type_: str, fields: Any, gdf: GeoDataFrame, **kwargs
|
|
@@ -1174,6 +1237,15 @@ def _tooltip_popup(
|
|
|
1174
1237
|
return folium.GeoJsonPopup(fields, **kwargs)
|
|
1175
1238
|
|
|
1176
1239
|
|
|
1240
|
+
def _intersects_if_not_none_or_empty(obj: Any, other: Any) -> bool:
|
|
1241
|
+
if obj is None:
|
|
1242
|
+
return True
|
|
1243
|
+
obj = to_shapely(obj)
|
|
1244
|
+
if obj is None or obj.is_empty:
|
|
1245
|
+
return True
|
|
1246
|
+
return obj.intersects(to_shapely(other))
|
|
1247
|
+
|
|
1248
|
+
|
|
1177
1249
|
def _determine_label(
|
|
1178
1250
|
obj: Image | Band | ImageCollection, obj_name: str | None, out: list[dict], i: int
|
|
1179
1251
|
) -> str:
|
sgis/maps/legend.py
CHANGED
|
@@ -17,7 +17,8 @@ from geopandas import GeoDataFrame
|
|
|
17
17
|
from matplotlib.lines import Line2D
|
|
18
18
|
from pandas import Series
|
|
19
19
|
|
|
20
|
-
from ..geopandas_tools.bounds import
|
|
20
|
+
from ..geopandas_tools.bounds import bounds_to_points
|
|
21
|
+
from ..geopandas_tools.general import points_in_bounds
|
|
21
22
|
|
|
22
23
|
# the geopandas._explore raises a deprication warning. Ignoring for now.
|
|
23
24
|
warnings.filterwarnings(
|
|
@@ -333,13 +334,21 @@ class Legend:
|
|
|
333
334
|
diffx = maxx - minx
|
|
334
335
|
diffy = maxy - miny
|
|
335
336
|
|
|
336
|
-
points =
|
|
337
|
+
points = pd.concat(
|
|
338
|
+
[
|
|
339
|
+
points_in_bounds(gdf, 30),
|
|
340
|
+
bounds_to_points(gdf)
|
|
341
|
+
.geometry.explode(ignore_index=True)
|
|
342
|
+
.to_frame("geometry"),
|
|
343
|
+
]
|
|
344
|
+
)
|
|
345
|
+
|
|
337
346
|
gdf = gdf.loc[:, ~gdf.columns.str.contains("index|level_")]
|
|
338
347
|
joined = points.sjoin_nearest(gdf, distance_col="nearest")
|
|
339
348
|
|
|
340
|
-
max_distance = max(joined
|
|
349
|
+
max_distance = max(joined["nearest"])
|
|
341
350
|
|
|
342
|
-
best_position = joined.loc[joined
|
|
351
|
+
best_position = joined.loc[joined["nearest"] == max_distance].drop_duplicates(
|
|
343
352
|
"geometry"
|
|
344
353
|
)
|
|
345
354
|
|
sgis/maps/map.py
CHANGED
|
@@ -48,18 +48,18 @@ pd.options.mode.chained_assignment = None
|
|
|
48
48
|
# similar colors. The palette is like the "Set2" cmap from matplotlib, but with more
|
|
49
49
|
# colors. If more than 14 categories, the geopandas default cmap is used.
|
|
50
50
|
_CATEGORICAL_CMAP = {
|
|
51
|
-
0: "#
|
|
52
|
-
1: "#
|
|
53
|
-
2: "#
|
|
54
|
-
3: "#
|
|
55
|
-
4: "#
|
|
56
|
-
5: "#
|
|
57
|
-
6: "#
|
|
51
|
+
0: "#3b93ff",
|
|
52
|
+
1: "#ff3370",
|
|
53
|
+
2: "#f7cf19",
|
|
54
|
+
3: "#60e825",
|
|
55
|
+
4: "#ff8cc9",
|
|
56
|
+
5: "#804e00",
|
|
57
|
+
6: "#e3dc00",
|
|
58
58
|
7: "#00ffee",
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
10: "#750000",
|
|
59
|
+
9: "#870062",
|
|
60
|
+
10: "#751500",
|
|
62
61
|
11: "#1c6b00",
|
|
62
|
+
8: "#7cebb9",
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
DEFAULT_SCHEME = "quantiles"
|
|
@@ -147,6 +147,7 @@ class Map:
|
|
|
147
147
|
show_temp = show
|
|
148
148
|
|
|
149
149
|
show_args = show_temp[: len(gdfs)]
|
|
150
|
+
# gdfs that are in kwargs
|
|
150
151
|
show_kwargs = show_temp[len(gdfs) :]
|
|
151
152
|
self._gdfs = []
|
|
152
153
|
new_labels = []
|
|
@@ -169,6 +170,8 @@ class Map:
|
|
|
169
170
|
i = 0
|
|
170
171
|
for key, value in kwargs.items():
|
|
171
172
|
try:
|
|
173
|
+
if isinstance(value, Geometry):
|
|
174
|
+
value = to_gdf(value)
|
|
172
175
|
if not len(value):
|
|
173
176
|
continue
|
|
174
177
|
self._gdfs.append(to_gdf(value))
|
|
@@ -188,7 +191,7 @@ class Map:
|
|
|
188
191
|
f"length as gdfs ({len(gdfs)}). Got len {len(show)}"
|
|
189
192
|
)
|
|
190
193
|
|
|
191
|
-
if not any(len(gdf) for gdf in self._gdfs):
|
|
194
|
+
if not self._gdfs or not any(len(gdf) for gdf in self._gdfs):
|
|
192
195
|
self._gdfs = []
|
|
193
196
|
self._is_categorical = True
|
|
194
197
|
self._unique_values = []
|
|
@@ -222,6 +225,10 @@ class Map:
|
|
|
222
225
|
self._nan_idx = self._gdf[self._column].isna()
|
|
223
226
|
self._get_unique_values()
|
|
224
227
|
|
|
228
|
+
def __bool__(self) -> bool:
|
|
229
|
+
"""True of any gdfs with more than 0 rows."""
|
|
230
|
+
return bool(len(self._gdfs) + len(self._gdf))
|
|
231
|
+
|
|
225
232
|
def _get_unique_values(self) -> None:
|
|
226
233
|
if not self._is_categorical:
|
|
227
234
|
self._unique_values = self._get_unique_floats()
|
|
@@ -514,7 +521,7 @@ class Map:
|
|
|
514
521
|
|
|
515
522
|
def _check_if_categorical(self) -> bool:
|
|
516
523
|
"""Quite messy this..."""
|
|
517
|
-
if not self._column:
|
|
524
|
+
if not self._column or not self._gdfs:
|
|
518
525
|
return True
|
|
519
526
|
|
|
520
527
|
def is_maybe_km2():
|
|
@@ -564,7 +571,9 @@ class Map:
|
|
|
564
571
|
return False
|
|
565
572
|
|
|
566
573
|
if all_nan == len(self._gdfs):
|
|
567
|
-
raise ValueError(
|
|
574
|
+
raise ValueError(
|
|
575
|
+
f"All values are NaN in column {self.column!r}. {self._gdfs}"
|
|
576
|
+
)
|
|
568
577
|
|
|
569
578
|
if col_not_present == len(self._gdfs):
|
|
570
579
|
raise ValueError(f"{self.column} not found.")
|