ssb-sgis 1.0.2__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 +10 -6
- sgis/exceptions.py +2 -2
- sgis/geopandas_tools/bounds.py +17 -15
- sgis/geopandas_tools/buffer_dissolve_explode.py +24 -5
- sgis/geopandas_tools/conversion.py +15 -6
- sgis/geopandas_tools/duplicates.py +2 -2
- sgis/geopandas_tools/general.py +9 -5
- sgis/geopandas_tools/geometry_types.py +3 -3
- sgis/geopandas_tools/neighbors.py +3 -3
- sgis/geopandas_tools/point_operations.py +2 -2
- sgis/geopandas_tools/polygon_operations.py +5 -5
- sgis/geopandas_tools/sfilter.py +3 -3
- sgis/helpers.py +3 -3
- sgis/io/read_parquet.py +1 -1
- sgis/maps/examine.py +16 -2
- sgis/maps/explore.py +370 -57
- sgis/maps/legend.py +164 -72
- sgis/maps/map.py +184 -90
- sgis/maps/maps.py +92 -90
- sgis/maps/thematicmap.py +236 -83
- sgis/networkanalysis/closing_network_holes.py +2 -2
- sgis/networkanalysis/cutting_lines.py +3 -3
- sgis/networkanalysis/directednetwork.py +1 -1
- sgis/networkanalysis/finding_isolated_networks.py +2 -2
- sgis/networkanalysis/networkanalysis.py +7 -7
- sgis/networkanalysis/networkanalysisrules.py +1 -1
- sgis/networkanalysis/traveling_salesman.py +1 -1
- sgis/parallel/parallel.py +39 -19
- sgis/raster/__init__.py +0 -6
- sgis/raster/cube.py +51 -5
- sgis/raster/image_collection.py +2560 -0
- sgis/raster/indices.py +14 -5
- sgis/raster/raster.py +131 -236
- sgis/raster/sentinel_config.py +104 -0
- sgis/raster/zonal.py +0 -1
- {ssb_sgis-1.0.2.dist-info → ssb_sgis-1.0.3.dist-info}/METADATA +1 -1
- ssb_sgis-1.0.3.dist-info/RECORD +61 -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.3.dist-info}/LICENSE +0 -0
- {ssb_sgis-1.0.2.dist-info → ssb_sgis-1.0.3.dist-info}/WHEEL +0 -0
sgis/maps/legend.py
CHANGED
|
@@ -26,6 +26,82 @@ warnings.filterwarnings(
|
|
|
26
26
|
pd.options.mode.chained_assignment = None
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
LEGEND_KWARGS = {
|
|
30
|
+
"title",
|
|
31
|
+
"size",
|
|
32
|
+
"position",
|
|
33
|
+
"fontsize",
|
|
34
|
+
"title_fontsize",
|
|
35
|
+
"markersize",
|
|
36
|
+
"framealpha",
|
|
37
|
+
"edgecolor",
|
|
38
|
+
"kwargs",
|
|
39
|
+
"labelspacing",
|
|
40
|
+
"title_color",
|
|
41
|
+
"width",
|
|
42
|
+
"height",
|
|
43
|
+
"labels",
|
|
44
|
+
"pretty_labels",
|
|
45
|
+
"thousand_sep",
|
|
46
|
+
"decimal_mark",
|
|
47
|
+
"label_sep",
|
|
48
|
+
"label_suffix",
|
|
49
|
+
"rounding",
|
|
50
|
+
"facecolor",
|
|
51
|
+
"labelcolor",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
LOWERCASE_WORDS = {
|
|
55
|
+
"a",
|
|
56
|
+
"an",
|
|
57
|
+
"and",
|
|
58
|
+
"as",
|
|
59
|
+
"at",
|
|
60
|
+
"but",
|
|
61
|
+
"by",
|
|
62
|
+
"for",
|
|
63
|
+
"in",
|
|
64
|
+
"nor",
|
|
65
|
+
"of",
|
|
66
|
+
"on",
|
|
67
|
+
"or",
|
|
68
|
+
"the",
|
|
69
|
+
"up",
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def prettify_label(label: str) -> str:
|
|
74
|
+
"""Replace underscores with spaces and capitalize words that are all lowecase."""
|
|
75
|
+
return " ".join(
|
|
76
|
+
word.title() if word.islower() and word not in LOWERCASE_WORDS else word
|
|
77
|
+
for word in label.replace("_", " ").split()
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def prettify_number(x: int | float, rounding: int) -> int:
|
|
82
|
+
rounding = int(float(f"1e+{abs(rounding)}"))
|
|
83
|
+
rounded_down = int(x // rounding * rounding)
|
|
84
|
+
rounded_up = rounded_down + rounding
|
|
85
|
+
diff_up = abs(x - rounded_up)
|
|
86
|
+
diff_down = abs(x - rounded_down)
|
|
87
|
+
if diff_up < diff_down:
|
|
88
|
+
return rounded_up
|
|
89
|
+
else:
|
|
90
|
+
return rounded_down
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def prettify_bins(bins: list[int | float], rounding: int) -> list[int]:
|
|
94
|
+
return [
|
|
95
|
+
(
|
|
96
|
+
prettify_number(x, rounding)
|
|
97
|
+
if i != len(bins) - 1
|
|
98
|
+
else int(x)
|
|
99
|
+
# else prettify_number(x, rounding) + abs(rounding)
|
|
100
|
+
)
|
|
101
|
+
for i, x in enumerate(bins)
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
|
|
29
105
|
class Legend:
|
|
30
106
|
"""Holds the general attributes of the legend in the ThematicMap class.
|
|
31
107
|
|
|
@@ -36,27 +112,8 @@ class Legend:
|
|
|
36
112
|
If a numeric column is used, additional attributes can be found in the
|
|
37
113
|
ContinousLegend class.
|
|
38
114
|
|
|
39
|
-
Attributes:
|
|
40
|
-
title: Legend title. Defaults to the column name if used in the
|
|
41
|
-
ThematicMap class.
|
|
42
|
-
position: The legend's x and y position in the plot, specified as a tuple of
|
|
43
|
-
x and y position between 0 and 1. E.g. position=(0.8, 0.2) for a position
|
|
44
|
-
in the bottom right corner, (0.2, 0.8) for the upper left corner.
|
|
45
|
-
fontsize: Text size of the legend labels. Defaults to the size of
|
|
46
|
-
the ThematicMap class.
|
|
47
|
-
title_fontsize: Text size of the legend title. Defaults to the
|
|
48
|
-
size * 1.2 of the ThematicMap class.
|
|
49
|
-
markersize: Size of the color circles in the legend. Defaults to the size of
|
|
50
|
-
the ThematicMap class.
|
|
51
|
-
framealpha: Transparency of the legend background.
|
|
52
|
-
edgecolor: Color of the legend border. Defaults to #0f0f0f (almost black).
|
|
53
|
-
kwargs: Stores additional keyword arguments taken by the matplotlib legend
|
|
54
|
-
method. Specify this as e.g. m.legend.kwargs["labelcolor"] = "red", where
|
|
55
|
-
'm' is the name of the ThematicMap instance. See here:
|
|
56
|
-
https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.legend.html
|
|
57
|
-
|
|
58
115
|
Examples:
|
|
59
|
-
|
|
116
|
+
---------
|
|
60
117
|
Create ten points with a numeric column from 0 to 9.
|
|
61
118
|
|
|
62
119
|
>>> import sgis as sg
|
|
@@ -89,38 +146,31 @@ class Legend:
|
|
|
89
146
|
9 POINT (0.75000 0.25000) 9
|
|
90
147
|
|
|
91
148
|
Creating the ThematicMap instance will also create the legend. Since we
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
>>> m = sg.ThematicMap(
|
|
149
|
+
pass a numeric column, a ContinousLegend is created.
|
|
150
|
+
|
|
151
|
+
>>> m = sg.ThematicMap(
|
|
152
|
+
... points,
|
|
153
|
+
... column="number"
|
|
154
|
+
... legend_kwargs=dict(
|
|
155
|
+
... title="Meters",
|
|
156
|
+
... label_sep="to",
|
|
157
|
+
... label_suffix="num",
|
|
158
|
+
... rounding=2,
|
|
159
|
+
... position = (0.35, 0.28),
|
|
160
|
+
... title_fontsize=11,
|
|
161
|
+
... fontsize=9,
|
|
162
|
+
... markersize=7.5,
|
|
163
|
+
... ),
|
|
164
|
+
... )
|
|
165
|
+
>>> m.plot()
|
|
95
166
|
>>> m.legend
|
|
96
167
|
<sgis.maps.legend.ContinousLegend object at 0x00000222206738D0>
|
|
97
|
-
|
|
98
|
-
Changing the attributes that apply to both numeric and categorical columns.
|
|
99
|
-
|
|
100
|
-
>>> m.legend.title = "Meters"
|
|
101
|
-
>>> m.legend.title_fontsize = 11
|
|
102
|
-
>>> m.legend.fontsize = 9
|
|
103
|
-
>>> m.legend.markersize = 7.5
|
|
104
|
-
>>> m.legend.position = (0.35, 0.28)
|
|
105
|
-
>>> m.plot()
|
|
106
|
-
|
|
107
|
-
Additional matplotlib keyword arguments can be specified as kwargs.
|
|
108
|
-
|
|
109
|
-
>>> m.legend.kwargs["labelcolor"] = "red"
|
|
110
|
-
|
|
111
|
-
Since we are using a numeric column, the legend is of type ContinousLegend.
|
|
112
|
-
We can therefore also access the attributes that only apply to numeric columns.
|
|
113
|
-
|
|
114
|
-
>>> m.label_sep = "to"
|
|
115
|
-
>>> m.label_suffix = "num"
|
|
116
|
-
>>> m.rounding = 2
|
|
117
|
-
>>> m.plot()
|
|
118
|
-
|
|
119
168
|
"""
|
|
120
169
|
|
|
121
170
|
def __init__(
|
|
122
171
|
self,
|
|
123
172
|
title: str | None = None,
|
|
173
|
+
pretty_labels: bool = True,
|
|
124
174
|
labels: list[str] | None = None,
|
|
125
175
|
position: tuple[float] | None = None,
|
|
126
176
|
markersize: int | None = None,
|
|
@@ -135,6 +185,8 @@ class Legend:
|
|
|
135
185
|
Args:
|
|
136
186
|
title: Legend title. Defaults to the column name if used in the
|
|
137
187
|
ThematicMap class.
|
|
188
|
+
pretty_labels: If True, words will be capitalized and underscores turned to spaces.
|
|
189
|
+
If continous values, numbers will be rounded.
|
|
138
190
|
labels: Labels of the categories.
|
|
139
191
|
position: The legend's x and y position in the plot, specified as a tuple of
|
|
140
192
|
x and y position between 0 and 1. E.g. position=(0.8, 0.2) for a position
|
|
@@ -163,6 +215,7 @@ class Legend:
|
|
|
163
215
|
self._fontsize = fontsize
|
|
164
216
|
self._markersize = markersize
|
|
165
217
|
|
|
218
|
+
self.pretty_labels = pretty_labels
|
|
166
219
|
self.framealpha = framealpha
|
|
167
220
|
self.edgecolor = edgecolor
|
|
168
221
|
self.width = kwargs.pop("width", 0.1)
|
|
@@ -172,9 +225,23 @@ class Legend:
|
|
|
172
225
|
|
|
173
226
|
self.labels = labels
|
|
174
227
|
self._position = position
|
|
175
|
-
self.kwargs = kwargs
|
|
176
228
|
self._position_has_been_set = True if position else False
|
|
177
229
|
|
|
230
|
+
self.kwargs = {}
|
|
231
|
+
for key, value in kwargs.items():
|
|
232
|
+
if key not in LEGEND_KWARGS:
|
|
233
|
+
self.kwargs[key] = value
|
|
234
|
+
else:
|
|
235
|
+
try:
|
|
236
|
+
setattr(self, key, value)
|
|
237
|
+
except Exception:
|
|
238
|
+
setattr(self, f"_{key}", value)
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def valid_keywords(self) -> set[str]:
|
|
242
|
+
"""List all valid keywords for the class initialiser."""
|
|
243
|
+
return LEGEND_KWARGS
|
|
244
|
+
|
|
178
245
|
def _get_legend_sizes(self, size: int | float, kwargs: dict) -> None:
|
|
179
246
|
"""Adjust fontsize and markersize to size kwarg."""
|
|
180
247
|
if "title_fontsize" in kwargs:
|
|
@@ -218,6 +285,8 @@ class Legend:
|
|
|
218
285
|
|
|
219
286
|
self._patches, self._categories = [], []
|
|
220
287
|
for category, color in categories_colors.items():
|
|
288
|
+
if self.pretty_labels:
|
|
289
|
+
category = prettify_label(category)
|
|
221
290
|
if category == nan_label:
|
|
222
291
|
self._categories.append(nan_label)
|
|
223
292
|
else:
|
|
@@ -236,6 +305,8 @@ class Legend:
|
|
|
236
305
|
)
|
|
237
306
|
|
|
238
307
|
def _actually_add_legend(self, ax: matplotlib.axes.Axes) -> matplotlib.axes.Axes:
|
|
308
|
+
if self.pretty_labels:
|
|
309
|
+
self.title = prettify_label(self.title)
|
|
239
310
|
legend = ax.legend(
|
|
240
311
|
self._patches,
|
|
241
312
|
self._categories,
|
|
@@ -360,10 +431,6 @@ class ContinousLegend(Legend):
|
|
|
360
431
|
Defaults to None.
|
|
361
432
|
label_sep: Text to put in between the two numbers in each color group in
|
|
362
433
|
the legend. Defaults to '-'.
|
|
363
|
-
rounding: Number of decimals in the labels. By default, the rounding
|
|
364
|
-
depends on the column's maximum value and standard deviation.
|
|
365
|
-
OBS: The bins will not be rounded, meaning the labels might be wrong
|
|
366
|
-
if not bins are set manually.
|
|
367
434
|
thousand_sep: Separator between each thousand for large numbers. Defaults to
|
|
368
435
|
None, meaning no separator.
|
|
369
436
|
decimal_mark: Text to use as decimal point. Defaults to None, meaning '.' (dot)
|
|
@@ -371,7 +438,7 @@ class ContinousLegend(Legend):
|
|
|
371
438
|
decimal mark.
|
|
372
439
|
|
|
373
440
|
Examples:
|
|
374
|
-
|
|
441
|
+
---------
|
|
375
442
|
Create ten random points with a numeric column from 0 to 9.
|
|
376
443
|
|
|
377
444
|
>>> import sgis as sg
|
|
@@ -428,7 +495,7 @@ class ContinousLegend(Legend):
|
|
|
428
495
|
def __init__(
|
|
429
496
|
self,
|
|
430
497
|
labels: list[str] | None = None,
|
|
431
|
-
pretty_labels: bool =
|
|
498
|
+
pretty_labels: bool = True,
|
|
432
499
|
label_suffix: str | None = None,
|
|
433
500
|
label_sep: str = "-",
|
|
434
501
|
rounding: int | None = None,
|
|
@@ -474,7 +541,7 @@ class ContinousLegend(Legend):
|
|
|
474
541
|
self.label_sep = label_sep
|
|
475
542
|
self.label_suffix = "" if not label_suffix else label_suffix
|
|
476
543
|
self._rounding = rounding
|
|
477
|
-
self._rounding_has_been_set = True if rounding else False
|
|
544
|
+
# self._rounding_has_been_set = True if rounding else False
|
|
478
545
|
|
|
479
546
|
def _get_rounding(self, array: Series | np.ndarray) -> int:
|
|
480
547
|
def isinteger(x):
|
|
@@ -502,8 +569,10 @@ class ContinousLegend(Legend):
|
|
|
502
569
|
|
|
503
570
|
@staticmethod
|
|
504
571
|
def _set_rounding(bins, rounding: int | float) -> list[int | float]:
|
|
505
|
-
if rounding
|
|
572
|
+
if not rounding:
|
|
506
573
|
return [int(round(bin_, 0)) for bin_ in bins]
|
|
574
|
+
elif rounding <= 0:
|
|
575
|
+
return [int(round(bin_, rounding)) for bin_ in bins]
|
|
507
576
|
else:
|
|
508
577
|
return [round(bin_, rounding) for bin_ in bins]
|
|
509
578
|
|
|
@@ -520,8 +589,8 @@ class ContinousLegend(Legend):
|
|
|
520
589
|
) -> None:
|
|
521
590
|
# TODO: clean up this messy method
|
|
522
591
|
|
|
523
|
-
for attr in self.
|
|
524
|
-
if attr in self.
|
|
592
|
+
for attr in self.kwargs:
|
|
593
|
+
if attr in self.__dict__:
|
|
525
594
|
self[attr] = self.kwargs.pop(attr)
|
|
526
595
|
|
|
527
596
|
self._patches, self._categories = [], []
|
|
@@ -544,9 +613,10 @@ class ContinousLegend(Legend):
|
|
|
544
613
|
if len(self.labels) != len(colors):
|
|
545
614
|
raise ValueError(
|
|
546
615
|
"Label list must be same length as the number of groups. "
|
|
547
|
-
f"Got k={len(colors)} and labels={len(
|
|
616
|
+
f"Got k={len(colors)} and labels={len(self.labels)}."
|
|
548
617
|
f"labels: {', '.join(self.labels)}"
|
|
549
618
|
f"colors: {', '.join(colors)}"
|
|
619
|
+
f"bins: {bins}"
|
|
550
620
|
)
|
|
551
621
|
self._categories = self.labels
|
|
552
622
|
|
|
@@ -576,42 +646,59 @@ class ContinousLegend(Legend):
|
|
|
576
646
|
|
|
577
647
|
min_ = np.min(bin_values[i])
|
|
578
648
|
max_ = np.max(bin_values[i])
|
|
579
|
-
|
|
580
|
-
max_rounded = self._set_rounding([max_], self._rounding)[0]
|
|
649
|
+
|
|
581
650
|
if self.pretty_labels:
|
|
582
|
-
if i
|
|
583
|
-
cat1 = int(
|
|
584
|
-
|
|
585
|
-
|
|
651
|
+
if i == 0:
|
|
652
|
+
cat1 = int(min_) if (self.rounding or 0) <= 0 else min_
|
|
653
|
+
|
|
654
|
+
is_last = i == len(bins) - 2
|
|
655
|
+
if is_last:
|
|
656
|
+
cat2 = int(max_) if (self.rounding or 0) <= 0 else max_
|
|
657
|
+
|
|
658
|
+
if (self.rounding or 0) <= 0:
|
|
659
|
+
cat1 = int(cat1)
|
|
660
|
+
cat2 = int(cat2 - 1) if not is_last else int(cat2)
|
|
661
|
+
elif (self.rounding or 0) > 0:
|
|
662
|
+
cat1 = round(cat1, self._rounding)
|
|
663
|
+
cat2 = round(
|
|
664
|
+
cat2 - float(f"1e-{self._rounding}"), self._rounding
|
|
665
|
+
)
|
|
666
|
+
else:
|
|
667
|
+
cat1 = round(cat1, self._rounding)
|
|
668
|
+
cat2 = round(cat2, self._rounding)
|
|
586
669
|
|
|
587
670
|
cat1 = self._format_number(cat1)
|
|
588
671
|
cat2 = self._format_number(cat2)
|
|
589
672
|
|
|
590
673
|
if min_ == max_:
|
|
591
|
-
label = self.
|
|
674
|
+
label = self._get_two_value_label(cat1, cat2)
|
|
592
675
|
self._categories.append(label)
|
|
593
676
|
continue
|
|
594
677
|
|
|
595
|
-
label = self.
|
|
678
|
+
label = self._get_two_value_label(cat1, cat2)
|
|
596
679
|
self._categories.append(label)
|
|
597
680
|
|
|
598
|
-
|
|
681
|
+
continue
|
|
682
|
+
|
|
683
|
+
min_rounded = self._set_rounding([min_], self._rounding)[0]
|
|
684
|
+
max_rounded = self._set_rounding([max_], self._rounding)[0]
|
|
685
|
+
if min_ == max_:
|
|
599
686
|
min_rounded = self._format_number(min_rounded)
|
|
600
|
-
label = self.
|
|
687
|
+
label = self._get_one_value_label(min_rounded)
|
|
601
688
|
self._categories.append(label)
|
|
602
689
|
else:
|
|
603
690
|
min_rounded = self._format_number(min_rounded)
|
|
604
691
|
max_rounded = self._format_number(max_rounded)
|
|
605
|
-
label = self.
|
|
692
|
+
label = self._get_two_value_label(min_rounded, max_rounded)
|
|
606
693
|
self._categories.append(label)
|
|
607
694
|
|
|
608
|
-
def
|
|
695
|
+
def _get_two_value_label(self, value1: int | float, value2: int | float) -> str:
|
|
609
696
|
return (
|
|
610
697
|
f"{value1} {self.label_suffix} {self.label_sep} "
|
|
611
698
|
f"{value2} {self.label_suffix}"
|
|
612
699
|
)
|
|
613
700
|
|
|
614
|
-
def
|
|
701
|
+
def _get_one_value_label(self, value1: int | float) -> str:
|
|
615
702
|
return f"{value1} {self.label_suffix}"
|
|
616
703
|
|
|
617
704
|
def _format_number(self, number: int | float) -> int | float:
|
|
@@ -640,10 +727,15 @@ class ContinousLegend(Legend):
|
|
|
640
727
|
|
|
641
728
|
@property
|
|
642
729
|
def rounding(self) -> int:
|
|
643
|
-
"""Number
|
|
730
|
+
"""Number of decimals in the labels.
|
|
731
|
+
|
|
732
|
+
By default, the rounding
|
|
733
|
+
depends on the column's maximum value and standard deviation.
|
|
734
|
+
OBS: The bins will not be rounded, meaning the labels might be wrong
|
|
735
|
+
if not bins are set manually.
|
|
736
|
+
"""
|
|
644
737
|
return self._rounding
|
|
645
738
|
|
|
646
739
|
@rounding.setter
|
|
647
740
|
def rounding(self, new_value: int) -> None:
|
|
648
741
|
self._rounding = new_value
|
|
649
|
-
self._rounding_has_been_set = True
|