ssb-sgis 1.2.7__py3-none-any.whl → 1.2.8__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/geopandas_tools/buffer_dissolve_explode.py +1 -1
- sgis/maps/explore.py +7 -3
- sgis/maps/maps.py +5 -2
- sgis/maps/norge_i_bilder.json +4792 -2380
- sgis/maps/wms.py +159 -34
- {ssb_sgis-1.2.7.dist-info → ssb_sgis-1.2.8.dist-info}/METADATA +1 -1
- {ssb_sgis-1.2.7.dist-info → ssb_sgis-1.2.8.dist-info}/RECORD +9 -9
- {ssb_sgis-1.2.7.dist-info → ssb_sgis-1.2.8.dist-info}/LICENSE +0 -0
- {ssb_sgis-1.2.7.dist-info → ssb_sgis-1.2.8.dist-info}/WHEEL +0 -0
sgis/maps/wms.py
CHANGED
|
@@ -4,18 +4,33 @@ import json
|
|
|
4
4
|
import re
|
|
5
5
|
from collections.abc import Iterable
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
+
from io import BytesIO
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from typing import Any
|
|
9
10
|
from urllib.request import urlopen
|
|
10
11
|
|
|
11
12
|
import folium
|
|
13
|
+
import numpy as np
|
|
14
|
+
import pandas as pd
|
|
12
15
|
import shapely
|
|
13
|
-
|
|
16
|
+
from geopandas import GeoDataFrame
|
|
17
|
+
from geopandas import GeoSeries
|
|
18
|
+
from shapely import Geometry
|
|
19
|
+
from shapely import get_exterior_ring
|
|
20
|
+
from shapely import make_valid
|
|
21
|
+
from shapely import polygons
|
|
22
|
+
from shapely import set_precision
|
|
23
|
+
from shapely import simplify
|
|
24
|
+
from shapely.errors import GEOSException
|
|
25
|
+
|
|
26
|
+
from ..geopandas_tools.conversion import to_gdf
|
|
14
27
|
from ..geopandas_tools.conversion import to_shapely
|
|
28
|
+
from ..geopandas_tools.sfilter import sfilter
|
|
29
|
+
from ..raster.image_collection import Band
|
|
15
30
|
|
|
16
31
|
JSON_PATH = Path(__file__).parent / "norge_i_bilder.json"
|
|
17
32
|
|
|
18
|
-
JSON_YEARS = [str(year) for year in range(
|
|
33
|
+
JSON_YEARS = [str(year) for year in range(2006, datetime.datetime.now().year + 1)]
|
|
19
34
|
|
|
20
35
|
DEFAULT_YEARS: tuple[str] = tuple(
|
|
21
36
|
str(year)
|
|
@@ -57,7 +72,7 @@ class NorgeIBilderWms(WmsLoader):
|
|
|
57
72
|
show: bool | Iterable[int] | int = False
|
|
58
73
|
_use_json: bool = True
|
|
59
74
|
|
|
60
|
-
def load_tiles(self) -> None:
|
|
75
|
+
def load_tiles(self, verbose: bool = False) -> None:
|
|
61
76
|
"""Load all Norge i bilder tiles into self.tiles."""
|
|
62
77
|
url = "https://wms.geonorge.no/skwms1/wms.nib-prosjekter?SERVICE=WMS&REQUEST=GetCapabilities"
|
|
63
78
|
|
|
@@ -107,7 +122,10 @@ class NorgeIBilderWms(WmsLoader):
|
|
|
107
122
|
this_tile["name"] = name
|
|
108
123
|
this_tile["bbox"] = this_bbox
|
|
109
124
|
year = name.split(" ")[-1]
|
|
110
|
-
|
|
125
|
+
is_year_or_interval: bool = all(
|
|
126
|
+
part.isnumeric() and len(part) == 4 for part in year.split("-")
|
|
127
|
+
)
|
|
128
|
+
if is_year_or_interval:
|
|
111
129
|
this_tile["year"] = year
|
|
112
130
|
else:
|
|
113
131
|
this_tile["year"] = "9999"
|
|
@@ -116,41 +134,123 @@ class NorgeIBilderWms(WmsLoader):
|
|
|
116
134
|
|
|
117
135
|
self.tiles = sorted(all_tiles, key=lambda x: x["year"])
|
|
118
136
|
|
|
119
|
-
|
|
120
|
-
|
|
137
|
+
masks = self._get_norge_i_bilder_polygon_masks(verbose=verbose)
|
|
138
|
+
for tile in self.tiles:
|
|
139
|
+
mask = masks.get(tile["name"], None)
|
|
140
|
+
tile["geometry"] = mask
|
|
141
|
+
|
|
142
|
+
def _get_norge_i_bilder_polygon_masks(self, verbose: bool):
|
|
143
|
+
from owslib.util import ServiceException
|
|
144
|
+
from owslib.wms import WebMapService
|
|
145
|
+
from PIL import Image
|
|
146
|
+
|
|
147
|
+
relevant_names: dict[str, str] = {x["name"]: x["bbox"] for x in self.tiles}
|
|
148
|
+
assert len(relevant_names), relevant_names
|
|
149
|
+
|
|
150
|
+
url = "https://wms.geonorge.no/skwms1/wms.nib-mosaikk?SERVICE=WMS&REQUEST=GetCapabilities"
|
|
151
|
+
wms = WebMapService(url, version="1.3.0")
|
|
152
|
+
out = {}
|
|
153
|
+
# ttiles = {wms[layer].title: [] for layer in list(wms.contents)}
|
|
154
|
+
# for layer in list(wms.contents):
|
|
155
|
+
# if wms[layer].title not in relevant_names:
|
|
156
|
+
# continue
|
|
157
|
+
# ttiles[wms[layer].title].append(layer)
|
|
158
|
+
# import pandas as pd
|
|
159
|
+
|
|
160
|
+
# df = pd.Series(ttiles).to_frame("title")
|
|
161
|
+
# df["n"] = df["title"].str.len()
|
|
162
|
+
# df = df.sort_values("n")
|
|
163
|
+
# for x in df["title"]:
|
|
164
|
+
# if len(x) == 1:
|
|
165
|
+
# continue
|
|
166
|
+
# bounds = {tuple(wms[layer].boundingBoxWGS84) for layer in x}
|
|
167
|
+
# if len(bounds) <= 1:
|
|
168
|
+
# continue
|
|
169
|
+
# print()
|
|
170
|
+
# for layer in x:
|
|
171
|
+
# print(layer)
|
|
172
|
+
# print(wms[layer].title)
|
|
173
|
+
# bbox = wms[layer].boundingBoxWGS84
|
|
174
|
+
# print(bbox)
|
|
175
|
+
|
|
176
|
+
for layer in list(wms.contents):
|
|
177
|
+
title = wms[layer].title
|
|
178
|
+
if title not in relevant_names:
|
|
179
|
+
continue
|
|
180
|
+
bbox = wms[layer].boundingBoxWGS84
|
|
181
|
+
bbox = tuple(to_gdf(bbox, crs=4326).to_crs(25832).total_bounds)
|
|
182
|
+
|
|
183
|
+
existing_bbox = relevant_names[title]
|
|
184
|
+
existing_bbox = to_gdf(existing_bbox, crs=4326).to_crs(25832).union_all()
|
|
185
|
+
if not to_shapely(bbox).intersects(existing_bbox):
|
|
186
|
+
continue
|
|
187
|
+
diffx = bbox[2] - bbox[0]
|
|
188
|
+
diffy = bbox[3] - bbox[1]
|
|
189
|
+
width = int(diffx / 40)
|
|
190
|
+
height = int(diffy / 40)
|
|
191
|
+
if not bbox:
|
|
192
|
+
continue
|
|
193
|
+
try:
|
|
194
|
+
img = wms.getmap(
|
|
195
|
+
layers=[layer],
|
|
196
|
+
styles=[""], # Empty unless you know the style
|
|
197
|
+
srs="EPSG:25832",
|
|
198
|
+
bbox=bbox,
|
|
199
|
+
size=(width, height),
|
|
200
|
+
format="image/jpeg",
|
|
201
|
+
transparent=True,
|
|
202
|
+
bgcolor="#FFFFFF",
|
|
203
|
+
)
|
|
204
|
+
except (ServiceException, AttributeError) as e:
|
|
205
|
+
if verbose:
|
|
206
|
+
print(type(e), e)
|
|
207
|
+
continue
|
|
208
|
+
|
|
209
|
+
arr = np.array(Image.open(BytesIO(img.read())))
|
|
210
|
+
if not np.sum(arr):
|
|
211
|
+
continue
|
|
212
|
+
|
|
213
|
+
band = Band(
|
|
214
|
+
np.where(np.any(arr != 0, axis=-1), 1, 0), bounds=bbox, crs=25832
|
|
215
|
+
)
|
|
216
|
+
polygon = band.to_geopandas()[lambda x: x["value"] == 1].geometry.values
|
|
217
|
+
polygon = make_valid(polygons(get_exterior_ring(polygon)))
|
|
218
|
+
polygon = make_valid(set_precision(polygon, 1))
|
|
219
|
+
polygon = make_valid(simplify(polygon, 100))
|
|
220
|
+
polygon = make_valid(set_precision(polygon, 1))
|
|
221
|
+
polygon = GeoSeries(polygon, crs=25832).to_crs(4326)
|
|
222
|
+
if verbose:
|
|
223
|
+
print(f"Layer name: {layer}")
|
|
224
|
+
print(f"Title: {wms[layer].title}")
|
|
225
|
+
print(f"Bounding box: {wms[layer].boundingBoxWGS84}")
|
|
226
|
+
print(f"polygon: {polygon}")
|
|
227
|
+
print("-" * 40)
|
|
228
|
+
|
|
229
|
+
for x in [0, 0.1, 0.001, 1]:
|
|
230
|
+
try:
|
|
231
|
+
out[title] = make_valid(polygon.buffer(x).make_valid().union_all())
|
|
232
|
+
except GEOSException:
|
|
233
|
+
pass
|
|
234
|
+
break
|
|
235
|
+
|
|
236
|
+
return out
|
|
237
|
+
|
|
238
|
+
def get_tiles(self, mask: Any, max_zoom: int = 40) -> list[folium.WmsTileLayer]:
|
|
239
|
+
"""Get all Norge i bilder tiles intersecting with a mask (bbox or polygon)."""
|
|
121
240
|
if self.tiles is None:
|
|
122
241
|
self.load_tiles()
|
|
123
242
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
bbox = to_shapely(bbox)
|
|
243
|
+
if not isinstance(mask, (GeoSeries | GeoDataFrame | Geometry)):
|
|
244
|
+
mask = to_shapely(mask)
|
|
127
245
|
|
|
128
246
|
if isinstance(self.show, bool):
|
|
129
247
|
show = self.show
|
|
130
248
|
else:
|
|
131
249
|
show = False
|
|
132
250
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
name = tile["name"]
|
|
138
|
-
|
|
139
|
-
if (
|
|
140
|
-
not name
|
|
141
|
-
or not any(year in name for year in self.years)
|
|
142
|
-
or (
|
|
143
|
-
self.contains
|
|
144
|
-
and not any(re.search(x, name.lower()) for x in self.contains)
|
|
145
|
-
)
|
|
146
|
-
or (
|
|
147
|
-
self.not_contains
|
|
148
|
-
and any(re.search(x, name.lower()) for x in self.not_contains)
|
|
149
|
-
)
|
|
150
|
-
):
|
|
151
|
-
continue
|
|
152
|
-
|
|
153
|
-
all_tiles[name] = folium.WmsTileLayer(
|
|
251
|
+
relevant_tiles = self._filter_tiles(mask)
|
|
252
|
+
tile_layers = {
|
|
253
|
+
name: folium.WmsTileLayer(
|
|
154
254
|
url="https://wms.geonorge.no/skwms1/wms.nib-prosjekter",
|
|
155
255
|
name=name,
|
|
156
256
|
layers=name,
|
|
@@ -161,16 +261,37 @@ class NorgeIBilderWms(WmsLoader):
|
|
|
161
261
|
show=show,
|
|
162
262
|
max_zoom=max_zoom,
|
|
163
263
|
)
|
|
264
|
+
for name in relevant_tiles["name"]
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if not len(tile_layers):
|
|
268
|
+
return tile_layers
|
|
164
269
|
|
|
165
270
|
if isinstance(self.show, int):
|
|
166
|
-
tile =
|
|
271
|
+
tile = tile_layers[list(tile_layers)[self.show]]
|
|
167
272
|
tile.show = True
|
|
168
273
|
elif isinstance(self.show, Iterable):
|
|
169
274
|
for i in self.show:
|
|
170
|
-
tile =
|
|
275
|
+
tile = tile_layers[list(tile_layers)[i]]
|
|
171
276
|
tile.show = True
|
|
172
277
|
|
|
173
|
-
return
|
|
278
|
+
return tile_layers
|
|
279
|
+
|
|
280
|
+
def _filter_tiles(self, mask):
|
|
281
|
+
"""Filter relevant dates with pandas and geopandas because fast."""
|
|
282
|
+
df = pd.DataFrame(self.tiles)
|
|
283
|
+
filt = (df["name"].notna()) & (df["year"].str.contains("|".join(self.years)))
|
|
284
|
+
if self.contains:
|
|
285
|
+
for x in self.contains:
|
|
286
|
+
filt &= df["name"].str.contains(x)
|
|
287
|
+
if self.not_contains:
|
|
288
|
+
for x in self.not_contains:
|
|
289
|
+
filt &= ~df["name"].str.contains(x)
|
|
290
|
+
df = df[filt]
|
|
291
|
+
geoms = np.where(df["geometry"].notna(), df["geometry"], df["bbox"])
|
|
292
|
+
geoms = GeoSeries(geoms)
|
|
293
|
+
assert geoms.index.is_unique
|
|
294
|
+
return df.iloc[sfilter(geoms, mask).index]
|
|
174
295
|
|
|
175
296
|
def __post_init__(self) -> None:
|
|
176
297
|
"""Fix typings."""
|
|
@@ -195,7 +316,11 @@ class NorgeIBilderWms(WmsLoader):
|
|
|
195
316
|
return
|
|
196
317
|
self.tiles = [
|
|
197
318
|
{
|
|
198
|
-
key:
|
|
319
|
+
key: (
|
|
320
|
+
value
|
|
321
|
+
if key not in ["bbox", "geometry"]
|
|
322
|
+
else shapely.wkt.loads(value)
|
|
323
|
+
)
|
|
199
324
|
for key, value in tile.items()
|
|
200
325
|
}
|
|
201
326
|
for tile in self.tiles
|
|
@@ -4,7 +4,7 @@ sgis/debug_config.py,sha256=Tfr19kU46hSkkspsIJcrUWvlhaL4U3-f8xEPkujSCAQ,593
|
|
|
4
4
|
sgis/exceptions.py,sha256=WNaEBPNNx0rmz-YDzlFX4vIE7ocJQruUTqS2RNAu2zU,660
|
|
5
5
|
sgis/geopandas_tools/__init__.py,sha256=bo8lFMcltOz7TtWAi52_ekR2gd3mjfBfKeMDV5zuqFY,28
|
|
6
6
|
sgis/geopandas_tools/bounds.py,sha256=YJyF0gp78hFAjLLZmDquRKCBAtbt7QouG3snTcJeNQs,23822
|
|
7
|
-
sgis/geopandas_tools/buffer_dissolve_explode.py,sha256=
|
|
7
|
+
sgis/geopandas_tools/buffer_dissolve_explode.py,sha256=z9HvakazR_prXH862e8-gEe7UFbeI4rRTbUaBgPeMBk,19552
|
|
8
8
|
sgis/geopandas_tools/centerlines.py,sha256=Q65Sx01SeAlulBEd9oaZkB2maBBNdLcJwAbTILg4SPU,11848
|
|
9
9
|
sgis/geopandas_tools/cleaning.py,sha256=fST0xFztmyn-QUOAfvjZmu7aO_zPiolWK7gd7TR6ffI,24393
|
|
10
10
|
sgis/geopandas_tools/conversion.py,sha256=CrasgWHAnUmLC5tP73ZTDjQ6ahKFHQGqWj86PUif24M,24176
|
|
@@ -28,15 +28,15 @@ 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
|
|
30
30
|
sgis/maps/examine.py,sha256=Pb0dH8JazU5E2svfQrzHO1Bi-sjy5SeyY6zoeMO34jE,9369
|
|
31
|
-
sgis/maps/explore.py,sha256=
|
|
31
|
+
sgis/maps/explore.py,sha256=azfNNcEEiNp2lJxWN1Emf7kcmG60WL_ok_ilMw0-IL8,47914
|
|
32
32
|
sgis/maps/httpserver.py,sha256=TETSGOgLjKl3TquPGoIP0tCJCz7BIwmXrrzSCT7jhXE,2550
|
|
33
33
|
sgis/maps/legend.py,sha256=qq2RkebuaNAdFztlXrDOWbN0voeK5w5VycmRKyx0NdM,26512
|
|
34
34
|
sgis/maps/map.py,sha256=XWf3QJ6a4gZno2NziK1dKLRktJGGr-vn6eHudBlW9Uc,30758
|
|
35
|
-
sgis/maps/maps.py,sha256=
|
|
36
|
-
sgis/maps/norge_i_bilder.json,sha256=
|
|
35
|
+
sgis/maps/maps.py,sha256=fLK5WUlQ2YTm7t-8260lYxCFvpZN6j0Y-bVYCyv8NAY,23249
|
|
36
|
+
sgis/maps/norge_i_bilder.json,sha256=G9DIN_2vyn-18UF5wUC-koZxFCbiNxMu0BbCJhMFJUk,15050340
|
|
37
37
|
sgis/maps/thematicmap.py,sha256=Z3o_Bca0oty5Cn35pZfX5Qy52sXDVIMVSFD6IlZrovo,25111
|
|
38
38
|
sgis/maps/tilesources.py,sha256=F4mFHxPwkiPJdVKzNkScTX6xbJAMIUtlTq4mQ83oguw,1746
|
|
39
|
-
sgis/maps/wms.py,sha256=
|
|
39
|
+
sgis/maps/wms.py,sha256=oB7jeE3rSRhM-xz7t95hFLjtuG18mw7CV4wNx6IuBes,12001
|
|
40
40
|
sgis/networkanalysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
41
|
sgis/networkanalysis/_get_route.py,sha256=dMX4Vm6O90ISIZPjQWuZMVMuEubkeSdC2osMCbFvrRU,7750
|
|
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.
|
|
65
|
-
ssb_sgis-1.2.
|
|
66
|
-
ssb_sgis-1.2.
|
|
67
|
-
ssb_sgis-1.2.
|
|
64
|
+
ssb_sgis-1.2.8.dist-info/LICENSE,sha256=np3IfD5m0ZUofn_kVzDZqliozuiO6wrktw3LRPjyEiI,1073
|
|
65
|
+
ssb_sgis-1.2.8.dist-info/METADATA,sha256=3bsjQU53D8bZVZ7J2VgZDeUeB0wP6hhnXJBKsvLz18M,11740
|
|
66
|
+
ssb_sgis-1.2.8.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
67
|
+
ssb_sgis-1.2.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|