maps4fs 1.5.0__tar.gz → 1.5.7__tar.gz
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-1.5.0 → maps4fs-1.5.7}/PKG-INFO +11 -4
- {maps4fs-1.5.0 → maps4fs-1.5.7}/README.md +10 -3
- {maps4fs-1.5.0 → maps4fs-1.5.7}/maps4fs/__init__.py +3 -2
- {maps4fs-1.5.0 → maps4fs-1.5.7}/maps4fs/generator/background.py +74 -110
- {maps4fs-1.5.0 → maps4fs-1.5.7}/maps4fs/generator/component.py +10 -0
- {maps4fs-1.5.0 → maps4fs-1.5.7}/maps4fs/generator/dem.py +22 -106
- maps4fs-1.5.7/maps4fs/generator/dtm.py +333 -0
- {maps4fs-1.5.0 → maps4fs-1.5.7}/maps4fs/generator/game.py +2 -1
- {maps4fs-1.5.0 → maps4fs-1.5.7}/maps4fs/generator/i3d.py +4 -14
- {maps4fs-1.5.0 → maps4fs-1.5.7}/maps4fs/generator/map.py +17 -134
- maps4fs-1.5.7/maps4fs/generator/satellite.py +92 -0
- maps4fs-1.5.7/maps4fs/generator/settings.py +150 -0
- {maps4fs-1.5.0 → maps4fs-1.5.7}/maps4fs.egg-info/PKG-INFO +11 -4
- {maps4fs-1.5.0 → maps4fs-1.5.7}/maps4fs.egg-info/SOURCES.txt +3 -0
- {maps4fs-1.5.0 → maps4fs-1.5.7}/pyproject.toml +1 -1
- {maps4fs-1.5.0 → maps4fs-1.5.7}/tests/test_generator.py +10 -1
- {maps4fs-1.5.0 → maps4fs-1.5.7}/LICENSE.md +0 -0
- {maps4fs-1.5.0 → maps4fs-1.5.7}/maps4fs/generator/__init__.py +0 -0
- {maps4fs-1.5.0 → maps4fs-1.5.7}/maps4fs/generator/config.py +0 -0
- {maps4fs-1.5.0 → maps4fs-1.5.7}/maps4fs/generator/grle.py +0 -0
- {maps4fs-1.5.0 → maps4fs-1.5.7}/maps4fs/generator/qgis.py +0 -0
- {maps4fs-1.5.0 → maps4fs-1.5.7}/maps4fs/generator/texture.py +0 -0
- {maps4fs-1.5.0 → maps4fs-1.5.7}/maps4fs/logger.py +0 -0
- {maps4fs-1.5.0 → maps4fs-1.5.7}/maps4fs/toolbox/__init__.py +0 -0
- {maps4fs-1.5.0 → maps4fs-1.5.7}/maps4fs/toolbox/background.py +0 -0
- {maps4fs-1.5.0 → maps4fs-1.5.7}/maps4fs/toolbox/dem.py +0 -0
- {maps4fs-1.5.0 → maps4fs-1.5.7}/maps4fs.egg-info/dependency_links.txt +0 -0
- {maps4fs-1.5.0 → maps4fs-1.5.7}/maps4fs.egg-info/requires.txt +0 -0
- {maps4fs-1.5.0 → maps4fs-1.5.7}/maps4fs.egg-info/top_level.txt +0 -0
- {maps4fs-1.5.0 → maps4fs-1.5.7}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: maps4fs
|
3
|
-
Version: 1.5.
|
3
|
+
Version: 1.5.7
|
4
4
|
Summary: Generate map templates for Farming Simulator from real places.
|
5
5
|
Author-email: iwatkot <iwatkot@gmail.com>
|
6
6
|
License: MIT License
|
@@ -75,6 +75,8 @@ Requires-Dist: pydantic
|
|
75
75
|
🌲 Automatically generates forests 🆕<br>
|
76
76
|
🌊 Automatically generates water planes 🆕<br>
|
77
77
|
📈 Automatically generates splines 🆕<br>
|
78
|
+
🛰️ Automatically downloads high resolution satellite images 🆕<br>
|
79
|
+
🏔️ Allows to use multiple DTM providers for elevation models 🆕<br>
|
78
80
|
🌍 Based on real-world data from OpenStreetMap<br>
|
79
81
|
🗺️ Supports [custom OSM maps](/docs/custom_osm.md)<br>
|
80
82
|
🏞️ Generates height map using SRTM dataset<br>
|
@@ -473,8 +475,6 @@ You can also apply some advanced settings to the map generation process. Note th
|
|
473
475
|
|
474
476
|
### DEM Advanced settings
|
475
477
|
|
476
|
-
- Auto process: the tool will automatically try to find suitable multiplier. As a result, the DEM image WILL not match real world values. If this option is disabled, you'll probably see completely black DEM image, but it's not empty. It's just you can't see the values of 16-bit image by eye, because they're too small. Learn more what's DEM image and how to work with it in [docs](docs/dem.md). By default, it's set to True.
|
477
|
-
|
478
478
|
- Multiplier: the height of the map is multiplied by this value. So the DEM map is just a 16-bit grayscale image, which means that the maximum available value there is 65535, while the actual difference between the deepest and the highest point on Earth is about 20 km. Just note that this setting mostly does not matter, because you can always adjust it in the Giants Editor, learn more about the DEM file and the heightScale parameter in [docs](docs/dem.md). By default, it's set to 1.
|
479
479
|
|
480
480
|
- Blur radius: the radius of the Gaussian blur filter applied to the DEM map. By default, it's set to 21. This filter just makes the DEM map smoother, so the height transitions will be more natural. You can set it to 1 to disable the filter, but it will result in a Minecraft-like map.
|
@@ -511,10 +511,16 @@ You can also apply some advanced settings to the map generation process. Note th
|
|
511
511
|
|
512
512
|
- Skip drains - if enabled, the tool will not generate the drains and ditches on the map. By default, it's set to False. Use this if you don't need the drains on the map.
|
513
513
|
|
514
|
-
|
514
|
+
### Splines Advanced settings
|
515
515
|
|
516
516
|
- Splines density - number of points, which will be added (interpolate) between each pair of existing points. The higher the value, the denser the spline will be. It can smooth the splines, but high values can in opposite make the splines look unnatural.
|
517
517
|
|
518
|
+
### Satellite Advanced settings
|
519
|
+
|
520
|
+
- Download images - if enabled, the tool will download the satellite images for the background terrain and the overview image. If you already have the images, you can turn it off.
|
521
|
+
- Satellite margin - the margin around the map in meters. It's useful when you want to have some space around the map on the satellite images. By default, it's set to 100.
|
522
|
+
- Zoom level - the zoom level of the satellite images. The higher the value, the more detailed the images will be. By default, it's set to 14 and this option is disabled on a public version of the app.
|
523
|
+
|
518
524
|
## Expert Settings
|
519
525
|
The tool also supports the expert settings. Do not use them until you read the documentation and understand what they do. Here's the list of the expert settings:
|
520
526
|
|
@@ -555,3 +561,4 @@ But also, I want to thank the people who helped me with the project in some way,
|
|
555
561
|
- [Tox3](https://github.com/Tox3) - for the manual tests of the app.
|
556
562
|
- [Lucandia](https://github.com/Lucandia) - for the awesome StreamLit [widget to preview STL files](https://github.com/Lucandia/streamlit_stl).
|
557
563
|
- [H4rdB4se](https://github.com/H4rdB4se) - for investigating the issue with custom OSM files and finding a proper way to work with the files in JOSM.
|
564
|
+
- [kbrandwijk](https://github.com/kbrandwijk) - for providing [awesome tool](https://github.com/Paint-a-Farm/satmap_downloader) to download the satellite images from the Google Maps and giving a permission to modify it and create a Python Package.
|
@@ -49,6 +49,8 @@
|
|
49
49
|
🌲 Automatically generates forests 🆕<br>
|
50
50
|
🌊 Automatically generates water planes 🆕<br>
|
51
51
|
📈 Automatically generates splines 🆕<br>
|
52
|
+
🛰️ Automatically downloads high resolution satellite images 🆕<br>
|
53
|
+
🏔️ Allows to use multiple DTM providers for elevation models 🆕<br>
|
52
54
|
🌍 Based on real-world data from OpenStreetMap<br>
|
53
55
|
🗺️ Supports [custom OSM maps](/docs/custom_osm.md)<br>
|
54
56
|
🏞️ Generates height map using SRTM dataset<br>
|
@@ -447,8 +449,6 @@ You can also apply some advanced settings to the map generation process. Note th
|
|
447
449
|
|
448
450
|
### DEM Advanced settings
|
449
451
|
|
450
|
-
- Auto process: the tool will automatically try to find suitable multiplier. As a result, the DEM image WILL not match real world values. If this option is disabled, you'll probably see completely black DEM image, but it's not empty. It's just you can't see the values of 16-bit image by eye, because they're too small. Learn more what's DEM image and how to work with it in [docs](docs/dem.md). By default, it's set to True.
|
451
|
-
|
452
452
|
- Multiplier: the height of the map is multiplied by this value. So the DEM map is just a 16-bit grayscale image, which means that the maximum available value there is 65535, while the actual difference between the deepest and the highest point on Earth is about 20 km. Just note that this setting mostly does not matter, because you can always adjust it in the Giants Editor, learn more about the DEM file and the heightScale parameter in [docs](docs/dem.md). By default, it's set to 1.
|
453
453
|
|
454
454
|
- Blur radius: the radius of the Gaussian blur filter applied to the DEM map. By default, it's set to 21. This filter just makes the DEM map smoother, so the height transitions will be more natural. You can set it to 1 to disable the filter, but it will result in a Minecraft-like map.
|
@@ -485,10 +485,16 @@ You can also apply some advanced settings to the map generation process. Note th
|
|
485
485
|
|
486
486
|
- Skip drains - if enabled, the tool will not generate the drains and ditches on the map. By default, it's set to False. Use this if you don't need the drains on the map.
|
487
487
|
|
488
|
-
|
488
|
+
### Splines Advanced settings
|
489
489
|
|
490
490
|
- Splines density - number of points, which will be added (interpolate) between each pair of existing points. The higher the value, the denser the spline will be. It can smooth the splines, but high values can in opposite make the splines look unnatural.
|
491
491
|
|
492
|
+
### Satellite Advanced settings
|
493
|
+
|
494
|
+
- Download images - if enabled, the tool will download the satellite images for the background terrain and the overview image. If you already have the images, you can turn it off.
|
495
|
+
- Satellite margin - the margin around the map in meters. It's useful when you want to have some space around the map on the satellite images. By default, it's set to 100.
|
496
|
+
- Zoom level - the zoom level of the satellite images. The higher the value, the more detailed the images will be. By default, it's set to 14 and this option is disabled on a public version of the app.
|
497
|
+
|
492
498
|
## Expert Settings
|
493
499
|
The tool also supports the expert settings. Do not use them until you read the documentation and understand what they do. Here's the list of the expert settings:
|
494
500
|
|
@@ -529,3 +535,4 @@ But also, I want to thank the people who helped me with the project in some way,
|
|
529
535
|
- [Tox3](https://github.com/Tox3) - for the manual tests of the app.
|
530
536
|
- [Lucandia](https://github.com/Lucandia) - for the awesome StreamLit [widget to preview STL files](https://github.com/Lucandia/streamlit_stl).
|
531
537
|
- [H4rdB4se](https://github.com/H4rdB4se) - for investigating the issue with custom OSM files and finding a proper way to work with the files in JOSM.
|
538
|
+
- [kbrandwijk](https://github.com/kbrandwijk) - for providing [awesome tool](https://github.com/Paint-a-Farm/satmap_downloader) to download the satellite images from the Google Maps and giving a permission to modify it and create a Python Package.
|
@@ -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,
|
@@ -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)
|
@@ -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.
|
@@ -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")
|