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.
@@ -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
- RESIZE_FACTOR = 1 / 4
23
- SIMPLIFY_FACTOR = 10
24
- FULL_RESIZE_FACTOR = 1 / 8
25
- FULL_SIMPLIFY_FACTOR = 20
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
- Args:
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
- map_height (int): The height of the map in pixels.
34
- map_width (int): The width of the map in pixels.
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
- """Prepares the component for processing. Registers the tiles around the map by moving
42
- clockwise from North, then clockwise."""
43
- self.tiles: list[Tile] = []
44
- origin = self.coordinates
45
-
46
- # Getting a list of 8 tiles around the map starting from the N(North) tile.
47
- for path_step in get_steps(self.map_height, self.map_width):
48
- # Getting the destination coordinates for the current tile.
49
- if path_step.angle is None:
50
- # For the case when generating the overview map, which has the same
51
- # center as the main map.
52
- tile_coordinates = self.coordinates
53
- else:
54
- tile_coordinates = path_step.get_destination(origin)
55
-
56
- # Create a Tile component, which is needed to save the DEM image.
57
- tile = Tile(
58
- game=self.game,
59
- coordinates=tile_coordinates,
60
- map_height=path_step.size[1],
61
- map_width=path_step.size[0],
62
- map_directory=self.map_directory,
63
- logger=self.logger,
64
- tile_code=path_step.code,
65
- auto_process=False,
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
- # Update the origin for the next tile.
72
- origin = tile_coordinates
73
- self.tiles.append(tile)
74
- self.logger.debug(
75
- "Registered tile: %s, coordinates: %s, size: %s",
76
- tile.code,
77
- tile_coordinates,
78
- path_step.size,
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 tile in self.tiles:
86
- tile.process()
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
- self.generate_obj_files()
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
- def info_sequence(self) -> dict[str, dict[str, str | float | int]]:
91
- """Returns a dictionary with information about the tiles around the map.
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, dict[str, float | int]] -- A dictionary with information about the tiles.
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
- for tile in self.tiles:
101
- north, south, east, west = tile.bbox
102
- epsg3857_string = tile.get_epsg3857_string()
103
- epsg3857_string_with_margin = tile.get_epsg3857_string(add_margin=True)
104
-
105
- tile_entry = {
106
- "center_latitude": tile.coordinates[0],
107
- "center_longitude": tile.coordinates[1],
108
- "epsg3857_string": epsg3857_string,
109
- "epsg3857_string_with_margin": epsg3857_string_with_margin,
110
- "height": tile.map_height,
111
- "width": tile.map_width,
112
- "north": north,
113
- "south": south,
114
- "east": east,
115
- "west": west,
116
- }
117
- if tile.code is not None:
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
- qgis_layers = [
125
- (f"Background_{tile.code}", *tile.get_espg3857_bbox()) for tile in self.tiles
126
- ]
127
- qgis_layers_with_margin = [
128
- (f"Background_{tile.code}_margin", *tile.get_espg3857_bbox(add_margin=True))
129
- for tile in self.tiles
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 tiles and generates 3D obj files based on DEM data.
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 tile in self.tiles:
141
- # Read DEM data from the tile.
142
- dem_path = tile.dem_path
143
- if not os.path.isfile(dem_path):
144
- self.logger.warning("DEM file not found, generation will be stopped: %s", dem_path)
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.info("DEM file for tile %s found: %s", tile.code, dem_path)
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
- base_directory = os.path.dirname(dem_path)
150
- save_path = os.path.join(base_directory, f"{tile.code}.obj")
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
- dem_data = cv2.imread(tile.dem_path, cv2.IMREAD_UNCHANGED) # pylint: disable=no-member
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
- @timeit
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=resize_factor, fy=resize_factor
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, resize_factor
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.info(
190
- "Starting to generate a mesh for tile %s with shape: %s x %s. "
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
- self.logger.info("Mesh generated with %s faces, will be simplified", len(mesh.faces))
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
- # Simplify the mesh to reduce the number of faces.
222
- mesh = mesh.simplify_quadric_decimation(face_count=len(faces) // simplify_factor)
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.info("Obj file saved: %s", save_path)
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
- """Generates a preview by combining all tiles into one image.
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 preview images."""
253
-
254
- self.logger.info("Generating a preview image for the background DEM")
255
-
256
- image_height = self.map_height + DEFAULT_DISTANCE * 2
257
- image_width = self.map_width + DEFAULT_DISTANCE * 2
258
- self.logger.debug("Full size of the preview image: %s x %s", image_width, image_height)
259
-
260
- image = np.zeros((image_height, image_width), np.uint16) # pylint: disable=no-member
261
- self.logger.debug("Empty image created: %s", image.shape)
262
-
263
- for tile in self.tiles:
264
- # pylint: disable=no-member
265
- if tile.code == PATH_FULL_NAME:
266
- continue
267
- tile_image = cv2.imread(tile.dem_path, cv2.IMREAD_UNCHANGED)
268
-
269
- self.logger.debug(
270
- "Tile %s image shape: %s, dtype: %s, max: %s, min: %s",
271
- tile.code,
272
- tile_image.shape,
273
- tile_image.dtype,
274
- tile_image.max(),
275
- tile_image.min(),
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
- tile_height, tile_width = tile_image.shape
279
- self.logger.debug("Tile %s size: %s x %s", tile.code, tile_width, tile_height)
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
- # pylint: disable=possibly-used-before-assignment
316
- image[y:y2, x:x2] = tile_image
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
- # Save image to the map directory.
319
- preview_path = os.path.join(self.previews_directory, "background_dem.png")
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
- # pylint: disable=no-member
322
- image = cv2.normalize(image, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U) # type: ignore
323
- image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR) # type: ignore
324
- cv2.imwrite(preview_path, image)
325
-
326
- return [preview_path, self.stl_preview_path]
327
-
328
-
329
- # Creates tiles around the map.
330
- # The one on corners 2048x2048, on sides and in the middle map_size x 2048.
331
- # So 2048 is a distance FROM the edge of the map, but the other size depends on the map size.
332
- # But for corner tiles it's always 2048.
333
-
334
- # In the beginning we have coordinates of the central point of the map and it's size.
335
- # We need to calculate the coordinates of central points all 8 tiles around the map.
336
-
337
- # Latitude is a vertical line, Longitude is a horizontal line.
338
-
339
- # 2048
340
- # | |
341
- # ____________________|_________|___
342
- # | | | |
343
- # | NW | N | NE | 2048
344
- # |_________|_________|_________|___
345
- # | | | |
346
- # | W | C | E |
347
- # |_________|_________|_________|
348
- # | | | |
349
- # | SW | S | SE |
350
- # |_________|_________|_________|
351
- #
352
- # N = C map_height / 2 + 1024; N_width = map_width; N_height = 2048
353
- # NW = N - map_width / 2 - 1024; NW_width = 2048; NW_height = 2048
354
- # and so on...
355
-
356
- # lat, lon = 45.28565000315636, 20.237121355049904
357
- # dst = 1024
358
-
359
- # # N
360
- # destination = distance(meters=dst).destination((lat, lon), 0)
361
- # lat, lon = destination.latitude, destination.longitude
362
- # print(lat, lon)
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