ssb-sgis 0.1.4__py3-none-any.whl → 0.1.6__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/geopandas_tools/general.py +18 -7
- sgis/geopandas_tools/geometry_types.py +1 -1
- sgis/geopandas_tools/line_operations.py +1 -1
- sgis/geopandas_tools/overlay.py +11 -0
- sgis/maps/explore.py +62 -99
- sgis/maps/legend.py +300 -203
- sgis/maps/map.py +69 -74
- sgis/maps/maps.py +9 -5
- sgis/maps/thematicmap.py +145 -61
- sgis/networkanalysis/_get_route.py +168 -114
- sgis/networkanalysis/_od_cost_matrix.py +7 -9
- sgis/networkanalysis/_points.py +0 -18
- sgis/networkanalysis/directednetwork.py +2 -2
- sgis/networkanalysis/network.py +16 -25
- sgis/networkanalysis/networkanalysis.py +301 -123
- sgis/networkanalysis/networkanalysisrules.py +2 -2
- {ssb_sgis-0.1.4.dist-info → ssb_sgis-0.1.6.dist-info}/METADATA +17 -9
- ssb_sgis-0.1.6.dist-info/RECORD +35 -0
- ssb_sgis-0.1.4.dist-info/RECORD +0 -35
- {ssb_sgis-0.1.4.dist-info → ssb_sgis-0.1.6.dist-info}/LICENSE +0 -0
- {ssb_sgis-0.1.4.dist-info → ssb_sgis-0.1.6.dist-info}/WHEEL +0 -0
sgis/maps/map.py
CHANGED
|
@@ -119,53 +119,68 @@ class Map:
|
|
|
119
119
|
list(self._gdf.loc[~self._nan_idx, self._column].unique())
|
|
120
120
|
)
|
|
121
121
|
|
|
122
|
-
def _get_unique_floats(self) ->
|
|
123
|
-
"""
|
|
122
|
+
def _get_unique_floats(self) -> np.array:
|
|
123
|
+
"""Get unique floats by multiplying, then converting to integer.
|
|
124
124
|
|
|
125
|
-
|
|
125
|
+
Find a multiplier that makes the max value greater than +- 1_000_000.
|
|
126
|
+
Because floats don't always equal each other. This will make very
|
|
127
|
+
similar values count as the same value in the color classification.
|
|
126
128
|
"""
|
|
127
129
|
array = self._gdf.loc[~self._nan_idx, self._column]
|
|
128
130
|
|
|
129
|
-
self.
|
|
131
|
+
self._min = np.min(array)
|
|
132
|
+
self._max = np.max(array)
|
|
133
|
+
self._get_multiplier(array)
|
|
130
134
|
|
|
131
135
|
unique = array.reset_index(drop=True).drop_duplicates()
|
|
132
|
-
|
|
133
136
|
as_int = self._array_to_large_int(unique)
|
|
134
137
|
no_duplicates = as_int.drop_duplicates()
|
|
135
|
-
return list(sorted(unique.loc[no_duplicates.index]))
|
|
136
138
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
+
return np.sort(np.array(unique.loc[no_duplicates.index]))
|
|
140
|
+
|
|
141
|
+
def _array_to_large_int(self, array: np.ndarray):
|
|
139
142
|
"""Multiply values in float array, then convert to integer."""
|
|
140
|
-
max_ = np.max(array)
|
|
141
|
-
min_ = np.min(array)
|
|
142
143
|
|
|
143
|
-
|
|
144
|
-
unique_multiplied = array * np.emath.logn(1.25, np.abs(np.mean(array)) + 1)
|
|
145
|
-
else:
|
|
146
|
-
unique_multiplied = array
|
|
147
|
-
while max_ < 1_000_000:
|
|
148
|
-
unique_multiplied = unique_multiplied * 10
|
|
149
|
-
max_ = np.max(unique_multiplied)
|
|
144
|
+
unique_multiplied = array * self._multiplier
|
|
150
145
|
|
|
151
146
|
return unique_multiplied.astype(np.int64)
|
|
152
147
|
|
|
148
|
+
def _get_multiplier(self, array: np.ndarray):
|
|
149
|
+
"""Find the number of zeros needed to push the max value of the array above
|
|
150
|
+
+-1_000_000.
|
|
151
|
+
|
|
152
|
+
Adding this as an attribute to use later in _classify_from_bins.
|
|
153
|
+
"""
|
|
154
|
+
multiplier = 10
|
|
155
|
+
max_ = np.max(array * multiplier)
|
|
156
|
+
|
|
157
|
+
if self._max > 0:
|
|
158
|
+
while max_ < 1_000_000:
|
|
159
|
+
multiplier *= 10
|
|
160
|
+
max_ = np.max(array * multiplier)
|
|
161
|
+
else:
|
|
162
|
+
while max_ > -1_000_000:
|
|
163
|
+
multiplier *= 10
|
|
164
|
+
max_ = np.max(array * multiplier)
|
|
165
|
+
|
|
166
|
+
self._multiplier: int = multiplier
|
|
167
|
+
|
|
153
168
|
def _add_minmax_to_bins(self, bins: list[float | int]) -> list[float | int]:
|
|
154
169
|
"""If values are outside the bin range, add max and/or min values of array."""
|
|
155
170
|
# make sure they are lists
|
|
156
171
|
bins = [bin for bin in bins]
|
|
157
172
|
|
|
158
|
-
if min(bins) > 0 and min(self._gdf[self._column]) < min(bins)
|
|
159
|
-
bins = [min(self._gdf[self._column])
|
|
173
|
+
if min(bins) > 0 and min(self._gdf[self._column]) < min(bins):
|
|
174
|
+
bins = [min(self._gdf[self._column])] + bins
|
|
160
175
|
|
|
161
|
-
if min(bins) < 0 and min(self._gdf[self._column]) < min(bins)
|
|
162
|
-
bins = [min(self._gdf[self._column])
|
|
176
|
+
if min(bins) < 0 and min(self._gdf[self._column]) < min(bins):
|
|
177
|
+
bins = [min(self._gdf[self._column])] + bins
|
|
163
178
|
|
|
164
|
-
if max(bins) > 0 and max(self._gdf[self._column]) > max(bins)
|
|
165
|
-
bins = bins + [max(self._gdf[self._column])
|
|
179
|
+
if max(bins) > 0 and max(self._gdf[self._column]) > max(bins):
|
|
180
|
+
bins = bins + [max(self._gdf[self._column])]
|
|
166
181
|
|
|
167
|
-
if max(bins) < 0 and max(self._gdf[self._column]) < max(bins)
|
|
168
|
-
bins = bins + [max(self._gdf[self._column])
|
|
182
|
+
if max(bins) < 0 and max(self._gdf[self._column]) < max(bins):
|
|
183
|
+
bins = bins + [max(self._gdf[self._column])]
|
|
169
184
|
|
|
170
185
|
return bins
|
|
171
186
|
|
|
@@ -194,7 +209,7 @@ class Map:
|
|
|
194
209
|
"""Create bins if not already done and adjust k if needed."""
|
|
195
210
|
|
|
196
211
|
if not hasattr(self, "scheme"):
|
|
197
|
-
self.scheme = self.kwargs.
|
|
212
|
+
self.scheme = self.kwargs.pop("scheme", "fisherjenks")
|
|
198
213
|
|
|
199
214
|
if self.scheme is None:
|
|
200
215
|
return
|
|
@@ -204,7 +219,6 @@ class Map:
|
|
|
204
219
|
if len(self.bins) <= self._k and len(self.bins) != len(self._unique_values):
|
|
205
220
|
warnings.warn(f"Could not create {self._k} classes.")
|
|
206
221
|
self._k = len(self.bins)
|
|
207
|
-
self.bins = self._add_minmax_to_bins(self.bins)
|
|
208
222
|
else:
|
|
209
223
|
self.bins = self._add_minmax_to_bins(self.bins)
|
|
210
224
|
if len(self._unique_values) > len(self.bins):
|
|
@@ -285,10 +299,10 @@ class Map:
|
|
|
285
299
|
return False
|
|
286
300
|
|
|
287
301
|
if all_nan == len(self._gdfs):
|
|
288
|
-
raise ValueError(f"All values are NaN in column {self.
|
|
302
|
+
raise ValueError(f"All values are NaN in column {self.column!r}.")
|
|
289
303
|
|
|
290
304
|
if col_not_present == len(self._gdfs):
|
|
291
|
-
raise ValueError(f"{self.
|
|
305
|
+
raise ValueError(f"{self.column} not found.")
|
|
292
306
|
|
|
293
307
|
return False
|
|
294
308
|
|
|
@@ -323,7 +337,7 @@ class Map:
|
|
|
323
337
|
|
|
324
338
|
self._gdf["color"] = self._gdf[self._column].map(self._categories_colors_dict)
|
|
325
339
|
|
|
326
|
-
def _create_bins(self, gdf, column) -> np.ndarray:
|
|
340
|
+
def _create_bins(self, gdf: GeoDataFrame, column: str) -> np.ndarray:
|
|
327
341
|
"""Make bin list of length k + 1, or length of unique values.
|
|
328
342
|
|
|
329
343
|
The returned bins sometimes have two almost identical
|
|
@@ -332,14 +346,6 @@ class Map:
|
|
|
332
346
|
much faster than the one from Mapclassifier.
|
|
333
347
|
"""
|
|
334
348
|
|
|
335
|
-
if hasattr(self, "scheme"):
|
|
336
|
-
scheme = self.scheme
|
|
337
|
-
else:
|
|
338
|
-
scheme = self.kwargs.get("scheme", "fisherjenks")
|
|
339
|
-
|
|
340
|
-
if scheme is None:
|
|
341
|
-
return
|
|
342
|
-
|
|
343
349
|
n_classes = (
|
|
344
350
|
self._k if len(self._unique_values) > self._k else len(self._unique_values)
|
|
345
351
|
)
|
|
@@ -347,12 +353,12 @@ class Map:
|
|
|
347
353
|
if self._k == len(self._unique_values) - 1:
|
|
348
354
|
n_classes = self._k - 1
|
|
349
355
|
|
|
350
|
-
if scheme == "fisherjenks":
|
|
356
|
+
if self.scheme == "fisherjenks":
|
|
351
357
|
bins = jenks_breaks(gdf.loc[~self._nan_idx, column], n_classes=n_classes)
|
|
352
358
|
else:
|
|
353
359
|
binning = classify(
|
|
354
360
|
np.asarray(gdf.loc[~self._nan_idx, column]),
|
|
355
|
-
scheme=scheme,
|
|
361
|
+
scheme=self.scheme,
|
|
356
362
|
k=self._k,
|
|
357
363
|
)
|
|
358
364
|
bins = binning.bins
|
|
@@ -367,13 +373,7 @@ class Map:
|
|
|
367
373
|
if len(unique_bins) == len(self._unique_values):
|
|
368
374
|
return np.array(unique_bins)
|
|
369
375
|
|
|
370
|
-
|
|
371
|
-
binarray = np.where(
|
|
372
|
-
binarray > 0,
|
|
373
|
-
binarray + binarray / 100_000,
|
|
374
|
-
binarray - binarray / 100_000,
|
|
375
|
-
)
|
|
376
|
-
return binarray
|
|
376
|
+
return np.array(bins)
|
|
377
377
|
|
|
378
378
|
def change_cmap(self, cmap: str, start: int = 0, stop: int = 256):
|
|
379
379
|
"""Change the color palette of the plot.
|
|
@@ -391,47 +391,42 @@ class Map:
|
|
|
391
391
|
self._cmap_has_been_set = True
|
|
392
392
|
return self
|
|
393
393
|
|
|
394
|
-
def _get_continous_colors(self) ->
|
|
394
|
+
def _get_continous_colors(self, n: int) -> np.ndarray:
|
|
395
395
|
cmap = matplotlib.colormaps.get_cmap(self._cmap)
|
|
396
396
|
colors_ = [
|
|
397
397
|
colors.to_hex(cmap(int(i)))
|
|
398
|
-
for i in np.linspace(self.cmap_start, self.cmap_stop, num=self._k)
|
|
398
|
+
# for i in np.linspace(self.cmap_start, self.cmap_stop, num=self._k)
|
|
399
|
+
for i in np.linspace(self.cmap_start, self.cmap_stop, num=n)
|
|
399
400
|
]
|
|
400
401
|
if any(self._nan_idx):
|
|
401
402
|
colors_ = colors_ + [self.nan_color]
|
|
402
|
-
return colors_
|
|
403
|
-
|
|
404
|
-
def _classify_from_bins(self, gdf: GeoDataFrame) -> np.ndarray:
|
|
405
|
-
"""Place the values
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
gdf["col_as_int"] = self._array_to_large_int(gdf[self._column])
|
|
411
|
-
bins = np.array(sorted(gdf["col_as_int"].unique()))
|
|
403
|
+
return np.array(colors_)
|
|
404
|
+
|
|
405
|
+
def _classify_from_bins(self, gdf: GeoDataFrame, bins: np.ndarray) -> np.ndarray:
|
|
406
|
+
"""Place the column values into groups."""
|
|
407
|
+
if len(bins) == len(self._unique_values):
|
|
408
|
+
# if equal lenght, convert to integer and check for equality
|
|
409
|
+
gdf["col_as_int"] = self._array_to_large_int(gdf[self._column])
|
|
410
|
+
bins = self._array_to_large_int(self._unique_values)
|
|
412
411
|
classified = np.searchsorted(bins, gdf["col_as_int"])
|
|
413
412
|
else:
|
|
414
|
-
if
|
|
415
|
-
bins =
|
|
416
|
-
elif not any(self._nan_idx) and len(self.bins) == len(self.colorlist) + 1:
|
|
417
|
-
bins = self.bins[1:]
|
|
418
|
-
else:
|
|
419
|
-
bins = self.bins
|
|
413
|
+
if len(bins) == self._k + 1:
|
|
414
|
+
bins = bins[1:]
|
|
420
415
|
|
|
421
416
|
classified = np.searchsorted(bins, gdf[self._column])
|
|
422
417
|
|
|
423
|
-
|
|
424
|
-
self._bins_unique_values = {
|
|
425
|
-
i: list(set(gdf.loc[classified == i, self._column]))
|
|
426
|
-
for i, _ in enumerate(bins)
|
|
427
|
-
}
|
|
418
|
+
return classified
|
|
428
419
|
|
|
429
|
-
|
|
420
|
+
def _push_classification(self, classified: np.ndarray) -> np.ndarray:
|
|
421
|
+
"""Push classes downwards if gaps in classification sequence.
|
|
430
422
|
|
|
431
|
-
|
|
432
|
-
|
|
423
|
+
So from e.g. [0,2,4] to [0,1,2].
|
|
424
|
+
|
|
425
|
+
Otherwise, will get index error when classifying colors.
|
|
426
|
+
"""
|
|
427
|
+
rank_dict = {val: rank for rank, val in enumerate(np.unique(classified))}
|
|
433
428
|
|
|
434
|
-
return
|
|
429
|
+
return np.array([rank_dict[val] for val in classified])
|
|
435
430
|
|
|
436
431
|
@property
|
|
437
432
|
def k(self):
|
sgis/maps/maps.py
CHANGED
|
@@ -7,8 +7,6 @@ interactive map with layers that can be toggled on and off. The 'samplemap' and
|
|
|
7
7
|
The 'qtm' function shows a static map of one or more GeoDataFrames.
|
|
8
8
|
"""
|
|
9
9
|
from geopandas import GeoDataFrame, GeoSeries
|
|
10
|
-
from matplotlib.axes._axes import Axes
|
|
11
|
-
from matplotlib.figure import Figure
|
|
12
10
|
from shapely import Geometry
|
|
13
11
|
|
|
14
12
|
from ..exceptions import NotInJupyterError
|
|
@@ -39,12 +37,16 @@ def explore(
|
|
|
39
37
|
show_in_browser: bool = False,
|
|
40
38
|
**kwargs,
|
|
41
39
|
) -> None:
|
|
42
|
-
"""Interactive map of GeoDataFrames with layers that can be
|
|
40
|
+
"""Interactive map of GeoDataFrames with layers that can be toggled on/off.
|
|
43
41
|
|
|
44
42
|
It takes all the given GeoDataFrames and displays them together in an
|
|
45
43
|
interactive map with a common legend. If 'column' is not specified, each
|
|
46
44
|
GeoDataFrame is given a unique color.
|
|
47
45
|
|
|
46
|
+
If the column is of type string and only one GeoDataFrame is given, the unique
|
|
47
|
+
values will be split into separate GeoDataFrames so that each value can be toggled
|
|
48
|
+
on/off.
|
|
49
|
+
|
|
48
50
|
The coloring can be changed with the 'cmap' parameter. The default colormap is a
|
|
49
51
|
custom, strongly colored palette. If a numerical colum is given, the 'viridis'
|
|
50
52
|
palette is used.
|
|
@@ -299,6 +301,7 @@ def qtm(
|
|
|
299
301
|
legend: bool = True,
|
|
300
302
|
cmap: str | None = None,
|
|
301
303
|
k: int = 5,
|
|
304
|
+
**kwargs,
|
|
302
305
|
) -> None:
|
|
303
306
|
"""Quick, thematic map of one or more GeoDataFrames.
|
|
304
307
|
|
|
@@ -320,6 +323,7 @@ def qtm(
|
|
|
320
323
|
cmap: Color palette of the map. See:
|
|
321
324
|
https://matplotlib.org/stable/tutorials/colors/colormaps.html
|
|
322
325
|
k: Number of color groups.
|
|
326
|
+
**kwargs: Additional keyword arguments taken by the geopandas plot method.
|
|
323
327
|
|
|
324
328
|
See also:
|
|
325
329
|
ThematicMap: Class with more options for customising the plot.
|
|
@@ -329,7 +333,7 @@ def qtm(
|
|
|
329
333
|
|
|
330
334
|
m.title = title
|
|
331
335
|
|
|
332
|
-
if k and len(m._unique_values) >=
|
|
336
|
+
if k and len(m._unique_values) >= k:
|
|
333
337
|
m.k = k
|
|
334
338
|
|
|
335
339
|
if cmap:
|
|
@@ -338,4 +342,4 @@ def qtm(
|
|
|
338
342
|
if not legend:
|
|
339
343
|
m.legend = None
|
|
340
344
|
|
|
341
|
-
m.plot()
|
|
345
|
+
m.plot(**kwargs)
|
sgis/maps/thematicmap.py
CHANGED
|
@@ -3,10 +3,11 @@ import warnings
|
|
|
3
3
|
|
|
4
4
|
import matplotlib
|
|
5
5
|
import matplotlib.pyplot as plt
|
|
6
|
+
import numpy as np
|
|
6
7
|
import pandas as pd
|
|
7
8
|
from geopandas import GeoDataFrame
|
|
8
9
|
|
|
9
|
-
from .legend import Legend
|
|
10
|
+
from .legend import ContinousLegend, Legend
|
|
10
11
|
from .map import Map
|
|
11
12
|
|
|
12
13
|
|
|
@@ -103,7 +104,7 @@ class ThematicMap(Map):
|
|
|
103
104
|
if not self._is_categorical:
|
|
104
105
|
self._choose_cmap()
|
|
105
106
|
|
|
106
|
-
self.
|
|
107
|
+
self._create_legend()
|
|
107
108
|
|
|
108
109
|
def change_cmap(self, cmap: str, start: int = 0, stop: int = 256):
|
|
109
110
|
"""Change the color palette of the plot.
|
|
@@ -139,12 +140,90 @@ class ThematicMap(Map):
|
|
|
139
140
|
self.diffy = self.maxy - self.miny
|
|
140
141
|
return self
|
|
141
142
|
|
|
142
|
-
def plot(self) -> None:
|
|
143
|
+
def plot(self, **kwargs) -> None:
|
|
143
144
|
"""Creates the final plot.
|
|
144
145
|
|
|
145
146
|
This method should be run after customising the map, but before saving.
|
|
146
147
|
"""
|
|
147
148
|
|
|
149
|
+
__test = kwargs.pop("__test", False)
|
|
150
|
+
include_legend = bool(kwargs.pop("legend", self.legend))
|
|
151
|
+
|
|
152
|
+
if "color" in kwargs:
|
|
153
|
+
kwargs.pop("column", None)
|
|
154
|
+
self.legend = None
|
|
155
|
+
include_legend = False
|
|
156
|
+
elif hasattr(self, "color"):
|
|
157
|
+
kwargs.pop("column", None)
|
|
158
|
+
kwargs["color"] = self.color
|
|
159
|
+
self.legend = None
|
|
160
|
+
include_legend = False
|
|
161
|
+
|
|
162
|
+
elif self._is_categorical:
|
|
163
|
+
kwargs = self._prepare_categorical_plot(kwargs)
|
|
164
|
+
self.legend._prepare_categorical_legend(
|
|
165
|
+
categories_colors=self._categories_colors_dict,
|
|
166
|
+
nan_label=self.nan_label,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
else:
|
|
170
|
+
kwargs = self._prepare_continous_plot(kwargs)
|
|
171
|
+
if self.legend:
|
|
172
|
+
if not self.legend._rounding_has_been_set:
|
|
173
|
+
self.legend._rounding = self.legend._get_rounding(
|
|
174
|
+
array=self._gdf.loc[~self._nan_idx, self._column]
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
self.legend._prepare_continous_legend(
|
|
178
|
+
bins=self.bins,
|
|
179
|
+
colors=self._unique_colors,
|
|
180
|
+
nan_label=self.nan_label,
|
|
181
|
+
bin_values=self._bins_unique_values,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if self.legend and not self.legend._position_has_been_set:
|
|
185
|
+
self.legend._position = self.legend._get_best_legend_position(
|
|
186
|
+
self._gdf, k=self._k + bool(len(self._nan_idx))
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if __test:
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
self._prepare_plot(**kwargs)
|
|
193
|
+
|
|
194
|
+
if self.legend:
|
|
195
|
+
self.ax = self.legend._actually_add_legend(ax=self.ax)
|
|
196
|
+
|
|
197
|
+
# if self.legend:
|
|
198
|
+
# self._actually_add_legend()
|
|
199
|
+
|
|
200
|
+
self._gdf.plot(legend=include_legend, ax=self.ax, **kwargs)
|
|
201
|
+
|
|
202
|
+
def save(self, path: str) -> None:
|
|
203
|
+
"""Save figure as image file.
|
|
204
|
+
|
|
205
|
+
To be run after the plot method.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
path: File path.
|
|
209
|
+
"""
|
|
210
|
+
try:
|
|
211
|
+
plt.savefig(path)
|
|
212
|
+
except FileNotFoundError:
|
|
213
|
+
from dapla import FileClient
|
|
214
|
+
|
|
215
|
+
fs = FileClient.get_gcs_file_system()
|
|
216
|
+
with fs.open(path, "wb") as file:
|
|
217
|
+
plt.savefig(file)
|
|
218
|
+
|
|
219
|
+
def _prepare_plot(self, **kwargs):
|
|
220
|
+
"""Add figure and axis, title and background gdf."""
|
|
221
|
+
for attr in self.__dict__.keys():
|
|
222
|
+
if attr in self.kwargs:
|
|
223
|
+
self[attr] = self.kwargs.pop(attr)
|
|
224
|
+
if attr in kwargs:
|
|
225
|
+
self[attr] = kwargs.pop(attr)
|
|
226
|
+
|
|
148
227
|
self.fig, self.ax = self._get_matplotlib_figure_and_axix(
|
|
149
228
|
figsize=(self._size, self._size)
|
|
150
229
|
)
|
|
@@ -159,85 +238,82 @@ class ThematicMap(Map):
|
|
|
159
238
|
self.title, fontsize=self.title_fontsize, color=self.title_color
|
|
160
239
|
)
|
|
161
240
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
241
|
+
def _prepare_continous_plot(self, kwargs) -> dict:
|
|
242
|
+
"""Create bins and colors."""
|
|
243
|
+
self._prepare_continous_map()
|
|
244
|
+
|
|
245
|
+
if self.scheme is None:
|
|
246
|
+
self.legend = None
|
|
247
|
+
kwargs["column"] = self.column
|
|
248
|
+
return kwargs
|
|
249
|
+
|
|
250
|
+
elif self.bins is None:
|
|
251
|
+
kwargs["column"] = self.column
|
|
252
|
+
return kwargs
|
|
253
|
+
|
|
167
254
|
else:
|
|
168
|
-
self.
|
|
169
|
-
|
|
255
|
+
classified = self._classify_from_bins(self._gdf, bins=self.bins)
|
|
256
|
+
classified_sequential = self._push_classification(classified)
|
|
257
|
+
n_colors = len(np.unique(classified_sequential)) - any(self._nan_idx)
|
|
258
|
+
self._unique_colors = self._get_continous_colors(n=n_colors)
|
|
259
|
+
self._bins_unique_values = self._make_bin_value_dict(
|
|
260
|
+
self._gdf, classified_sequential
|
|
261
|
+
)
|
|
262
|
+
colorarray = self._unique_colors[classified_sequential]
|
|
263
|
+
kwargs["color"] = colorarray
|
|
170
264
|
|
|
171
|
-
if self.legend:
|
|
172
|
-
|
|
173
|
-
self.
|
|
174
|
-
|
|
175
|
-
)
|
|
265
|
+
if self.legend and not self.legend._rounding_has_been_set:
|
|
266
|
+
self.bins = self.legend._set_rounding(
|
|
267
|
+
bins=self.bins, rounding=self.legend._rounding
|
|
268
|
+
)
|
|
176
269
|
|
|
177
|
-
if
|
|
178
|
-
self.
|
|
179
|
-
array=self._gdf.loc[~self._nan_idx, self._column]
|
|
180
|
-
)
|
|
270
|
+
if any(self._nan_idx):
|
|
271
|
+
self.bins = self.bins + [self.nan_label]
|
|
181
272
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
273
|
+
return kwargs
|
|
274
|
+
|
|
275
|
+
def _prepare_categorical_plot(self, kwargs) -> dict:
|
|
276
|
+
"""Map values to colors."""
|
|
277
|
+
self._get_categorical_colors()
|
|
278
|
+
colorarray = self._gdf["color"]
|
|
279
|
+
|
|
280
|
+
kwargs["color"] = colorarray
|
|
281
|
+
return kwargs
|
|
282
|
+
|
|
283
|
+
def _actually_add_legend(self) -> None:
|
|
284
|
+
"""Add legend to the axis and fill it with colors and labels."""
|
|
285
|
+
if not self.legend._position_has_been_set:
|
|
286
|
+
self.legend._position = self.legend._get_best_legend_position(
|
|
287
|
+
self._gdf, k=self._k + bool(len(self._nan_idx))
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
if self._is_categorical:
|
|
185
291
|
self.ax = self.legend._actually_add_categorical_legend(
|
|
186
292
|
ax=self.ax,
|
|
187
293
|
categories_colors=self._categories_colors_dict,
|
|
188
294
|
nan_label=self.nan_label,
|
|
189
295
|
)
|
|
190
|
-
self._include_legend = True
|
|
191
|
-
elif self.scheme is None:
|
|
192
|
-
self._include_legend = True
|
|
193
296
|
else:
|
|
194
|
-
self._include_legend = True
|
|
195
|
-
if not self.legend._rounding_has_been_set:
|
|
196
|
-
self.bins = self.legend._set_rounding(
|
|
197
|
-
bins=self.bins, rounding=self.legend._rounding
|
|
198
|
-
)
|
|
199
|
-
|
|
200
|
-
if any(self._nan_idx):
|
|
201
|
-
self.bins = self.bins + [self.nan_label]
|
|
202
|
-
|
|
203
297
|
self.ax = self.legend._actually_add_continous_legend(
|
|
204
298
|
ax=self.ax,
|
|
205
299
|
bins=self.bins,
|
|
206
|
-
colors=self.
|
|
300
|
+
colors=self._unique_colors,
|
|
207
301
|
nan_label=self.nan_label,
|
|
208
302
|
bin_values=self._bins_unique_values,
|
|
209
303
|
)
|
|
210
304
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
else:
|
|
214
|
-
self._gdf.plot(column=self.column, legend=self._include_legend, ax=self.ax)
|
|
215
|
-
|
|
216
|
-
def save(self, path: str) -> None:
|
|
217
|
-
"""Save figure as image file.
|
|
218
|
-
|
|
219
|
-
To be run after the plot method.
|
|
220
|
-
|
|
221
|
-
Args:
|
|
222
|
-
path: File path.
|
|
223
|
-
"""
|
|
224
|
-
try:
|
|
225
|
-
plt.savefig(path)
|
|
226
|
-
except FileNotFoundError:
|
|
227
|
-
from dapla import FileClient
|
|
228
|
-
|
|
229
|
-
fs = FileClient.get_gcs_file_system()
|
|
230
|
-
with fs.open(path, "wb") as file:
|
|
231
|
-
plt.savefig(file)
|
|
232
|
-
|
|
233
|
-
def _add_legend(self):
|
|
305
|
+
def _create_legend(self):
|
|
306
|
+
"""Instantiate the Legend class."""
|
|
234
307
|
kwargs = {}
|
|
235
308
|
if self._black:
|
|
236
309
|
kwargs["facecolor"] = "#0f0f0f"
|
|
237
310
|
kwargs["labelcolor"] = "#fefefe"
|
|
238
311
|
kwargs["title_color"] = "#fefefe"
|
|
239
312
|
|
|
240
|
-
|
|
313
|
+
if self._is_categorical:
|
|
314
|
+
self.legend = Legend(title=self._column, size=self._size, **kwargs)
|
|
315
|
+
else:
|
|
316
|
+
self.legend = ContinousLegend(title=self._column, size=self._size, **kwargs)
|
|
241
317
|
|
|
242
318
|
def _choose_cmap(self):
|
|
243
319
|
"""kwargs is to catch start and stop points for the cmap in __init__."""
|
|
@@ -250,6 +326,14 @@ class ThematicMap(Map):
|
|
|
250
326
|
self.cmap_start = 23
|
|
251
327
|
self.cmap_stop = 256
|
|
252
328
|
|
|
329
|
+
def _make_bin_value_dict(self, gdf, classified) -> dict:
|
|
330
|
+
"""Dict with unique values of all bins. Used in labels in ContinousLegend."""
|
|
331
|
+
bins_unique_values = {
|
|
332
|
+
i: list(set(gdf.loc[classified == i, self._column]))
|
|
333
|
+
for i, _ in enumerate(np.unique(classified))
|
|
334
|
+
}
|
|
335
|
+
return bins_unique_values
|
|
336
|
+
|
|
253
337
|
def _actually_add_background(self):
|
|
254
338
|
self.ax.set_xlim([self.minx - self.diffx * 0.03, self.maxx + self.diffx * 0.03])
|
|
255
339
|
self.ax.set_ylim([self.miny - self.diffy * 0.03, self.maxy + self.diffy * 0.03])
|
|
@@ -271,7 +355,6 @@ class ThematicMap(Map):
|
|
|
271
355
|
self.nan_color = "#666666"
|
|
272
356
|
if not self._is_categorical:
|
|
273
357
|
self.change_cmap("viridis")
|
|
274
|
-
self._add_legend()
|
|
275
358
|
|
|
276
359
|
else:
|
|
277
360
|
self.facecolor, self.title_color, self.bg_gdf_color = (
|
|
@@ -282,7 +365,8 @@ class ThematicMap(Map):
|
|
|
282
365
|
self.nan_color = "#c2c2c2"
|
|
283
366
|
if not self._is_categorical:
|
|
284
367
|
self.change_cmap("RdPu", start=23)
|
|
285
|
-
|
|
368
|
+
|
|
369
|
+
self._create_legend()
|
|
286
370
|
|
|
287
371
|
def __getitem__(self, item):
|
|
288
372
|
return getattr(self, item)
|