ssb-sgis 1.0.1__py3-none-any.whl → 1.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sgis/__init__.py +107 -121
- sgis/exceptions.py +5 -3
- sgis/geopandas_tools/__init__.py +1 -0
- sgis/geopandas_tools/bounds.py +86 -47
- sgis/geopandas_tools/buffer_dissolve_explode.py +62 -39
- sgis/geopandas_tools/centerlines.py +53 -44
- sgis/geopandas_tools/cleaning.py +87 -104
- sgis/geopandas_tools/conversion.py +164 -107
- sgis/geopandas_tools/duplicates.py +33 -19
- sgis/geopandas_tools/general.py +84 -52
- sgis/geopandas_tools/geometry_types.py +24 -10
- sgis/geopandas_tools/neighbors.py +23 -11
- sgis/geopandas_tools/overlay.py +136 -53
- sgis/geopandas_tools/point_operations.py +11 -10
- sgis/geopandas_tools/polygon_operations.py +53 -61
- sgis/geopandas_tools/polygons_as_rings.py +121 -78
- sgis/geopandas_tools/sfilter.py +17 -17
- sgis/helpers.py +116 -58
- sgis/io/dapla_functions.py +32 -23
- sgis/io/opener.py +13 -6
- sgis/io/read_parquet.py +2 -2
- sgis/maps/examine.py +55 -28
- sgis/maps/explore.py +471 -112
- sgis/maps/httpserver.py +12 -12
- sgis/maps/legend.py +285 -134
- sgis/maps/map.py +248 -129
- sgis/maps/maps.py +123 -119
- sgis/maps/thematicmap.py +260 -94
- sgis/maps/tilesources.py +3 -8
- sgis/networkanalysis/_get_route.py +5 -4
- sgis/networkanalysis/_od_cost_matrix.py +44 -1
- sgis/networkanalysis/_points.py +10 -4
- sgis/networkanalysis/_service_area.py +5 -2
- sgis/networkanalysis/closing_network_holes.py +22 -64
- sgis/networkanalysis/cutting_lines.py +58 -46
- sgis/networkanalysis/directednetwork.py +16 -8
- sgis/networkanalysis/finding_isolated_networks.py +6 -5
- sgis/networkanalysis/network.py +15 -13
- sgis/networkanalysis/networkanalysis.py +79 -61
- sgis/networkanalysis/networkanalysisrules.py +21 -17
- sgis/networkanalysis/nodes.py +2 -3
- sgis/networkanalysis/traveling_salesman.py +6 -3
- sgis/parallel/parallel.py +372 -142
- sgis/raster/base.py +9 -3
- sgis/raster/cube.py +331 -213
- sgis/raster/cubebase.py +15 -29
- sgis/raster/image_collection.py +2560 -0
- sgis/raster/indices.py +17 -12
- sgis/raster/raster.py +356 -275
- sgis/raster/sentinel_config.py +104 -0
- sgis/raster/zonal.py +38 -14
- {ssb_sgis-1.0.1.dist-info → ssb_sgis-1.0.3.dist-info}/LICENSE +1 -1
- {ssb_sgis-1.0.1.dist-info → ssb_sgis-1.0.3.dist-info}/METADATA +87 -16
- ssb_sgis-1.0.3.dist-info/RECORD +61 -0
- {ssb_sgis-1.0.1.dist-info → ssb_sgis-1.0.3.dist-info}/WHEEL +1 -1
- sgis/raster/bands.py +0 -48
- sgis/raster/gradient.py +0 -78
- sgis/raster/methods_as_functions.py +0 -124
- sgis/raster/torchgeo.py +0 -150
- ssb_sgis-1.0.1.dist-info/RECORD +0 -63
sgis/maps/map.py
CHANGED
|
@@ -4,32 +4,37 @@ 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
|
|
9
|
+
from typing import Any
|
|
7
10
|
|
|
8
11
|
import matplotlib
|
|
9
12
|
import matplotlib.colors as colors
|
|
10
13
|
import numpy as np
|
|
11
14
|
import pandas as pd
|
|
12
|
-
from geopandas import GeoDataFrame
|
|
15
|
+
from geopandas import GeoDataFrame
|
|
16
|
+
from geopandas import GeoSeries
|
|
13
17
|
from jenkspy import jenks_breaks
|
|
14
18
|
from mapclassify import classify
|
|
15
19
|
from shapely import Geometry
|
|
16
20
|
|
|
17
21
|
from ..geopandas_tools.conversion import to_gdf
|
|
18
|
-
from ..geopandas_tools.general import
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
rename_geometry_if,
|
|
23
|
-
)
|
|
22
|
+
from ..geopandas_tools.general import _rename_geometry_if
|
|
23
|
+
from ..geopandas_tools.general import clean_geoms
|
|
24
|
+
from ..geopandas_tools.general import drop_inactive_geometry_columns
|
|
25
|
+
from ..geopandas_tools.general import get_common_crs
|
|
24
26
|
from ..helpers import get_object_name
|
|
25
|
-
|
|
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
|
|
26
31
|
|
|
27
32
|
try:
|
|
28
33
|
from torchgeo.datasets.geo import RasterDataset
|
|
29
34
|
except ImportError:
|
|
30
35
|
|
|
31
36
|
class RasterDataset:
|
|
32
|
-
"""Placeholder"""
|
|
37
|
+
"""Placeholder."""
|
|
33
38
|
|
|
34
39
|
|
|
35
40
|
# the geopandas._explore raises a deprication warning. Ignoring for now.
|
|
@@ -60,8 +65,16 @@ _CATEGORICAL_CMAP = {
|
|
|
60
65
|
DEFAULT_SCHEME = "quantiles"
|
|
61
66
|
|
|
62
67
|
|
|
63
|
-
def proper_fillna(val, fill_val):
|
|
64
|
-
"""fillna doesn't
|
|
68
|
+
def proper_fillna(val: Any, fill_val: Any) -> Any:
|
|
69
|
+
"""Manually handle missing values when fillna doesn't work as expected.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
val: The value to check and fill.
|
|
73
|
+
fill_val: The value to fill in.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
The original value or the filled value if conditions are met.
|
|
77
|
+
"""
|
|
65
78
|
try:
|
|
66
79
|
if "NAType" in val.__class__.__name__:
|
|
67
80
|
return fill_val
|
|
@@ -84,15 +97,28 @@ class Map:
|
|
|
84
97
|
self,
|
|
85
98
|
*gdfs: GeoDataFrame,
|
|
86
99
|
column: str | None = None,
|
|
87
|
-
labels: tuple[str] | None = None,
|
|
88
100
|
k: int = 5,
|
|
89
101
|
bins: tuple[float] | None = None,
|
|
90
102
|
nan_label: str = "Missing",
|
|
91
103
|
nan_color="#c2c2c2",
|
|
92
104
|
scheme: str = DEFAULT_SCHEME,
|
|
105
|
+
cmap: str | None = None,
|
|
93
106
|
**kwargs,
|
|
94
|
-
):
|
|
107
|
+
) -> None:
|
|
108
|
+
"""Initialiser.
|
|
95
109
|
|
|
110
|
+
Args:
|
|
111
|
+
*gdfs: Variable length GeoDataFrame list.
|
|
112
|
+
column: The column name to work with.
|
|
113
|
+
k: Number of bins or classes for classification (default: 5).
|
|
114
|
+
bins: Predefined bins for data classification.
|
|
115
|
+
nan_label: Label for missing data.
|
|
116
|
+
nan_color: Color for missing data.
|
|
117
|
+
scheme: Classification scheme to be used.
|
|
118
|
+
cmap (str): Colormap of the plot. See:
|
|
119
|
+
https://matplotlib.org/stable/tutorials/colors/colormaps.html
|
|
120
|
+
**kwargs: Arbitrary keyword arguments.
|
|
121
|
+
"""
|
|
96
122
|
gdfs, column, kwargs = self._separate_args(gdfs, column, kwargs)
|
|
97
123
|
|
|
98
124
|
self._column = column
|
|
@@ -100,27 +126,14 @@ class Map:
|
|
|
100
126
|
self._k = k
|
|
101
127
|
self.nan_label = nan_label
|
|
102
128
|
self.nan_color = nan_color
|
|
103
|
-
self._cmap =
|
|
129
|
+
self._cmap = cmap
|
|
104
130
|
self.scheme = scheme
|
|
105
131
|
|
|
106
|
-
if not all(isinstance(gdf, GeoDataFrame) for gdf in gdfs):
|
|
107
|
-
gdfs = [
|
|
108
|
-
to_gdf(gdf) if not isinstance(gdf, GeoDataFrame) else gdf
|
|
109
|
-
for gdf in gdfs
|
|
110
|
-
]
|
|
111
|
-
if not all(isinstance(gdf, GeoDataFrame) for gdf in gdfs):
|
|
112
|
-
raise ValueError("gdfs must be GeoDataFrames.")
|
|
113
|
-
|
|
114
|
-
if "namedict" in kwargs:
|
|
115
|
-
for i, gdf in enumerate(gdfs):
|
|
116
|
-
gdf.name = kwargs["namedict"][i]
|
|
117
|
-
kwargs.pop("namedict")
|
|
118
|
-
|
|
119
132
|
# need to get the object names of the gdfs before copying. Only getting,
|
|
120
133
|
# not setting, labels. So the original gdfs don't get the label column.
|
|
121
|
-
self.labels =
|
|
122
|
-
|
|
123
|
-
|
|
134
|
+
self.labels: list[str] = [
|
|
135
|
+
_determine_best_name(gdf, column, i) for i, gdf in enumerate(gdfs)
|
|
136
|
+
]
|
|
124
137
|
|
|
125
138
|
show = kwargs.pop("show", True)
|
|
126
139
|
if isinstance(show, (int, bool)):
|
|
@@ -138,7 +151,7 @@ class Map:
|
|
|
138
151
|
self._gdfs = []
|
|
139
152
|
new_labels = []
|
|
140
153
|
self.show = []
|
|
141
|
-
for label, gdf, show in zip(self.labels, gdfs, show_args):
|
|
154
|
+
for label, gdf, show in zip(self.labels, gdfs, show_args, strict=False):
|
|
142
155
|
if not len(gdf):
|
|
143
156
|
continue
|
|
144
157
|
|
|
@@ -146,31 +159,18 @@ class Map:
|
|
|
146
159
|
if not len(gdf):
|
|
147
160
|
continue
|
|
148
161
|
|
|
149
|
-
self._gdfs.append(gdf)
|
|
162
|
+
self._gdfs.append(to_gdf(gdf))
|
|
150
163
|
new_labels.append(label)
|
|
151
164
|
self.show.append(show)
|
|
152
165
|
self.labels = new_labels
|
|
153
166
|
|
|
154
|
-
# if len(self._gdfs):
|
|
155
|
-
# last_show = self.show[-1]
|
|
156
|
-
# else:
|
|
157
|
-
# last_show = show
|
|
158
|
-
|
|
159
167
|
# pop all geometry-like items from kwargs into self._gdfs
|
|
160
168
|
self.kwargs = {}
|
|
161
169
|
i = 0
|
|
162
170
|
for key, value in kwargs.items():
|
|
163
|
-
# if isinstance(value, GeoDataFrame):
|
|
164
|
-
# self._gdfs.append(value)
|
|
165
|
-
# self.labels.append(key)
|
|
166
|
-
# try:
|
|
167
|
-
# show = show_kwargs[i]
|
|
168
|
-
# except IndexError:
|
|
169
|
-
# pass
|
|
170
|
-
# self.show.append(show)
|
|
171
|
-
# i += 1
|
|
172
|
-
# continue
|
|
173
171
|
try:
|
|
172
|
+
if not len(value):
|
|
173
|
+
continue
|
|
174
174
|
self._gdfs.append(to_gdf(value))
|
|
175
175
|
self.labels.append(key)
|
|
176
176
|
try:
|
|
@@ -189,10 +189,10 @@ class Map:
|
|
|
189
189
|
)
|
|
190
190
|
|
|
191
191
|
if not any(len(gdf) for gdf in self._gdfs):
|
|
192
|
-
|
|
193
|
-
self._gdfs = None
|
|
192
|
+
self._gdfs = []
|
|
194
193
|
self._is_categorical = True
|
|
195
194
|
self._unique_values = []
|
|
195
|
+
self._nan_idx = []
|
|
196
196
|
return
|
|
197
197
|
|
|
198
198
|
if not self.labels:
|
|
@@ -222,7 +222,7 @@ class Map:
|
|
|
222
222
|
self._nan_idx = self._gdf[self._column].isna()
|
|
223
223
|
self._get_unique_values()
|
|
224
224
|
|
|
225
|
-
def _get_unique_values(self):
|
|
225
|
+
def _get_unique_values(self) -> None:
|
|
226
226
|
if not self._is_categorical:
|
|
227
227
|
self._unique_values = self._get_unique_floats()
|
|
228
228
|
else:
|
|
@@ -243,7 +243,7 @@ class Map:
|
|
|
243
243
|
Because floats don't always equal each other. This will make very
|
|
244
244
|
similar values count as the same value in the color classification.
|
|
245
245
|
"""
|
|
246
|
-
array = self._gdf.loc[~self._nan_idx, self._column]
|
|
246
|
+
array = self._gdf.loc[list(~self._nan_idx), self._column]
|
|
247
247
|
self._min = np.min(array)
|
|
248
248
|
self._max = np.max(array)
|
|
249
249
|
self._get_multiplier(array)
|
|
@@ -254,7 +254,7 @@ class Map:
|
|
|
254
254
|
|
|
255
255
|
return np.sort(np.array(unique.loc[no_duplicates.index]))
|
|
256
256
|
|
|
257
|
-
def _array_to_large_int(self, array):
|
|
257
|
+
def _array_to_large_int(self, array: np.ndarray | pd.Series) -> pd.Series:
|
|
258
258
|
"""Multiply values in float array, then convert to integer."""
|
|
259
259
|
if not isinstance(array, pd.Series):
|
|
260
260
|
array = pd.Series(array)
|
|
@@ -266,9 +266,8 @@ class Map:
|
|
|
266
266
|
|
|
267
267
|
return pd.concat([unique_multiplied, isna]).sort_index()
|
|
268
268
|
|
|
269
|
-
def _get_multiplier(self, array: np.ndarray):
|
|
270
|
-
"""Find the number of zeros needed to push the max value of the array above
|
|
271
|
-
+-1_000_000.
|
|
269
|
+
def _get_multiplier(self, array: np.ndarray) -> None:
|
|
270
|
+
"""Find the number of zeros needed to push the max value of the array above +-1_000_000.
|
|
272
271
|
|
|
273
272
|
Adding this as an attribute to use later in _classify_from_bins.
|
|
274
273
|
"""
|
|
@@ -293,31 +292,72 @@ class Map:
|
|
|
293
292
|
def _add_minmax_to_bins(self, bins: list[float | int]) -> list[float | int]:
|
|
294
293
|
"""If values are outside the bin range, add max and/or min values of array."""
|
|
295
294
|
# make sure they are lists
|
|
296
|
-
bins = [
|
|
297
|
-
|
|
298
|
-
if min(bins) > 0 and min(
|
|
299
|
-
|
|
300
|
-
):
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
295
|
+
bins = [bin_ for bin_ in bins]
|
|
296
|
+
|
|
297
|
+
if min(bins) > 0 and min(
|
|
298
|
+
self._gdf.loc[list(~self._nan_idx), self._column]
|
|
299
|
+
) < min(bins):
|
|
300
|
+
num = min(self._gdf.loc[list(~self._nan_idx), self._column])
|
|
301
|
+
# if isinstance(num, float):
|
|
302
|
+
# num -= (
|
|
303
|
+
# float(f"1e-{abs(self.legend.rounding)}")
|
|
304
|
+
# if self.legend and self.legend.rounding
|
|
305
|
+
# else 0
|
|
306
|
+
# )
|
|
307
|
+
bins = [num] + bins
|
|
308
|
+
|
|
309
|
+
if min(bins) < 0 and min(
|
|
310
|
+
self._gdf.loc[list(~self._nan_idx), self._column]
|
|
311
|
+
) < min(bins):
|
|
312
|
+
num = min(self._gdf.loc[list(~self._nan_idx), self._column])
|
|
313
|
+
# if isinstance(num, float):
|
|
314
|
+
# num -= (
|
|
315
|
+
# float(f"1e-{abs(self.legend.rounding)}")
|
|
316
|
+
# if self.legend and self.legend.rounding
|
|
317
|
+
# else 0
|
|
318
|
+
# )
|
|
319
|
+
bins = [num] + bins
|
|
307
320
|
|
|
308
321
|
if max(bins) > 0 and max(
|
|
309
322
|
self._gdf.loc[self._gdf[self._column].notna(), self._column]
|
|
310
323
|
) > max(bins):
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
324
|
+
num = max(self._gdf.loc[self._gdf[self._column].notna(), self._column])
|
|
325
|
+
# if isinstance(num, float):
|
|
326
|
+
# num += (
|
|
327
|
+
# float(f"1e-{abs(self.legend.rounding)}")
|
|
328
|
+
# if self.legend and self.legend.rounding
|
|
329
|
+
# else 0
|
|
330
|
+
# )
|
|
331
|
+
bins = bins + [num]
|
|
314
332
|
|
|
315
333
|
if max(bins) < 0 and max(
|
|
316
334
|
self._gdf.loc[self._gdf[self._column].notna(), self._column]
|
|
317
335
|
) < max(bins):
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
336
|
+
num = max(self._gdf.loc[self._gdf[self._column].notna(), self._column])
|
|
337
|
+
# if isinstance(num, float):
|
|
338
|
+
# num += (
|
|
339
|
+
# float(f"1e-{abs(self.legend.rounding)}")
|
|
340
|
+
# if self.legend and self.legend.rounding
|
|
341
|
+
# else 0
|
|
342
|
+
# )
|
|
343
|
+
|
|
344
|
+
bins = bins + [num]
|
|
345
|
+
|
|
346
|
+
def adjust_bin(num: int | float, i: int) -> int | float:
|
|
347
|
+
if isinstance(num, int):
|
|
348
|
+
return num
|
|
349
|
+
adjuster = (
|
|
350
|
+
float(f"1e-{abs(self.legend.rounding)}")
|
|
351
|
+
if self.legend and self.legend.rounding
|
|
352
|
+
else 0
|
|
353
|
+
)
|
|
354
|
+
if i == 0:
|
|
355
|
+
return num - adjuster
|
|
356
|
+
elif i == len(bins) - 1:
|
|
357
|
+
return num + adjuster
|
|
358
|
+
return num
|
|
359
|
+
|
|
360
|
+
bins = [adjust_bin(x, i) for i, x in enumerate(bins)]
|
|
321
361
|
|
|
322
362
|
return bins
|
|
323
363
|
|
|
@@ -329,16 +369,26 @@ class Map:
|
|
|
329
369
|
) -> tuple[tuple[GeoDataFrame], str]:
|
|
330
370
|
"""Separate GeoDataFrames from string (column argument)."""
|
|
331
371
|
|
|
332
|
-
def as_dict(obj):
|
|
372
|
+
def as_dict(obj) -> dict:
|
|
333
373
|
if hasattr(obj, "__dict__"):
|
|
334
374
|
return obj.__dict__
|
|
335
375
|
elif isinstance(obj, dict):
|
|
336
376
|
return obj
|
|
337
|
-
raise TypeError
|
|
338
|
-
|
|
339
|
-
allowed_types = (
|
|
377
|
+
raise TypeError(type(obj))
|
|
378
|
+
|
|
379
|
+
allowed_types = (
|
|
380
|
+
GeoDataFrame,
|
|
381
|
+
GeoSeries,
|
|
382
|
+
Geometry,
|
|
383
|
+
RasterDataset,
|
|
384
|
+
ImageCollection,
|
|
385
|
+
Image,
|
|
386
|
+
Band,
|
|
387
|
+
)
|
|
340
388
|
|
|
341
|
-
gdfs
|
|
389
|
+
gdfs = ()
|
|
390
|
+
more_gdfs = {}
|
|
391
|
+
i = 0
|
|
342
392
|
for arg in args:
|
|
343
393
|
if isinstance(arg, str):
|
|
344
394
|
if column is None:
|
|
@@ -349,12 +399,31 @@ class Map:
|
|
|
349
399
|
)
|
|
350
400
|
elif isinstance(arg, allowed_types):
|
|
351
401
|
gdfs = gdfs + (arg,)
|
|
402
|
+
# elif isinstance(arg, Sequence) and not isinstance(arg, str):
|
|
352
403
|
elif isinstance(arg, dict) or hasattr(arg, "__dict__"):
|
|
353
404
|
# add dicts or classes with GeoDataFrames to kwargs
|
|
354
|
-
more_gdfs = {}
|
|
355
405
|
for key, value in as_dict(arg).items():
|
|
356
406
|
if isinstance(value, allowed_types):
|
|
357
407
|
more_gdfs[key] = value
|
|
408
|
+
elif isinstance(value, dict) or hasattr(value, "__dict__"):
|
|
409
|
+
# elif isinstance(value, Sequence) and not isinstance(value, str):
|
|
410
|
+
try:
|
|
411
|
+
# same as above, one level down
|
|
412
|
+
more_gdfs |= {
|
|
413
|
+
k: v
|
|
414
|
+
for k, v in as_dict(value).items()
|
|
415
|
+
if isinstance(v, allowed_types)
|
|
416
|
+
}
|
|
417
|
+
except Exception:
|
|
418
|
+
# ignore all exceptions
|
|
419
|
+
pass
|
|
420
|
+
|
|
421
|
+
elif isinstance(arg, Sequence) and not isinstance(arg, str):
|
|
422
|
+
# add dicts or classes with GeoDataFrames to kwargs
|
|
423
|
+
for value in arg:
|
|
424
|
+
if isinstance(value, allowed_types):
|
|
425
|
+
name = _determine_best_name(value, column, i)
|
|
426
|
+
more_gdfs[name] = value
|
|
358
427
|
elif isinstance(value, dict) or hasattr(value, "__dict__"):
|
|
359
428
|
try:
|
|
360
429
|
# same as above, one level down
|
|
@@ -366,18 +435,24 @@ class Map:
|
|
|
366
435
|
except Exception:
|
|
367
436
|
# no need to raise here
|
|
368
437
|
pass
|
|
438
|
+
elif isinstance(value, Sequence) and not isinstance(value, str):
|
|
439
|
+
for x in value:
|
|
440
|
+
if not isinstance(x, allowed_types):
|
|
441
|
+
continue
|
|
442
|
+
name = _determine_best_name(value, column, i)
|
|
443
|
+
more_gdfs[name] = x
|
|
444
|
+
i += 1
|
|
369
445
|
|
|
370
|
-
|
|
446
|
+
kwargs |= more_gdfs
|
|
371
447
|
|
|
372
448
|
return gdfs, column, kwargs
|
|
373
449
|
|
|
374
|
-
def _prepare_continous_map(self):
|
|
450
|
+
def _prepare_continous_map(self) -> None:
|
|
375
451
|
"""Create bins if not already done and adjust k if needed."""
|
|
376
|
-
|
|
377
452
|
if self.scheme is None:
|
|
378
453
|
return
|
|
379
454
|
|
|
380
|
-
if
|
|
455
|
+
if self.bins is None:
|
|
381
456
|
self.bins = self._create_bins(self._gdf, self._column)
|
|
382
457
|
if len(self.bins) <= self._k and len(self.bins) != len(self._unique_values):
|
|
383
458
|
self._k = len(self.bins)
|
|
@@ -389,17 +464,6 @@ class Map:
|
|
|
389
464
|
self._unique_values = self.nan_label
|
|
390
465
|
self._k = 1
|
|
391
466
|
|
|
392
|
-
def _get_labels(self, gdfs: tuple[GeoDataFrame]) -> None:
|
|
393
|
-
"""Putting the labels/names in a list before copying the gdfs."""
|
|
394
|
-
self.labels: list[str] = []
|
|
395
|
-
for i, gdf in enumerate(gdfs):
|
|
396
|
-
if hasattr(gdf, "name") and isinstance(gdf.name, str):
|
|
397
|
-
name = gdf.name
|
|
398
|
-
else:
|
|
399
|
-
name = get_object_name(gdf)
|
|
400
|
-
name = name or str(i)
|
|
401
|
-
self.labels.append(name)
|
|
402
|
-
|
|
403
467
|
def _set_labels(self) -> None:
|
|
404
468
|
"""Setting the labels after copying the gdfs."""
|
|
405
469
|
gdfs = []
|
|
@@ -408,7 +472,9 @@ class Map:
|
|
|
408
472
|
gdfs.append(gdf)
|
|
409
473
|
self._gdfs = gdfs
|
|
410
474
|
|
|
411
|
-
def _to_common_crs_and_one_geom_col(
|
|
475
|
+
def _to_common_crs_and_one_geom_col(
|
|
476
|
+
self, gdfs: list[GeoDataFrame]
|
|
477
|
+
) -> list[GeoDataFrame]:
|
|
412
478
|
"""Need common crs and max one geometry column."""
|
|
413
479
|
crs_list = list({gdf.crs for gdf in gdfs if gdf.crs is not None})
|
|
414
480
|
if crs_list:
|
|
@@ -416,7 +482,7 @@ class Map:
|
|
|
416
482
|
new_gdfs = []
|
|
417
483
|
for gdf in gdfs:
|
|
418
484
|
gdf = gdf.reset_index(drop=True)
|
|
419
|
-
gdf = drop_inactive_geometry_columns(gdf).pipe(
|
|
485
|
+
gdf = drop_inactive_geometry_columns(gdf).pipe(_rename_geometry_if)
|
|
420
486
|
if crs_list:
|
|
421
487
|
try:
|
|
422
488
|
gdf = gdf.to_crs(self.crs)
|
|
@@ -451,7 +517,18 @@ class Map:
|
|
|
451
517
|
if not self._column:
|
|
452
518
|
return True
|
|
453
519
|
|
|
520
|
+
def is_maybe_km2():
|
|
521
|
+
if "area" in self._column and (
|
|
522
|
+
"km2" in self._column
|
|
523
|
+
or "kilomet" in self._column
|
|
524
|
+
and ("sq" in self._column or "2" in self._column)
|
|
525
|
+
):
|
|
526
|
+
return True
|
|
527
|
+
else:
|
|
528
|
+
return False
|
|
529
|
+
|
|
454
530
|
maybe_area = 1 if "area" in self._column else 0
|
|
531
|
+
maybe_area_km2 = 1 if is_maybe_km2() else 0
|
|
455
532
|
maybe_length = (
|
|
456
533
|
1 if any(x in self._column for x in ["meter", "metre", "leng"]) else 0
|
|
457
534
|
)
|
|
@@ -460,7 +537,10 @@ class Map:
|
|
|
460
537
|
col_not_present = 0
|
|
461
538
|
for gdf in self._gdfs:
|
|
462
539
|
if self._column not in gdf:
|
|
463
|
-
if
|
|
540
|
+
if maybe_area_km2 and unit_is_meters(gdf):
|
|
541
|
+
gdf["area_km2"] = gdf.area / 1_000_000
|
|
542
|
+
maybe_area_km2 += 1
|
|
543
|
+
elif maybe_area:
|
|
464
544
|
gdf["area"] = gdf.area
|
|
465
545
|
maybe_area += 1
|
|
466
546
|
elif maybe_length:
|
|
@@ -473,6 +553,9 @@ class Map:
|
|
|
473
553
|
all_nan += 1
|
|
474
554
|
return True
|
|
475
555
|
|
|
556
|
+
if maybe_area_km2 > 1:
|
|
557
|
+
self._column = "area_km2"
|
|
558
|
+
return False
|
|
476
559
|
if maybe_area > 1:
|
|
477
560
|
self._column = "area"
|
|
478
561
|
return False
|
|
@@ -488,7 +571,7 @@ class Map:
|
|
|
488
571
|
|
|
489
572
|
return False
|
|
490
573
|
|
|
491
|
-
def
|
|
574
|
+
def _make_categories_colors_dict(self) -> None:
|
|
492
575
|
# custom categorical cmap
|
|
493
576
|
if not self._cmap and len(self._unique_values) <= len(_CATEGORICAL_CMAP):
|
|
494
577
|
self._categories_colors_dict = {
|
|
@@ -510,6 +593,7 @@ class Map:
|
|
|
510
593
|
for i, category in enumerate(self._unique_values)
|
|
511
594
|
}
|
|
512
595
|
|
|
596
|
+
def _fix_nans(self) -> None:
|
|
513
597
|
if any(self._nan_idx):
|
|
514
598
|
self._gdf[self._column] = self._gdf[self._column].fillna(self.nan_label)
|
|
515
599
|
self._categories_colors_dict[self.nan_label] = self.nan_color
|
|
@@ -530,8 +614,7 @@ class Map:
|
|
|
530
614
|
If 'scheme' is not specified, the jenks_breaks function is used, which is
|
|
531
615
|
much faster than the one from Mapclassifier.
|
|
532
616
|
"""
|
|
533
|
-
|
|
534
|
-
if not len(gdf.loc[~self._nan_idx, column]):
|
|
617
|
+
if not len(gdf.loc[list(~self._nan_idx), column]):
|
|
535
618
|
return np.array([0])
|
|
536
619
|
|
|
537
620
|
n_classes = (
|
|
@@ -547,29 +630,26 @@ class Map:
|
|
|
547
630
|
n_classes = len(self._unique_values)
|
|
548
631
|
|
|
549
632
|
if self.scheme == "jenks":
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
)
|
|
554
|
-
bins = self._add_minmax_to_bins(bins)
|
|
555
|
-
except Exception:
|
|
556
|
-
pass
|
|
633
|
+
bins = jenks_breaks(
|
|
634
|
+
gdf.loc[list(~self._nan_idx), column], n_classes=n_classes
|
|
635
|
+
)
|
|
557
636
|
else:
|
|
558
637
|
binning = classify(
|
|
559
|
-
np.asarray(gdf.loc[~self._nan_idx, column]),
|
|
638
|
+
np.asarray(gdf.loc[list(~self._nan_idx), column]),
|
|
560
639
|
scheme=self.scheme,
|
|
561
|
-
k=self._k,
|
|
640
|
+
# k=self._k,
|
|
641
|
+
k=n_classes,
|
|
562
642
|
)
|
|
563
643
|
bins = binning.bins
|
|
564
|
-
bins = self._add_minmax_to_bins(bins)
|
|
565
644
|
|
|
566
|
-
|
|
567
|
-
unique_bins.sort()
|
|
645
|
+
bins = self._add_minmax_to_bins(bins)
|
|
568
646
|
|
|
569
|
-
|
|
570
|
-
|
|
647
|
+
unique_bins = list({round(bin_, 5) for bin_ in bins})
|
|
648
|
+
unique_bins.sort()
|
|
571
649
|
|
|
572
|
-
if len(unique_bins) == len(
|
|
650
|
+
if self._k == len(self._unique_values) - 1 or len(unique_bins) == len(
|
|
651
|
+
self._unique_values
|
|
652
|
+
):
|
|
573
653
|
return np.array(unique_bins)
|
|
574
654
|
|
|
575
655
|
if len(unique_bins) == len(bins) - 1:
|
|
@@ -577,7 +657,7 @@ class Map:
|
|
|
577
657
|
|
|
578
658
|
return np.array(bins)
|
|
579
659
|
|
|
580
|
-
def change_cmap(self, cmap: str, start: int = 0, stop: int = 256):
|
|
660
|
+
def change_cmap(self, cmap: str, start: int = 0, stop: int = 256) -> "Map":
|
|
581
661
|
"""Change the color palette of the plot.
|
|
582
662
|
|
|
583
663
|
Args:
|
|
@@ -606,6 +686,7 @@ class Map:
|
|
|
606
686
|
|
|
607
687
|
def _classify_from_bins(self, gdf: GeoDataFrame, bins: np.ndarray) -> np.ndarray:
|
|
608
688
|
"""Place the column values into groups."""
|
|
689
|
+
bins = bins.copy()
|
|
609
690
|
|
|
610
691
|
# if equal lenght, convert to integer and check for equality
|
|
611
692
|
if len(bins) == len(self._unique_values):
|
|
@@ -621,6 +702,14 @@ class Map:
|
|
|
621
702
|
if len(bins) == self._k + 1:
|
|
622
703
|
bins = bins[1:]
|
|
623
704
|
|
|
705
|
+
if (
|
|
706
|
+
self.legend
|
|
707
|
+
and self.legend.rounding
|
|
708
|
+
and (self.legend.rounding or 1) <= 0
|
|
709
|
+
):
|
|
710
|
+
bins[0] = bins[0] - 1
|
|
711
|
+
bins[-1] = bins[-1] + 1
|
|
712
|
+
|
|
624
713
|
if gdf[self._column].isna().all():
|
|
625
714
|
return np.repeat(len(bins), len(gdf))
|
|
626
715
|
|
|
@@ -647,11 +736,12 @@ class Map:
|
|
|
647
736
|
return np.array([rank_dict[val] for val in classified])
|
|
648
737
|
|
|
649
738
|
@property
|
|
650
|
-
def k(self):
|
|
739
|
+
def k(self) -> int:
|
|
740
|
+
"""Number of bins."""
|
|
651
741
|
return self._k
|
|
652
742
|
|
|
653
743
|
@k.setter
|
|
654
|
-
def k(self, new_value:
|
|
744
|
+
def k(self, new_value: int) -> None:
|
|
655
745
|
if not self._is_categorical and new_value > len(self._unique_values):
|
|
656
746
|
raise ValueError(
|
|
657
747
|
"'k' cannot be greater than the number of unique values in the column.'"
|
|
@@ -661,55 +751,84 @@ class Map:
|
|
|
661
751
|
self._k = int(new_value)
|
|
662
752
|
|
|
663
753
|
@property
|
|
664
|
-
def cmap(self):
|
|
754
|
+
def cmap(self) -> str:
|
|
755
|
+
"""Colormap."""
|
|
665
756
|
return self._cmap
|
|
666
757
|
|
|
667
758
|
@cmap.setter
|
|
668
|
-
def cmap(self, new_value:
|
|
759
|
+
def cmap(self, new_value: str) -> None:
|
|
669
760
|
self._cmap = new_value
|
|
670
|
-
|
|
761
|
+
if not self._is_categorical:
|
|
762
|
+
self.change_cmap(cmap=new_value, start=self.cmap_start, stop=self.cmap_stop)
|
|
671
763
|
|
|
672
764
|
@property
|
|
673
|
-
def gdf(self):
|
|
765
|
+
def gdf(self) -> GeoDataFrame:
|
|
766
|
+
"""All GeoDataFrames concated."""
|
|
674
767
|
return self._gdf
|
|
675
768
|
|
|
676
769
|
@gdf.setter
|
|
677
|
-
def gdf(self, _):
|
|
770
|
+
def gdf(self, _) -> None:
|
|
678
771
|
raise ValueError(
|
|
679
772
|
"Cannot change 'gdf' after init. Put the GeoDataFrames into "
|
|
680
773
|
"the class initialiser."
|
|
681
774
|
)
|
|
682
775
|
|
|
683
776
|
@property
|
|
684
|
-
def gdfs(self):
|
|
777
|
+
def gdfs(self) -> list[GeoDataFrame]:
|
|
778
|
+
"""All GeoDataFrames as a list."""
|
|
685
779
|
return self._gdfs
|
|
686
780
|
|
|
687
781
|
@gdfs.setter
|
|
688
|
-
def gdfs(self, _):
|
|
782
|
+
def gdfs(self, _) -> None:
|
|
689
783
|
raise ValueError(
|
|
690
784
|
"Cannot change 'gdfs' after init. Put the GeoDataFrames into "
|
|
691
785
|
"the class initialiser."
|
|
692
786
|
)
|
|
693
787
|
|
|
694
788
|
@property
|
|
695
|
-
def column(self):
|
|
789
|
+
def column(self) -> str | None:
|
|
790
|
+
"""Column to use as colormap."""
|
|
696
791
|
return self._column
|
|
697
792
|
|
|
698
793
|
@column.setter
|
|
699
|
-
def column(self, _):
|
|
794
|
+
def column(self, _) -> None:
|
|
700
795
|
raise ValueError(
|
|
701
796
|
"Cannot change 'column' after init. Specify 'column' in the "
|
|
702
797
|
"class initialiser."
|
|
703
798
|
)
|
|
704
799
|
|
|
705
|
-
def __setitem__(self, item, new_item):
|
|
800
|
+
def __setitem__(self, item: Any, new_item: Any) -> None:
|
|
801
|
+
"""Set an attribute with square brackets."""
|
|
706
802
|
return setattr(self, item, new_item)
|
|
707
803
|
|
|
708
|
-
def __getitem__(self, item):
|
|
804
|
+
def __getitem__(self, item: Any) -> Any:
|
|
805
|
+
"""Get an attribute with square brackets."""
|
|
709
806
|
return getattr(self, item)
|
|
710
807
|
|
|
711
|
-
def get(self, key, default=None):
|
|
808
|
+
def get(self, key: Any, default: Any | None = None) -> Any:
|
|
809
|
+
"""Get an attribute with default value if not present."""
|
|
712
810
|
try:
|
|
713
811
|
return self[key]
|
|
714
812
|
except (KeyError, ValueError, IndexError, AttributeError):
|
|
715
813
|
return default
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
def _determine_best_name(obj: Any, column: str | None, i: int) -> str:
|
|
817
|
+
try:
|
|
818
|
+
# Frame 3: actual object name Frame 2: maps.py:explore(). Frame 1: __init__. Frame 0: this function.
|
|
819
|
+
return str(get_object_name(obj, start=3))
|
|
820
|
+
except ValueError:
|
|
821
|
+
if isinstance(obj, GeoSeries) and obj.name:
|
|
822
|
+
return str(obj.name)
|
|
823
|
+
elif isinstance(obj, GeoDataFrame) and len(obj.columns) == 2 and not column:
|
|
824
|
+
series = obj.drop(columns=obj._geometry_column_name).iloc[:, 0]
|
|
825
|
+
if (
|
|
826
|
+
len(series.unique()) == 1
|
|
827
|
+
and mean(isinstance(x, str) for x in series) > 0.5
|
|
828
|
+
):
|
|
829
|
+
return str(next(iter(series)))
|
|
830
|
+
elif series.name:
|
|
831
|
+
return str(series.name)
|
|
832
|
+
else:
|
|
833
|
+
# generic label e.g. Image(1)
|
|
834
|
+
return f"{obj.__class__.__name__}({i})"
|