ssb-sgis 1.0.13__py3-none-any.whl → 1.0.15__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 ADDED
@@ -0,0 +1,187 @@
1
+ import abc
2
+ import datetime
3
+ import json
4
+ import re
5
+ from collections.abc import Iterable
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+ from typing import Any
9
+ from urllib.request import urlopen
10
+
11
+ import folium
12
+ import shapely
13
+
14
+ from ..geopandas_tools.conversion import to_shapely
15
+
16
+ JSON_PATH = Path(__file__).parent / "norge_i_bilder.json"
17
+
18
+ DEFAULT_YEARS: tuple[str] = tuple(
19
+ str(year)
20
+ for year in range(
21
+ int(datetime.datetime.now().year) - 8,
22
+ int(datetime.datetime.now().year) + 1,
23
+ )
24
+ )
25
+
26
+
27
+ @dataclass
28
+ class WmsLoader(abc.ABC):
29
+ """Abstract base class for wms loaders.
30
+
31
+ Child classes must implement the method 'get_tiles',
32
+ which should return a list of folium.WmsTileLayer.
33
+ """
34
+
35
+ @abc.abstractmethod
36
+ def get_tiles(self, bbox: Any, max_zoom: int = 40) -> list[folium.WmsTileLayer]:
37
+ """Get all tiles intersecting with a bbox."""
38
+
39
+ @abc.abstractmethod
40
+ def load_tiles(self) -> None:
41
+ """Load all tiles into self.tiles.
42
+
43
+ Not needed in sgis.explore.
44
+ """
45
+ pass
46
+
47
+
48
+ @dataclass
49
+ class NorgeIBilderWms(WmsLoader):
50
+ """Loads Norge i bilder tiles as folium.WmsTiles."""
51
+
52
+ years: Iterable[int | str] = DEFAULT_YEARS
53
+ contains: str | Iterable[str] | None = None
54
+ not_contains: str | Iterable[str] | None = None
55
+
56
+ def load_tiles(self) -> None:
57
+ """Load all Norge i bilder tiles into self.tiles."""
58
+ url = "https://wms.geonorge.no/skwms1/wms.nib-prosjekter?SERVICE=WMS&REQUEST=GetCapabilities"
59
+
60
+ name_pattern = r"<Name>(.*?)</Name>"
61
+ bbox_pattern = (
62
+ r"<EX_GeographicBoundingBox>.*?"
63
+ r"<westBoundLongitude>(.*?)</westBoundLongitude>.*?"
64
+ r"<eastBoundLongitude>(.*?)</eastBoundLongitude>.*?"
65
+ r"<southBoundLatitude>(.*?)</southBoundLatitude>.*?"
66
+ r"<northBoundLatitude>(.*?)</northBoundLatitude>.*?</EX_GeographicBoundingBox>"
67
+ )
68
+
69
+ all_tiles: list[dict] = []
70
+ with urlopen(url) as file:
71
+ xml_data: str = file.read().decode("utf-8")
72
+
73
+ for text in xml_data.split('<Layer queryable="1">')[1:]:
74
+
75
+ # Extract bounding box values
76
+ bbox_match = re.search(bbox_pattern, text, re.DOTALL)
77
+ if bbox_match:
78
+ minx, maxx, miny, maxy = (
79
+ float(bbox_match.group(i)) for i in [1, 2, 3, 4]
80
+ )
81
+ this_bbox = shapely.box(minx, miny, maxx, maxy)
82
+ else:
83
+ this_bbox = None
84
+
85
+ name_match = re.search(name_pattern, text, re.DOTALL)
86
+ name = name_match.group(1) if name_match else None
87
+
88
+ if (
89
+ not name
90
+ or not any(year in name for year in self.years)
91
+ or (
92
+ self.contains
93
+ and not any(re.search(x, name.lower()) for x in self.contains)
94
+ )
95
+ or (
96
+ self.not_contains
97
+ and any(re.search(x, name.lower()) for x in self.not_contains)
98
+ )
99
+ ):
100
+ continue
101
+
102
+ this_tile = {}
103
+ this_tile["name"] = name
104
+ this_tile["bbox"] = this_bbox
105
+ year = name.split(" ")[-1]
106
+ if year.isnumeric() and len(year) == 4:
107
+ this_tile["year"] = year
108
+ else:
109
+ this_tile["year"] = "9999"
110
+ all_tiles.append(this_tile)
111
+
112
+ self.tiles = sorted(all_tiles, key=lambda x: x["year"])
113
+
114
+ def get_tiles(self, bbox: Any, max_zoom: int = 40) -> list[folium.WmsTileLayer]:
115
+ """Get all Norge i bilder tiles intersecting with a bbox."""
116
+ if self.tiles is None:
117
+ self.load_tiles()
118
+
119
+ all_tiles = {}
120
+
121
+ bbox = to_shapely(bbox)
122
+
123
+ for tile in self.tiles:
124
+ if not tile["bbox"] or not tile["bbox"].intersects(bbox):
125
+ continue
126
+
127
+ name = tile["name"]
128
+
129
+ if (
130
+ not name
131
+ or not any(year in name for year in self.years)
132
+ or (
133
+ self.contains
134
+ and not any(re.search(x, name.lower()) for x in self.contains)
135
+ )
136
+ or (
137
+ self.not_contains
138
+ and any(re.search(x, name.lower()) for x in self.not_contains)
139
+ )
140
+ ):
141
+ continue
142
+
143
+ all_tiles[name] = folium.WmsTileLayer(
144
+ url="https://wms.geonorge.no/skwms1/wms.nib-prosjekter",
145
+ name=name,
146
+ layers=name,
147
+ format="image/png", # Tile format
148
+ transparent=True, # Allow transparency
149
+ version="1.3.0", # WMS version
150
+ attr="&copy; <a href='https://www.geonorge.no/'>Geonorge</a>",
151
+ show=False,
152
+ max_zoom=max_zoom,
153
+ )
154
+
155
+ return all_tiles
156
+
157
+ def __post_init__(self) -> None:
158
+ """Fix typings."""
159
+ if self.contains and isinstance(self.contains, str):
160
+ self.contains = [self.contains.lower()]
161
+ elif self.contains:
162
+ self.contains = [x.lower() for x in self.contains]
163
+
164
+ if self.not_contains and isinstance(self.not_contains, str):
165
+ self.not_contains = [self.not_contains.lower()]
166
+ elif self.not_contains:
167
+ self.not_contains = [x.lower() for x in self.not_contains]
168
+
169
+ self.years = [str(int(year)) for year in self.years]
170
+
171
+ if all(year in DEFAULT_YEARS for year in self.years):
172
+ try:
173
+ with open(JSON_PATH, encoding="utf-8") as file:
174
+ self.tiles = json.load(file)
175
+ except FileNotFoundError:
176
+ self.tiles = None
177
+ return
178
+ self.tiles = [
179
+ {
180
+ key: value if key != "bbox" else shapely.wkt.loads(value)
181
+ for key, value in tile.items()
182
+ }
183
+ for tile in self.tiles
184
+ if any(str(year) in tile["name"] for year in self.years)
185
+ ]
186
+ else:
187
+ self.tiles = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ssb-sgis
3
- Version: 1.0.13
3
+ Version: 1.0.15
4
4
  Summary: GIS functions used at Statistics Norway.
5
5
  Home-page: https://github.com/statisticsnorway/ssb-sgis
6
6
  License: MIT
@@ -1,4 +1,4 @@
1
- sgis/__init__.py,sha256=kqHf60QUo0_TaB1ikBQXkxCQK4hUjSM6u_GKfLJ3C-4,7351
1
+ sgis/__init__.py,sha256=Bh-W4cB6-1uc-xRzUxqxECwwoennpdlikZI3gwXtZ7E,7389
2
2
  sgis/debug_config.py,sha256=Tfr19kU46hSkkspsIJcrUWvlhaL4U3-f8xEPkujSCAQ,593
3
3
  sgis/exceptions.py,sha256=WNaEBPNNx0rmz-YDzlFX4vIE7ocJQruUTqS2RNAu2zU,660
4
4
  sgis/geopandas_tools/__init__.py,sha256=bo8lFMcltOz7TtWAi52_ekR2gd3mjfBfKeMDV5zuqFY,28
@@ -24,13 +24,15 @@ sgis/io/opener.py,sha256=HWO3G1NB6bpXKM94JadCD513vjat1o1TFjWGWzyVasg,898
24
24
  sgis/io/read_parquet.py,sha256=FvZYv1rLkUlrSaUY6QW6E1yntmntTeQuZ9ZRgCDO4IM,3776
25
25
  sgis/maps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
26
  sgis/maps/examine.py,sha256=Pb0dH8JazU5E2svfQrzHO1Bi-sjy5SeyY6zoeMO34jE,9369
27
- sgis/maps/explore.py,sha256=dHwQmD_8U9dLaBJHs2VKJvqK-yNUhH6yZoInF0iFrAE,46237
27
+ sgis/maps/explore.py,sha256=JX9ZTeHVsV1KsXP2YNVBa21Edvc6EksKdbb3V6W0tKs,47051
28
28
  sgis/maps/httpserver.py,sha256=7Od9JMCtntcIQKk_TchetojMHzFHT9sPw7GANahI97c,1982
29
29
  sgis/maps/legend.py,sha256=lVRVCkhPmJRjGK23obFJZAO3qp6du1LYnobkkN7DPkc,26279
30
30
  sgis/maps/map.py,sha256=smaf9i53EoRZWmZjn9UuqlhzUvVs1XKo2ItIpHxyuik,29592
31
- sgis/maps/maps.py,sha256=9uidfWhbdJzO6lzDUjn4EWJMpCtc4st3uy4dRbNRVtQ,22430
31
+ sgis/maps/maps.py,sha256=gxu0rgcVygjudRtM1dVRmsUMilMUIg3vG-UgvASM91E,23072
32
+ sgis/maps/norge_i_bilder.json,sha256=W_mFfte3DxugWbEudZ5fadZ2JeFYb0hyab2Quf4oJME,481311
32
33
  sgis/maps/thematicmap.py,sha256=yAE1xEfubJcDmBlOJf-Q3SVae1ZHIEMP-YB95Wy8cRw,21691
33
34
  sgis/maps/tilesources.py,sha256=F4mFHxPwkiPJdVKzNkScTX6xbJAMIUtlTq4mQ83oguw,1746
35
+ sgis/maps/wms.py,sha256=XHlCszR0raPbmUc2wYpQ_XRHnSJ6c1ic3w2dNnfMRm4,6252
34
36
  sgis/networkanalysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
37
  sgis/networkanalysis/_get_route.py,sha256=9I3t9pnccUPr4mozy3TJCOpGCCf3UOIojmsbifubZbA,6368
36
38
  sgis/networkanalysis/_od_cost_matrix.py,sha256=zkyPX7ObT996ahaFJ2oI0D0SqQWbWyfy_qLtXwValPg,3434
@@ -54,7 +56,7 @@ sgis/raster/indices.py,sha256=-J1HYmnT240iozvgagvyis6K0_GHZHRuUrPOgyoeIrY,223
54
56
  sgis/raster/regex.py,sha256=kYhVpRYzoXutx1dSYmqMoselWXww7MMEsTPmLZwHjbM,3759
55
57
  sgis/raster/sentinel_config.py,sha256=nySDqn2R8M6W8jguoBeSAK_zzbAsqmaI59i32446FwY,1268
56
58
  sgis/raster/zonal.py,sha256=D4Gyptw-yOLTCO41peIuYbY-DANsJCG19xXDlf1QAz4,2299
57
- ssb_sgis-1.0.13.dist-info/LICENSE,sha256=np3IfD5m0ZUofn_kVzDZqliozuiO6wrktw3LRPjyEiI,1073
58
- ssb_sgis-1.0.13.dist-info/METADATA,sha256=H0AhyHQ3_Va2WmbAkd5IgycphRpYVw0GyctqeIAqQt0,11741
59
- ssb_sgis-1.0.13.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
60
- ssb_sgis-1.0.13.dist-info/RECORD,,
59
+ ssb_sgis-1.0.15.dist-info/LICENSE,sha256=np3IfD5m0ZUofn_kVzDZqliozuiO6wrktw3LRPjyEiI,1073
60
+ ssb_sgis-1.0.15.dist-info/METADATA,sha256=bRaj-9WssZ9IsI2IPEtI_uyLGhpEWd52xfMst-vI3g4,11741
61
+ ssb_sgis-1.0.15.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
62
+ ssb_sgis-1.0.15.dist-info/RECORD,,