ssb-sgis 1.2.17__py3-none-any.whl → 1.3.1__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/maps/wms.py CHANGED
@@ -3,7 +3,6 @@ import datetime
3
3
  import json
4
4
  import re
5
5
  from collections.abc import Iterable
6
- from dataclasses import dataclass
7
6
  from io import BytesIO
8
7
  from pathlib import Path
9
8
  from typing import Any
@@ -28,20 +27,14 @@ from ..geopandas_tools.conversion import to_shapely
28
27
  from ..geopandas_tools.sfilter import sfilter
29
28
  from ..raster.image_collection import Band
30
29
 
31
- JSON_PATH = Path(__file__).parent / "norge_i_bilder.json"
32
-
33
- JSON_YEARS = [str(year) for year in range(2006, datetime.datetime.now().year + 1)]
34
-
35
- DEFAULT_YEARS: tuple[str] = tuple(
36
- str(year)
37
- for year in range(
30
+ DEFAULT_YEARS: tuple[int] = tuple(
31
+ range(
38
32
  int(datetime.datetime.now().year) - 10,
39
33
  int(datetime.datetime.now().year) + 1,
40
34
  )
41
35
  )
42
36
 
43
37
 
44
- @dataclass
45
38
  class WmsLoader(abc.ABC):
46
39
  """Abstract base class for wms loaders.
47
40
 
@@ -49,32 +42,84 @@ class WmsLoader(abc.ABC):
49
42
  which should return a list of folium.WmsTileLayer.
50
43
  """
51
44
 
45
+ _min_year: int = 1900
46
+
47
+ @abc.abstractmethod
48
+ def filter_tiles(
49
+ self, mask: GeoDataFrame | GeoSeries | Geometry | tuple[float]
50
+ ) -> list[str]:
51
+ """Filter relevant dates with pandas and geopandas because fast."""
52
+
52
53
  @abc.abstractmethod
53
54
  def get_tiles(self, bbox: Any, max_zoom: int = 40) -> list[folium.WmsTileLayer]:
54
55
  """Get all tiles intersecting with a bbox."""
55
56
 
56
57
  @abc.abstractmethod
57
58
  def load_tiles(self) -> None:
58
- """Load all tiles into self.tiles.
59
+ """Load all tiles into self._tiles.
59
60
 
60
61
  Not needed in sgis.explore.
61
62
  """
62
63
  pass
63
64
 
65
+ def __repr__(self) -> str:
66
+ """Print representation."""
67
+ return str(self)
68
+
69
+ def __str__(self) -> str:
70
+ """String representation."""
71
+
72
+ def maybe_to_string(value: Any):
73
+ if isinstance(value, str):
74
+ return f"'{value}'"
75
+ return value
76
+
77
+ txt = ", ".join(
78
+ f"{k}={maybe_to_string(v)}"
79
+ for k, v in self.__dict__.items()
80
+ if not k.startswith("_")
81
+ )
82
+ return f"{self.__class__.__name__}({txt})"
83
+
64
84
 
65
- @dataclass
66
85
  class NorgeIBilderWms(WmsLoader):
67
- """Loads Norge i bilder tiles as folium.WmsTiles."""
86
+ """Loads Norge i bilder tiles as folium.WmsTiles.
87
+
88
+ Args:
89
+ years: list of years to search for images.
90
+ contains: substrings to include in image search.
91
+ not_contains: substrings to exclude in image search.
92
+ show: Whether to show all layers upon initialisation of the map.
93
+ _use_json: Whether to use pre-made json file of image names and bounds/masks
94
+ if all years are within range. Defaults to True (much faster).
95
+ """
68
96
 
69
- years: Iterable[int | str] = DEFAULT_YEARS
70
- contains: str | Iterable[str] | None = None
71
- not_contains: str | Iterable[str] | None = None
72
- show: bool | Iterable[int] | int = False
73
- _use_json: bool = True
74
97
  url: str = "https://wms.geonorge.no/skwms1/wms.nib-prosjekter"
98
+ _min_year: int = 1935
99
+ _json_path = Path(__file__).parent / "norge_i_bilder.json"
100
+
101
+ def __init__(
102
+ self,
103
+ years: Iterable[int | str] = DEFAULT_YEARS,
104
+ contains: str | Iterable[str] | None = None,
105
+ not_contains: str | Iterable[str] | None = None,
106
+ show: bool | Iterable[int] | int = False,
107
+ _use_json: bool = True,
108
+ ) -> None:
109
+ """Initialiser."""
110
+ self.years = [int(year) for year in years]
111
+ self.contains = contains
112
+ self.not_contains = not_contains
113
+ self.show = show
114
+ self._use_json = _use_json
115
+
116
+ if self._use_json:
117
+ self._load_from_json()
118
+ else:
119
+ self._tiles = None
75
120
 
76
121
  def load_tiles(self, verbose: bool = False) -> None:
77
- """Load all Norge i bilder tiles into self.tiles."""
122
+ """Load all Norge i bilder tiles into self._tiles."""
78
123
  name_pattern = r"<Name>(.*?)</Name>"
79
124
  bbox_pattern = (
80
125
  r"<EX_GeographicBoundingBox>.*?"
@@ -84,8 +129,12 @@ class NorgeIBilderWms(WmsLoader):
84
129
  r"<northBoundLatitude>(.*?)</northBoundLatitude>.*?</EX_GeographicBoundingBox>"
85
130
  )
86
131
 
132
+ url: str = (
133
+ "https://wms.geonorge.no/skwms1/wms.nib-prosjekter?SERVICE=WMS&REQUEST=GetCapabilities"
134
+ )
135
+
87
136
  all_tiles: list[dict] = []
88
- with urlopen(self.url) as file:
137
+ with urlopen(url) as file:
89
138
  xml_data: str = file.read().decode("utf-8")
90
139
 
91
140
  for text in xml_data.split('<Layer queryable="1">')[1:]:
@@ -105,14 +154,20 @@ class NorgeIBilderWms(WmsLoader):
105
154
 
106
155
  if (
107
156
  not name
108
- or not any(year in name for year in self.years)
157
+ or not any(str(year) in name for year in self.years)
109
158
  or (
110
159
  self.contains
111
- and not any(re.search(x, name.lower()) for x in self.contains)
160
+ and not any(
161
+ re.search(x, name.lower())
162
+ for x in _string_as_list(self.contains)
163
+ )
112
164
  )
113
165
  or (
114
166
  self.not_contains
115
- and any(re.search(x, name.lower()) for x in self.not_contains)
167
+ and any(
168
+ re.search(x, name.lower())
169
+ for x in _string_as_list(self.not_contains)
170
+ )
116
171
  )
117
172
  ):
118
173
  continue
@@ -131,10 +186,10 @@ class NorgeIBilderWms(WmsLoader):
131
186
 
132
187
  all_tiles.append(this_tile)
133
188
 
134
- self.tiles = sorted(all_tiles, key=lambda x: (x["year"]))
189
+ self._tiles = sorted(all_tiles, key=lambda x: (x["year"]))
135
190
 
136
191
  masks = self._get_norge_i_bilder_polygon_masks(verbose=verbose)
137
- for tile in self.tiles:
192
+ for tile in self._tiles:
138
193
  mask = masks.get(tile["name"], None)
139
194
  tile["geometry"] = mask
140
195
 
@@ -143,7 +198,7 @@ class NorgeIBilderWms(WmsLoader):
143
198
  from owslib.wms import WebMapService
144
199
  from PIL import Image
145
200
 
146
- relevant_names: dict[str, str] = {x["name"]: x["bbox"] for x in self.tiles}
201
+ relevant_names: dict[str, str] = {x["name"]: x["bbox"] for x in self._tiles}
147
202
  assert len(relevant_names), relevant_names
148
203
 
149
204
  url = "https://wms.geonorge.no/skwms1/wms.nib-mosaikk?SERVICE=WMS&REQUEST=GetCapabilities"
@@ -214,7 +269,7 @@ class NorgeIBilderWms(WmsLoader):
214
269
 
215
270
  def get_tiles(self, mask: Any, max_zoom: int = 40) -> list[folium.WmsTileLayer]:
216
271
  """Get all Norge i bilder tiles intersecting with a mask (bbox or polygon)."""
217
- if self.tiles is None:
272
+ if self._tiles is None:
218
273
  self.load_tiles()
219
274
 
220
275
  if not isinstance(mask, (GeoSeries | GeoDataFrame | Geometry)):
@@ -225,7 +280,7 @@ class NorgeIBilderWms(WmsLoader):
225
280
  else:
226
281
  show = False
227
282
 
228
- relevant_tiles = self._filter_tiles(mask)
283
+ relevant_tiles = self.filter_tiles(mask)
229
284
  tile_layers = {
230
285
  name: folium.WmsTileLayer(
231
286
  url="https://wms.geonorge.no/skwms1/wms.nib-prosjekter",
@@ -238,7 +293,7 @@ class NorgeIBilderWms(WmsLoader):
238
293
  show=show,
239
294
  max_zoom=max_zoom,
240
295
  )
241
- for name in relevant_tiles["name"]
296
+ for name in relevant_tiles
242
297
  }
243
298
 
244
299
  if not len(tile_layers):
@@ -254,59 +309,51 @@ class NorgeIBilderWms(WmsLoader):
254
309
 
255
310
  return tile_layers
256
311
 
257
- def _filter_tiles(self, mask):
312
+ def filter_tiles(
313
+ self, mask: GeoDataFrame | GeoSeries | Geometry | tuple[float]
314
+ ) -> list[str]:
258
315
  """Filter relevant dates with pandas and geopandas because fast."""
259
- if not self.tiles:
260
- return pd.DataFrame(columns=["name", "year", "geometry", "bbox"])
261
- df = pd.DataFrame(self.tiles)
262
- filt = (df["name"].notna()) & (df["year"].str.contains("|".join(self.years)))
316
+ if not self._tiles:
317
+ return []
318
+ df = pd.DataFrame(self._tiles)
319
+ filt = (df["name"].notna()) & (
320
+ df["year"].str.contains("|".join([str(year) for year in self.years]))
321
+ )
263
322
  if self.contains:
264
- for x in self.contains:
323
+ for x in _string_as_list(self.contains):
265
324
  filt &= df["name"].str.contains(x)
266
325
  if self.not_contains:
267
- for x in self.not_contains:
326
+ for x in _string_as_list(self.not_contains):
268
327
  filt &= ~df["name"].str.contains(x)
269
328
  df = df[filt]
270
329
  geoms = np.where(df["geometry"].notna(), df["geometry"], df["bbox"])
271
330
  geoms = GeoSeries(geoms)
272
331
  assert geoms.index.is_unique
273
- return df.iloc[sfilter(geoms, mask).index]
274
-
275
- def __post_init__(self) -> None:
276
- """Fix typings."""
277
- if self.contains and isinstance(self.contains, str):
278
- self.contains = [self.contains]
279
- elif self.contains:
280
- self.contains = [x for x in self.contains]
281
- if self.not_contains and isinstance(self.not_contains, str):
282
- self.not_contains = [self.not_contains]
283
- elif self.not_contains:
284
- self.not_contains = [x for x in self.not_contains]
285
-
286
- self.years = [str(int(year)) for year in self.years]
287
-
288
- if self._use_json and all(year in JSON_YEARS for year in self.years):
289
- try:
290
- with open(JSON_PATH, encoding="utf-8") as file:
291
- self.tiles = json.load(file)
292
- except FileNotFoundError:
293
- self.tiles = None
294
- return
295
- self.tiles = [
296
- {
297
- key: (
298
- value
299
- if key not in ["bbox", "geometry"]
300
- else shapely.wkt.loads(value)
301
- )
302
- for key, value in tile.items()
303
- }
304
- for tile in self.tiles
305
- if any(str(year) in tile["name"] for year in self.years)
306
- ]
307
- else:
308
- self.tiles = None
332
+ return list(df.iloc[sfilter(geoms, mask).index]["name"])
333
+
334
+ def _load_from_json(self) -> None:
335
+ """Load tiles from json file."""
336
+ try:
337
+ with open(self._json_path, encoding="utf-8") as file:
338
+ self._tiles = json.load(file)
339
+ except FileNotFoundError:
340
+ self._tiles = None
341
+ return
342
+ self._tiles = [
343
+ {
344
+ key: (
345
+ value
346
+ if key not in ["bbox", "geometry"]
347
+ else shapely.wkt.loads(value)
348
+ )
349
+ for key, value in tile.items()
350
+ }
351
+ for tile in self._tiles
352
+ if any(str(year) in tile["name"] for year in self.years)
353
+ ]
309
354
 
310
- def __repr__(self) -> str:
311
- """Print representation."""
312
- return f"{self.__class__.__name__}({len(self.tiles or [])})"
355
+
356
+ def _string_as_list(x: str | list[str]) -> list[str] | None:
357
+ if not x:
358
+ return None
359
+ return [x] if isinstance(x, str) else list(x)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ssb-sgis
3
- Version: 1.2.17
3
+ Version: 1.3.1
4
4
  Summary: GIS functions used at Statistics Norway.
5
5
  Home-page: https://github.com/statisticsnorway/ssb-sgis
6
6
  License: MIT
@@ -9,7 +9,7 @@ sgis/geopandas_tools/centerlines.py,sha256=Q65Sx01SeAlulBEd9oaZkB2maBBNdLcJwAbTI
9
9
  sgis/geopandas_tools/cleaning.py,sha256=fST0xFztmyn-QUOAfvjZmu7aO_zPiolWK7gd7TR6ffI,24393
10
10
  sgis/geopandas_tools/conversion.py,sha256=iX954YEpobmn_R1Ecx_zDU1RlWq_67pfbzMXtwTH04I,24162
11
11
  sgis/geopandas_tools/duplicates.py,sha256=TDDM4u1n7SIkyJrOfl1Lno92AmUPqtXBHsj1IUKC0hI,14992
12
- sgis/geopandas_tools/general.py,sha256=YRpNEdwTHyFdQOdAfbCmYXS7PxoDjXxoagwpteXkYdI,43937
12
+ sgis/geopandas_tools/general.py,sha256=DTQM8p-krgR3gA3miP931eCrxDCpF1ya7dRiMy3K_bs,44099
13
13
  sgis/geopandas_tools/geocoding.py,sha256=sZjUW52ULhQWDLmU51C9_itBePkDuWkp8swvYaiYmJk,679
14
14
  sgis/geopandas_tools/geometry_types.py,sha256=ijQDbQaZPqPGjBl707H4yooNXpk21RXyatI7itnvqLk,7603
15
15
  sgis/geopandas_tools/neighbors.py,sha256=VZGOwwC3-C6KpwLQ3j0K5cOVInmckxIXoGMqPGkemk4,17606
@@ -23,7 +23,7 @@ sgis/geopandas_tools/utils.py,sha256=X0pRvB1tWgV_0BCrRS1HU9LtLGnZCpvVPxyqM9JGb0Y
23
23
  sgis/helpers.py,sha256=4N6vFWQ3TYVzRHNcWY_fNa_GkFuaZB3vtCkkFND-qs0,9628
24
24
  sgis/io/__init__.py,sha256=uyBr20YDqB2bQttrd5q1JuGOvX32A-MSvS7Wmw5f5qg,177
25
25
  sgis/io/_is_dapla.py,sha256=wmfkSe98IrLhUg3dtXZusV6OVC8VlY1kbc5EQDf3P-Q,358
26
- sgis/io/dapla_functions.py,sha256=quq2HkeR0MZSacllDXjVJlthZebT2ATbU6mjsxODOVA,31796
26
+ sgis/io/dapla_functions.py,sha256=YkS2QqNyZ_OcZXXUKnHEItvnO9vZ22k7RK30p-kGl0E,31861
27
27
  sgis/io/opener.py,sha256=HWO3G1NB6bpXKM94JadCD513vjat1o1TFjWGWzyVasg,898
28
28
  sgis/io/read_parquet.py,sha256=FvZYv1rLkUlrSaUY6QW6E1yntmntTeQuZ9ZRgCDO4IM,3776
29
29
  sgis/maps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -33,10 +33,10 @@ sgis/maps/httpserver.py,sha256=I7tTn3hFaTCc-E-T_o9v0nXwMGaS2Xqd4MlWbq8k-J4,3014
33
33
  sgis/maps/legend.py,sha256=gTEWCVIZH1cw6ULVVrJqRR4__uOZvrT3xxQ5lhZMVR4,26899
34
34
  sgis/maps/map.py,sha256=iGa0o7NlUMErJCYJpVzkpDvuWJc8iDN_-tH2X2WcBlI,30802
35
35
  sgis/maps/maps.py,sha256=fLK5WUlQ2YTm7t-8260lYxCFvpZN6j0Y-bVYCyv8NAY,23249
36
- sgis/maps/norge_i_bilder.json,sha256=G9DIN_2vyn-18UF5wUC-koZxFCbiNxMu0BbCJhMFJUk,15050340
36
+ sgis/maps/norge_i_bilder.json,sha256=wUYY8VTanI-C4bpNk7bmnQb8S-S3CBUIRLgdOoydnoQ,20197311
37
37
  sgis/maps/thematicmap.py,sha256=ZtV4Hfylr1ST_cPzi11_lFIsTdY3D1o1EZQbPXZLwyM,25187
38
38
  sgis/maps/tilesources.py,sha256=F4mFHxPwkiPJdVKzNkScTX6xbJAMIUtlTq4mQ83oguw,1746
39
- sgis/maps/wms.py,sha256=3ZPf-H3sirvHeLQdXGwE3j6w1sFQCkExsEWkvHl7a4g,11233
39
+ sgis/maps/wms.py,sha256=sCVpKxH1Rsd14GECW7BFh8yaWngpVWYvw9Yhuez1yW8,12482
40
40
  sgis/networkanalysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
41
  sgis/networkanalysis/_get_route.py,sha256=3m5xQdQqGtt51vcI5fcmYQAOQCeBtL6sorDoPxBNf84,7818
42
42
  sgis/networkanalysis/_od_cost_matrix.py,sha256=zkyPX7ObT996ahaFJ2oI0D0SqQWbWyfy_qLtXwValPg,3434
@@ -61,7 +61,7 @@ sgis/raster/indices.py,sha256=efJmgfPg_VuSzXFosXV661IendF8CwPFWtMhyP4TMUg,222
61
61
  sgis/raster/regex.py,sha256=4idTJ9vFtsGtbxcjJrx2VrpJJuDMP3bLdqF93Vc_cmY,3752
62
62
  sgis/raster/sentinel_config.py,sha256=nySDqn2R8M6W8jguoBeSAK_zzbAsqmaI59i32446FwY,1268
63
63
  sgis/raster/zonal.py,sha256=D4Gyptw-yOLTCO41peIuYbY-DANsJCG19xXDlf1QAz4,2299
64
- ssb_sgis-1.2.17.dist-info/LICENSE,sha256=np3IfD5m0ZUofn_kVzDZqliozuiO6wrktw3LRPjyEiI,1073
65
- ssb_sgis-1.2.17.dist-info/METADATA,sha256=9bFaOD-bXLRJ_2MGquhQqYXOwttbTd2E71s_O94yNAw,11625
66
- ssb_sgis-1.2.17.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
67
- ssb_sgis-1.2.17.dist-info/RECORD,,
64
+ ssb_sgis-1.3.1.dist-info/LICENSE,sha256=np3IfD5m0ZUofn_kVzDZqliozuiO6wrktw3LRPjyEiI,1073
65
+ ssb_sgis-1.3.1.dist-info/METADATA,sha256=mDvP7_0YnM5lKhKcu3IsX3OYwc-vMIL8mT09tBR_u6o,11624
66
+ ssb_sgis-1.3.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
67
+ ssb_sgis-1.3.1.dist-info/RECORD,,