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.
Files changed (39) hide show
  1. sgis/__init__.py +10 -3
  2. sgis/debug_config.py +24 -0
  3. sgis/geopandas_tools/bounds.py +16 -21
  4. sgis/geopandas_tools/buffer_dissolve_explode.py +112 -30
  5. sgis/geopandas_tools/centerlines.py +4 -91
  6. sgis/geopandas_tools/cleaning.py +1576 -583
  7. sgis/geopandas_tools/conversion.py +24 -14
  8. sgis/geopandas_tools/duplicates.py +27 -6
  9. sgis/geopandas_tools/general.py +259 -100
  10. sgis/geopandas_tools/geometry_types.py +1 -1
  11. sgis/geopandas_tools/neighbors.py +16 -12
  12. sgis/geopandas_tools/overlay.py +7 -3
  13. sgis/geopandas_tools/point_operations.py +3 -3
  14. sgis/geopandas_tools/polygon_operations.py +505 -100
  15. sgis/geopandas_tools/polygons_as_rings.py +40 -8
  16. sgis/geopandas_tools/sfilter.py +26 -9
  17. sgis/io/dapla_functions.py +238 -19
  18. sgis/maps/examine.py +11 -10
  19. sgis/maps/explore.py +227 -155
  20. sgis/maps/legend.py +13 -4
  21. sgis/maps/map.py +22 -13
  22. sgis/maps/maps.py +100 -29
  23. sgis/maps/thematicmap.py +25 -18
  24. sgis/networkanalysis/_service_area.py +6 -1
  25. sgis/networkanalysis/cutting_lines.py +12 -5
  26. sgis/networkanalysis/finding_isolated_networks.py +13 -6
  27. sgis/networkanalysis/networkanalysis.py +10 -12
  28. sgis/parallel/parallel.py +27 -10
  29. sgis/raster/base.py +208 -0
  30. sgis/raster/cube.py +3 -3
  31. sgis/raster/image_collection.py +1421 -724
  32. sgis/raster/indices.py +10 -7
  33. sgis/raster/raster.py +7 -7
  34. sgis/raster/sentinel_config.py +33 -17
  35. {ssb_sgis-1.0.3.dist-info → ssb_sgis-1.0.5.dist-info}/METADATA +6 -7
  36. ssb_sgis-1.0.5.dist-info/RECORD +62 -0
  37. ssb_sgis-1.0.3.dist-info/RECORD +0 -61
  38. {ssb_sgis-1.0.3.dist-info → ssb_sgis-1.0.5.dist-info}/LICENSE +0 -0
  39. {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 gpd.GeoSeries(box(*band.bounds), crs=band.crs)
192
- .to_crs(4326)
193
- .unary_union.bounds
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
- raster_data_dict["cmap"] = plt.get_cmap(band.cmap) if band.cmap else None
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 = 15,
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
- show_was_none = True
291
+ self._show_was_none = True
422
292
  show = True
423
293
  else:
424
- show_was_none = False
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, **new_gdfs)
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 show_was_none and len(self._gdfs) > 6:
496
- self.show = [False] * len(self._gdfs)
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
- arr = raster_data_dict["arr"]
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, bounds=[[miny, minx], [maxy, maxx]], **kwargs
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 points_in_bounds
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 = points_in_bounds(gdf, 30)
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.nearest)
349
+ max_distance = max(joined["nearest"])
341
350
 
342
- best_position = joined.loc[joined.nearest == max_distance].drop_duplicates(
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: "#4576ff",
52
- 1: "#ff455e",
53
- 2: "#ffa617",
54
- 3: "#ff8cc9",
55
- 4: "#804e00",
56
- 5: "#99ff00",
57
- 6: "#fff700",
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
- 8: "#36d19b",
60
- 9: "#94006b",
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(f"All values are NaN in column {self.column!r}.")
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.")