maps4fs 2.0.2__py3-none-any.whl → 2.0.3__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 (40) hide show
  1. maps4fs/__init__.py +3 -2
  2. maps4fs/generator/component/dem.py +1 -2
  3. maps4fs/generator/map.py +2 -1
  4. {maps4fs-2.0.2.dist-info → maps4fs-2.0.3.dist-info}/METADATA +4 -44
  5. maps4fs-2.0.3.dist-info/RECORD +27 -0
  6. maps4fs/generator/dtm/__init__.py +0 -27
  7. maps4fs/generator/dtm/arctic.py +0 -74
  8. maps4fs/generator/dtm/baden.py +0 -31
  9. maps4fs/generator/dtm/base/wcs.py +0 -80
  10. maps4fs/generator/dtm/base/wms.py +0 -71
  11. maps4fs/generator/dtm/bavaria.py +0 -113
  12. maps4fs/generator/dtm/canada.py +0 -37
  13. maps4fs/generator/dtm/czech.py +0 -36
  14. maps4fs/generator/dtm/denmark.py +0 -50
  15. maps4fs/generator/dtm/dtm.py +0 -543
  16. maps4fs/generator/dtm/england.py +0 -31
  17. maps4fs/generator/dtm/finland.py +0 -56
  18. maps4fs/generator/dtm/flanders.py +0 -34
  19. maps4fs/generator/dtm/france.py +0 -69
  20. maps4fs/generator/dtm/hessen.py +0 -31
  21. maps4fs/generator/dtm/italy.py +0 -40
  22. maps4fs/generator/dtm/lithuania.py +0 -66
  23. maps4fs/generator/dtm/mv.py +0 -42
  24. maps4fs/generator/dtm/niedersachsen.py +0 -38
  25. maps4fs/generator/dtm/norway.py +0 -41
  26. maps4fs/generator/dtm/nrw.py +0 -30
  27. maps4fs/generator/dtm/rema.py +0 -74
  28. maps4fs/generator/dtm/sachsenanhalt.py +0 -36
  29. maps4fs/generator/dtm/scotland.py +0 -118
  30. maps4fs/generator/dtm/spain.py +0 -33
  31. maps4fs/generator/dtm/srtm.py +0 -122
  32. maps4fs/generator/dtm/switzerland.py +0 -104
  33. maps4fs/generator/dtm/thuringia.py +0 -60
  34. maps4fs/generator/dtm/usgs_wcs.py +0 -35
  35. maps4fs/generator/dtm/utils.py +0 -61
  36. maps4fs/generator/dtm/wales.py +0 -123
  37. maps4fs-2.0.2.dist-info/RECORD +0 -58
  38. {maps4fs-2.0.2.dist-info → maps4fs-2.0.3.dist-info}/WHEEL +0 -0
  39. {maps4fs-2.0.2.dist-info → maps4fs-2.0.3.dist-info}/licenses/LICENSE.md +0 -0
  40. {maps4fs-2.0.2.dist-info → maps4fs-2.0.3.dist-info}/top_level.txt +0 -0
@@ -1,50 +0,0 @@
1
- """This module contains provider of Denmark data."""
2
-
3
- from maps4fs.generator.dtm.base.wcs import WCSProvider
4
- from maps4fs.generator.dtm.dtm import DTMProvider, DTMProviderSettings
5
-
6
-
7
- class DenmarkProviderSettings(DTMProviderSettings):
8
- """Settings for the Denmark provider."""
9
-
10
- token: str = ""
11
-
12
-
13
- class DenmarkProvider(WCSProvider, DTMProvider):
14
- """Provider of Denmark data."""
15
-
16
- _code = "denmark"
17
- _name = "Denmark"
18
- _region = "DK"
19
- _icon = "🇩🇰"
20
- _resolution = 0.4
21
- _author = "[kbrandwijk](https://github.com/kbrandwijk)"
22
- _is_community = True
23
- _is_base = False
24
- _settings = DenmarkProviderSettings
25
- _extents = [(57.7690657013977, 54.4354651516217, 15.5979112056959, 8.00830949937517)]
26
-
27
- _instructions = (
28
- "ℹ️ This provider requires an access token. See [here](https://confluence"
29
- ".sdfi.dk/display/MYD/How+to+create+a+user) for more information on "
30
- "how to create one, then enter it below in the settings field for token."
31
- )
32
-
33
- _url = "https://api.dataforsyningen.dk/dhm_wcs_DAF"
34
- _wcs_version = "1.0.0"
35
- _source_crs = "EPSG:25832"
36
- _tile_size = 1000
37
-
38
- def get_wcs_parameters(self, tile):
39
- if not self.user_settings.token:
40
- raise ValueError("A token is required for this provider.")
41
-
42
- return {
43
- "identifier": "dhm_terraen",
44
- "bbox": (tile[1], tile[0], tile[3], tile[2]),
45
- "crs": "EPSG:25832",
46
- "width": 2500,
47
- "height": 2500,
48
- "format": "GTiff",
49
- "token": self.user_settings.token,
50
- }
@@ -1,543 +0,0 @@
1
- """This module contains the DTMProvider class and its subclasses. DTMProvider class is used to
2
- define different providers of digital terrain models (DTM) data. Each provider has its own URL
3
- and specific settings for downloading and processing the data."""
4
-
5
- from __future__ import annotations
6
-
7
- import os
8
- from abc import ABC, abstractmethod
9
- from typing import TYPE_CHECKING, Any, Type
10
- from zipfile import ZipFile
11
-
12
- import numpy as np
13
- import osmnx as ox
14
- import rasterio
15
- import requests
16
- from pydantic import BaseModel
17
- from rasterio.enums import Resampling
18
- from rasterio.merge import merge
19
- from rasterio.warp import calculate_default_transform, reproject
20
- from tqdm import tqdm
21
-
22
- from maps4fs.logger import Logger
23
-
24
- if TYPE_CHECKING:
25
- from maps4fs.generator.map import Map
26
-
27
-
28
- class DTMProviderSettings(BaseModel):
29
- """Base class for DTM provider settings models."""
30
-
31
-
32
- class DTMProvider(ABC):
33
- """Base class for DTM providers."""
34
-
35
- _code: str | None = None
36
- _name: str | None = None
37
- _region: str | None = None
38
- _icon: str | None = None
39
- _resolution: float | str | None = None
40
-
41
- _url: str | None = None
42
-
43
- _author: str | None = None
44
- _contributors: str | None = None
45
- _is_community: bool = False
46
- _is_base: bool = False
47
- _settings: Type[DTMProviderSettings] | None = DTMProviderSettings
48
-
49
- """Bounding box of the provider in the format (north, south, east, west)."""
50
- _extents: list[tuple[float, float, float, float]] | None = None
51
-
52
- _instructions: str | None = None
53
-
54
- _base_instructions = None
55
-
56
- # AdvancedSettings that should be changed by the DTM Provider if it's selected.
57
- # * This feature has effect only in the WebUI of the app and ignored in Python package.
58
- # The first level of the dictionary is a category name,
59
- # for example: TextureSettings, DEMSettings, etc.
60
- # The second level is a name of particular setting in the category.
61
- # Example: {"DEMSettings": {"blur_radius": 35}}
62
- _default_settings: dict[str, dict[str, Any]] = {}
63
-
64
- def __init__(
65
- self,
66
- coordinates: tuple[float, float],
67
- user_settings: DTMProviderSettings | None,
68
- size: int,
69
- directory: str,
70
- logger: Logger,
71
- map: Map,
72
- ):
73
- self._coordinates = coordinates
74
- self._user_settings = user_settings
75
- self._size = size
76
-
77
- if not self._code:
78
- raise ValueError("Provider code must be defined.")
79
- self._tile_directory = os.path.join(directory, self._code)
80
- os.makedirs(self._tile_directory, exist_ok=True)
81
-
82
- self.logger = logger
83
- self.map = map
84
-
85
- @classmethod
86
- def default_settings(cls) -> dict[str, dict[str, Any]]:
87
- """Default settings of the provider.
88
-
89
- Returns:
90
- dict: Default settings of the provider.
91
- """
92
- return cls._default_settings
93
-
94
- @classmethod
95
- def name(cls) -> str | None:
96
- """Name of the provider.
97
-
98
- Returns:
99
- str: Provider name.
100
- """
101
- return cls._name
102
-
103
- @classmethod
104
- def code(cls) -> str | None:
105
- """Code of the provider.
106
-
107
- Returns:
108
- str: Provider code.
109
- """
110
- return cls._code
111
-
112
- @property
113
- def coordinates(self) -> tuple[float, float]:
114
- """Coordinates of the center point of the DTM data.
115
-
116
- Returns:
117
- tuple[float, float]: Coordinates of the center point of the DTM data.
118
- """
119
- return self._coordinates
120
-
121
- @property
122
- def size(self) -> int:
123
- """Size of the DTM data in meters.
124
-
125
- Returns:
126
- int: Size of the DTM data.
127
- """
128
- return self._size
129
-
130
- @property
131
- def url(self) -> str | None:
132
- """URL of the provider.
133
-
134
- Returns:
135
- str: URL of the provider or None if not defined.
136
- """
137
- return self._url
138
-
139
- def formatted_url(self, **kwargs) -> str:
140
- """Formatted URL of the provider.
141
-
142
- Arguments:
143
- **kwargs: Keyword arguments to format the URL.
144
-
145
- Returns:
146
- str: Formatted URL of the provider.
147
- """
148
- if not self.url:
149
- raise ValueError("URL must be defined.")
150
- return self.url.format(**kwargs)
151
-
152
- @classmethod
153
- def author(cls) -> str | None:
154
- """Author of the provider.
155
-
156
- Returns:
157
- str: Author of the provider.
158
- """
159
- return cls._author
160
-
161
- @classmethod
162
- def contributors(cls) -> str | None:
163
- """Contributors of the provider.
164
-
165
- Returns:
166
- str: Contributors of the provider.
167
- """
168
- return cls._contributors
169
-
170
- @classmethod
171
- def is_base(cls) -> bool:
172
- """Is the provider a base provider.
173
-
174
- Returns:
175
- bool: True if the provider is a base provider, False otherwise.
176
- """
177
- return cls._is_base
178
-
179
- @classmethod
180
- def is_community(cls) -> bool:
181
- """Is the provider a community-driven project.
182
-
183
- Returns:
184
- bool: True if the provider is a community-driven project, False otherwise.
185
- """
186
- return cls._is_community
187
-
188
- @classmethod
189
- def settings(cls) -> Type[DTMProviderSettings] | None:
190
- """Settings model of the provider.
191
-
192
- Returns:
193
- Type[DTMProviderSettings]: Settings model of the provider.
194
- """
195
- return cls._settings
196
-
197
- @classmethod
198
- def instructions(cls) -> str | None:
199
- """Instructions for using the provider.
200
-
201
- Returns:
202
- str: Instructions for using the provider.
203
- """
204
- return cls._instructions
205
-
206
- @classmethod
207
- def base_instructions(cls) -> str | None:
208
- """Instructions for using any provider.
209
-
210
- Returns:
211
- str: Instructions for using any provider.
212
- """
213
- return cls._base_instructions
214
-
215
- @property
216
- def user_settings(self) -> DTMProviderSettings | None:
217
- """User settings of the provider.
218
-
219
- Returns:
220
- DTMProviderSettings: User settings of the provider.
221
- """
222
- return self._user_settings
223
-
224
- @classmethod
225
- def description(cls) -> str:
226
- """Description of the provider.
227
-
228
- Returns:
229
- str: Provider description.
230
- """
231
- return f"{cls._icon} {cls._region} [{cls._resolution} m/px] {cls._name}"
232
-
233
- @classmethod
234
- def get_provider_by_code(cls, code: str) -> Type[DTMProvider] | None:
235
- """Get a provider by its code.
236
-
237
- Arguments:
238
- code (str): Provider code.
239
-
240
- Returns:
241
- DTMProvider: Provider class or None if not found.
242
- """
243
- for provider in cls.__subclasses__():
244
- if provider.code() == code:
245
- return provider
246
- return None
247
-
248
- @classmethod
249
- def get_valid_provider_descriptions(
250
- cls, lat_lon: tuple[float, float], default_code: str = "srtm30"
251
- ) -> dict[str, str]:
252
- """Get descriptions of all providers, where keys are provider codes and
253
- values are provider descriptions.
254
-
255
- Arguments:
256
- lat_lon (tuple): Latitude and longitude of the center point.
257
- default_code (str): Default provider code.
258
-
259
- Returns:
260
- dict: Provider descriptions.
261
- """
262
- providers: dict[str, str] = {}
263
- for provider in cls.__subclasses__():
264
- if not provider.is_base() and provider.inside_bounding_box(lat_lon):
265
- code = provider.code()
266
- if code is not None:
267
- providers[code] = provider.description()
268
-
269
- # Sort the dictionary, to make sure that the default provider is the first one.
270
- providers = dict(sorted(providers.items(), key=lambda item: item[0] != default_code))
271
-
272
- return providers
273
-
274
- @classmethod
275
- def inside_bounding_box(cls, lat_lon: tuple[float, float]) -> bool:
276
- """Check if the coordinates are inside the bounding box of the provider.
277
-
278
- Returns:
279
- bool: True if the coordinates are inside the bounding box, False otherwise.
280
- """
281
- lat, lon = lat_lon
282
- extents = cls._extents
283
- if extents is None:
284
- return True
285
- for extent in extents:
286
- if extent[0] >= lat >= extent[1] and extent[2] >= lon >= extent[3]:
287
- return True
288
- return False
289
-
290
- @abstractmethod
291
- def download_tiles(self) -> list[str]:
292
- """Download tiles from the provider.
293
-
294
- Returns:
295
- list: List of paths to the downloaded tiles.
296
- """
297
- raise NotImplementedError
298
-
299
- def get_numpy(self) -> np.ndarray:
300
- """Get numpy array of the tile.
301
- Resulting array must be 16 bit (signed or unsigned) integer, and it should be already
302
- windowed to the bounding box of ROI. It also must have only one channel.
303
-
304
- Returns:
305
- np.ndarray: Numpy array of the tile.
306
- """
307
- # download tiles using DTM provider implementation
308
- tiles = self.download_tiles()
309
- self.logger.debug(f"Downloaded {len(tiles)} DEM tiles")
310
-
311
- # merge tiles if necessary
312
- if len(tiles) > 1:
313
- self.logger.debug("Multiple tiles downloaded. Merging tiles")
314
- tile, _ = self.merge_geotiff(tiles)
315
- else:
316
- tile = tiles[0]
317
-
318
- # determine CRS of the resulting tile and reproject if necessary
319
- with rasterio.open(tile) as src:
320
- crs = src.crs
321
- if crs != "EPSG:4326":
322
- self.logger.debug(f"Reprojecting GeoTIFF from {crs} to EPSG:4326...")
323
- tile = self.reproject_geotiff(tile)
324
-
325
- # extract region of interest from the tile
326
- data = self.extract_roi(tile)
327
-
328
- return data
329
-
330
- # region helpers
331
- def get_bbox(self) -> tuple[float, float, float, float]:
332
- """Get bounding box of the tile based on the center point and size.
333
-
334
- Returns:
335
- tuple: Bounding box of the tile (north, south, east, west).
336
- """
337
- west, south, east, north = ox.utils_geo.bbox_from_point( # type: ignore
338
- self.coordinates, dist=self.size // 2, project_utm=False
339
- )
340
- bbox = float(north), float(south), float(east), float(west)
341
- return bbox
342
-
343
- def download_tif_files(self, urls: list[str], output_path: str) -> list[str]:
344
- """Download GeoTIFF files from the given URLs.
345
-
346
- Arguments:
347
- urls (list): List of URLs to download GeoTIFF files from.
348
- output_path (str): Path to save the downloaded GeoTIFF files.
349
-
350
- Returns:
351
- list: List of paths to the downloaded GeoTIFF files.
352
- """
353
- tif_files: list[str] = []
354
-
355
- existing_file_urls = [
356
- f for f in urls if os.path.exists(os.path.join(output_path, os.path.basename(f)))
357
- ]
358
-
359
- for url in existing_file_urls:
360
- self.logger.debug("File already exists: %s", os.path.basename(url))
361
- file_name = os.path.basename(url)
362
- file_path = os.path.join(output_path, file_name)
363
- if file_name.endswith(".zip"):
364
- file_path = self.unzip_img_from_tif(file_name, output_path)
365
- tif_files.append(file_path)
366
-
367
- for url in tqdm(
368
- (u for u in urls if u not in existing_file_urls),
369
- desc="Downloading tiles",
370
- unit="tile",
371
- initial=len(tif_files),
372
- total=len(urls),
373
- ):
374
- try:
375
- file_name = os.path.basename(url)
376
- file_path = os.path.join(output_path, file_name)
377
- self.logger.debug("Retrieving TIFF: %s", file_name)
378
-
379
- # Send a GET request to the file URL
380
- response = requests.get(url, stream=True, timeout=60)
381
- response.raise_for_status() # Raise an error for HTTP status codes 4xx/5xx
382
-
383
- # Write the content of the response to the file
384
- with open(file_path, "wb") as file:
385
- for chunk in response.iter_content(chunk_size=8192):
386
- file.write(chunk)
387
-
388
- self.logger.debug("File downloaded successfully: %s", file_path)
389
-
390
- if file_name.endswith(".zip"):
391
- file_path = self.unzip_img_from_tif(file_name, output_path)
392
-
393
- tif_files.append(file_path)
394
- except requests.exceptions.RequestException as e:
395
- self.logger.error("Failed to download file: %s", e)
396
- return tif_files
397
-
398
- def unzip_img_from_tif(self, file_name: str, output_path: str) -> str:
399
- """Unpacks the .img file from the zip file.
400
-
401
- Arguments:
402
- file_name (str): Name of the file to unzip.
403
- output_path (str): Path to the output directory.
404
-
405
- Returns:
406
- str: Path to the unzipped file.
407
-
408
- Raises:
409
- FileNotFoundError: If no .img or .tif file is found in the zip file
410
- """
411
- file_path = os.path.join(output_path, file_name)
412
- img_file_name = file_name.replace(".zip", ".img")
413
- tif_file_name = file_name.replace(".zip", ".tif")
414
- img_file_path = os.path.join(output_path, img_file_name)
415
- tif_file_path = os.path.join(output_path, tif_file_name)
416
- if os.path.exists(img_file_path):
417
- self.logger.debug("File already exists: %s", img_file_name)
418
- return img_file_path
419
- if os.path.exists(tif_file_path):
420
- self.logger.debug("File already exists: %s", tif_file_name)
421
- return tif_file_path
422
- with ZipFile(file_path, "r") as f_in:
423
- if img_file_name in f_in.namelist():
424
- f_in.extract(img_file_name, output_path)
425
- self.logger.debug("Unzipped file %s to %s", file_name, img_file_name)
426
- return img_file_path
427
- if tif_file_name in f_in.namelist():
428
- f_in.extract(tif_file_name, output_path)
429
- self.logger.debug("Unzipped file %s to %s", file_name, tif_file_name)
430
- return tif_file_path
431
- raise FileNotFoundError("No .img or .tif file found in the zip file.")
432
-
433
- def reproject_geotiff(self, input_tiff: str) -> str:
434
- """Reproject a GeoTIFF file to a new coordinate reference system (CRS).
435
-
436
- Arguments:
437
- input_tiff (str): Path to the input GeoTIFF file.
438
-
439
- Returns:
440
- str: Path to the reprojected GeoTIFF file.
441
- """
442
- output_tiff = os.path.join(self._tile_directory, "reprojected.tif")
443
-
444
- # Open the source GeoTIFF
445
- self.logger.debug("Reprojecting GeoTIFF to EPSG:4326 CRS...")
446
- with rasterio.open(input_tiff) as src:
447
- # Get the transform, width, and height of the target CRS
448
- transform, width, height = calculate_default_transform(
449
- src.crs, "EPSG:4326", src.width, src.height, *src.bounds
450
- )
451
-
452
- # Update the metadata for the target GeoTIFF
453
- kwargs = src.meta.copy()
454
- kwargs.update(
455
- {
456
- "crs": "EPSG:4326",
457
- "transform": transform,
458
- "width": width,
459
- "height": height,
460
- "nodata": None,
461
- }
462
- )
463
-
464
- # Open the destination GeoTIFF file and reproject
465
- with rasterio.open(output_tiff, "w", **kwargs) as dst:
466
- for i in range(1, src.count + 1): # Iterate over all raster bands
467
- reproject(
468
- source=rasterio.band(src, i),
469
- destination=rasterio.band(dst, i),
470
- src_transform=src.transform,
471
- src_crs=src.crs,
472
- dst_transform=transform,
473
- dst_crs="EPSG:4326",
474
- resampling=Resampling.average, # Choose resampling method
475
- )
476
-
477
- self.logger.debug("Reprojected GeoTIFF saved to %s", output_tiff)
478
- return output_tiff
479
-
480
- def merge_geotiff(self, input_files: list[str]) -> tuple[str, str]:
481
- """Merge multiple GeoTIFF files into a single GeoTIFF file.
482
-
483
- Arguments:
484
- input_files (list): List of input GeoTIFF files to merge.
485
- """
486
- output_file = os.path.join(self._tile_directory, "merged.tif")
487
- # Open all input GeoTIFF files as datasets
488
- self.logger.debug("Merging tiff files...")
489
- datasets = [rasterio.open(file) for file in input_files]
490
-
491
- # Merge datasets
492
- crs = datasets[0].crs
493
- mosaic, out_transform = merge(datasets, nodata=0)
494
-
495
- # Get metadata from the first file and update it for the output
496
- out_meta = datasets[0].meta.copy()
497
- out_meta.update(
498
- {
499
- "driver": "GTiff",
500
- "height": mosaic.shape[1],
501
- "width": mosaic.shape[2],
502
- "transform": out_transform,
503
- "count": mosaic.shape[0], # Number of bands
504
- }
505
- )
506
-
507
- # Write merged GeoTIFF to the output file
508
- with rasterio.open(output_file, "w", **out_meta) as dest:
509
- dest.write(mosaic)
510
-
511
- self.logger.debug("GeoTIFF images merged successfully into %s", output_file)
512
- return output_file, crs
513
-
514
- def extract_roi(self, tile_path: str) -> np.ndarray:
515
- """Extract region of interest (ROI) from the GeoTIFF file.
516
-
517
- Arguments:
518
- tile_path (str): Path to the GeoTIFF file.
519
-
520
- Raises:
521
- ValueError: If the tile does not contain any data.
522
-
523
- Returns:
524
- np.ndarray: Numpy array of the ROI.
525
- """
526
- north, south, east, west = self.get_bbox()
527
- with rasterio.open(tile_path) as src:
528
- self.logger.debug("Opened tile, shape: %s, dtype: %s.", src.shape, src.dtypes[0])
529
- window = rasterio.windows.from_bounds(west, south, east, north, src.transform)
530
- self.logger.debug(
531
- "Window parameters. Column offset: %s, row offset: %s, width: %s, height: %s.",
532
- window.col_off,
533
- window.row_off,
534
- window.width,
535
- window.height,
536
- )
537
- data = src.read(1, window=window, masked=True)
538
- if not data.size > 0:
539
- raise ValueError("No data in the tile.")
540
-
541
- return data
542
-
543
- # endregion
@@ -1,31 +0,0 @@
1
- """This module contains provider of England data."""
2
-
3
- from maps4fs.generator.dtm.base.wcs import WCSProvider
4
- from maps4fs.generator.dtm.dtm import DTMProvider
5
-
6
-
7
- class England1MProvider(WCSProvider, DTMProvider):
8
- """Provider of England data."""
9
-
10
- _code = "england1m"
11
- _name = "England DGM1"
12
- _region = "UK"
13
- _icon = "🏴󠁧󠁢󠁥󠁮󠁧󠁿"
14
- _resolution = 1
15
- _author = "[kbrandwijk](https://github.com/kbrandwijk)"
16
- _is_community = True
17
- _instructions = None
18
- _is_base = False
19
- _extents = [(55.87708724246775, 49.85060473351981, 2.0842821419111135, -7.104775741839742)]
20
-
21
- _url = "https://environment.data.gov.uk/geoservices/datasets/13787b9a-26a4-4775-8523-806d13af58fc/wcs" # pylint: disable=line-too-long
22
- _wcs_version = "2.0.1"
23
- _source_crs = "EPSG:27700"
24
- _tile_size = 1000
25
-
26
- def get_wcs_parameters(self, tile):
27
- return {
28
- "identifier": ["13787b9a-26a4-4775-8523-806d13af58fc:Lidar_Composite_Elevation_DTM_1m"],
29
- "subsets": [("E", str(tile[1]), str(tile[3])), ("N", str(tile[0]), str(tile[2]))],
30
- "format": "tiff",
31
- }
@@ -1,56 +0,0 @@
1
- """This module contains provider of Finland data."""
2
-
3
- from owslib.util import Authentication
4
-
5
- from maps4fs.generator.dtm.base.wcs import WCSProvider
6
- from maps4fs.generator.dtm.dtm import DTMProvider, DTMProviderSettings
7
-
8
-
9
- class FinlandProviderSettings(DTMProviderSettings):
10
- """Settings for the Finland provider."""
11
-
12
- api_key: str = ""
13
-
14
-
15
- class FinlandProvider(WCSProvider, DTMProvider):
16
- """Provider of Finland data."""
17
-
18
- _code = "finland"
19
- _name = "Finland"
20
- _region = "FI"
21
- _icon = "🇫🇮"
22
- _resolution = 2
23
- _settings = FinlandProviderSettings
24
- _author = "[kbrandwijk](https://github.com/kbrandwijk)"
25
- _is_community = True
26
- _is_base = False
27
- _extents = [(70.09, 59.45, 31.59, 19.08)]
28
-
29
- _url = "https://avoin-karttakuva.maanmittauslaitos.fi/ortokuvat-ja-korkeusmallit/wcs/v2"
30
- _wcs_version = "2.0.1"
31
- _source_crs = "EPSG:3067"
32
- _tile_size = 1000
33
-
34
- _instructions = (
35
- "ℹ️ This provider requires an API Key. See [here](https://www.maanmittausl"
36
- "aitos.fi/rajapinnat/api-avaimen-ohje) for more information on how to create one, then "
37
- "enter it below in the settings field for API Key."
38
- )
39
-
40
- def get_wcs_instance_parameters(self):
41
- if not self.user_settings.api_key:
42
- raise ValueError("API Key is required for this provider.")
43
-
44
- settings = super().get_wcs_instance_parameters()
45
- settings["auth"] = Authentication(
46
- username=self.user_settings.api_key, password=self.user_settings.api_key
47
- )
48
- return settings
49
-
50
- def get_wcs_parameters(self, tile: tuple[float, float, float, float]) -> dict:
51
- return {
52
- "identifier": ["korkeusmalli_2m"],
53
- "subsets": [("N", str(tile[0]), str(tile[2])), ("E", str(tile[1]), str(tile[3]))],
54
- "format": "image/tiff",
55
- "timeout": 600,
56
- }