maps4fs 0.9.93__py3-none-any.whl → 1.1.6__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/generator/background.py +279 -227
- maps4fs/generator/component.py +220 -32
- maps4fs/generator/config.py +15 -11
- maps4fs/generator/dem.py +81 -98
- maps4fs/generator/game.py +19 -3
- maps4fs/generator/grle.py +186 -0
- maps4fs/generator/i3d.py +239 -25
- maps4fs/generator/map.py +29 -18
- maps4fs/generator/qgis.py +5 -5
- maps4fs/generator/texture.py +233 -38
- maps4fs/logger.py +1 -25
- maps4fs/toolbox/background.py +63 -0
- maps4fs/toolbox/dem.py +3 -3
- maps4fs-1.1.6.dist-info/LICENSE.md +190 -0
- {maps4fs-0.9.93.dist-info → maps4fs-1.1.6.dist-info}/METADATA +111 -58
- maps4fs-1.1.6.dist-info/RECORD +21 -0
- maps4fs/generator/path_steps.py +0 -83
- maps4fs/generator/tile.py +0 -55
- maps4fs-0.9.93.dist-info/LICENSE.md +0 -21
- maps4fs-0.9.93.dist-info/RECORD +0 -21
- {maps4fs-0.9.93.dist-info → maps4fs-1.1.6.dist-info}/WHEEL +0 -0
- {maps4fs-0.9.93.dist-info → maps4fs-1.1.6.dist-info}/top_level.txt +0 -0
maps4fs/generator/background.py
CHANGED
@@ -4,6 +4,7 @@ around the map."""
|
|
4
4
|
from __future__ import annotations
|
5
5
|
|
6
6
|
import os
|
7
|
+
import shutil
|
7
8
|
|
8
9
|
import cv2
|
9
10
|
import numpy as np
|
@@ -14,167 +15,226 @@ from maps4fs.generator.dem import (
|
|
14
15
|
DEFAULT_BLUR_RADIUS,
|
15
16
|
DEFAULT_MULTIPLIER,
|
16
17
|
DEFAULT_PLATEAU,
|
18
|
+
DEM,
|
17
19
|
)
|
18
|
-
from maps4fs.generator.path_steps import DEFAULT_DISTANCE, PATH_FULL_NAME, get_steps
|
19
|
-
from maps4fs.generator.tile import Tile
|
20
|
-
from maps4fs.logger import timeit
|
21
20
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
DEFAULT_DISTANCE = 2048
|
22
|
+
RESIZE_FACTOR = 1 / 8
|
23
|
+
FULL_NAME = "FULL"
|
24
|
+
FULL_PREVIEW_NAME = "PREVIEW"
|
25
|
+
ELEMENTS = [FULL_NAME, FULL_PREVIEW_NAME]
|
26
26
|
|
27
27
|
|
28
28
|
class Background(Component):
|
29
29
|
"""Component for creating 3D obj files based on DEM data around the map.
|
30
30
|
|
31
|
-
|
31
|
+
Arguments:
|
32
|
+
game (Game): The game instance for which the map is generated.
|
32
33
|
coordinates (tuple[float, float]): The latitude and longitude of the center of the map.
|
33
|
-
|
34
|
-
|
34
|
+
map_size (int): The size of the map in pixels (it's a square).
|
35
|
+
rotated_map_size (int): The size of the map in pixels after rotation.
|
36
|
+
rotation (int): The rotation angle of the map.
|
35
37
|
map_directory (str): The directory where the map files are stored.
|
36
38
|
logger (Any, optional): The logger to use. Must have at least three basic methods: debug,
|
37
39
|
info, warning. If not provided, default logging will be used.
|
38
40
|
"""
|
39
41
|
|
42
|
+
# pylint: disable=R0801
|
40
43
|
def preprocess(self) -> None:
|
41
|
-
"""
|
42
|
-
|
43
|
-
self.
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
44
|
+
"""Registers the DEMs for the background terrain."""
|
45
|
+
self.light_version = self.kwargs.get("light_version", False)
|
46
|
+
self.stl_preview_path: str | None = None
|
47
|
+
|
48
|
+
if self.rotation:
|
49
|
+
self.logger.debug("Rotation is enabled: %s.", self.rotation)
|
50
|
+
output_size_multiplier = 1.5
|
51
|
+
else:
|
52
|
+
output_size_multiplier = 1
|
53
|
+
|
54
|
+
background_size = self.map_size + DEFAULT_DISTANCE * 2
|
55
|
+
rotated_size = int(background_size * output_size_multiplier)
|
56
|
+
|
57
|
+
self.background_directory = os.path.join(self.map_directory, "background")
|
58
|
+
os.makedirs(self.background_directory, exist_ok=True)
|
59
|
+
|
60
|
+
autoprocesses = [self.kwargs.get("auto_process", False), False]
|
61
|
+
dems = []
|
62
|
+
|
63
|
+
for name, autoprocess in zip(ELEMENTS, autoprocesses):
|
64
|
+
dem = DEM(
|
65
|
+
self.game,
|
66
|
+
self.coordinates,
|
67
|
+
background_size,
|
68
|
+
rotated_size,
|
69
|
+
self.rotation,
|
70
|
+
self.map_directory,
|
71
|
+
self.logger,
|
72
|
+
auto_process=autoprocess,
|
66
73
|
blur_radius=self.kwargs.get("blur_radius", DEFAULT_BLUR_RADIUS),
|
67
74
|
multiplier=self.kwargs.get("multiplier", DEFAULT_MULTIPLIER),
|
68
75
|
plateau=self.kwargs.get("plateau", DEFAULT_PLATEAU),
|
69
76
|
)
|
77
|
+
dem.preprocess()
|
78
|
+
dem.is_preview = self.is_preview(name) # type: ignore
|
79
|
+
dem.set_output_resolution((rotated_size, rotated_size))
|
80
|
+
dem.set_dem_path(os.path.join(self.background_directory, f"{name}.png"))
|
81
|
+
dems.append(dem)
|
70
82
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
83
|
+
self.dems = dems
|
84
|
+
|
85
|
+
def is_preview(self, name: str) -> bool:
|
86
|
+
"""Checks if the DEM is a preview.
|
87
|
+
|
88
|
+
Arguments:
|
89
|
+
name (str): The name of the DEM.
|
90
|
+
|
91
|
+
Returns:
|
92
|
+
bool: True if the DEM is a preview, False otherwise.
|
93
|
+
"""
|
94
|
+
return name == FULL_PREVIEW_NAME
|
80
95
|
|
81
96
|
def process(self) -> None:
|
82
97
|
"""Launches the component processing. Iterates over all tiles and processes them
|
83
98
|
as a result the DEM files will be saved, then based on them the obj files will be
|
84
99
|
generated."""
|
85
|
-
for
|
86
|
-
|
100
|
+
for dem in self.dems:
|
101
|
+
dem.process()
|
102
|
+
if not dem.is_preview: # type: ignore
|
103
|
+
cutted_dem_path = self.cutout(dem.dem_path)
|
104
|
+
if self.game.additional_dem_name is not None:
|
105
|
+
self.make_copy(cutted_dem_path, self.game.additional_dem_name)
|
106
|
+
|
107
|
+
if not self.light_version:
|
108
|
+
self.generate_obj_files()
|
109
|
+
else:
|
110
|
+
self.logger.info("Light version is enabled, obj files will not be generated.")
|
111
|
+
|
112
|
+
def make_copy(self, dem_path: str, dem_name: str) -> None:
|
113
|
+
"""Copies DEM data to additional DEM file.
|
87
114
|
|
88
|
-
|
115
|
+
Arguments:
|
116
|
+
dem_path (str): Path to the DEM file.
|
117
|
+
dem_name (str): Name of the additional DEM file.
|
118
|
+
"""
|
119
|
+
dem_directory = os.path.dirname(dem_path)
|
89
120
|
|
90
|
-
|
91
|
-
|
121
|
+
additional_dem_path = os.path.join(dem_directory, dem_name)
|
122
|
+
|
123
|
+
shutil.copyfile(dem_path, additional_dem_path)
|
124
|
+
self.logger.info("Additional DEM data was copied to %s.", additional_dem_path)
|
125
|
+
|
126
|
+
def info_sequence(self) -> dict[str, str | float | int]:
|
127
|
+
"""Returns a dictionary with information about the background terrain.
|
92
128
|
Adds the EPSG:3857 string to the data for convenient usage in QGIS.
|
93
129
|
|
94
130
|
Returns:
|
95
|
-
dict[str,
|
131
|
+
dict[str, str, float | int] -- A dictionary with information about the background
|
132
|
+
terrain.
|
96
133
|
"""
|
97
|
-
data = {}
|
98
134
|
self.qgis_sequence()
|
99
135
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
data[tile.code] = tile_entry
|
119
|
-
|
136
|
+
dem = self.dems[0]
|
137
|
+
|
138
|
+
north, south, east, west = dem.bbox
|
139
|
+
epsg3857_string = dem.get_epsg3857_string()
|
140
|
+
epsg3857_string_with_margin = dem.get_epsg3857_string(add_margin=True)
|
141
|
+
|
142
|
+
data = {
|
143
|
+
"center_latitude": dem.coordinates[0],
|
144
|
+
"center_longitude": dem.coordinates[1],
|
145
|
+
"epsg3857_string": epsg3857_string,
|
146
|
+
"epsg3857_string_with_margin": epsg3857_string_with_margin,
|
147
|
+
"height": dem.map_size,
|
148
|
+
"width": dem.map_size,
|
149
|
+
"north": north,
|
150
|
+
"south": south,
|
151
|
+
"east": east,
|
152
|
+
"west": west,
|
153
|
+
}
|
120
154
|
return data # type: ignore
|
121
155
|
|
122
156
|
def qgis_sequence(self) -> None:
|
123
157
|
"""Generates QGIS scripts for creating bounding box layers and rasterizing them."""
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
]
|
131
|
-
|
132
|
-
layers = qgis_layers + qgis_layers_with_margin
|
133
|
-
|
134
|
-
self.create_qgis_scripts(layers)
|
158
|
+
qgis_layer = (f"Background_{FULL_NAME}", *self.dems[0].get_espg3857_bbox())
|
159
|
+
qgis_layer_with_margin = (
|
160
|
+
f"Background_{FULL_NAME}_margin",
|
161
|
+
*self.dems[0].get_espg3857_bbox(add_margin=True),
|
162
|
+
)
|
163
|
+
self.create_qgis_scripts([qgis_layer, qgis_layer_with_margin])
|
135
164
|
|
136
165
|
def generate_obj_files(self) -> None:
|
137
|
-
"""Iterates over all
|
166
|
+
"""Iterates over all dems and generates 3D obj files based on DEM data.
|
138
167
|
If at least one DEM file is missing, the generation will be stopped at all.
|
139
168
|
"""
|
140
|
-
for
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
169
|
+
for dem in self.dems:
|
170
|
+
if not os.path.isfile(dem.dem_path):
|
171
|
+
self.logger.warning(
|
172
|
+
"DEM file not found, generation will be stopped: %s", dem.dem_path
|
173
|
+
)
|
145
174
|
return
|
146
175
|
|
147
|
-
self.logger.
|
176
|
+
self.logger.debug("DEM file for found: %s", dem.dem_path)
|
177
|
+
|
178
|
+
filename = os.path.splitext(os.path.basename(dem.dem_path))[0]
|
179
|
+
save_path = os.path.join(self.background_directory, f"{filename}.obj")
|
180
|
+
self.logger.debug("Generating obj file in path: %s", save_path)
|
181
|
+
|
182
|
+
dem_data = cv2.imread(dem.dem_path, cv2.IMREAD_UNCHANGED) # pylint: disable=no-member
|
183
|
+
self.plane_from_np(dem_data, save_path, is_preview=dem.is_preview) # type: ignore
|
184
|
+
|
185
|
+
# pylint: disable=too-many-locals
|
186
|
+
def cutout(self, dem_path: str) -> str:
|
187
|
+
"""Cuts out the center of the DEM (the actual map) and saves it as a separate file.
|
188
|
+
|
189
|
+
Arguments:
|
190
|
+
dem_path (str): The path to the DEM file.
|
191
|
+
|
192
|
+
Returns:
|
193
|
+
str -- The path to the cutout DEM file.
|
194
|
+
"""
|
195
|
+
dem_data = cv2.imread(dem_path, cv2.IMREAD_UNCHANGED) # pylint: disable=no-member
|
196
|
+
|
197
|
+
center = (dem_data.shape[0] // 2, dem_data.shape[1] // 2)
|
198
|
+
half_size = self.map_size // 2
|
199
|
+
x1 = center[0] - half_size
|
200
|
+
x2 = center[0] + half_size
|
201
|
+
y1 = center[1] - half_size
|
202
|
+
y2 = center[1] + half_size
|
203
|
+
dem_data = dem_data[x1:x2, y1:y2]
|
204
|
+
|
205
|
+
output_size = self.map_size + 1
|
206
|
+
|
207
|
+
main_dem_path = self.game.dem_file_path(self.map_directory)
|
208
|
+
|
209
|
+
try:
|
210
|
+
os.remove(main_dem_path)
|
211
|
+
except FileNotFoundError:
|
212
|
+
pass
|
213
|
+
|
214
|
+
# pylint: disable=no-member
|
215
|
+
resized_dem_data = cv2.resize(
|
216
|
+
dem_data, (output_size, output_size), interpolation=cv2.INTER_LINEAR
|
217
|
+
)
|
148
218
|
|
149
|
-
|
150
|
-
|
151
|
-
self.logger.debug("Generating obj file for tile %s in path: %s", tile.code, save_path)
|
219
|
+
cv2.imwrite(main_dem_path, resized_dem_data) # pylint: disable=no-member
|
220
|
+
self.logger.info("DEM cutout saved: %s", main_dem_path)
|
152
221
|
|
153
|
-
|
154
|
-
self.plane_from_np(tile.code, dem_data, save_path)
|
222
|
+
return main_dem_path
|
155
223
|
|
156
224
|
# pylint: disable=too-many-locals
|
157
|
-
|
158
|
-
def plane_from_np(self, tile_code: str, dem_data: np.ndarray, save_path: str) -> None:
|
225
|
+
def plane_from_np(self, dem_data: np.ndarray, save_path: str, is_preview: bool = False) -> None:
|
159
226
|
"""Generates a 3D obj file based on DEM data.
|
160
227
|
|
161
228
|
Arguments:
|
162
|
-
tile_code (str) -- The code of the tile.
|
163
229
|
dem_data (np.ndarray) -- The DEM data as a numpy array.
|
164
230
|
save_path (str) -- The path where the obj file will be saved.
|
231
|
+
is_preview (bool, optional) -- If True, the preview mesh will be generated.
|
165
232
|
"""
|
166
|
-
if tile_code == PATH_FULL_NAME:
|
167
|
-
resize_factor = FULL_RESIZE_FACTOR
|
168
|
-
simplify_factor = FULL_SIMPLIFY_FACTOR
|
169
|
-
self.logger.info("Generating a full map obj file")
|
170
|
-
else:
|
171
|
-
resize_factor = RESIZE_FACTOR
|
172
|
-
simplify_factor = SIMPLIFY_FACTOR
|
173
233
|
dem_data = cv2.resize( # pylint: disable=no-member
|
174
|
-
dem_data, (0, 0), fx=
|
234
|
+
dem_data, (0, 0), fx=RESIZE_FACTOR, fy=RESIZE_FACTOR
|
175
235
|
)
|
176
236
|
self.logger.debug(
|
177
|
-
"DEM data resized to shape: %s with factor: %s", dem_data.shape,
|
237
|
+
"DEM data resized to shape: %s with factor: %s", dem_data.shape, RESIZE_FACTOR
|
178
238
|
)
|
179
239
|
|
180
240
|
# Invert the height values.
|
@@ -186,10 +246,8 @@ class Background(Component):
|
|
186
246
|
x, y = np.meshgrid(x, y)
|
187
247
|
z = dem_data
|
188
248
|
|
189
|
-
self.logger.
|
190
|
-
"Starting to generate a mesh for
|
191
|
-
"This may take a while...",
|
192
|
-
tile_code,
|
249
|
+
self.logger.debug(
|
250
|
+
"Starting to generate a mesh for with shape: %s x %s. This may take a while...",
|
193
251
|
cols,
|
194
252
|
rows,
|
195
253
|
)
|
@@ -216,17 +274,24 @@ class Background(Component):
|
|
216
274
|
mesh.apply_transform(rotation_matrix_y)
|
217
275
|
mesh.apply_transform(rotation_matrix_z)
|
218
276
|
|
219
|
-
|
277
|
+
if is_preview:
|
278
|
+
# Simplify the preview mesh to reduce the size of the file.
|
279
|
+
mesh = mesh.simplify_quadric_decimation(face_count=len(mesh.faces) // 2**7)
|
220
280
|
|
221
|
-
|
222
|
-
|
223
|
-
self.logger.debug("Mesh simplified to %s faces", len(mesh.faces))
|
224
|
-
|
225
|
-
if tile_code == PATH_FULL_NAME:
|
281
|
+
# Apply scale to make the preview mesh smaller in the UI.
|
282
|
+
mesh.apply_scale([0.5, 0.5, 0.5])
|
226
283
|
self.mesh_to_stl(mesh)
|
284
|
+
else:
|
285
|
+
multiplier = self.kwargs.get("multiplier", DEFAULT_MULTIPLIER)
|
286
|
+
if multiplier != 1:
|
287
|
+
z_scaling_factor = 1 / multiplier
|
288
|
+
else:
|
289
|
+
z_scaling_factor = 1 / 2**5
|
290
|
+
self.logger.debug("Z scaling factor: %s", z_scaling_factor)
|
291
|
+
mesh.apply_scale([1 / RESIZE_FACTOR, 1 / RESIZE_FACTOR, z_scaling_factor])
|
227
292
|
|
228
293
|
mesh.export(save_path)
|
229
|
-
self.logger.
|
294
|
+
self.logger.debug("Obj file saved: %s", save_path)
|
230
295
|
|
231
296
|
def mesh_to_stl(self, mesh: trimesh.Trimesh) -> None:
|
232
297
|
"""Converts the mesh to an STL file and saves it in the previews directory.
|
@@ -237,126 +302,113 @@ class Background(Component):
|
|
237
302
|
mesh (trimesh.Trimesh) -- The mesh to convert to an STL file.
|
238
303
|
"""
|
239
304
|
preview_path = os.path.join(self.previews_directory, "background_dem.stl")
|
240
|
-
mesh = mesh.simplify_quadric_decimation(percent=0.05)
|
241
305
|
mesh.export(preview_path)
|
242
306
|
|
243
307
|
self.logger.info("STL file saved: %s", preview_path)
|
244
308
|
|
245
309
|
self.stl_preview_path = preview_path # pylint: disable=attribute-defined-outside-init
|
246
310
|
|
311
|
+
# pylint: disable=no-member
|
247
312
|
def previews(self) -> list[str]:
|
248
|
-
"""
|
249
|
-
NOTE: The map itself is not included in the preview, so it will be empty.
|
313
|
+
"""Returns the path to the image previews paths and the path to the STL preview file.
|
250
314
|
|
251
315
|
Returns:
|
252
|
-
list[str] -- A list of paths to the
|
253
|
-
|
254
|
-
self.
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
)
|
316
|
+
list[str] -- A list of paths to the previews.
|
317
|
+
"""
|
318
|
+
preview_paths = self.dem_previews(self.game.dem_file_path(self.map_directory))
|
319
|
+
for dem in self.dems:
|
320
|
+
if dem.is_preview: # type: ignore
|
321
|
+
background_dem_preview_path = os.path.join(
|
322
|
+
self.previews_directory, "background_dem.png"
|
323
|
+
)
|
324
|
+
background_dem_preview_image = cv2.imread(dem.dem_path, cv2.IMREAD_UNCHANGED)
|
325
|
+
|
326
|
+
background_dem_preview_image = cv2.resize(
|
327
|
+
background_dem_preview_image, (0, 0), fx=1 / 4, fy=1 / 4
|
328
|
+
)
|
329
|
+
background_dem_preview_image = cv2.normalize( # type: ignore
|
330
|
+
background_dem_preview_image, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U
|
331
|
+
)
|
332
|
+
background_dem_preview_image = cv2.cvtColor(
|
333
|
+
background_dem_preview_image, cv2.COLOR_GRAY2BGR
|
334
|
+
)
|
335
|
+
|
336
|
+
cv2.imwrite(background_dem_preview_path, background_dem_preview_image)
|
337
|
+
preview_paths.append(background_dem_preview_path)
|
338
|
+
|
339
|
+
if self.stl_preview_path:
|
340
|
+
preview_paths.append(self.stl_preview_path)
|
341
|
+
|
342
|
+
return preview_paths
|
343
|
+
|
344
|
+
def dem_previews(self, image_path: str) -> list[str]:
|
345
|
+
"""Get list of preview images.
|
277
346
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
# Calculate the position based on the tile code
|
282
|
-
if tile.code == "N":
|
283
|
-
x = DEFAULT_DISTANCE
|
284
|
-
y = 0
|
285
|
-
elif tile.code == "NE":
|
286
|
-
x = self.map_width + DEFAULT_DISTANCE
|
287
|
-
y = 0
|
288
|
-
elif tile.code == "E":
|
289
|
-
x = self.map_width + DEFAULT_DISTANCE
|
290
|
-
y = DEFAULT_DISTANCE
|
291
|
-
elif tile.code == "SE":
|
292
|
-
x = self.map_width + DEFAULT_DISTANCE
|
293
|
-
y = self.map_height + DEFAULT_DISTANCE
|
294
|
-
elif tile.code == "S":
|
295
|
-
x = DEFAULT_DISTANCE
|
296
|
-
y = self.map_height + DEFAULT_DISTANCE
|
297
|
-
elif tile.code == "SW":
|
298
|
-
x = 0
|
299
|
-
y = self.map_height + DEFAULT_DISTANCE
|
300
|
-
elif tile.code == "W":
|
301
|
-
x = 0
|
302
|
-
y = DEFAULT_DISTANCE
|
303
|
-
elif tile.code == "NW":
|
304
|
-
x = 0
|
305
|
-
y = 0
|
306
|
-
|
307
|
-
# pylint: disable=possibly-used-before-assignment
|
308
|
-
x2 = x + tile_width
|
309
|
-
y2 = y + tile_height
|
310
|
-
|
311
|
-
self.logger.debug(
|
312
|
-
"Tile %s position. X from %s to %s, Y from %s to %s", tile.code, x, x2, y, y2
|
313
|
-
)
|
347
|
+
Arguments:
|
348
|
+
image_path (str): Path to the DEM file.
|
314
349
|
|
315
|
-
|
316
|
-
|
350
|
+
Returns:
|
351
|
+
list[str]: List of preview images.
|
352
|
+
"""
|
353
|
+
self.logger.debug("Starting DEM previews generation.")
|
354
|
+
return [self.grayscale_preview(image_path), self.colored_preview(image_path)]
|
317
355
|
|
318
|
-
|
319
|
-
|
356
|
+
def grayscale_preview(self, image_path: str) -> str:
|
357
|
+
"""Converts DEM image to grayscale RGB image and saves it to the map directory.
|
358
|
+
Returns path to the preview image.
|
320
359
|
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
#
|
361
|
-
|
362
|
-
|
360
|
+
Arguments:
|
361
|
+
image_path (str): Path to the DEM file.
|
362
|
+
|
363
|
+
Returns:
|
364
|
+
str: Path to the preview image.
|
365
|
+
"""
|
366
|
+
grayscale_dem_path = os.path.join(self.previews_directory, "dem_grayscale.png")
|
367
|
+
|
368
|
+
self.logger.debug("Creating grayscale preview of DEM data in %s.", grayscale_dem_path)
|
369
|
+
|
370
|
+
dem_data = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
|
371
|
+
dem_data_rgb = cv2.cvtColor(dem_data, cv2.COLOR_GRAY2RGB)
|
372
|
+
cv2.imwrite(grayscale_dem_path, dem_data_rgb)
|
373
|
+
return grayscale_dem_path
|
374
|
+
|
375
|
+
def colored_preview(self, image_path: str) -> str:
|
376
|
+
"""Converts DEM image to colored RGB image and saves it to the map directory.
|
377
|
+
Returns path to the preview image.
|
378
|
+
|
379
|
+
Arguments:
|
380
|
+
image_path (str): Path to the DEM file.
|
381
|
+
|
382
|
+
Returns:
|
383
|
+
list[str]: List with a single path to the DEM file
|
384
|
+
"""
|
385
|
+
colored_dem_path = os.path.join(self.previews_directory, "dem_colored.png")
|
386
|
+
|
387
|
+
self.logger.debug("Creating colored preview of DEM data in %s.", colored_dem_path)
|
388
|
+
|
389
|
+
dem_data = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
|
390
|
+
|
391
|
+
self.logger.debug(
|
392
|
+
"DEM data before normalization. Shape: %s, dtype: %s. Min: %s, max: %s.",
|
393
|
+
dem_data.shape,
|
394
|
+
dem_data.dtype,
|
395
|
+
dem_data.min(),
|
396
|
+
dem_data.max(),
|
397
|
+
)
|
398
|
+
|
399
|
+
# Create an empty array with the same shape and type as dem_data.
|
400
|
+
dem_data_normalized = np.empty_like(dem_data)
|
401
|
+
|
402
|
+
# Normalize the DEM data to the range [0, 255]
|
403
|
+
cv2.normalize(dem_data, dem_data_normalized, 0, 255, cv2.NORM_MINMAX)
|
404
|
+
self.logger.debug(
|
405
|
+
"DEM data after normalization. Shape: %s, dtype: %s. Min: %s, max: %s.",
|
406
|
+
dem_data_normalized.shape,
|
407
|
+
dem_data_normalized.dtype,
|
408
|
+
dem_data_normalized.min(),
|
409
|
+
dem_data_normalized.max(),
|
410
|
+
)
|
411
|
+
dem_data_colored = cv2.applyColorMap(dem_data_normalized, cv2.COLORMAP_JET)
|
412
|
+
|
413
|
+
cv2.imwrite(colored_dem_path, dem_data_colored)
|
414
|
+
return colored_dem_path
|