ssb-sgis 1.0.0__py3-none-any.whl → 1.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. sgis/__init__.py +97 -115
  2. sgis/exceptions.py +3 -1
  3. sgis/geopandas_tools/__init__.py +1 -0
  4. sgis/geopandas_tools/bounds.py +75 -38
  5. sgis/geopandas_tools/buffer_dissolve_explode.py +38 -34
  6. sgis/geopandas_tools/centerlines.py +53 -44
  7. sgis/geopandas_tools/cleaning.py +87 -104
  8. sgis/geopandas_tools/conversion.py +149 -101
  9. sgis/geopandas_tools/duplicates.py +31 -17
  10. sgis/geopandas_tools/general.py +76 -48
  11. sgis/geopandas_tools/geometry_types.py +21 -7
  12. sgis/geopandas_tools/neighbors.py +20 -8
  13. sgis/geopandas_tools/overlay.py +136 -53
  14. sgis/geopandas_tools/point_operations.py +9 -8
  15. sgis/geopandas_tools/polygon_operations.py +48 -56
  16. sgis/geopandas_tools/polygons_as_rings.py +121 -78
  17. sgis/geopandas_tools/sfilter.py +14 -14
  18. sgis/helpers.py +114 -56
  19. sgis/io/dapla_functions.py +32 -23
  20. sgis/io/opener.py +13 -6
  21. sgis/io/read_parquet.py +1 -1
  22. sgis/maps/examine.py +39 -26
  23. sgis/maps/explore.py +112 -66
  24. sgis/maps/httpserver.py +12 -12
  25. sgis/maps/legend.py +124 -65
  26. sgis/maps/map.py +66 -41
  27. sgis/maps/maps.py +31 -29
  28. sgis/maps/thematicmap.py +46 -33
  29. sgis/maps/tilesources.py +3 -8
  30. sgis/networkanalysis/_get_route.py +5 -4
  31. sgis/networkanalysis/_od_cost_matrix.py +44 -1
  32. sgis/networkanalysis/_points.py +10 -4
  33. sgis/networkanalysis/_service_area.py +5 -2
  34. sgis/networkanalysis/closing_network_holes.py +20 -62
  35. sgis/networkanalysis/cutting_lines.py +55 -43
  36. sgis/networkanalysis/directednetwork.py +15 -7
  37. sgis/networkanalysis/finding_isolated_networks.py +4 -3
  38. sgis/networkanalysis/network.py +15 -13
  39. sgis/networkanalysis/networkanalysis.py +72 -54
  40. sgis/networkanalysis/networkanalysisrules.py +20 -16
  41. sgis/networkanalysis/nodes.py +2 -3
  42. sgis/networkanalysis/traveling_salesman.py +5 -2
  43. sgis/parallel/parallel.py +337 -127
  44. sgis/raster/__init__.py +6 -0
  45. sgis/raster/base.py +9 -3
  46. sgis/raster/cube.py +280 -208
  47. sgis/raster/cubebase.py +15 -29
  48. sgis/raster/indices.py +3 -7
  49. sgis/raster/methods_as_functions.py +0 -124
  50. sgis/raster/raster.py +313 -127
  51. sgis/raster/torchgeo.py +58 -37
  52. sgis/raster/zonal.py +38 -13
  53. {ssb_sgis-1.0.0.dist-info → ssb_sgis-1.0.2.dist-info}/LICENSE +1 -1
  54. {ssb_sgis-1.0.0.dist-info → ssb_sgis-1.0.2.dist-info}/METADATA +89 -18
  55. ssb_sgis-1.0.2.dist-info/RECORD +61 -0
  56. {ssb_sgis-1.0.0.dist-info → ssb_sgis-1.0.2.dist-info}/WHEEL +1 -1
  57. sgis/raster/bands.py +0 -48
  58. sgis/raster/gradient.py +0 -78
  59. ssb_sgis-1.0.0.dist-info/RECORD +0 -63
sgis/maps/httpserver.py CHANGED
@@ -1,17 +1,17 @@
1
1
  import os
2
2
  import webbrowser
3
- from http.server import BaseHTTPRequestHandler, HTTPServer
3
+ from http.server import BaseHTTPRequestHandler
4
+ from http.server import HTTPServer
4
5
 
5
- from IPython.display import HTML, display
6
+ from IPython.display import HTML
7
+ from IPython.display import display
6
8
 
7
9
 
8
- def run_html_server(contents: str | None = None, port: int = 3000):
10
+ def run_html_server(contents: str | None = None, port: int = 3000) -> None:
9
11
  """Run a simple, temporary http web server for serving static HTML content."""
10
12
  if "JUPYTERHUB_SERVICE_PREFIX" in os.environ:
11
13
  # Create a link using the https://github.com/jupyterhub/jupyter-server-proxy
12
- display_address = os.environ["JUPYTERHUB_SERVICE_PREFIX"] + "proxy/{}/".format(
13
- port
14
- )
14
+ display_address = os.environ["JUPYTERHUB_SERVICE_PREFIX"] + f"proxy/{port}/"
15
15
  display_content = HTML(
16
16
  f"""
17
17
  <p>Click <a href='{display_address}'>here</a> to open in browser.</p>
@@ -20,15 +20,15 @@ def run_html_server(contents: str | None = None, port: int = 3000):
20
20
  )
21
21
  else:
22
22
  display_address = f"http://localhost:{port}"
23
- display_content = (
24
- f"Server started at http://localhost:{port}. "
25
- f"Click http://localhost:{port}/stop to stop server."
23
+ display_content = HTML(
24
+ f"""
25
+ <p>Click <a href='http://localhost:{port}'>here</a> to open in browser.</p>
26
+ <p>Click <a href='http://localhost:{port}/stop'>here</a> to stop.<p>"
27
+ """
26
28
  )
27
29
 
28
30
  class HTTPServerRequestHandler(BaseHTTPRequestHandler):
29
- """
30
- A handler of request for the server, hosting static content.
31
- """
31
+ """A handler of request for the server, hosting static content."""
32
32
 
33
33
  allow_reuse_address = True
34
34
 
sgis/maps/legend.py CHANGED
@@ -4,18 +4,21 @@ The Legend class is best accessed through the 'legend' attribute of the Thematic
4
4
  class.
5
5
 
6
6
  """
7
+
8
+ import itertools
7
9
  import warnings
10
+ from typing import Any
8
11
 
9
12
  import matplotlib
10
13
  import matplotlib.pyplot as plt
11
14
  import numpy as np
12
15
  import pandas as pd
16
+ from geopandas import GeoDataFrame
13
17
  from matplotlib.lines import Line2D
14
18
  from pandas import Series
15
19
 
16
20
  from ..geopandas_tools.bounds import points_in_bounds
17
21
 
18
-
19
22
  # the geopandas._explore raises a deprication warning. Ignoring for now.
20
23
  warnings.filterwarnings(
21
24
  action="ignore", category=matplotlib.MatplotlibDeprecationWarning
@@ -52,25 +55,38 @@ class Legend:
52
55
  'm' is the name of the ThematicMap instance. See here:
53
56
  https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.legend.html
54
57
 
55
- Examples
58
+ Examples:
56
59
  --------
57
- Create ten random points with a numeric column from 0 to 9.
60
+ Create ten points with a numeric column from 0 to 9.
58
61
 
59
62
  >>> import sgis as sg
60
- >>> points = sg.random_points(10)
63
+ >>> points = sg.to_gdf(
64
+ ... [
65
+ ... (0, 1),
66
+ ... (1, 0),
67
+ ... (1, 1),
68
+ ... (0, 0),
69
+ ... (0.5, 0.5),
70
+ ... (0.5, 0.25),
71
+ ... (0.25, 0.25),
72
+ ... (0.75, 0.75),
73
+ ... (0.25, 0.75),
74
+ ... (0.75, 0.25),
75
+ ... ]
76
+ ... )
61
77
  >>> points["number"] = range(10)
62
78
  >>> points
63
- geometry number
64
- 0 POINT (0.59780 0.50425) 0
65
- 1 POINT (0.07019 0.26167) 1
66
- 2 POINT (0.56475 0.15422) 2
67
- 3 POINT (0.87293 0.60316) 3
68
- 4 POINT (0.47373 0.20040) 4
69
- 5 POINT (0.98661 0.15614) 5
70
- 6 POINT (0.30951 0.77057) 6
71
- 7 POINT (0.47802 0.52824) 7
72
- 8 POINT (0.12215 0.96588) 8
73
- 9 POINT (0.02938 0.93467) 9
79
+ geometry number
80
+ 0 POINT (0.00000 1.00000) 0
81
+ 1 POINT (1.00000 0.00000) 1
82
+ 2 POINT (1.00000 1.00000) 2
83
+ 3 POINT (0.00000 0.00000) 3
84
+ 4 POINT (0.50000 0.50000) 4
85
+ 5 POINT (0.50000 0.25000) 5
86
+ 6 POINT (0.25000 0.25000) 6
87
+ 7 POINT (0.75000 0.75000) 7
88
+ 8 POINT (0.25000 0.75000) 8
89
+ 9 POINT (0.75000 0.25000) 9
74
90
 
75
91
  Creating the ThematicMap instance will also create the legend. Since we
76
92
  specify a numeric column, a ContinousLegend instance is created.
@@ -113,7 +129,30 @@ class Legend:
113
129
  framealpha: float = 1.0,
114
130
  edgecolor: str = "#0f0f0f",
115
131
  **kwargs,
116
- ):
132
+ ) -> None:
133
+ """Initialiser.
134
+
135
+ Args:
136
+ title: Legend title. Defaults to the column name if used in the
137
+ ThematicMap class.
138
+ labels: Labels of the categories.
139
+ position: The legend's x and y position in the plot, specified as a tuple of
140
+ x and y position between 0 and 1. E.g. position=(0.8, 0.2) for a position
141
+ in the bottom right corner, (0.2, 0.8) for the upper left corner.
142
+ fontsize: Text size of the legend labels. Defaults to the size of
143
+ the ThematicMap class.
144
+ title_fontsize: Text size of the legend title. Defaults to the
145
+ size * 1.2 of the ThematicMap class.
146
+ markersize: Size of the color circles in the legend. Defaults to the size of
147
+ the ThematicMap class.
148
+ framealpha: Transparency of the legend background.
149
+ edgecolor: Color of the legend border. Defaults to #0f0f0f (almost black).
150
+ kwargs: Stores additional keyword arguments taken by the matplotlib legend
151
+ method. Specify this as e.g. m.legend.kwargs["labelcolor"] = "red", where
152
+ 'm' is the name of the ThematicMap instance. See here:
153
+ https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.legend.html
154
+
155
+ """
117
156
  self.title = title
118
157
 
119
158
  if "size" in kwargs:
@@ -136,9 +175,8 @@ class Legend:
136
175
  self.kwargs = kwargs
137
176
  self._position_has_been_set = True if position else False
138
177
 
139
- def _get_legend_sizes(self, size, kwargs):
178
+ def _get_legend_sizes(self, size: int | float, kwargs: dict) -> None:
140
179
  """Adjust fontsize and markersize to size kwarg."""
141
-
142
180
  if "title_fontsize" in kwargs:
143
181
  self._title_fontsize = kwargs["title_fontsize"]
144
182
  self._title_fontsize_has_been_set = True
@@ -157,7 +195,9 @@ class Legend:
157
195
  else:
158
196
  self._markersize = size
159
197
 
160
- def _prepare_categorical_legend(self, categories_colors: dict, nan_label: str):
198
+ def _prepare_categorical_legend(
199
+ self, categories_colors: dict, nan_label: str
200
+ ) -> None:
161
201
  for attr in self.__dict__.keys():
162
202
  if attr in self.kwargs:
163
203
  self[attr] = self.kwargs.pop(attr)
@@ -195,7 +235,7 @@ class Legend:
195
235
  )
196
236
  )
197
237
 
198
- def _actually_add_legend(self, ax):
238
+ def _actually_add_legend(self, ax: matplotlib.axes.Axes) -> matplotlib.axes.Axes:
199
239
  legend = ax.legend(
200
240
  self._patches,
201
241
  self._categories,
@@ -215,7 +255,9 @@ class Legend:
215
255
 
216
256
  return ax
217
257
 
218
- def _get_best_legend_position(self, gdf, k: int):
258
+ def _get_best_legend_position(
259
+ self, gdf: GeoDataFrame, k: int
260
+ ) -> tuple[float, float]:
219
261
  minx, miny, maxx, maxy = gdf.total_bounds
220
262
  diffx = maxx - minx
221
263
  diffy = maxy - miny
@@ -243,51 +285,58 @@ class Legend:
243
285
 
244
286
  return bestx_01, besty_01
245
287
 
246
- def __getitem__(self, item):
288
+ def __getitem__(self, item: str) -> Any:
289
+ """Get attribute with square brackets."""
247
290
  return getattr(self, item)
248
291
 
249
- def __setitem__(self, key, value):
292
+ def __setitem__(self, key: str, value: Any) -> None:
293
+ """Set attribute with square brackets."""
250
294
  setattr(self, key, value)
251
295
 
252
- def get(self, key, default=None):
296
+ def get(self, key: Any, default: Any = None) -> Any:
297
+ """Get value of an attribute of the Legend."""
253
298
  try:
254
299
  return self[key]
255
300
  except (KeyError, ValueError, IndexError, AttributeError):
256
301
  return default
257
302
 
258
303
  @property
259
- def position(self):
304
+ def position(self) -> tuple[float, float]:
305
+ """Legend position in x, y."""
260
306
  return self._position
261
307
 
262
308
  @position.setter
263
- def position(self, new_value: bool):
309
+ def position(self, new_value: tuple[float, float]) -> None:
264
310
  self._position = new_value
265
311
  self._position_has_been_set = True
266
312
 
267
313
  @property
268
- def title_fontsize(self):
314
+ def title_fontsize(self) -> int:
315
+ """Legend title fontsize."""
269
316
  return self._title_fontsize
270
317
 
271
318
  @title_fontsize.setter
272
- def title_fontsize(self, new_value: bool):
319
+ def title_fontsize(self, new_value: int) -> None:
273
320
  self._title_fontsize = new_value
274
321
  self._title_fontsize_has_been_set = True
275
322
 
276
323
  @property
277
- def fontsize(self):
324
+ def fontsize(self) -> int:
325
+ """Legend fontsize."""
278
326
  return self._fontsize
279
327
 
280
328
  @fontsize.setter
281
- def fontsize(self, new_value: bool):
329
+ def fontsize(self, new_value: int) -> None:
282
330
  self._fontsize = new_value
283
331
  self._fontsize_has_been_set = True
284
332
 
285
333
  @property
286
- def markersize(self):
334
+ def markersize(self) -> int:
335
+ """Legend markersize."""
287
336
  return self._markersize
288
337
 
289
338
  @markersize.setter
290
- def markersize(self, new_value: bool):
339
+ def markersize(self, new_value: int) -> None:
291
340
  self._markersize = new_value
292
341
  self._markersize_has_been_set = True
293
342
 
@@ -321,7 +370,7 @@ class ContinousLegend(Legend):
321
370
  unless 'thousand_sep' is '.'. In this case, ',' (comma) will be used as
322
371
  decimal mark.
323
372
 
324
- Examples
373
+ Examples:
325
374
  --------
326
375
  Create ten random points with a numeric column from 0 to 9.
327
376
 
@@ -386,7 +435,36 @@ class ContinousLegend(Legend):
386
435
  thousand_sep: str | None = None,
387
436
  decimal_mark: str | None = None,
388
437
  **kwargs,
389
- ):
438
+ ) -> None:
439
+ """Initialiser.
440
+
441
+ Args:
442
+ labels: To manually set labels. If set, all other labeling attributes are
443
+ ignored. Should be given as a list of strings with the same length as
444
+ the number of color groups.
445
+ pretty_labels: If False (default), the minimum and maximum values of each
446
+ color group will be used as legend labels. If True, the labels will end
447
+ with the maximum value, but start at 1 + the maximum value of the previous
448
+ group. The labels will be correct but inaccurate.
449
+ label_suffix: The text to put after each number in the legend labels.
450
+ Defaults to None.
451
+ label_sep: Text to put in between the two numbers in each color group in
452
+ the legend. Defaults to '-'.
453
+ rounding: Number of decimals in the labels. By default, the rounding
454
+ depends on the column's maximum value and standard deviation.
455
+ OBS: The bins will not be rounded, meaning the labels might be wrong
456
+ if not bins are set manually.
457
+ thousand_sep: Separator between each thousand for large numbers. Defaults to
458
+ None, meaning no separator.
459
+ decimal_mark: Text to use as decimal point. Defaults to None, meaning '.' (dot)
460
+ unless 'thousand_sep' is '.'. In this case, ',' (comma) will be used as
461
+ decimal mark.
462
+ kwargs: Stores additional keyword arguments taken by the matplotlib legend
463
+ method. Specify this as e.g. m.legend.kwargs["labelcolor"] = "red", where
464
+ 'm' is the name of the ThematicMap instance. See here:
465
+ https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.legend.html
466
+
467
+ """
390
468
  super().__init__(**kwargs)
391
469
 
392
470
  self.pretty_labels = pretty_labels
@@ -423,13 +501,13 @@ class ContinousLegend(Legend):
423
501
  return int(abs(np.log10(std_))) + 1
424
502
 
425
503
  @staticmethod
426
- def _set_rounding(bins, rounding: int | float):
504
+ def _set_rounding(bins, rounding: int | float) -> list[int | float]:
427
505
  if rounding == 0:
428
- return [int(round(bin, 0)) for bin in bins]
506
+ return [int(round(bin_, 0)) for bin_ in bins]
429
507
  else:
430
- return [round(bin, rounding) for bin in bins]
508
+ return [round(bin_, rounding) for bin_ in bins]
431
509
 
432
- def _remove_max_legend_value(self):
510
+ def _remove_max_legend_value(self) -> None:
433
511
  if not self._legend:
434
512
  raise ValueError("Cannot modify legend before it is created.")
435
513
 
@@ -439,7 +517,7 @@ class ContinousLegend(Legend):
439
517
  colors: list[str],
440
518
  nan_label: str,
441
519
  bin_values: dict,
442
- ):
520
+ ) -> None:
443
521
  # TODO: clean up this messy method
444
522
 
445
523
  for attr in self.__dict__.keys():
@@ -491,7 +569,7 @@ class ContinousLegend(Legend):
491
569
  )
492
570
 
493
571
  else:
494
- for i, (cat1, cat2) in enumerate(zip(bins[:-1], bins[1:], strict=True)):
572
+ for i, (cat1, cat2) in enumerate(itertools.pairwise(bins)):
495
573
  if nan_label in str(cat1) or nan_label in str(cat2):
496
574
  self._categories.append(nan_label)
497
575
  continue
@@ -527,36 +605,16 @@ class ContinousLegend(Legend):
527
605
  label = self._two_value_label(min_rounded, max_rounded)
528
606
  self._categories.append(label)
529
607
 
530
- def _actually_add_legend(self, ax):
531
- legend = ax.legend(
532
- self._patches,
533
- self._categories,
534
- fontsize=self._fontsize,
535
- title=self.title,
536
- title_fontsize=self._title_fontsize,
537
- bbox_to_anchor=self._position + (self.width, self.height),
538
- fancybox=False,
539
- framealpha=self.framealpha,
540
- edgecolor=self.edgecolor,
541
- labelspacing=self.labelspacing,
542
- **self.kwargs,
543
- )
544
-
545
- if self.title_color:
546
- plt.setp(legend.get_title(), color=self.title_color)
547
-
548
- return ax
549
-
550
- def _two_value_label(self, value1, value2):
608
+ def _two_value_label(self, value1: int | float, value2: int | float) -> str:
551
609
  return (
552
610
  f"{value1} {self.label_suffix} {self.label_sep} "
553
611
  f"{value2} {self.label_suffix}"
554
612
  )
555
613
 
556
- def _one_value_label(self, value1):
614
+ def _one_value_label(self, value1: int | float) -> str:
557
615
  return f"{value1} {self.label_suffix}"
558
616
 
559
- def _format_number(self, number):
617
+ def _format_number(self, number: int | float) -> int | float:
560
618
  if not self.thousand_sep and not self.decimal_mark:
561
619
  return number
562
620
 
@@ -581,10 +639,11 @@ class ContinousLegend(Legend):
581
639
  return number
582
640
 
583
641
  @property
584
- def rounding(self):
642
+ def rounding(self) -> int:
643
+ """Number rounding."""
585
644
  return self._rounding
586
645
 
587
646
  @rounding.setter
588
- def rounding(self, new_value: bool):
647
+ def rounding(self, new_value: int) -> None:
589
648
  self._rounding = new_value
590
649
  self._rounding_has_been_set = True
sgis/maps/map.py CHANGED
@@ -4,32 +4,31 @@ This module holds the Map class, which is the basis for the Explore class.
4
4
  """
5
5
 
6
6
  import warnings
7
+ from typing import Any
7
8
 
8
9
  import matplotlib
9
10
  import matplotlib.colors as colors
10
11
  import numpy as np
11
12
  import pandas as pd
12
- from geopandas import GeoDataFrame, GeoSeries
13
+ from geopandas import GeoDataFrame
14
+ from geopandas import GeoSeries
13
15
  from jenkspy import jenks_breaks
14
16
  from mapclassify import classify
15
17
  from shapely import Geometry
16
18
 
17
19
  from ..geopandas_tools.conversion import to_gdf
18
- from ..geopandas_tools.general import (
19
- clean_geoms,
20
- drop_inactive_geometry_columns,
21
- get_common_crs,
22
- rename_geometry_if,
23
- )
20
+ from ..geopandas_tools.general import _rename_geometry_if
21
+ from ..geopandas_tools.general import clean_geoms
22
+ from ..geopandas_tools.general import drop_inactive_geometry_columns
23
+ from ..geopandas_tools.general import get_common_crs
24
24
  from ..helpers import get_object_name
25
25
 
26
-
27
26
  try:
28
27
  from torchgeo.datasets.geo import RasterDataset
29
28
  except ImportError:
30
29
 
31
30
  class RasterDataset:
32
- """Placeholder"""
31
+ """Placeholder."""
33
32
 
34
33
 
35
34
  # the geopandas._explore raises a deprication warning. Ignoring for now.
@@ -60,8 +59,16 @@ _CATEGORICAL_CMAP = {
60
59
  DEFAULT_SCHEME = "quantiles"
61
60
 
62
61
 
63
- def proper_fillna(val, fill_val):
64
- """fillna doesn't always work. So doing it manually."""
62
+ def proper_fillna(val: Any, fill_val: Any) -> Any:
63
+ """Manually handle missing values when fillna doesn't work as expected.
64
+
65
+ Args:
66
+ val: The value to check and fill.
67
+ fill_val: The value to fill in.
68
+
69
+ Returns:
70
+ The original value or the filled value if conditions are met.
71
+ """
65
72
  try:
66
73
  if "NAType" in val.__class__.__name__:
67
74
  return fill_val
@@ -91,8 +98,20 @@ class Map:
91
98
  nan_color="#c2c2c2",
92
99
  scheme: str = DEFAULT_SCHEME,
93
100
  **kwargs,
94
- ):
101
+ ) -> None:
102
+ """Initialiser.
95
103
 
104
+ Args:
105
+ *gdfs: Variable length GeoDataFrame list.
106
+ column: The column name to work with.
107
+ labels: Tuple of labels for each GeoDataFrame.
108
+ k: Number of bins or classes for classification (default: 5).
109
+ bins: Predefined bins for data classification.
110
+ nan_label: Label for missing data.
111
+ nan_color: Color for missing data.
112
+ scheme: Classification scheme to be used.
113
+ **kwargs: Arbitrary keyword arguments.
114
+ """
96
115
  gdfs, column, kwargs = self._separate_args(gdfs, column, kwargs)
97
116
 
98
117
  self._column = column
@@ -138,7 +157,7 @@ class Map:
138
157
  self._gdfs = []
139
158
  new_labels = []
140
159
  self.show = []
141
- for label, gdf, show in zip(self.labels, gdfs, show_args):
160
+ for label, gdf, show in zip(self.labels, gdfs, show_args, strict=False):
142
161
  if not len(gdf):
143
162
  continue
144
163
 
@@ -189,7 +208,7 @@ class Map:
189
208
  )
190
209
 
191
210
  if not any(len(gdf) for gdf in self._gdfs):
192
- warnings.warn("None of the GeoDataFrames have rows.")
211
+ warnings.warn("None of the GeoDataFrames have rows.", stacklevel=1)
193
212
  self._gdfs = None
194
213
  self._is_categorical = True
195
214
  self._unique_values = []
@@ -222,7 +241,7 @@ class Map:
222
241
  self._nan_idx = self._gdf[self._column].isna()
223
242
  self._get_unique_values()
224
243
 
225
- def _get_unique_values(self):
244
+ def _get_unique_values(self) -> None:
226
245
  if not self._is_categorical:
227
246
  self._unique_values = self._get_unique_floats()
228
247
  else:
@@ -254,7 +273,7 @@ class Map:
254
273
 
255
274
  return np.sort(np.array(unique.loc[no_duplicates.index]))
256
275
 
257
- def _array_to_large_int(self, array):
276
+ def _array_to_large_int(self, array: np.ndarray | pd.Series) -> pd.Series:
258
277
  """Multiply values in float array, then convert to integer."""
259
278
  if not isinstance(array, pd.Series):
260
279
  array = pd.Series(array)
@@ -266,9 +285,8 @@ class Map:
266
285
 
267
286
  return pd.concat([unique_multiplied, isna]).sort_index()
268
287
 
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.
288
+ def _get_multiplier(self, array: np.ndarray) -> None:
289
+ """Find the number of zeros needed to push the max value of the array above +-1_000_000.
272
290
 
273
291
  Adding this as an attribute to use later in _classify_from_bins.
274
292
  """
@@ -293,7 +311,7 @@ class Map:
293
311
  def _add_minmax_to_bins(self, bins: list[float | int]) -> list[float | int]:
294
312
  """If values are outside the bin range, add max and/or min values of array."""
295
313
  # make sure they are lists
296
- bins = [bin for bin in bins]
314
+ bins = [bin_ for bin_ in bins]
297
315
 
298
316
  if min(bins) > 0 and min(self._gdf.loc[~self._nan_idx, self._column]) < min(
299
317
  bins
@@ -371,9 +389,8 @@ class Map:
371
389
 
372
390
  return gdfs, column, kwargs
373
391
 
374
- def _prepare_continous_map(self):
392
+ def _prepare_continous_map(self) -> None:
375
393
  """Create bins if not already done and adjust k if needed."""
376
-
377
394
  if self.scheme is None:
378
395
  return
379
396
 
@@ -408,7 +425,9 @@ class Map:
408
425
  gdfs.append(gdf)
409
426
  self._gdfs = gdfs
410
427
 
411
- def _to_common_crs_and_one_geom_col(self, gdfs: list[GeoDataFrame]):
428
+ def _to_common_crs_and_one_geom_col(
429
+ self, gdfs: list[GeoDataFrame]
430
+ ) -> list[GeoDataFrame]:
412
431
  """Need common crs and max one geometry column."""
413
432
  crs_list = list({gdf.crs for gdf in gdfs if gdf.crs is not None})
414
433
  if crs_list:
@@ -416,7 +435,7 @@ class Map:
416
435
  new_gdfs = []
417
436
  for gdf in gdfs:
418
437
  gdf = gdf.reset_index(drop=True)
419
- gdf = drop_inactive_geometry_columns(gdf).pipe(rename_geometry_if)
438
+ gdf = drop_inactive_geometry_columns(gdf).pipe(_rename_geometry_if)
420
439
  if crs_list:
421
440
  try:
422
441
  gdf = gdf.to_crs(self.crs)
@@ -530,7 +549,6 @@ class Map:
530
549
  If 'scheme' is not specified, the jenks_breaks function is used, which is
531
550
  much faster than the one from Mapclassifier.
532
551
  """
533
-
534
552
  if not len(gdf.loc[~self._nan_idx, column]):
535
553
  return np.array([0])
536
554
 
@@ -563,7 +581,7 @@ class Map:
563
581
  bins = binning.bins
564
582
  bins = self._add_minmax_to_bins(bins)
565
583
 
566
- unique_bins = list({round(bin, 5) for bin in bins})
584
+ unique_bins = list({round(bin_, 5) for bin_ in bins})
567
585
  unique_bins.sort()
568
586
 
569
587
  if self._k == len(self._unique_values) - 1:
@@ -577,7 +595,7 @@ class Map:
577
595
 
578
596
  return np.array(bins)
579
597
 
580
- def change_cmap(self, cmap: str, start: int = 0, stop: int = 256):
598
+ def change_cmap(self, cmap: str, start: int = 0, stop: int = 256) -> "Map":
581
599
  """Change the color palette of the plot.
582
600
 
583
601
  Args:
@@ -606,7 +624,6 @@ class Map:
606
624
 
607
625
  def _classify_from_bins(self, gdf: GeoDataFrame, bins: np.ndarray) -> np.ndarray:
608
626
  """Place the column values into groups."""
609
-
610
627
  # if equal lenght, convert to integer and check for equality
611
628
  if len(bins) == len(self._unique_values):
612
629
  if gdf[self._column].isna().all():
@@ -647,11 +664,12 @@ class Map:
647
664
  return np.array([rank_dict[val] for val in classified])
648
665
 
649
666
  @property
650
- def k(self):
667
+ def k(self) -> int:
668
+ """Number of bins."""
651
669
  return self._k
652
670
 
653
671
  @k.setter
654
- def k(self, new_value: bool):
672
+ def k(self, new_value: int) -> None:
655
673
  if not self._is_categorical and new_value > len(self._unique_values):
656
674
  raise ValueError(
657
675
  "'k' cannot be greater than the number of unique values in the column.'"
@@ -661,54 +679,61 @@ class Map:
661
679
  self._k = int(new_value)
662
680
 
663
681
  @property
664
- def cmap(self):
682
+ def cmap(self) -> str:
683
+ """Colormap."""
665
684
  return self._cmap
666
685
 
667
686
  @cmap.setter
668
- def cmap(self, new_value: bool):
687
+ def cmap(self, new_value: str) -> None:
669
688
  self._cmap = new_value
670
689
  self.change_cmap(cmap=new_value, start=self.cmap_start, stop=self.cmap_stop)
671
690
 
672
691
  @property
673
- def gdf(self):
692
+ def gdf(self) -> GeoDataFrame:
693
+ """All GeoDataFrames concated."""
674
694
  return self._gdf
675
695
 
676
696
  @gdf.setter
677
- def gdf(self, _):
697
+ def gdf(self, _) -> None:
678
698
  raise ValueError(
679
699
  "Cannot change 'gdf' after init. Put the GeoDataFrames into "
680
700
  "the class initialiser."
681
701
  )
682
702
 
683
703
  @property
684
- def gdfs(self):
704
+ def gdfs(self) -> list[GeoDataFrame]:
705
+ """All GeoDataFrames as a list."""
685
706
  return self._gdfs
686
707
 
687
708
  @gdfs.setter
688
- def gdfs(self, _):
709
+ def gdfs(self, _) -> None:
689
710
  raise ValueError(
690
711
  "Cannot change 'gdfs' after init. Put the GeoDataFrames into "
691
712
  "the class initialiser."
692
713
  )
693
714
 
694
715
  @property
695
- def column(self):
716
+ def column(self) -> str | None:
717
+ """Column to use as colormap."""
696
718
  return self._column
697
719
 
698
720
  @column.setter
699
- def column(self, _):
721
+ def column(self, _) -> None:
700
722
  raise ValueError(
701
723
  "Cannot change 'column' after init. Specify 'column' in the "
702
724
  "class initialiser."
703
725
  )
704
726
 
705
- def __setitem__(self, item, new_item):
727
+ def __setitem__(self, item: Any, new_item: Any) -> None:
728
+ """Set an attribute with square brackets."""
706
729
  return setattr(self, item, new_item)
707
730
 
708
- def __getitem__(self, item):
731
+ def __getitem__(self, item: Any) -> Any:
732
+ """Get an attribute with square brackets."""
709
733
  return getattr(self, item)
710
734
 
711
- def get(self, key, default=None):
735
+ def get(self, key: Any, default: Any | None = None) -> Any:
736
+ """Get an attribute with default value if not present."""
712
737
  try:
713
738
  return self[key]
714
739
  except (KeyError, ValueError, IndexError, AttributeError):