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