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/map.py
CHANGED
|
@@ -4,6 +4,8 @@ This module holds the Map class, which is the basis for the Explore class.
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import warnings
|
|
7
|
+
from collections.abc import Sequence
|
|
8
|
+
from statistics import mean
|
|
7
9
|
from typing import Any
|
|
8
10
|
|
|
9
11
|
import matplotlib
|
|
@@ -22,6 +24,10 @@ from ..geopandas_tools.general import clean_geoms
|
|
|
22
24
|
from ..geopandas_tools.general import drop_inactive_geometry_columns
|
|
23
25
|
from ..geopandas_tools.general import get_common_crs
|
|
24
26
|
from ..helpers import get_object_name
|
|
27
|
+
from ..helpers import unit_is_meters
|
|
28
|
+
from ..raster.image_collection import Band
|
|
29
|
+
from ..raster.image_collection import Image
|
|
30
|
+
from ..raster.image_collection import ImageCollection
|
|
25
31
|
|
|
26
32
|
try:
|
|
27
33
|
from torchgeo.datasets.geo import RasterDataset
|
|
@@ -42,18 +48,18 @@ pd.options.mode.chained_assignment = None
|
|
|
42
48
|
# similar colors. The palette is like the "Set2" cmap from matplotlib, but with more
|
|
43
49
|
# colors. If more than 14 categories, the geopandas default cmap is used.
|
|
44
50
|
_CATEGORICAL_CMAP = {
|
|
45
|
-
0: "#
|
|
46
|
-
1: "#
|
|
47
|
-
2: "#
|
|
48
|
-
3: "#
|
|
49
|
-
4: "#
|
|
50
|
-
5: "#
|
|
51
|
-
6: "#
|
|
51
|
+
0: "#3b93ff",
|
|
52
|
+
1: "#ff3370",
|
|
53
|
+
2: "#f7cf19",
|
|
54
|
+
3: "#60e825",
|
|
55
|
+
4: "#ff8cc9",
|
|
56
|
+
5: "#804e00",
|
|
57
|
+
6: "#e3dc00",
|
|
52
58
|
7: "#00ffee",
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
10: "#750000",
|
|
59
|
+
9: "#870062",
|
|
60
|
+
10: "#751500",
|
|
56
61
|
11: "#1c6b00",
|
|
62
|
+
8: "#7cebb9",
|
|
57
63
|
}
|
|
58
64
|
|
|
59
65
|
DEFAULT_SCHEME = "quantiles"
|
|
@@ -91,12 +97,12 @@ class Map:
|
|
|
91
97
|
self,
|
|
92
98
|
*gdfs: GeoDataFrame,
|
|
93
99
|
column: str | None = None,
|
|
94
|
-
labels: tuple[str] | None = None,
|
|
95
100
|
k: int = 5,
|
|
96
101
|
bins: tuple[float] | None = None,
|
|
97
102
|
nan_label: str = "Missing",
|
|
98
103
|
nan_color="#c2c2c2",
|
|
99
104
|
scheme: str = DEFAULT_SCHEME,
|
|
105
|
+
cmap: str | None = None,
|
|
100
106
|
**kwargs,
|
|
101
107
|
) -> None:
|
|
102
108
|
"""Initialiser.
|
|
@@ -104,12 +110,13 @@ class Map:
|
|
|
104
110
|
Args:
|
|
105
111
|
*gdfs: Variable length GeoDataFrame list.
|
|
106
112
|
column: The column name to work with.
|
|
107
|
-
labels: Tuple of labels for each GeoDataFrame.
|
|
108
113
|
k: Number of bins or classes for classification (default: 5).
|
|
109
114
|
bins: Predefined bins for data classification.
|
|
110
115
|
nan_label: Label for missing data.
|
|
111
116
|
nan_color: Color for missing data.
|
|
112
117
|
scheme: Classification scheme to be used.
|
|
118
|
+
cmap (str): Colormap of the plot. See:
|
|
119
|
+
https://matplotlib.org/stable/tutorials/colors/colormaps.html
|
|
113
120
|
**kwargs: Arbitrary keyword arguments.
|
|
114
121
|
"""
|
|
115
122
|
gdfs, column, kwargs = self._separate_args(gdfs, column, kwargs)
|
|
@@ -119,27 +126,14 @@ class Map:
|
|
|
119
126
|
self._k = k
|
|
120
127
|
self.nan_label = nan_label
|
|
121
128
|
self.nan_color = nan_color
|
|
122
|
-
self._cmap =
|
|
129
|
+
self._cmap = cmap
|
|
123
130
|
self.scheme = scheme
|
|
124
131
|
|
|
125
|
-
if not all(isinstance(gdf, GeoDataFrame) for gdf in gdfs):
|
|
126
|
-
gdfs = [
|
|
127
|
-
to_gdf(gdf) if not isinstance(gdf, GeoDataFrame) else gdf
|
|
128
|
-
for gdf in gdfs
|
|
129
|
-
]
|
|
130
|
-
if not all(isinstance(gdf, GeoDataFrame) for gdf in gdfs):
|
|
131
|
-
raise ValueError("gdfs must be GeoDataFrames.")
|
|
132
|
-
|
|
133
|
-
if "namedict" in kwargs:
|
|
134
|
-
for i, gdf in enumerate(gdfs):
|
|
135
|
-
gdf.name = kwargs["namedict"][i]
|
|
136
|
-
kwargs.pop("namedict")
|
|
137
|
-
|
|
138
132
|
# need to get the object names of the gdfs before copying. Only getting,
|
|
139
133
|
# not setting, labels. So the original gdfs don't get the label column.
|
|
140
|
-
self.labels =
|
|
141
|
-
|
|
142
|
-
|
|
134
|
+
self.labels: list[str] = [
|
|
135
|
+
_determine_best_name(gdf, column, i) for i, gdf in enumerate(gdfs)
|
|
136
|
+
]
|
|
143
137
|
|
|
144
138
|
show = kwargs.pop("show", True)
|
|
145
139
|
if isinstance(show, (int, bool)):
|
|
@@ -153,6 +147,7 @@ class Map:
|
|
|
153
147
|
show_temp = show
|
|
154
148
|
|
|
155
149
|
show_args = show_temp[: len(gdfs)]
|
|
150
|
+
# gdfs that are in kwargs
|
|
156
151
|
show_kwargs = show_temp[len(gdfs) :]
|
|
157
152
|
self._gdfs = []
|
|
158
153
|
new_labels = []
|
|
@@ -165,31 +160,20 @@ class Map:
|
|
|
165
160
|
if not len(gdf):
|
|
166
161
|
continue
|
|
167
162
|
|
|
168
|
-
self._gdfs.append(gdf)
|
|
163
|
+
self._gdfs.append(to_gdf(gdf))
|
|
169
164
|
new_labels.append(label)
|
|
170
165
|
self.show.append(show)
|
|
171
166
|
self.labels = new_labels
|
|
172
167
|
|
|
173
|
-
# if len(self._gdfs):
|
|
174
|
-
# last_show = self.show[-1]
|
|
175
|
-
# else:
|
|
176
|
-
# last_show = show
|
|
177
|
-
|
|
178
168
|
# pop all geometry-like items from kwargs into self._gdfs
|
|
179
169
|
self.kwargs = {}
|
|
180
170
|
i = 0
|
|
181
171
|
for key, value in kwargs.items():
|
|
182
|
-
# if isinstance(value, GeoDataFrame):
|
|
183
|
-
# self._gdfs.append(value)
|
|
184
|
-
# self.labels.append(key)
|
|
185
|
-
# try:
|
|
186
|
-
# show = show_kwargs[i]
|
|
187
|
-
# except IndexError:
|
|
188
|
-
# pass
|
|
189
|
-
# self.show.append(show)
|
|
190
|
-
# i += 1
|
|
191
|
-
# continue
|
|
192
172
|
try:
|
|
173
|
+
if isinstance(value, Geometry):
|
|
174
|
+
value = to_gdf(value)
|
|
175
|
+
if not len(value):
|
|
176
|
+
continue
|
|
193
177
|
self._gdfs.append(to_gdf(value))
|
|
194
178
|
self.labels.append(key)
|
|
195
179
|
try:
|
|
@@ -207,11 +191,11 @@ class Map:
|
|
|
207
191
|
f"length as gdfs ({len(gdfs)}). Got len {len(show)}"
|
|
208
192
|
)
|
|
209
193
|
|
|
210
|
-
if not any(len(gdf) for gdf in self._gdfs):
|
|
211
|
-
|
|
212
|
-
self._gdfs = None
|
|
194
|
+
if not self._gdfs or not any(len(gdf) for gdf in self._gdfs):
|
|
195
|
+
self._gdfs = []
|
|
213
196
|
self._is_categorical = True
|
|
214
197
|
self._unique_values = []
|
|
198
|
+
self._nan_idx = []
|
|
215
199
|
return
|
|
216
200
|
|
|
217
201
|
if not self.labels:
|
|
@@ -241,6 +225,10 @@ class Map:
|
|
|
241
225
|
self._nan_idx = self._gdf[self._column].isna()
|
|
242
226
|
self._get_unique_values()
|
|
243
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
|
+
|
|
244
232
|
def _get_unique_values(self) -> None:
|
|
245
233
|
if not self._is_categorical:
|
|
246
234
|
self._unique_values = self._get_unique_floats()
|
|
@@ -262,7 +250,7 @@ class Map:
|
|
|
262
250
|
Because floats don't always equal each other. This will make very
|
|
263
251
|
similar values count as the same value in the color classification.
|
|
264
252
|
"""
|
|
265
|
-
array = self._gdf.loc[~self._nan_idx, self._column]
|
|
253
|
+
array = self._gdf.loc[list(~self._nan_idx), self._column]
|
|
266
254
|
self._min = np.min(array)
|
|
267
255
|
self._max = np.max(array)
|
|
268
256
|
self._get_multiplier(array)
|
|
@@ -313,29 +301,70 @@ class Map:
|
|
|
313
301
|
# make sure they are lists
|
|
314
302
|
bins = [bin_ for bin_ in bins]
|
|
315
303
|
|
|
316
|
-
if min(bins) > 0 and min(
|
|
317
|
-
|
|
318
|
-
):
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
304
|
+
if min(bins) > 0 and min(
|
|
305
|
+
self._gdf.loc[list(~self._nan_idx), self._column]
|
|
306
|
+
) < min(bins):
|
|
307
|
+
num = min(self._gdf.loc[list(~self._nan_idx), self._column])
|
|
308
|
+
# if isinstance(num, float):
|
|
309
|
+
# num -= (
|
|
310
|
+
# float(f"1e-{abs(self.legend.rounding)}")
|
|
311
|
+
# if self.legend and self.legend.rounding
|
|
312
|
+
# else 0
|
|
313
|
+
# )
|
|
314
|
+
bins = [num] + bins
|
|
315
|
+
|
|
316
|
+
if min(bins) < 0 and min(
|
|
317
|
+
self._gdf.loc[list(~self._nan_idx), self._column]
|
|
318
|
+
) < min(bins):
|
|
319
|
+
num = min(self._gdf.loc[list(~self._nan_idx), self._column])
|
|
320
|
+
# if isinstance(num, float):
|
|
321
|
+
# num -= (
|
|
322
|
+
# float(f"1e-{abs(self.legend.rounding)}")
|
|
323
|
+
# if self.legend and self.legend.rounding
|
|
324
|
+
# else 0
|
|
325
|
+
# )
|
|
326
|
+
bins = [num] + bins
|
|
325
327
|
|
|
326
328
|
if max(bins) > 0 and max(
|
|
327
329
|
self._gdf.loc[self._gdf[self._column].notna(), self._column]
|
|
328
330
|
) > max(bins):
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
331
|
+
num = max(self._gdf.loc[self._gdf[self._column].notna(), self._column])
|
|
332
|
+
# if isinstance(num, float):
|
|
333
|
+
# num += (
|
|
334
|
+
# float(f"1e-{abs(self.legend.rounding)}")
|
|
335
|
+
# if self.legend and self.legend.rounding
|
|
336
|
+
# else 0
|
|
337
|
+
# )
|
|
338
|
+
bins = bins + [num]
|
|
332
339
|
|
|
333
340
|
if max(bins) < 0 and max(
|
|
334
341
|
self._gdf.loc[self._gdf[self._column].notna(), self._column]
|
|
335
342
|
) < max(bins):
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
343
|
+
num = max(self._gdf.loc[self._gdf[self._column].notna(), self._column])
|
|
344
|
+
# if isinstance(num, float):
|
|
345
|
+
# num += (
|
|
346
|
+
# float(f"1e-{abs(self.legend.rounding)}")
|
|
347
|
+
# if self.legend and self.legend.rounding
|
|
348
|
+
# else 0
|
|
349
|
+
# )
|
|
350
|
+
|
|
351
|
+
bins = bins + [num]
|
|
352
|
+
|
|
353
|
+
def adjust_bin(num: int | float, i: int) -> int | float:
|
|
354
|
+
if isinstance(num, int):
|
|
355
|
+
return num
|
|
356
|
+
adjuster = (
|
|
357
|
+
float(f"1e-{abs(self.legend.rounding)}")
|
|
358
|
+
if self.legend and self.legend.rounding
|
|
359
|
+
else 0
|
|
360
|
+
)
|
|
361
|
+
if i == 0:
|
|
362
|
+
return num - adjuster
|
|
363
|
+
elif i == len(bins) - 1:
|
|
364
|
+
return num + adjuster
|
|
365
|
+
return num
|
|
366
|
+
|
|
367
|
+
bins = [adjust_bin(x, i) for i, x in enumerate(bins)]
|
|
339
368
|
|
|
340
369
|
return bins
|
|
341
370
|
|
|
@@ -347,16 +376,26 @@ class Map:
|
|
|
347
376
|
) -> tuple[tuple[GeoDataFrame], str]:
|
|
348
377
|
"""Separate GeoDataFrames from string (column argument)."""
|
|
349
378
|
|
|
350
|
-
def as_dict(obj):
|
|
379
|
+
def as_dict(obj) -> dict:
|
|
351
380
|
if hasattr(obj, "__dict__"):
|
|
352
381
|
return obj.__dict__
|
|
353
382
|
elif isinstance(obj, dict):
|
|
354
383
|
return obj
|
|
355
|
-
raise TypeError
|
|
356
|
-
|
|
357
|
-
allowed_types = (
|
|
384
|
+
raise TypeError(type(obj))
|
|
385
|
+
|
|
386
|
+
allowed_types = (
|
|
387
|
+
GeoDataFrame,
|
|
388
|
+
GeoSeries,
|
|
389
|
+
Geometry,
|
|
390
|
+
RasterDataset,
|
|
391
|
+
ImageCollection,
|
|
392
|
+
Image,
|
|
393
|
+
Band,
|
|
394
|
+
)
|
|
358
395
|
|
|
359
|
-
gdfs
|
|
396
|
+
gdfs = ()
|
|
397
|
+
more_gdfs = {}
|
|
398
|
+
i = 0
|
|
360
399
|
for arg in args:
|
|
361
400
|
if isinstance(arg, str):
|
|
362
401
|
if column is None:
|
|
@@ -367,12 +406,31 @@ class Map:
|
|
|
367
406
|
)
|
|
368
407
|
elif isinstance(arg, allowed_types):
|
|
369
408
|
gdfs = gdfs + (arg,)
|
|
409
|
+
# elif isinstance(arg, Sequence) and not isinstance(arg, str):
|
|
370
410
|
elif isinstance(arg, dict) or hasattr(arg, "__dict__"):
|
|
371
411
|
# add dicts or classes with GeoDataFrames to kwargs
|
|
372
|
-
more_gdfs = {}
|
|
373
412
|
for key, value in as_dict(arg).items():
|
|
374
413
|
if isinstance(value, allowed_types):
|
|
375
414
|
more_gdfs[key] = value
|
|
415
|
+
elif isinstance(value, dict) or hasattr(value, "__dict__"):
|
|
416
|
+
# elif isinstance(value, Sequence) and not isinstance(value, str):
|
|
417
|
+
try:
|
|
418
|
+
# same as above, one level down
|
|
419
|
+
more_gdfs |= {
|
|
420
|
+
k: v
|
|
421
|
+
for k, v in as_dict(value).items()
|
|
422
|
+
if isinstance(v, allowed_types)
|
|
423
|
+
}
|
|
424
|
+
except Exception:
|
|
425
|
+
# ignore all exceptions
|
|
426
|
+
pass
|
|
427
|
+
|
|
428
|
+
elif isinstance(arg, Sequence) and not isinstance(arg, str):
|
|
429
|
+
# add dicts or classes with GeoDataFrames to kwargs
|
|
430
|
+
for value in arg:
|
|
431
|
+
if isinstance(value, allowed_types):
|
|
432
|
+
name = _determine_best_name(value, column, i)
|
|
433
|
+
more_gdfs[name] = value
|
|
376
434
|
elif isinstance(value, dict) or hasattr(value, "__dict__"):
|
|
377
435
|
try:
|
|
378
436
|
# same as above, one level down
|
|
@@ -384,8 +442,15 @@ class Map:
|
|
|
384
442
|
except Exception:
|
|
385
443
|
# no need to raise here
|
|
386
444
|
pass
|
|
445
|
+
elif isinstance(value, Sequence) and not isinstance(value, str):
|
|
446
|
+
for x in value:
|
|
447
|
+
if not isinstance(x, allowed_types):
|
|
448
|
+
continue
|
|
449
|
+
name = _determine_best_name(value, column, i)
|
|
450
|
+
more_gdfs[name] = x
|
|
451
|
+
i += 1
|
|
387
452
|
|
|
388
|
-
|
|
453
|
+
kwargs |= more_gdfs
|
|
389
454
|
|
|
390
455
|
return gdfs, column, kwargs
|
|
391
456
|
|
|
@@ -394,7 +459,7 @@ class Map:
|
|
|
394
459
|
if self.scheme is None:
|
|
395
460
|
return
|
|
396
461
|
|
|
397
|
-
if
|
|
462
|
+
if self.bins is None:
|
|
398
463
|
self.bins = self._create_bins(self._gdf, self._column)
|
|
399
464
|
if len(self.bins) <= self._k and len(self.bins) != len(self._unique_values):
|
|
400
465
|
self._k = len(self.bins)
|
|
@@ -406,17 +471,6 @@ class Map:
|
|
|
406
471
|
self._unique_values = self.nan_label
|
|
407
472
|
self._k = 1
|
|
408
473
|
|
|
409
|
-
def _get_labels(self, gdfs: tuple[GeoDataFrame]) -> None:
|
|
410
|
-
"""Putting the labels/names in a list before copying the gdfs."""
|
|
411
|
-
self.labels: list[str] = []
|
|
412
|
-
for i, gdf in enumerate(gdfs):
|
|
413
|
-
if hasattr(gdf, "name") and isinstance(gdf.name, str):
|
|
414
|
-
name = gdf.name
|
|
415
|
-
else:
|
|
416
|
-
name = get_object_name(gdf)
|
|
417
|
-
name = name or str(i)
|
|
418
|
-
self.labels.append(name)
|
|
419
|
-
|
|
420
474
|
def _set_labels(self) -> None:
|
|
421
475
|
"""Setting the labels after copying the gdfs."""
|
|
422
476
|
gdfs = []
|
|
@@ -467,10 +521,21 @@ class Map:
|
|
|
467
521
|
|
|
468
522
|
def _check_if_categorical(self) -> bool:
|
|
469
523
|
"""Quite messy this..."""
|
|
470
|
-
if not self._column:
|
|
524
|
+
if not self._column or not self._gdfs:
|
|
471
525
|
return True
|
|
472
526
|
|
|
527
|
+
def is_maybe_km2():
|
|
528
|
+
if "area" in self._column and (
|
|
529
|
+
"km2" in self._column
|
|
530
|
+
or "kilomet" in self._column
|
|
531
|
+
and ("sq" in self._column or "2" in self._column)
|
|
532
|
+
):
|
|
533
|
+
return True
|
|
534
|
+
else:
|
|
535
|
+
return False
|
|
536
|
+
|
|
473
537
|
maybe_area = 1 if "area" in self._column else 0
|
|
538
|
+
maybe_area_km2 = 1 if is_maybe_km2() else 0
|
|
474
539
|
maybe_length = (
|
|
475
540
|
1 if any(x in self._column for x in ["meter", "metre", "leng"]) else 0
|
|
476
541
|
)
|
|
@@ -479,7 +544,10 @@ class Map:
|
|
|
479
544
|
col_not_present = 0
|
|
480
545
|
for gdf in self._gdfs:
|
|
481
546
|
if self._column not in gdf:
|
|
482
|
-
if
|
|
547
|
+
if maybe_area_km2 and unit_is_meters(gdf):
|
|
548
|
+
gdf["area_km2"] = gdf.area / 1_000_000
|
|
549
|
+
maybe_area_km2 += 1
|
|
550
|
+
elif maybe_area:
|
|
483
551
|
gdf["area"] = gdf.area
|
|
484
552
|
maybe_area += 1
|
|
485
553
|
elif maybe_length:
|
|
@@ -492,6 +560,9 @@ class Map:
|
|
|
492
560
|
all_nan += 1
|
|
493
561
|
return True
|
|
494
562
|
|
|
563
|
+
if maybe_area_km2 > 1:
|
|
564
|
+
self._column = "area_km2"
|
|
565
|
+
return False
|
|
495
566
|
if maybe_area > 1:
|
|
496
567
|
self._column = "area"
|
|
497
568
|
return False
|
|
@@ -500,14 +571,16 @@ class Map:
|
|
|
500
571
|
return False
|
|
501
572
|
|
|
502
573
|
if all_nan == len(self._gdfs):
|
|
503
|
-
raise ValueError(
|
|
574
|
+
raise ValueError(
|
|
575
|
+
f"All values are NaN in column {self.column!r}. {self._gdfs}"
|
|
576
|
+
)
|
|
504
577
|
|
|
505
578
|
if col_not_present == len(self._gdfs):
|
|
506
579
|
raise ValueError(f"{self.column} not found.")
|
|
507
580
|
|
|
508
581
|
return False
|
|
509
582
|
|
|
510
|
-
def
|
|
583
|
+
def _make_categories_colors_dict(self) -> None:
|
|
511
584
|
# custom categorical cmap
|
|
512
585
|
if not self._cmap and len(self._unique_values) <= len(_CATEGORICAL_CMAP):
|
|
513
586
|
self._categories_colors_dict = {
|
|
@@ -529,6 +602,7 @@ class Map:
|
|
|
529
602
|
for i, category in enumerate(self._unique_values)
|
|
530
603
|
}
|
|
531
604
|
|
|
605
|
+
def _fix_nans(self) -> None:
|
|
532
606
|
if any(self._nan_idx):
|
|
533
607
|
self._gdf[self._column] = self._gdf[self._column].fillna(self.nan_label)
|
|
534
608
|
self._categories_colors_dict[self.nan_label] = self.nan_color
|
|
@@ -549,7 +623,7 @@ class Map:
|
|
|
549
623
|
If 'scheme' is not specified, the jenks_breaks function is used, which is
|
|
550
624
|
much faster than the one from Mapclassifier.
|
|
551
625
|
"""
|
|
552
|
-
if not len(gdf.loc[~self._nan_idx, column]):
|
|
626
|
+
if not len(gdf.loc[list(~self._nan_idx), column]):
|
|
553
627
|
return np.array([0])
|
|
554
628
|
|
|
555
629
|
n_classes = (
|
|
@@ -565,29 +639,26 @@ class Map:
|
|
|
565
639
|
n_classes = len(self._unique_values)
|
|
566
640
|
|
|
567
641
|
if self.scheme == "jenks":
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
)
|
|
572
|
-
bins = self._add_minmax_to_bins(bins)
|
|
573
|
-
except Exception:
|
|
574
|
-
pass
|
|
642
|
+
bins = jenks_breaks(
|
|
643
|
+
gdf.loc[list(~self._nan_idx), column], n_classes=n_classes
|
|
644
|
+
)
|
|
575
645
|
else:
|
|
576
646
|
binning = classify(
|
|
577
|
-
np.asarray(gdf.loc[~self._nan_idx, column]),
|
|
647
|
+
np.asarray(gdf.loc[list(~self._nan_idx), column]),
|
|
578
648
|
scheme=self.scheme,
|
|
579
|
-
k=self._k,
|
|
649
|
+
# k=self._k,
|
|
650
|
+
k=n_classes,
|
|
580
651
|
)
|
|
581
652
|
bins = binning.bins
|
|
582
|
-
|
|
653
|
+
|
|
654
|
+
bins = self._add_minmax_to_bins(bins)
|
|
583
655
|
|
|
584
656
|
unique_bins = list({round(bin_, 5) for bin_ in bins})
|
|
585
657
|
unique_bins.sort()
|
|
586
658
|
|
|
587
|
-
if self._k == len(self._unique_values) - 1
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
if len(unique_bins) == len(self._unique_values):
|
|
659
|
+
if self._k == len(self._unique_values) - 1 or len(unique_bins) == len(
|
|
660
|
+
self._unique_values
|
|
661
|
+
):
|
|
591
662
|
return np.array(unique_bins)
|
|
592
663
|
|
|
593
664
|
if len(unique_bins) == len(bins) - 1:
|
|
@@ -624,6 +695,8 @@ class Map:
|
|
|
624
695
|
|
|
625
696
|
def _classify_from_bins(self, gdf: GeoDataFrame, bins: np.ndarray) -> np.ndarray:
|
|
626
697
|
"""Place the column values into groups."""
|
|
698
|
+
bins = bins.copy()
|
|
699
|
+
|
|
627
700
|
# if equal lenght, convert to integer and check for equality
|
|
628
701
|
if len(bins) == len(self._unique_values):
|
|
629
702
|
if gdf[self._column].isna().all():
|
|
@@ -638,6 +711,14 @@ class Map:
|
|
|
638
711
|
if len(bins) == self._k + 1:
|
|
639
712
|
bins = bins[1:]
|
|
640
713
|
|
|
714
|
+
if (
|
|
715
|
+
self.legend
|
|
716
|
+
and self.legend.rounding
|
|
717
|
+
and (self.legend.rounding or 1) <= 0
|
|
718
|
+
):
|
|
719
|
+
bins[0] = bins[0] - 1
|
|
720
|
+
bins[-1] = bins[-1] + 1
|
|
721
|
+
|
|
641
722
|
if gdf[self._column].isna().all():
|
|
642
723
|
return np.repeat(len(bins), len(gdf))
|
|
643
724
|
|
|
@@ -686,7 +767,8 @@ class Map:
|
|
|
686
767
|
@cmap.setter
|
|
687
768
|
def cmap(self, new_value: str) -> None:
|
|
688
769
|
self._cmap = new_value
|
|
689
|
-
|
|
770
|
+
if not self._is_categorical:
|
|
771
|
+
self.change_cmap(cmap=new_value, start=self.cmap_start, stop=self.cmap_stop)
|
|
690
772
|
|
|
691
773
|
@property
|
|
692
774
|
def gdf(self) -> GeoDataFrame:
|
|
@@ -738,3 +820,24 @@ class Map:
|
|
|
738
820
|
return self[key]
|
|
739
821
|
except (KeyError, ValueError, IndexError, AttributeError):
|
|
740
822
|
return default
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
def _determine_best_name(obj: Any, column: str | None, i: int) -> str:
|
|
826
|
+
try:
|
|
827
|
+
# Frame 3: actual object name Frame 2: maps.py:explore(). Frame 1: __init__. Frame 0: this function.
|
|
828
|
+
return str(get_object_name(obj, start=3))
|
|
829
|
+
except ValueError:
|
|
830
|
+
if isinstance(obj, GeoSeries) and obj.name:
|
|
831
|
+
return str(obj.name)
|
|
832
|
+
elif isinstance(obj, GeoDataFrame) and len(obj.columns) == 2 and not column:
|
|
833
|
+
series = obj.drop(columns=obj._geometry_column_name).iloc[:, 0]
|
|
834
|
+
if (
|
|
835
|
+
len(series.unique()) == 1
|
|
836
|
+
and mean(isinstance(x, str) for x in series) > 0.5
|
|
837
|
+
):
|
|
838
|
+
return str(next(iter(series)))
|
|
839
|
+
elif series.name:
|
|
840
|
+
return str(series.name)
|
|
841
|
+
else:
|
|
842
|
+
# generic label e.g. Image(1)
|
|
843
|
+
return f"{obj.__class__.__name__}({i})"
|