maps4fs 1.4.1__py3-none-any.whl → 1.5.7__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.
- maps4fs/__init__.py +3 -2
- maps4fs/generator/background.py +74 -110
- maps4fs/generator/component.py +10 -0
- maps4fs/generator/dem.py +22 -106
- maps4fs/generator/dtm.py +333 -0
- maps4fs/generator/game.py +2 -1
- maps4fs/generator/grle.py +5 -0
- maps4fs/generator/i3d.py +27 -14
- maps4fs/generator/map.py +36 -118
- maps4fs/generator/satellite.py +92 -0
- maps4fs/generator/settings.py +150 -0
- maps4fs/generator/texture.py +14 -2
- {maps4fs-1.4.1.dist-info → maps4fs-1.5.7.dist-info}/METADATA +48 -27
- maps4fs-1.5.7.dist-info/RECORD +24 -0
- maps4fs-1.4.1.dist-info/RECORD +0 -21
- {maps4fs-1.4.1.dist-info → maps4fs-1.5.7.dist-info}/LICENSE.md +0 -0
- {maps4fs-1.4.1.dist-info → maps4fs-1.5.7.dist-info}/WHEEL +0 -0
- {maps4fs-1.4.1.dist-info → maps4fs-1.5.7.dist-info}/top_level.txt +0 -0
maps4fs/__init__.py
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
# pylint: disable=missing-module-docstring
|
2
|
+
from maps4fs.generator.dtm import DTMProvider
|
2
3
|
from maps4fs.generator.game import Game
|
3
|
-
from maps4fs.generator.map import
|
4
|
+
from maps4fs.generator.map import Map
|
5
|
+
from maps4fs.generator.settings import (
|
4
6
|
BackgroundSettings,
|
5
7
|
DEMSettings,
|
6
8
|
GRLESettings,
|
7
9
|
I3DSettings,
|
8
|
-
Map,
|
9
10
|
SettingsModel,
|
10
11
|
SplineSettings,
|
11
12
|
TextureSettings,
|
maps4fs/generator/background.py
CHANGED
@@ -57,36 +57,23 @@ class Background(Component):
|
|
57
57
|
os.makedirs(self.background_directory, exist_ok=True)
|
58
58
|
os.makedirs(self.water_directory, exist_ok=True)
|
59
59
|
|
60
|
-
|
61
|
-
self.output_paths = [
|
62
|
-
os.path.join(self.background_directory, f"{name}.png") for name in ELEMENTS
|
63
|
-
]
|
60
|
+
self.output_path = os.path.join(self.background_directory, f"{FULL_NAME}.png")
|
64
61
|
self.not_substracted_path = os.path.join(self.background_directory, "not_substracted.png")
|
65
62
|
self.not_resized_path = os.path.join(self.background_directory, "not_resized.png")
|
66
63
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
dem.preprocess()
|
81
|
-
dem.is_preview = self.is_preview(name) # type: ignore
|
82
|
-
if dem.is_preview: # type: ignore
|
83
|
-
dem.multiplier = 1
|
84
|
-
dem.auto_process = autoprocess
|
85
|
-
dem.set_output_resolution((self.rotated_size, self.rotated_size))
|
86
|
-
dem.set_dem_path(output_path)
|
87
|
-
dems.append(dem)
|
88
|
-
|
89
|
-
self.dems = dems
|
64
|
+
self.dem = DEM(
|
65
|
+
self.game,
|
66
|
+
self.map,
|
67
|
+
self.coordinates,
|
68
|
+
self.background_size,
|
69
|
+
self.rotated_size,
|
70
|
+
self.rotation,
|
71
|
+
self.map_directory,
|
72
|
+
self.logger,
|
73
|
+
)
|
74
|
+
self.dem.preprocess()
|
75
|
+
self.dem.set_output_resolution((self.rotated_size, self.rotated_size))
|
76
|
+
self.dem.set_dem_path(self.output_path)
|
90
77
|
|
91
78
|
def is_preview(self, name: str) -> bool:
|
92
79
|
"""Checks if the DEM is a preview.
|
@@ -104,21 +91,17 @@ class Background(Component):
|
|
104
91
|
as a result the DEM files will be saved, then based on them the obj files will be
|
105
92
|
generated."""
|
106
93
|
self.create_background_textures()
|
94
|
+
self.dem.process()
|
107
95
|
|
108
|
-
|
109
|
-
|
110
|
-
if not dem.is_preview: # type: ignore
|
111
|
-
shutil.copyfile(dem.dem_path, self.not_substracted_path)
|
112
|
-
self.cutout(dem.dem_path, save_path=self.not_resized_path)
|
96
|
+
shutil.copyfile(self.dem.dem_path, self.not_substracted_path)
|
97
|
+
self.cutout(self.dem.dem_path, save_path=self.not_resized_path)
|
113
98
|
|
114
99
|
if self.map.dem_settings.water_depth:
|
115
100
|
self.subtraction()
|
116
101
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
if self.game.additional_dem_name is not None:
|
121
|
-
self.make_copy(cutted_dem_path, self.game.additional_dem_name)
|
102
|
+
cutted_dem_path = self.cutout(self.dem.dem_path)
|
103
|
+
if self.game.additional_dem_name is not None:
|
104
|
+
self.make_copy(cutted_dem_path, self.game.additional_dem_name)
|
122
105
|
|
123
106
|
if self.map.background_settings.generate_background:
|
124
107
|
self.generate_obj_files()
|
@@ -149,19 +132,17 @@ class Background(Component):
|
|
149
132
|
"""
|
150
133
|
self.qgis_sequence()
|
151
134
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
epsg3857_string = dem.get_epsg3857_string()
|
156
|
-
epsg3857_string_with_margin = dem.get_epsg3857_string(add_margin=True)
|
135
|
+
north, south, east, west = self.dem.bbox
|
136
|
+
epsg3857_string = self.dem.get_epsg3857_string()
|
137
|
+
epsg3857_string_with_margin = self.dem.get_epsg3857_string(add_margin=True)
|
157
138
|
|
158
139
|
data = {
|
159
|
-
"center_latitude": dem.coordinates[0],
|
160
|
-
"center_longitude": dem.coordinates[1],
|
140
|
+
"center_latitude": self.dem.coordinates[0],
|
141
|
+
"center_longitude": self.dem.coordinates[1],
|
161
142
|
"epsg3857_string": epsg3857_string,
|
162
143
|
"epsg3857_string_with_margin": epsg3857_string_with_margin,
|
163
|
-
"height": dem.map_size,
|
164
|
-
"width": dem.map_size,
|
144
|
+
"height": self.dem.map_size,
|
145
|
+
"width": self.dem.map_size,
|
165
146
|
"north": north,
|
166
147
|
"south": south,
|
167
148
|
"east": east,
|
@@ -171,10 +152,10 @@ class Background(Component):
|
|
171
152
|
|
172
153
|
def qgis_sequence(self) -> None:
|
173
154
|
"""Generates QGIS scripts for creating bounding box layers and rasterizing them."""
|
174
|
-
qgis_layer = (f"Background_{FULL_NAME}", *self.
|
155
|
+
qgis_layer = (f"Background_{FULL_NAME}", *self.dem.get_espg3857_bbox())
|
175
156
|
qgis_layer_with_margin = (
|
176
157
|
f"Background_{FULL_NAME}_margin",
|
177
|
-
*self.
|
158
|
+
*self.dem.get_espg3857_bbox(add_margin=True),
|
178
159
|
)
|
179
160
|
self.create_qgis_scripts([qgis_layer, qgis_layer_with_margin])
|
180
161
|
|
@@ -182,21 +163,20 @@ class Background(Component):
|
|
182
163
|
"""Iterates over all dems and generates 3D obj files based on DEM data.
|
183
164
|
If at least one DEM file is missing, the generation will be stopped at all.
|
184
165
|
"""
|
185
|
-
|
186
|
-
|
187
|
-
self.
|
188
|
-
|
189
|
-
|
190
|
-
return
|
166
|
+
if not os.path.isfile(self.dem.dem_path):
|
167
|
+
self.logger.warning(
|
168
|
+
"DEM file not found, generation will be stopped: %s", self.dem.dem_path
|
169
|
+
)
|
170
|
+
return
|
191
171
|
|
192
|
-
|
172
|
+
self.logger.debug("DEM file for found: %s", self.dem.dem_path)
|
193
173
|
|
194
|
-
|
195
|
-
|
196
|
-
|
174
|
+
filename = os.path.splitext(os.path.basename(self.dem.dem_path))[0]
|
175
|
+
save_path = os.path.join(self.background_directory, f"{filename}.obj")
|
176
|
+
self.logger.debug("Generating obj file in path: %s", save_path)
|
197
177
|
|
198
|
-
|
199
|
-
|
178
|
+
dem_data = cv2.imread(self.dem.dem_path, cv2.IMREAD_UNCHANGED) # pylint: disable=no-member
|
179
|
+
self.plane_from_np(dem_data, save_path) # type: ignore
|
200
180
|
|
201
181
|
# pylint: disable=too-many-locals
|
202
182
|
def cutout(self, dem_path: str, save_path: str | None = None) -> str:
|
@@ -248,7 +228,6 @@ class Background(Component):
|
|
248
228
|
self,
|
249
229
|
dem_data: np.ndarray,
|
250
230
|
save_path: str,
|
251
|
-
is_preview: bool = False,
|
252
231
|
include_zeros: bool = True,
|
253
232
|
) -> None:
|
254
233
|
"""Generates a 3D obj file based on DEM data.
|
@@ -256,7 +235,6 @@ class Background(Component):
|
|
256
235
|
Arguments:
|
257
236
|
dem_data (np.ndarray) -- The DEM data as a numpy array.
|
258
237
|
save_path (str) -- The path where the obj file will be saved.
|
259
|
-
is_preview (bool, optional) -- If True, the preview mesh will be generated.
|
260
238
|
include_zeros (bool, optional) -- If True, the mesh will include the zero height values.
|
261
239
|
"""
|
262
240
|
resize_factor = 1 / self.map.background_settings.resize_factor
|
@@ -315,25 +293,21 @@ class Background(Component):
|
|
315
293
|
mesh.apply_transform(rotation_matrix_y)
|
316
294
|
mesh.apply_transform(rotation_matrix_z)
|
317
295
|
|
318
|
-
if
|
296
|
+
# if not include_zeros:
|
297
|
+
z_scaling_factor = 1 / self.map.dem_settings.multiplier
|
298
|
+
self.logger.debug("Z scaling factor: %s", z_scaling_factor)
|
299
|
+
mesh.apply_scale([1 / resize_factor, 1 / resize_factor, z_scaling_factor])
|
300
|
+
|
301
|
+
mesh.export(save_path)
|
302
|
+
self.logger.debug("Obj file saved: %s", save_path)
|
303
|
+
|
304
|
+
if include_zeros:
|
319
305
|
# Simplify the preview mesh to reduce the size of the file.
|
320
306
|
mesh = mesh.simplify_quadric_decimation(face_count=len(mesh.faces) // 2**7)
|
321
307
|
|
322
308
|
# Apply scale to make the preview mesh smaller in the UI.
|
323
309
|
mesh.apply_scale([0.5, 0.5, 0.5])
|
324
310
|
self.mesh_to_stl(mesh)
|
325
|
-
else:
|
326
|
-
if not include_zeros:
|
327
|
-
multiplier = self.map.dem_settings.multiplier
|
328
|
-
if multiplier != 1:
|
329
|
-
z_scaling_factor = 1 / multiplier
|
330
|
-
else:
|
331
|
-
z_scaling_factor = 1 / 2**5
|
332
|
-
self.logger.debug("Z scaling factor: %s", z_scaling_factor)
|
333
|
-
mesh.apply_scale([1 / resize_factor, 1 / resize_factor, z_scaling_factor])
|
334
|
-
|
335
|
-
mesh.export(save_path)
|
336
|
-
self.logger.debug("Obj file saved: %s", save_path)
|
337
311
|
|
338
312
|
def mesh_to_stl(self, mesh: trimesh.Trimesh) -> None:
|
339
313
|
"""Converts the mesh to an STL file and saves it in the previews directory.
|
@@ -358,25 +332,22 @@ class Background(Component):
|
|
358
332
|
list[str] -- A list of paths to the previews.
|
359
333
|
"""
|
360
334
|
preview_paths = self.dem_previews(self.game.dem_file_path(self.map_directory))
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
cv2.imwrite(background_dem_preview_path, background_dem_preview_image)
|
379
|
-
preview_paths.append(background_dem_preview_path)
|
335
|
+
|
336
|
+
background_dem_preview_path = os.path.join(self.previews_directory, "background_dem.png")
|
337
|
+
background_dem_preview_image = cv2.imread(self.dem.dem_path, cv2.IMREAD_UNCHANGED)
|
338
|
+
|
339
|
+
background_dem_preview_image = cv2.resize(
|
340
|
+
background_dem_preview_image, (0, 0), fx=1 / 4, fy=1 / 4
|
341
|
+
)
|
342
|
+
background_dem_preview_image = cv2.normalize( # type: ignore
|
343
|
+
background_dem_preview_image, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U
|
344
|
+
)
|
345
|
+
background_dem_preview_image = cv2.cvtColor(
|
346
|
+
background_dem_preview_image, cv2.COLOR_GRAY2BGR
|
347
|
+
)
|
348
|
+
|
349
|
+
cv2.imwrite(background_dem_preview_path, background_dem_preview_image)
|
350
|
+
preview_paths.append(background_dem_preview_path)
|
380
351
|
|
381
352
|
if self.stl_preview_path:
|
382
353
|
preview_paths.append(self.stl_preview_path)
|
@@ -525,18 +496,15 @@ class Background(Component):
|
|
525
496
|
bool
|
526
497
|
)
|
527
498
|
|
528
|
-
|
529
|
-
if FULL_PREVIEW_NAME in output_path:
|
530
|
-
continue
|
531
|
-
dem_image = cv2.imread(output_path, cv2.IMREAD_UNCHANGED)
|
499
|
+
dem_image = cv2.imread(self.output_path, cv2.IMREAD_UNCHANGED)
|
532
500
|
|
533
|
-
|
534
|
-
|
535
|
-
|
501
|
+
# Create a mask where water_resources_image is 255 (or not 0)
|
502
|
+
# Subtract water_depth from dem_image where mask is True
|
503
|
+
dem_image[mask] = dem_image[mask] - self.map.dem_settings.water_depth
|
536
504
|
|
537
|
-
|
538
|
-
|
539
|
-
|
505
|
+
# Save the modified dem_image back to the output path
|
506
|
+
cv2.imwrite(self.output_path, dem_image)
|
507
|
+
self.logger.debug("Water depth subtracted from DEM data: %s", self.output_path)
|
540
508
|
|
541
509
|
def generate_water_resources_obj(self) -> None:
|
542
510
|
"""Generates 3D obj files based on water resources data."""
|
@@ -550,9 +518,7 @@ class Background(Component):
|
|
550
518
|
plane_water.astype(np.uint8), np.ones((5, 5), np.uint8), iterations=5
|
551
519
|
).astype(np.uint8)
|
552
520
|
plane_save_path = os.path.join(self.water_directory, "plane_water.obj")
|
553
|
-
self.plane_from_np(
|
554
|
-
dilated_plane_water, plane_save_path, is_preview=False, include_zeros=False
|
555
|
-
)
|
521
|
+
self.plane_from_np(dilated_plane_water, plane_save_path, include_zeros=False)
|
556
522
|
|
557
523
|
# Single channeled 16 bit DEM image of terrain.
|
558
524
|
background_dem = cv2.imread(self.not_substracted_path, cv2.IMREAD_UNCHANGED)
|
@@ -570,6 +536,4 @@ class Background(Component):
|
|
570
536
|
elevated_water = np.where(mask, background_dem, elevated_water)
|
571
537
|
elevated_save_path = os.path.join(self.water_directory, "elevated_water.obj")
|
572
538
|
|
573
|
-
self.plane_from_np(
|
574
|
-
elevated_water, elevated_save_path, is_preview=False, include_zeros=False
|
575
|
-
)
|
539
|
+
self.plane_from_np(elevated_water, elevated_save_path, include_zeros=False)
|
maps4fs/generator/component.py
CHANGED
@@ -68,6 +68,7 @@ class Component:
|
|
68
68
|
os.makedirs(self.previews_directory, exist_ok=True)
|
69
69
|
os.makedirs(self.scripts_directory, exist_ok=True)
|
70
70
|
os.makedirs(self.info_layers_directory, exist_ok=True)
|
71
|
+
os.makedirs(self.satellite_directory, exist_ok=True)
|
71
72
|
|
72
73
|
self.save_bbox()
|
73
74
|
self.preprocess()
|
@@ -123,6 +124,15 @@ class Component:
|
|
123
124
|
"""
|
124
125
|
return os.path.join(self.map_directory, "scripts")
|
125
126
|
|
127
|
+
@property
|
128
|
+
def satellite_directory(self) -> str:
|
129
|
+
"""The directory where the satellite images are stored.
|
130
|
+
|
131
|
+
Returns:
|
132
|
+
str: The directory where the satellite images are stored.
|
133
|
+
"""
|
134
|
+
return os.path.join(self.map_directory, "satellite")
|
135
|
+
|
126
136
|
@property
|
127
137
|
def generation_info_path(self) -> str:
|
128
138
|
"""The path to the generation info JSON file.
|
maps4fs/generator/dem.py
CHANGED
@@ -1,19 +1,15 @@
|
|
1
1
|
"""This module contains DEM class for processing Digital Elevation Model data."""
|
2
2
|
|
3
|
-
import gzip
|
4
|
-
import math
|
5
3
|
import os
|
6
|
-
import shutil
|
7
4
|
|
8
5
|
import cv2
|
9
6
|
import numpy as np
|
10
|
-
|
11
|
-
import
|
7
|
+
|
8
|
+
# import rasterio # type: ignore
|
12
9
|
from pympler import asizeof # type: ignore
|
13
10
|
|
14
11
|
from maps4fs.generator.component import Component
|
15
|
-
|
16
|
-
SRTM = "https://elevation-tiles-prod.s3.amazonaws.com/skadi/{latitude_band}/{tile_name}.hgt.gz"
|
12
|
+
from maps4fs.generator.dtm import DTMProvider
|
17
13
|
|
18
14
|
|
19
15
|
# pylint: disable=R0903, R0902
|
@@ -61,7 +57,13 @@ class DEM(Component):
|
|
61
57
|
self.blur_radius,
|
62
58
|
)
|
63
59
|
|
64
|
-
self.
|
60
|
+
self.dtm_provider: DTMProvider = self.map.dtm_provider( # type: ignore
|
61
|
+
coordinates=self.coordinates,
|
62
|
+
user_settings=self.map.dtm_provider_settings,
|
63
|
+
size=self.map_rotated_size,
|
64
|
+
directory=self.temp_dir,
|
65
|
+
logger=self.logger,
|
66
|
+
)
|
65
67
|
|
66
68
|
@property
|
67
69
|
def dem_path(self) -> str:
|
@@ -132,36 +134,29 @@ class DEM(Component):
|
|
132
134
|
def process(self) -> None:
|
133
135
|
"""Reads SRTM file, crops it to map size, normalizes and blurs it,
|
134
136
|
saves to map directory."""
|
135
|
-
north, south, east, west = self.bbox
|
136
137
|
|
137
138
|
dem_output_resolution = self.output_resolution
|
138
139
|
self.logger.debug("DEM output resolution: %s.", dem_output_resolution)
|
139
140
|
|
140
|
-
|
141
|
-
|
142
|
-
|
141
|
+
try:
|
142
|
+
data = self.dtm_provider.get_numpy()
|
143
|
+
except Exception as e: # pylint: disable=W0718
|
144
|
+
self.logger.error("Failed to get DEM data from SRTM: %s.", e)
|
143
145
|
self._save_empty_dem(dem_output_resolution)
|
144
146
|
return
|
145
147
|
|
146
|
-
|
147
|
-
self.logger.
|
148
|
-
|
149
|
-
|
150
|
-
"Window parameters. Column offset: %s, row offset: %s, width: %s, height: %s.",
|
151
|
-
window.col_off,
|
152
|
-
window.row_off,
|
153
|
-
window.width,
|
154
|
-
window.height,
|
155
|
-
)
|
156
|
-
data = src.read(1, window=window)
|
148
|
+
if len(data.shape) != 2:
|
149
|
+
self.logger.error("DTM provider returned incorrect data: more than 1 channel.")
|
150
|
+
self._save_empty_dem(dem_output_resolution)
|
151
|
+
return
|
157
152
|
|
158
|
-
if
|
159
|
-
self.logger.
|
153
|
+
if data.dtype not in ["int16", "uint16"]:
|
154
|
+
self.logger.error("DTM provider returned incorrect data type: %s.", data.dtype)
|
160
155
|
self._save_empty_dem(dem_output_resolution)
|
161
156
|
return
|
162
157
|
|
163
158
|
self.logger.debug(
|
164
|
-
"DEM data was
|
159
|
+
"DEM data was retrieved from DTM provider. Shape: %s, dtype: %s. Min: %s, max: %s.",
|
165
160
|
data.shape,
|
166
161
|
data.dtype,
|
167
162
|
data.min(),
|
@@ -184,11 +179,7 @@ class DEM(Component):
|
|
184
179
|
resampled_data.dtype,
|
185
180
|
)
|
186
181
|
|
187
|
-
if self.
|
188
|
-
self.logger.debug("Auto processing is enabled, will normalize DEM data.")
|
189
|
-
resampled_data = self._normalize_dem(resampled_data)
|
190
|
-
else:
|
191
|
-
self.logger.debug("Auto processing is disabled, DEM data will not be normalized.")
|
182
|
+
if self.multiplier != 1:
|
192
183
|
resampled_data = resampled_data * self.multiplier
|
193
184
|
|
194
185
|
self.logger.debug(
|
@@ -276,81 +267,6 @@ class DEM(Component):
|
|
276
267
|
output_width=output_width,
|
277
268
|
)
|
278
269
|
|
279
|
-
def _tile_info(self, lat: float, lon: float) -> tuple[str, str]:
|
280
|
-
"""Returns latitude band and tile name for SRTM tile from coordinates.
|
281
|
-
|
282
|
-
Arguments:
|
283
|
-
lat (float): Latitude.
|
284
|
-
lon (float): Longitude.
|
285
|
-
|
286
|
-
Returns:
|
287
|
-
tuple[str, str]: Latitude band and tile name.
|
288
|
-
"""
|
289
|
-
tile_latitude = math.floor(lat)
|
290
|
-
tile_longitude = math.floor(lon)
|
291
|
-
|
292
|
-
latitude_band = f"N{abs(tile_latitude):02d}" if lat >= 0 else f"S{abs(tile_latitude):02d}"
|
293
|
-
if lon < 0:
|
294
|
-
tile_name = f"{latitude_band}W{abs(tile_longitude):03d}"
|
295
|
-
else:
|
296
|
-
tile_name = f"{latitude_band}E{abs(tile_longitude):03d}"
|
297
|
-
|
298
|
-
self.logger.debug(
|
299
|
-
"Detected tile name: %s for coordinates: lat %s, lon %s.", tile_name, lat, lon
|
300
|
-
)
|
301
|
-
return latitude_band, tile_name
|
302
|
-
|
303
|
-
def _download_tile(self) -> str | None:
|
304
|
-
"""Downloads SRTM tile from Amazon S3 using coordinates.
|
305
|
-
|
306
|
-
Returns:
|
307
|
-
str: Path to compressed tile or None if download failed.
|
308
|
-
"""
|
309
|
-
latitude_band, tile_name = self._tile_info(*self.coordinates)
|
310
|
-
compressed_file_path = os.path.join(self.gz_dir, f"{tile_name}.hgt.gz")
|
311
|
-
url = SRTM.format(latitude_band=latitude_band, tile_name=tile_name)
|
312
|
-
self.logger.debug("Trying to get response from %s...", url)
|
313
|
-
response = requests.get(url, stream=True, timeout=10)
|
314
|
-
|
315
|
-
if response.status_code == 200:
|
316
|
-
self.logger.debug("Response received. Saving to %s...", compressed_file_path)
|
317
|
-
with open(compressed_file_path, "wb") as f:
|
318
|
-
for chunk in response.iter_content(chunk_size=8192):
|
319
|
-
f.write(chunk)
|
320
|
-
self.logger.debug("Compressed tile successfully downloaded.")
|
321
|
-
else:
|
322
|
-
self.logger.error("Response was failed with status code %s.", response.status_code)
|
323
|
-
return None
|
324
|
-
|
325
|
-
return compressed_file_path
|
326
|
-
|
327
|
-
def _srtm_tile(self) -> str | None:
|
328
|
-
"""Determines SRTM tile name from coordinates downloads it if necessary, and decompresses.
|
329
|
-
|
330
|
-
Returns:
|
331
|
-
str: Path to decompressed tile or None if download failed.
|
332
|
-
"""
|
333
|
-
latitude_band, tile_name = self._tile_info(*self.coordinates)
|
334
|
-
self.logger.debug("SRTM tile name %s from latitude band %s.", tile_name, latitude_band)
|
335
|
-
|
336
|
-
decompressed_file_path = os.path.join(self.hgt_dir, f"{tile_name}.hgt")
|
337
|
-
if os.path.isfile(decompressed_file_path):
|
338
|
-
self.logger.debug(
|
339
|
-
"Decompressed tile already exists: %s, skipping download.",
|
340
|
-
decompressed_file_path,
|
341
|
-
)
|
342
|
-
return decompressed_file_path
|
343
|
-
|
344
|
-
compressed_file_path = self._download_tile()
|
345
|
-
if not compressed_file_path:
|
346
|
-
self.logger.error("Download from SRTM failed, DEM file will be filled with zeros.")
|
347
|
-
return None
|
348
|
-
with gzip.open(compressed_file_path, "rb") as f_in:
|
349
|
-
with open(decompressed_file_path, "wb") as f_out:
|
350
|
-
shutil.copyfileobj(f_in, f_out)
|
351
|
-
self.logger.debug("Tile decompressed to %s.", decompressed_file_path)
|
352
|
-
return decompressed_file_path
|
353
|
-
|
354
270
|
def _save_empty_dem(self, dem_output_resolution: tuple[int, int]) -> None:
|
355
271
|
"""Saves empty DEM file filled with zeros."""
|
356
272
|
dem_data = np.zeros(dem_output_resolution, dtype="uint16")
|