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
@@ -1,11 +1,10 @@
1
- """Functions for reading and writing GeoDataFrames in Statistics Norway's GCS Dapla.
2
- """
1
+ """Functions for reading and writing GeoDataFrames in Statistics Norway's GCS Dapla."""
3
2
 
4
3
  from pathlib import Path
5
- from typing import Optional
6
4
 
7
5
  import dapla as dp
8
6
  import geopandas as gpd
7
+ import joblib
9
8
  import pandas as pd
10
9
  from geopandas import GeoDataFrame
11
10
  from geopandas.io.arrow import _geopandas_to_arrow
@@ -14,9 +13,9 @@ from pyarrow import parquet
14
13
 
15
14
 
16
15
  def read_geopandas(
17
- gcs_path: str | Path,
16
+ gcs_path: str | Path | list[str | Path],
18
17
  pandas_fallback: bool = False,
19
- file_system: Optional[dp.gcs.GCSFileSystem] = None,
18
+ file_system: dp.gcs.GCSFileSystem | None = None,
20
19
  **kwargs,
21
20
  ) -> GeoDataFrame | DataFrame:
22
21
  """Reads geoparquet or other geodata from a file on GCS.
@@ -28,25 +27,35 @@ def read_geopandas(
28
27
  Does not currently read shapefiles or filegeodatabases.
29
28
 
30
29
  Args:
31
- gcs_path: path to a file on Google Cloud Storage.
30
+ gcs_path: path to one or more files on Google Cloud Storage.
31
+ Multiple paths are read with threading.
32
32
  pandas_fallback: If False (default), an exception is raised if the file can
33
33
  not be read with geopandas and the number of rows is more than 0. If True,
34
- the file will be read as
34
+ the file will be read with pandas if geopandas fails.
35
+ file_system: Optional file system.
35
36
  **kwargs: Additional keyword arguments passed to geopandas' read_parquet
36
37
  or read_file, depending on the file type.
37
38
 
38
- Returns:
39
+ Returns:
39
40
  A GeoDataFrame if it has rows. If zero rows, a pandas DataFrame is returned.
40
41
  """
42
+ if file_system is None:
43
+ file_system = dp.FileClient.get_gcs_file_system()
44
+
45
+ if isinstance(gcs_path, (list, tuple)):
46
+ kwargs |= {"file_system": file_system, "pandas_fallback": pandas_fallback}
47
+ # recursive read with threads
48
+ with joblib.Parallel(n_jobs=len(gcs_path), backend="threading") as parallel:
49
+ dfs: list[GeoDataFrame] = parallel(
50
+ joblib.delayed(read_geopandas)(x, **kwargs) for x in gcs_path
51
+ )
52
+ return pd.concat(dfs)
41
53
 
42
54
  if not isinstance(gcs_path, str):
43
55
  try:
44
56
  gcs_path = str(gcs_path)
45
- except TypeError:
46
- raise TypeError(f"Unexpected type {type(gcs_path)}.")
47
-
48
- if file_system is None:
49
- file_system = dp.FileClient.get_gcs_file_system()
57
+ except TypeError as e:
58
+ raise TypeError(f"Unexpected type {type(gcs_path)}.") from e
50
59
 
51
60
  if "parquet" in gcs_path or "prqt" in gcs_path:
52
61
  with file_system.open(gcs_path, mode="rb") as file:
@@ -77,11 +86,11 @@ def read_geopandas(
77
86
 
78
87
 
79
88
  def write_geopandas(
80
- df: gpd.GeoDataFrame,
89
+ df: GeoDataFrame,
81
90
  gcs_path: str | Path,
82
91
  overwrite: bool = True,
83
92
  pandas_fallback: bool = False,
84
- file_system: Optional[dp.gcs.GCSFileSystem] = None,
93
+ file_system: dp.gcs.GCSFileSystem | None = None,
85
94
  **kwargs,
86
95
  ) -> None:
87
96
  """Writes a GeoDataFrame to the speficied format.
@@ -93,10 +102,13 @@ def write_geopandas(
93
102
  df: The GeoDataFrame to write.
94
103
  gcs_path: The path to the file you want to write to.
95
104
  overwrite: Whether to overwrite the file if it exists. Defaults to True.
105
+ pandas_fallback: If False (default), an exception is raised if the file can
106
+ not be written with geopandas and the number of rows is more than 0. If True,
107
+ the file will be written without geo-metadata if >0 rows.
108
+ file_system: Optional file sustem.
96
109
  **kwargs: Additional keyword arguments passed to parquet.write_table
97
110
  (for parquet) or geopandas' to_file method (if not parquet).
98
111
  """
99
-
100
112
  if not isinstance(gcs_path, str):
101
113
  try:
102
114
  gcs_path = str(gcs_path)
@@ -109,7 +121,8 @@ def write_geopandas(
109
121
  if file_system is None:
110
122
  file_system = dp.FileClient.get_gcs_file_system()
111
123
 
112
- pd.io.parquet.BaseImpl.validate_dataframe(df)
124
+ if not isinstance(df, GeoDataFrame):
125
+ raise ValueError("DataFrame must be GeoDataFrame.")
113
126
 
114
127
  if not len(df):
115
128
  if pandas_fallback:
@@ -152,7 +165,6 @@ def exists(path: str | Path) -> bool:
152
165
  Returns:
153
166
  True if the path exists, False if not.
154
167
  """
155
-
156
168
  file_system = dp.FileClient.get_gcs_file_system()
157
169
  return file_system.exists(path)
158
170
 
@@ -185,7 +197,7 @@ def check_files(
185
197
  ]
186
198
  folderinfo = [x["name"] for x in info if x["storageClass"] == "DIRECTORY"]
187
199
 
188
- fileinfo += get_files_in_subfolders(folderinfo)
200
+ fileinfo += _get_files_in_subfolders(folderinfo)
189
201
 
190
202
  df = pd.DataFrame(fileinfo, columns=["path", "kb", "updated"])
191
203
 
@@ -224,12 +236,9 @@ def check_files(
224
236
  return df.loc[lambda x: x.index > the_time, ["kb", "mb", "name", "child", "path"]]
225
237
 
226
238
 
227
- def get_files_in_subfolders(folderinfo: list[dict]) -> list[dict]:
239
+ def _get_files_in_subfolders(folderinfo: list[dict]) -> list[tuple]:
228
240
  file_system = dp.FileClient.get_gcs_file_system()
229
241
 
230
- if isinstance(folderinfo, (str, Path)):
231
- folderinfo = [folderinfo]
232
-
233
242
  fileinfo = []
234
243
 
235
244
  while folderinfo:
sgis/io/opener.py CHANGED
@@ -1,19 +1,26 @@
1
+ from collections.abc import Generator
1
2
  from contextlib import contextmanager
2
-
3
+ from typing import Any
3
4
 
4
5
  try:
5
- import dapla as dp
6
+ from dapla import FileClient
7
+ from dapla.gcs import GCSFileSystem
6
8
  except ImportError:
7
- pass
9
+
10
+ class GCSFileSystem: # type: ignore[no-redef]
11
+ """Placeholder."""
12
+
8
13
 
9
14
  from ._is_dapla import is_dapla
10
15
 
11
16
 
12
17
  @contextmanager
13
- def opener(path, mode="rb", file_system=None):
18
+ def opener(
19
+ path, mode: str = "rb", file_system: GCSFileSystem | None = None
20
+ ) -> Generator[str | Any, None, None]:
14
21
  """Yields a gcs buffer if in Dapla, otherwise yields the path.
15
22
 
16
- Example
23
+ Example:
17
24
  -------
18
25
  >>> with opener(path) as file:
19
26
  >>> with rasterio.open(file) as src:
@@ -21,7 +28,7 @@ def opener(path, mode="rb", file_system=None):
21
28
  """
22
29
  if is_dapla():
23
30
  if file_system is None:
24
- file_system = dp.FileClient.get_gcs_file_system()
31
+ file_system = FileClient.get_gcs_file_system()
25
32
  yield file_system.open(str(path), mode=mode)
26
33
  else:
27
34
  yield str(path)
sgis/io/read_parquet.py CHANGED
@@ -14,7 +14,7 @@ def read_parquet_url(url: str) -> GeoDataFrame:
14
14
  Returns:
15
15
  A GeoDataFrame.
16
16
 
17
- Examples
17
+ Examples:
18
18
  --------
19
19
  >>> from sgis import read_parquet_url
20
20
  >>> url = "https://media.githubusercontent.com/media/statisticsnorway/ssb-sgis/main/tests/testdata/points_oslo.parquet"
sgis/maps/examine.py CHANGED
@@ -4,7 +4,9 @@ import numpy as np
4
4
  from ..geopandas_tools.bounds import get_total_bounds
5
5
  from ..helpers import unit_is_degrees
6
6
  from .map import Map
7
- from .maps import clipmap, explore, samplemap
7
+ from .maps import clipmap
8
+ from .maps import explore
9
+ from .maps import samplemap
8
10
 
9
11
 
10
12
  class Examine:
@@ -19,19 +21,7 @@ class Examine:
19
21
  first geometry in 'mask_gdf' (or the first speficied gdf). The 'next' method
20
22
  can then be repeated.
21
23
 
22
- Args:
23
- *gdfs: One or more GeoDataFrames. The rows of the first GeoDataFrame
24
- will be used as masks, unless 'mask_gdf' is specified.
25
- column: Column to use as colors.
26
- mask_gdf: Optional GeoDataFrame to use as mask iterator. The geometries
27
- of mask_gdf will not be shown.
28
- size: Number of meters (or other crs unit) to buffer the mask geometry
29
- before clipping.
30
- sort_values: Optional sorting column(s) of the mask GeoDataFrame. Rows
31
- will be iterated through from the top.
32
- **kwargs: Additional keyword arguments passed to sgis.clipmap.
33
-
34
- Examples
24
+ Examples:
35
25
  --------
36
26
  Create the examiner.
37
27
 
@@ -78,7 +68,24 @@ class Examine:
78
68
  size: int | float = 1000,
79
69
  only_show_mask: bool = True,
80
70
  **kwargs,
81
- ):
71
+ ) -> None:
72
+ """Initialiser.
73
+
74
+ Args:
75
+ *gdfs: One or more GeoDataFrames. The rows of the first GeoDataFrame
76
+ will be used as masks, unless 'mask_gdf' is specified.
77
+ column: Column to use as colors.
78
+ mask_gdf: Optional GeoDataFrame to use as mask iterator. The geometries
79
+ of mask_gdf will not be shown.
80
+ size: Number of meters (or other crs unit) to buffer the mask geometry
81
+ before clipping.
82
+ sort_values: Optional sorting column(s) of the mask GeoDataFrame. Rows
83
+ will be iterated through from the top.
84
+ only_show_mask: If True (default), show only the mask GeoDataFrame by default.
85
+ The other layers can be toggled on.
86
+ **kwargs: Additional keyword arguments passed to sgis.clipmap.
87
+
88
+ """
82
89
  gdfs, column, kwargs = Map._separate_args(gdfs, column, kwargs)
83
90
 
84
91
  if mask_gdf is None:
@@ -87,7 +94,9 @@ class Examine:
87
94
  self.mask_gdf = mask_gdf
88
95
 
89
96
  m = Map(*gdfs, column=column, **kwargs)
90
- self._gdfs: dict[str, gpd.GeoDataFrame] = dict(zip(m.labels, m.gdfs))
97
+ self._gdfs: dict[str, gpd.GeoDataFrame] = dict(
98
+ zip(m.labels, m.gdfs, strict=False)
99
+ )
91
100
 
92
101
  self.indices = list(range(len(self.mask_gdf)))
93
102
  self.i = 0
@@ -121,7 +130,7 @@ class Examine:
121
130
  elif not kwargs.get("show", True):
122
131
  self.kwargs["show"] = [False] * len(self._gdfs)
123
132
 
124
- def next(self, i: int | None = None, **kwargs):
133
+ def next(self, i: int | None = None, **kwargs) -> None:
125
134
  """Displays a map of geometries within the next row of the mask gdf.
126
135
 
127
136
  Args:
@@ -151,7 +160,7 @@ class Examine:
151
160
  )
152
161
  self.i += 1
153
162
 
154
- def sample(self, **kwargs):
163
+ def sample(self, **kwargs) -> None:
155
164
  """Takes a sample index of the mask and displays a map of this area.
156
165
 
157
166
  Args:
@@ -171,7 +180,7 @@ class Examine:
171
180
  **self.kwargs,
172
181
  )
173
182
 
174
- def current(self, i: int | None = None, **kwargs):
183
+ def current(self, i: int | None = None, **kwargs) -> None:
175
184
  """Repeat the last shown map."""
176
185
  if kwargs:
177
186
  kwargs = self._fix_kwargs(kwargs)
@@ -190,7 +199,7 @@ class Examine:
190
199
  **self.kwargs,
191
200
  )
192
201
 
193
- def explore(self, **kwargs):
202
+ def explore(self, **kwargs) -> None:
194
203
  """Show all rows like the function explore."""
195
204
  if kwargs:
196
205
  kwargs = self._fix_kwargs(kwargs)
@@ -202,7 +211,7 @@ class Examine:
202
211
  **self.kwargs,
203
212
  )
204
213
 
205
- def clipmap(self, **kwargs):
214
+ def clipmap(self, **kwargs) -> None:
206
215
  """Show all rows like the function clipmap."""
207
216
  if kwargs:
208
217
  kwargs = self._fix_kwargs(kwargs)
@@ -214,7 +223,7 @@ class Examine:
214
223
  **self.kwargs,
215
224
  )
216
225
 
217
- def samplemap(self, **kwargs):
226
+ def samplemap(self, **kwargs) -> None:
218
227
  """Show all rows like the function samplemap."""
219
228
  if kwargs:
220
229
  kwargs = self._fix_kwargs(kwargs)
@@ -241,21 +250,25 @@ class Examine:
241
250
  return gdfs
242
251
 
243
252
  @property
244
- def bounds(self):
253
+ def bounds(self) -> tuple[float, float, float, float]:
254
+ """Total bounds of all GeoDataFrames."""
245
255
  return get_total_bounds(*list(self.gdfs.values()))
246
256
 
247
- def _fix_kwargs(self, kwargs) -> dict:
257
+ def _fix_kwargs(self, kwargs: dict) -> dict:
248
258
  self.size = kwargs.pop("size", self.size)
249
259
  self.column = kwargs.pop("column", self.column)
250
260
  return kwargs
251
261
 
252
262
  def __repr__(self) -> str:
263
+ """Representation."""
253
264
  return f"{self.__class__.__name__}(indices={len(self.indices)}, current={self.i}, n_gdfs={len(self._gdfs)})"
254
265
 
255
- def __add__(self, scalar):
266
+ def __add__(self, scalar: int) -> "Examine":
267
+ """Add a number to the index."""
256
268
  self.i += scalar
257
269
  return self
258
270
 
259
- def __sub__(self, scalar):
271
+ def __sub__(self, scalar: int) -> "Examine":
272
+ """Subtract a number from the index."""
260
273
  self.i -= scalar
261
274
  return self
sgis/maps/explore.py CHANGED
@@ -9,6 +9,8 @@ import warnings
9
9
  from collections.abc import Iterable
10
10
  from numbers import Number
11
11
  from statistics import mean
12
+ from typing import Any
13
+ from typing import ClassVar
12
14
 
13
15
  import branca as bc
14
16
  import folium
@@ -17,27 +19,30 @@ import numpy as np
17
19
  import pandas as pd
18
20
  import xyzservices
19
21
  from folium import plugins
20
- from geopandas import GeoDataFrame, GeoSeries
22
+ from geopandas import GeoDataFrame
23
+ from geopandas import GeoSeries
21
24
  from IPython.display import display
22
25
  from jinja2 import Template
23
26
  from pandas.api.types import is_datetime64_any_dtype
24
27
  from shapely import Geometry
25
28
  from shapely.geometry import LineString
26
29
 
27
- from ..geopandas_tools.conversion import from_4326, to_gdf
28
- from ..geopandas_tools.general import clean_geoms, make_all_singlepart
29
- from ..geopandas_tools.geometry_types import get_geom_type, to_single_geom_type
30
+ from ..geopandas_tools.conversion import to_gdf
31
+ from ..geopandas_tools.general import clean_geoms
32
+ from ..geopandas_tools.general import make_all_singlepart
33
+ from ..geopandas_tools.geometry_types import get_geom_type
34
+ from ..geopandas_tools.geometry_types import to_single_geom_type
30
35
  from .httpserver import run_html_server
31
36
  from .map import Map
32
- from .tilesources import kartverket, xyz
33
-
37
+ from .tilesources import kartverket
38
+ from .tilesources import xyz
34
39
 
35
40
  try:
36
41
  from torchgeo.datasets.geo import RasterDataset
37
42
  except ImportError:
38
43
 
39
44
  class RasterDataset:
40
- """Placeholder"""
45
+ """Placeholder."""
41
46
 
42
47
 
43
48
  # the geopandas._explore raises a deprication warning. Ignoring for now.
@@ -96,7 +101,10 @@ class MeasureControlFix(plugins.MeasureControl):
96
101
  """
97
102
  )
98
103
 
99
- def __init__(self, active_color="red", completed_color="red", **kwargs):
104
+ def __init__(
105
+ self, active_color: str = "red", completed_color: str = "red", **kwargs
106
+ ) -> None:
107
+ """Run super __init__ after the new _template class attribute is made."""
100
108
  super().__init__(
101
109
  active_color=active_color, completed_color=completed_color, **kwargs
102
110
  )
@@ -146,8 +154,10 @@ def to_tile(tile: str | xyzservices.TileProvider, max_zoom: int) -> folium.TileL
146
154
 
147
155
 
148
156
  class Explore(Map):
157
+ """Class for displaying and saving html maps of multiple GeoDataFrames."""
158
+
149
159
  # class attribute that can be overridden locally
150
- tiles = (
160
+ tiles: ClassVar[tuple[str]] = (
151
161
  "grunnkart",
152
162
  "norge_i_bilder",
153
163
  "dark",
@@ -161,17 +171,41 @@ class Explore(Map):
161
171
  column: str | None = None,
162
172
  popup: bool = True,
163
173
  max_zoom: int = 40,
164
- smooth_factor: float = 1.5,
174
+ smooth_factor: float = 1.1,
165
175
  browser: bool = False,
166
176
  prefer_canvas: bool = True,
167
177
  measure_control: bool = True,
168
178
  geocoder: bool = False,
169
- save=None,
179
+ save: str | None = None,
170
180
  show: bool | Iterable[bool] | None = None,
171
181
  text: str | None = None,
172
182
  decimals: int = 6,
173
183
  **kwargs,
174
- ):
184
+ ) -> None:
185
+ """Initialiser.
186
+
187
+ Args:
188
+ *gdfs: One or more GeoDataFrames.
189
+ mask: Optional mask to clip to.
190
+ column: Optional column to color the data by.
191
+ popup: Whether to make the data popups clickable.
192
+ max_zoom: Max levels of zoom.
193
+ smooth_factor: Float of 1 or higher, 1 meaning no smoothing
194
+ of lines.
195
+ browser: Whether to open the map in a browser tab.
196
+ prefer_canvas: Option.
197
+ measure_control: Whether to include measurement box.
198
+ geocoder: Whether to include search bar for addresses.
199
+ save: Optional file path to an html file. The map will then
200
+ be saved instead of displayed.
201
+ show: Whether to show or hide the data upon creating the map.
202
+ If False, the data can be toggled on later. 'show' can also be
203
+ a sequence of boolean values the same length as the number of
204
+ GeoDataFrames.
205
+ text: Optional text for a text box in the map.
206
+ decimals: Number of decimals in the coordinates.
207
+ **kwargs: Additional keyword arguments passed to
208
+ """
175
209
  self.popup = popup
176
210
  self.max_zoom = max_zoom
177
211
  self.smooth_factor = smooth_factor
@@ -195,12 +229,12 @@ class Explore(Map):
195
229
  else:
196
230
  show_was_none = False
197
231
 
198
- self.raster_datasets = tuple(
199
- raster_dataset_to_background_map(x)
200
- for x in gdfs
201
- if isinstance(x, RasterDataset)
202
- )
203
- self.tiles # += self.raster_datasets
232
+ self.raster_datasets = [] # tuple(
233
+ # raster_dataset_to_background_map(x)
234
+ # for x in gdfs
235
+ # if isinstance(x, RasterDataset)
236
+ # )
237
+ # self.tiles # += self.raster_datasets
204
238
 
205
239
  super().__init__(*gdfs, column=column, show=show, **kwargs)
206
240
 
@@ -262,14 +296,20 @@ class Explore(Map):
262
296
 
263
297
  self.original_crs = self.gdf.crs
264
298
 
265
- def __repr__(self):
299
+ def __repr__(self) -> str:
300
+ """Representation."""
266
301
  return f"{self.__class__.__name__}()"
267
302
 
268
303
  def explore(
269
- self, column: str | None = None, center=None, size=None, **kwargs
304
+ self,
305
+ column: str | None = None,
306
+ center: Any | None = None,
307
+ size: int | None = None,
308
+ **kwargs,
270
309
  ) -> None:
310
+ """Explore all the data."""
271
311
  if not any(len(gdf) for gdf in self._gdfs) and not len(self.raster_datasets):
272
- warnings.warn("None of the GeoDataFrames have rows.")
312
+ warnings.warn("None of the GeoDataFrames have rows.", stacklevel=1)
273
313
  return
274
314
  if column:
275
315
  self._column = column
@@ -311,6 +351,7 @@ class Explore(Map):
311
351
  sample_from_first: bool = True,
312
352
  **kwargs,
313
353
  ) -> None:
354
+ """Explore a sample of the data."""
314
355
  if column:
315
356
  self._column = column
316
357
  self._update_column()
@@ -347,10 +388,11 @@ class Explore(Map):
347
388
 
348
389
  def clipmap(
349
390
  self,
350
- mask,
391
+ mask: Any,
351
392
  column: str | None = None,
352
393
  **kwargs,
353
394
  ) -> None:
395
+ """Explore the data within a mask extent."""
354
396
  if column:
355
397
  self._column = column
356
398
  self._update_column()
@@ -368,7 +410,7 @@ class Explore(Map):
368
410
  self._gdf = pd.concat(gdfs, ignore_index=True)
369
411
  self._explore(**kwargs)
370
412
 
371
- def _explore(self, **kwargs):
413
+ def _explore(self, **kwargs) -> None:
372
414
  self.kwargs = self.kwargs | kwargs
373
415
 
374
416
  if self._is_categorical:
@@ -384,7 +426,7 @@ class Explore(Map):
384
426
  else:
385
427
  display(self.map)
386
428
 
387
- def _split_categories(self):
429
+ def _split_categories(self) -> None:
388
430
  new_gdfs, new_labels, new_shows = [], [], []
389
431
  for cat in self._unique_values:
390
432
  gdf = self.gdf.loc[self.gdf[self.column] == cat]
@@ -396,7 +438,7 @@ class Explore(Map):
396
438
  self.labels = new_labels
397
439
  self.show = new_shows
398
440
 
399
- def _to_single_geom_type(self, gdf) -> GeoDataFrame:
441
+ def _to_single_geom_type(self, gdf: GeoDataFrame) -> GeoDataFrame:
400
442
  gdf = clean_geoms(gdf)
401
443
 
402
444
  if get_geom_type(gdf) != "mixed":
@@ -420,16 +462,16 @@ class Explore(Map):
420
462
 
421
463
  assert get_geom_type(gdf) != "mixed", gdf.geom_type.value_counts()
422
464
 
423
- warnings.warn(mess)
465
+ warnings.warn(mess, stacklevel=1)
424
466
 
425
467
  return gdf
426
468
 
427
- def _update_column(self):
469
+ def _update_column(self) -> None:
428
470
  self._is_categorical = self._check_if_categorical()
429
471
  self._fillna_if_col_is_missing()
430
472
  self._gdf = pd.concat(self._gdfs, ignore_index=True)
431
473
 
432
- def _create_categorical_map(self):
474
+ def _create_categorical_map(self) -> None:
433
475
  self._get_categorical_colors()
434
476
 
435
477
  gdf = self._prepare_gdf_for_map(self._gdf)
@@ -478,7 +520,7 @@ class Explore(Map):
478
520
 
479
521
  def _add_tiles(
480
522
  self, mapobj: folium.Map, tiles: list[str, xyzservices.TileProvider]
481
- ):
523
+ ) -> None:
482
524
  for tile in tiles:
483
525
  to_tile(tile, max_zoom=self.max_zoom).add_to(mapobj)
484
526
 
@@ -549,7 +591,7 @@ class Explore(Map):
549
591
  return [col for col in gdf.columns if col not in COLS_TO_DROP]
550
592
 
551
593
  @staticmethod
552
- def _prepare_gdf_for_map(gdf):
594
+ def _prepare_gdf_for_map(gdf: GeoDataFrame) -> GeoDataFrame:
553
595
  if isinstance(gdf, GeoSeries):
554
596
  gdf = gdf.to_frame("geometry")
555
597
 
@@ -567,13 +609,13 @@ class Explore(Map):
567
609
 
568
610
  def _make_folium_map(
569
611
  self,
570
- bounds,
571
- attr=None,
572
- tiles=None,
573
- width="100%",
574
- height="100%",
575
- control_scale=True,
576
- map_kwds=None,
612
+ bounds: tuple[float, float, float, float],
613
+ attr: Any = None,
614
+ tiles: Any = None,
615
+ width: str = "100%",
616
+ height: str = "100%",
617
+ control_scale: bool = True,
618
+ map_kwds: dict | None = None,
577
619
  **kwargs,
578
620
  ):
579
621
  if not map_kwds:
@@ -679,21 +721,29 @@ class Explore(Map):
679
721
 
680
722
  def _make_geojson(
681
723
  self,
682
- df,
724
+ df: GeoDataFrame,
683
725
  show: bool,
684
- color=None,
685
- tooltip=True,
686
- popup=False,
687
- highlight=True,
688
- marker_type=None,
689
- marker_kwds={},
690
- style_kwds={},
691
- highlight_kwds={},
692
- tooltip_kwds={},
693
- popup_kwds={},
694
- map_kwds={},
726
+ color: str | None = None,
727
+ tooltip: bool = True,
728
+ popup: bool = False,
729
+ highlight: bool = True,
730
+ marker_type: str | None = None,
731
+ marker_kwds: dict | None = None,
732
+ style_kwds: dict | None = None,
733
+ highlight_kwds: dict | None = None,
734
+ tooltip_kwds: dict | None = None,
735
+ popup_kwds: dict | None = None,
736
+ map_kwds: dict | None = None,
695
737
  **kwargs,
696
- ):
738
+ ) -> folium.GeoJson:
739
+
740
+ marker_kwds = marker_kwds or {}
741
+ style_kwds = style_kwds or {}
742
+ highlight_kwds = highlight_kwds or {}
743
+ tooltip_kwds = tooltip_kwds or {}
744
+ popup_kwds = popup_kwds or {}
745
+ map_kwds = map_kwds or {}
746
+
697
747
  gdf = df.copy()
698
748
 
699
749
  # convert LinearRing to LineString
@@ -816,9 +866,10 @@ class Explore(Map):
816
866
  )
817
867
 
818
868
 
819
- def _tooltip_popup(type, fields, gdf, **kwds):
820
- """get tooltip or popup"""
821
-
869
+ def _tooltip_popup(
870
+ type_: str, fields: Any, gdf: GeoDataFrame, **kwargs
871
+ ) -> folium.GeoJsonTooltip | folium.GeoJsonPopup:
872
+ """Get tooltip or popup."""
822
873
  # specify fields to show in the tooltip
823
874
  if fields is False or fields is None or fields == 0:
824
875
  return None
@@ -836,20 +887,16 @@ def _tooltip_popup(type, fields, gdf, **kwds):
836
887
 
837
888
  # Cast fields to str
838
889
  fields = list(map(str, fields))
839
- if type == "tooltip":
840
- return folium.GeoJsonTooltip(fields, **kwds)
841
- elif type == "popup":
842
- return folium.GeoJsonPopup(fields, **kwds)
843
-
890
+ if type_ == "tooltip":
891
+ return folium.GeoJsonTooltip(fields, **kwargs)
892
+ elif type_ == "popup":
893
+ return folium.GeoJsonPopup(fields, **kwargs)
844
894
 
845
- def raster_dataset_to_background_map(dataset: RasterDataset):
846
- crs = dataset.crs
847
- bbox = dataset.bounds
848
895
 
849
-
850
- def _categorical_legend(m, title, categories, colors):
851
- """
852
- Add categorical legend to a map
896
+ def _categorical_legend(
897
+ m: folium.Map, title: str, categories: list[str], colors: list[str]
898
+ ) -> None:
899
+ """Add categorical legend to a map.
853
900
 
854
901
  The implementation is using the code originally written by Michel Metran
855
902
  (@michelmetran) and released on GitHub
@@ -868,7 +915,6 @@ def _categorical_legend(m, title, categories, colors):
868
915
  colors : list-like
869
916
  list of colors (in the same order as categories)
870
917
  """
871
-
872
918
  # Header to Add
873
919
  head = """
874
920
  {% macro header(this, kwargs) %}