maps4fs 1.8.171__py3-none-any.whl → 1.8.173__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/component/background.py +18 -12
- maps4fs/generator/component/base/component_mesh.py +128 -11
- maps4fs/generator/dem.py +138 -93
- maps4fs/generator/dtm/dtm.py +2 -153
- maps4fs/generator/dtm/srtm.py +2 -0
- maps4fs/generator/settings.py +11 -7
- {maps4fs-1.8.171.dist-info → maps4fs-1.8.173.dist-info}/METADATA +10 -7
- {maps4fs-1.8.171.dist-info → maps4fs-1.8.173.dist-info}/RECORD +11 -11
- {maps4fs-1.8.171.dist-info → maps4fs-1.8.173.dist-info}/LICENSE.md +0 -0
- {maps4fs-1.8.171.dist-info → maps4fs-1.8.173.dist-info}/WHEEL +0 -0
- {maps4fs-1.8.171.dist-info → maps4fs-1.8.173.dist-info}/top_level.txt +0 -0
@@ -235,24 +235,16 @@ class Background(MeshComponent, ImageComponent):
|
|
235
235
|
This setting is used for a Background Terrain, where the center part where the
|
236
236
|
playable area is will be cut out.
|
237
237
|
"""
|
238
|
-
resize_factor = 1 / self.map.background_settings.resize_factor
|
239
|
-
dem_data = cv2.resize(dem_data, (0, 0), fx=resize_factor, fy=resize_factor)
|
240
|
-
if remove_center:
|
241
|
-
half_size = int(self.map_size // 2 * resize_factor)
|
242
|
-
dem_data = self.cut_out_np(dem_data, half_size, set_zeros=True)
|
243
|
-
self.logger.debug("Center removed from DEM data.")
|
244
|
-
self.logger.debug(
|
245
|
-
"DEM data resized to shape: %s with factor: %s", dem_data.shape, resize_factor
|
246
|
-
)
|
247
|
-
|
248
238
|
mesh = self.mesh_from_np(
|
249
239
|
dem_data,
|
250
240
|
include_zeros=include_zeros,
|
251
241
|
z_scaling_factor=self.get_z_scaling_factor(),
|
252
|
-
resize_factor=resize_factor,
|
242
|
+
resize_factor=self.map.background_settings.resize_factor,
|
253
243
|
apply_decimation=self.map.background_settings.apply_decimation,
|
254
244
|
decimation_percent=self.map.background_settings.decimation_percent,
|
255
245
|
decimation_agression=self.map.background_settings.decimation_agression,
|
246
|
+
remove_center=remove_center,
|
247
|
+
remove_size=self.map_size,
|
256
248
|
)
|
257
249
|
|
258
250
|
mesh.export(save_path)
|
@@ -414,10 +406,18 @@ class Background(MeshComponent, ImageComponent):
|
|
414
406
|
water_resources_image = cv2.imread(self.water_resources_path, cv2.IMREAD_UNCHANGED)
|
415
407
|
dem_image = cv2.imread(self.output_path, cv2.IMREAD_UNCHANGED)
|
416
408
|
|
409
|
+
# fall back to default value for height_scale 255, it is defined as float | None
|
410
|
+
# but it is always set at this point
|
411
|
+
z_scaling_factor: float = (
|
412
|
+
self.map.shared_settings.mesh_z_scaling_factor
|
413
|
+
if self.map.shared_settings.mesh_z_scaling_factor is not None
|
414
|
+
else 257
|
415
|
+
)
|
416
|
+
|
417
417
|
dem_image = self.subtract_by_mask(
|
418
418
|
dem_image,
|
419
419
|
water_resources_image,
|
420
|
-
self.map.dem_settings.water_depth,
|
420
|
+
int(self.map.dem_settings.water_depth * z_scaling_factor),
|
421
421
|
)
|
422
422
|
|
423
423
|
# Save the modified dem_image back to the output path
|
@@ -432,6 +432,12 @@ class Background(MeshComponent, ImageComponent):
|
|
432
432
|
|
433
433
|
# Single channeled 8 bit image, where the water have values of 255, and the rest 0.
|
434
434
|
plane_water = cv2.imread(self.water_resources_path, cv2.IMREAD_UNCHANGED)
|
435
|
+
|
436
|
+
# Check if the image contains non-zero values.
|
437
|
+
if not np.any(plane_water):
|
438
|
+
self.logger.warning("Water resources image is empty, skipping water generation.")
|
439
|
+
return
|
440
|
+
|
435
441
|
dilated_plane_water = cv2.dilate(
|
436
442
|
plane_water.astype(np.uint8), np.ones((5, 5), np.uint8), iterations=5
|
437
443
|
).astype(np.uint8)
|
@@ -53,10 +53,12 @@ class MeshComponent(Component):
|
|
53
53
|
image: np.ndarray,
|
54
54
|
include_zeros: bool,
|
55
55
|
z_scaling_factor: float,
|
56
|
-
resize_factor:
|
56
|
+
resize_factor: int,
|
57
57
|
apply_decimation: bool,
|
58
58
|
decimation_percent: int,
|
59
59
|
decimation_agression: int,
|
60
|
+
remove_center: bool,
|
61
|
+
remove_size: int,
|
60
62
|
) -> trimesh.Trimesh:
|
61
63
|
"""Generates a mesh from the given numpy array.
|
62
64
|
|
@@ -64,16 +66,21 @@ class MeshComponent(Component):
|
|
64
66
|
image (np.ndarray): The numpy array to generate the mesh from.
|
65
67
|
include_zeros (bool): Whether to include zero values in the mesh.
|
66
68
|
z_scaling_factor (float): The scaling factor for the Z-axis.
|
67
|
-
resize_factor (
|
69
|
+
resize_factor (int): The resizing factor.
|
68
70
|
apply_decimation (bool): Whether to apply decimation to the mesh.
|
69
71
|
decimation_percent (int): The percent of the decimation.
|
70
72
|
decimation_agression (int): The agression of the decimation.
|
73
|
+
remove_center (bool): Whether to remove the center from the mesh.
|
74
|
+
remove_size (int): The size of the center to remove.
|
71
75
|
|
72
76
|
Returns:
|
73
77
|
trimesh.Trimesh: The generated mesh.
|
74
78
|
"""
|
79
|
+
output_x_size, _ = image.shape
|
75
80
|
image = image.max() - image
|
76
81
|
|
82
|
+
image = image[::resize_factor, ::resize_factor]
|
83
|
+
|
77
84
|
rows, cols = image.shape
|
78
85
|
x = np.linspace(0, cols - 1, cols)
|
79
86
|
y = np.linspace(0, rows - 1, rows)
|
@@ -106,15 +113,7 @@ class MeshComponent(Component):
|
|
106
113
|
|
107
114
|
faces_np = np.array(faces)
|
108
115
|
mesh = trimesh.Trimesh(vertices=vertices, faces=faces_np)
|
109
|
-
|
110
|
-
# Apply rotation: 180 degrees around Y-axis and Z-axis
|
111
|
-
rotation_matrix_y = trimesh.transformations.rotation_matrix(np.pi, [0, 1, 0])
|
112
|
-
rotation_matrix_z = trimesh.transformations.rotation_matrix(np.pi, [0, 0, 1])
|
113
|
-
mesh.apply_transform(rotation_matrix_y)
|
114
|
-
mesh.apply_transform(rotation_matrix_z)
|
115
|
-
|
116
|
-
# if not include_zeros:
|
117
|
-
mesh.apply_scale([1 / resize_factor, 1 / resize_factor, z_scaling_factor])
|
116
|
+
mesh = MeshComponent.rotate_mesh(mesh)
|
118
117
|
|
119
118
|
if apply_decimation:
|
120
119
|
percent = decimation_percent / 100
|
@@ -122,4 +121,122 @@ class MeshComponent(Component):
|
|
122
121
|
percent=percent, aggression=decimation_agression
|
123
122
|
)
|
124
123
|
|
124
|
+
try:
|
125
|
+
if not mesh.is_watertight:
|
126
|
+
mesh = MeshComponent.fix_mesh(mesh)
|
127
|
+
except Exception:
|
128
|
+
pass
|
129
|
+
|
130
|
+
mesh = MeshComponent.mesh_to_output_size(
|
131
|
+
mesh,
|
132
|
+
resize_factor,
|
133
|
+
z_scaling_factor,
|
134
|
+
output_x_size,
|
135
|
+
skip_resize_to_expected_size=not include_zeros,
|
136
|
+
)
|
137
|
+
|
138
|
+
if remove_center:
|
139
|
+
mesh = MeshComponent.remove_center_from_mesh(mesh, remove_size)
|
140
|
+
|
125
141
|
return mesh
|
142
|
+
|
143
|
+
@staticmethod
|
144
|
+
def rotate_mesh(mesh: trimesh.Trimesh) -> trimesh.Trimesh:
|
145
|
+
"""Rotates the given mesh by 180 degrees around the Y-axis and Z-axis.
|
146
|
+
|
147
|
+
Arguments:
|
148
|
+
mesh (trimesh.Trimesh): The mesh to rotate.
|
149
|
+
|
150
|
+
Returns:
|
151
|
+
trimesh.Trimesh: The rotated mesh.
|
152
|
+
"""
|
153
|
+
mesh_copy = mesh.copy()
|
154
|
+
|
155
|
+
rotation_matrices = [
|
156
|
+
trimesh.transformations.rotation_matrix(np.pi, [0, 1, 0]),
|
157
|
+
trimesh.transformations.rotation_matrix(np.pi, [0, 0, 1]),
|
158
|
+
]
|
159
|
+
|
160
|
+
for rotation_matrix in tqdm(rotation_matrices, desc="Rotating mesh", unit="rotation"):
|
161
|
+
mesh_copy.apply_transform(rotation_matrix)
|
162
|
+
|
163
|
+
return mesh_copy
|
164
|
+
|
165
|
+
@staticmethod
|
166
|
+
def fix_mesh(mesh: trimesh.Trimesh) -> trimesh.Trimesh:
|
167
|
+
"""Fixes the given mesh by filling holes, fixing normals, fixing winding, fixing inversion,
|
168
|
+
fixing broken faces, and stitching.
|
169
|
+
|
170
|
+
Arguments:
|
171
|
+
mesh (trimesh.Trimesh): The mesh to fix.
|
172
|
+
|
173
|
+
Returns:
|
174
|
+
trimesh.Trimesh: The fixed mesh.
|
175
|
+
"""
|
176
|
+
mesh_copy = mesh.copy()
|
177
|
+
|
178
|
+
fix_methods = [
|
179
|
+
trimesh.repair.fill_holes,
|
180
|
+
trimesh.repair.fix_normals,
|
181
|
+
trimesh.repair.fix_winding,
|
182
|
+
trimesh.repair.fix_inversion,
|
183
|
+
trimesh.repair.broken_faces,
|
184
|
+
trimesh.repair.stitch,
|
185
|
+
]
|
186
|
+
|
187
|
+
for method in tqdm(fix_methods, desc="Fixing mesh", unit="method"):
|
188
|
+
method(mesh_copy) # type: ignore
|
189
|
+
|
190
|
+
return mesh_copy
|
191
|
+
|
192
|
+
@staticmethod
|
193
|
+
def mesh_to_output_size(
|
194
|
+
mesh: trimesh.Trimesh,
|
195
|
+
resize_factor: int,
|
196
|
+
z_scaling_factor: float,
|
197
|
+
expected_size: int,
|
198
|
+
skip_resize_to_expected_size: bool = False,
|
199
|
+
) -> trimesh.Trimesh:
|
200
|
+
"""Resizes the given mesh to the expected size.
|
201
|
+
|
202
|
+
Arguments:
|
203
|
+
mesh (trimesh.Trimesh): The mesh to resize.
|
204
|
+
resize_factor (int): The resizing factor.
|
205
|
+
z_scaling_factor (float): The scaling factor for the Z-axis.
|
206
|
+
expected_size (int): The expected size.
|
207
|
+
skip_resize_to_expected_size (bool): Whether to skip resizing to the expected size.
|
208
|
+
|
209
|
+
Returns:
|
210
|
+
trimesh.Trimesh: The resized mesh.
|
211
|
+
"""
|
212
|
+
mesh_copy = mesh.copy()
|
213
|
+
|
214
|
+
mesh_copy.apply_scale([resize_factor / 1, resize_factor / 1, z_scaling_factor])
|
215
|
+
|
216
|
+
if not skip_resize_to_expected_size:
|
217
|
+
x_size, y_size, _ = mesh_copy.extents
|
218
|
+
x_resize_factor = expected_size / x_size
|
219
|
+
y_resize_factor = expected_size / y_size
|
220
|
+
|
221
|
+
mesh_copy.apply_scale([x_resize_factor, y_resize_factor, 1])
|
222
|
+
return mesh_copy
|
223
|
+
|
224
|
+
@staticmethod
|
225
|
+
def remove_center_from_mesh(mesh: trimesh.Trimesh, remove_size: int) -> trimesh.Trimesh:
|
226
|
+
"""Removes the center from the given mesh.
|
227
|
+
|
228
|
+
Arguments:
|
229
|
+
mesh (trimesh.Trimesh): The mesh to remove the center from.
|
230
|
+
remove_size (int): The size of the center to remove.
|
231
|
+
|
232
|
+
Returns:
|
233
|
+
trimesh.Trimesh: The mesh with the center removed.
|
234
|
+
"""
|
235
|
+
mesh_copy = mesh.copy()
|
236
|
+
|
237
|
+
_, _, z_size = mesh_copy.extents
|
238
|
+
|
239
|
+
cube_mesh = trimesh.creation.box([remove_size, remove_size, z_size * 4])
|
240
|
+
cube_mesh.apply_translation(mesh_copy.centroid - cube_mesh.centroid)
|
241
|
+
|
242
|
+
return trimesh.boolean.difference([mesh_copy, cube_mesh], check_volume=False)
|
maps4fs/generator/dem.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
"""This module contains DEM class for processing Digital Elevation Model data."""
|
2
2
|
|
3
|
-
import
|
3
|
+
import math
|
4
4
|
from typing import Any
|
5
5
|
|
6
6
|
import cv2
|
@@ -31,10 +31,6 @@ class DEM(Component):
|
|
31
31
|
def preprocess(self) -> None:
|
32
32
|
self._dem_path = self.game.dem_file_path(self.map_directory)
|
33
33
|
self.temp_dir = "temp"
|
34
|
-
self.hgt_dir = os.path.join(self.temp_dir, "hgt")
|
35
|
-
self.gz_dir = os.path.join(self.temp_dir, "gz")
|
36
|
-
os.makedirs(self.hgt_dir, exist_ok=True)
|
37
|
-
os.makedirs(self.gz_dir, exist_ok=True)
|
38
34
|
|
39
35
|
self.logger.debug("Map size: %s x %s.", self.map_size, self.map_size)
|
40
36
|
self.logger.debug(
|
@@ -116,24 +112,8 @@ class DEM(Component):
|
|
116
112
|
)
|
117
113
|
return dem_size, dem_size
|
118
114
|
|
119
|
-
def to_ground(self, data: np.ndarray) -> np.ndarray:
|
120
|
-
"""Receives the signed 16-bit integer array and converts it to the ground level.
|
121
|
-
If the min value is negative, it will become zero value and the rest of the values
|
122
|
-
will be shifted accordingly.
|
123
|
-
"""
|
124
|
-
# For examlem, min value was -50, it will become 0 and for all values we'll +50.
|
125
|
-
|
126
|
-
if data.min() < 0:
|
127
|
-
self.logger.debug("Array contains negative values, will be shifted to the ground.")
|
128
|
-
data = data + abs(data.min())
|
129
|
-
|
130
|
-
self.logger.debug(
|
131
|
-
"Array was shifted to the ground. Min: %s, max: %s.", data.min(), data.max()
|
132
|
-
)
|
133
|
-
return data
|
134
|
-
|
135
115
|
def process(self) -> None:
|
136
|
-
"""Reads
|
116
|
+
"""Reads DTM file, crops it to map size, normalizes and blurs it,
|
137
117
|
saves to map directory."""
|
138
118
|
|
139
119
|
dem_output_resolution = self.output_resolution
|
@@ -142,7 +122,7 @@ class DEM(Component):
|
|
142
122
|
try:
|
143
123
|
data = self.dtm_provider.get_numpy()
|
144
124
|
except Exception as e: # pylint: disable=W0718
|
145
|
-
self.logger.error("Failed to get DEM data from
|
125
|
+
self.logger.error("Failed to get DEM data from DTM provider: %s.", e)
|
146
126
|
self._save_empty_dem(dem_output_resolution)
|
147
127
|
return
|
148
128
|
|
@@ -151,7 +131,7 @@ class DEM(Component):
|
|
151
131
|
self._save_empty_dem(dem_output_resolution)
|
152
132
|
return
|
153
133
|
|
154
|
-
if data.dtype not in ["int16", "uint16"]:
|
134
|
+
if data.dtype not in ["int16", "uint16", "float", "float32"]:
|
155
135
|
self.logger.error("DTM provider returned incorrect data type: %s.", data.dtype)
|
156
136
|
self._save_empty_dem(dem_output_resolution)
|
157
137
|
return
|
@@ -164,93 +144,158 @@ class DEM(Component):
|
|
164
144
|
data.max(),
|
165
145
|
)
|
166
146
|
|
167
|
-
data
|
147
|
+
# 1. Resize DEM data to the output resolution.
|
148
|
+
resampled_data = self.resize_to_output(data)
|
168
149
|
|
169
|
-
|
170
|
-
|
171
|
-
).astype("uint16")
|
150
|
+
# 2. Apply multiplier (-10 to 120.4 becomes -20 to 240.8)
|
151
|
+
resampled_data = self.apply_multiplier(resampled_data)
|
172
152
|
|
173
|
-
|
174
|
-
|
153
|
+
# 3. Raise terrain, and optionally lower to plateau level+water depth
|
154
|
+
# e.g. -20 to 240.8m becomes 20 to 280.8m
|
155
|
+
resampled_data = self.raise_or_lower(resampled_data)
|
156
|
+
|
157
|
+
# 4. Determine actual height scale value using ceiling
|
158
|
+
# e.g. 255 becomes 280.8+10 = 291
|
159
|
+
height_scale_value = self.determine_height_scale(resampled_data)
|
160
|
+
|
161
|
+
# 5. Normalize DEM data to 16-bit unsigned integer range (0 to 65535)
|
162
|
+
# e.g. multiply by 65535/291, clip and return as uint16
|
163
|
+
resampled_data = self.normalize_data(resampled_data, height_scale_value)
|
164
|
+
|
165
|
+
# 6. Blur DEM data.
|
166
|
+
resampled_data = self.apply_blur(resampled_data)
|
167
|
+
|
168
|
+
cv2.imwrite(self._dem_path, resampled_data)
|
169
|
+
self.logger.debug("DEM data was saved to %s.", self._dem_path)
|
170
|
+
|
171
|
+
if self.rotation:
|
172
|
+
self.rotate_dem()
|
175
173
|
|
174
|
+
def normalize_data(self, data: np.ndarray, height_scale_value: int) -> np.ndarray:
|
175
|
+
"""Normalize DEM data to 16-bit unsigned integer range (0 to 65535).
|
176
|
+
|
177
|
+
Arguments:
|
178
|
+
data (np.ndarray): DEM data.
|
179
|
+
height_scale_value (int): Height scale value.
|
180
|
+
|
181
|
+
Returns:
|
182
|
+
np.ndarray: Normalized DEM data.
|
183
|
+
"""
|
184
|
+
normalized_data = np.clip((data / height_scale_value) * 65535, 0, 65535).astype(np.uint16)
|
176
185
|
self.logger.debug(
|
177
|
-
"
|
178
|
-
|
179
|
-
|
180
|
-
|
186
|
+
"DEM data was normalized and clipped to 16-bit unsigned integer range. "
|
187
|
+
"Min: %s, max: %s.",
|
188
|
+
normalized_data.min(),
|
189
|
+
normalized_data.max(),
|
181
190
|
)
|
191
|
+
return normalized_data
|
192
|
+
|
193
|
+
def determine_height_scale(self, data: np.ndarray) -> int:
|
194
|
+
"""Determine height scale value using ceiling.
|
195
|
+
|
196
|
+
Arguments:
|
197
|
+
data (np.ndarray): DEM data.
|
198
|
+
|
199
|
+
Returns:
|
200
|
+
int: Height scale value.
|
201
|
+
"""
|
202
|
+
height_scale = self.map.dem_settings.minimum_height_scale
|
203
|
+
adjusted_height_scale = math.ceil(
|
204
|
+
max(height_scale, data.max() + self.map.dem_settings.ceiling)
|
205
|
+
)
|
206
|
+
|
207
|
+
self.map.shared_settings.height_scale_value = adjusted_height_scale # type: ignore
|
208
|
+
self.map.shared_settings.mesh_z_scaling_factor = 65535 / adjusted_height_scale
|
209
|
+
self.map.shared_settings.height_scale_multiplier = adjusted_height_scale / 255
|
210
|
+
self.map.shared_settings.change_height_scale = True # type: ignore
|
211
|
+
|
212
|
+
self.logger.debug("Height scale value is %s.", adjusted_height_scale)
|
213
|
+
return adjusted_height_scale
|
214
|
+
|
215
|
+
def raise_or_lower(self, data: np.ndarray) -> np.ndarray:
|
216
|
+
"""Raise or lower terrain to the level of plateau+water depth."""
|
217
|
+
|
218
|
+
if not self.map.dem_settings.adjust_terrain_to_ground_level:
|
219
|
+
return data
|
220
|
+
|
221
|
+
desired_ground_level = self.map.dem_settings.plateau + self.map.dem_settings.water_depth
|
222
|
+
current_ground_level = data.min()
|
182
223
|
|
183
|
-
|
184
|
-
resampled_data = resampled_data * self.multiplier
|
185
|
-
|
186
|
-
self.logger.debug(
|
187
|
-
"DEM data was multiplied by %s. Min: %s, max: %s. Data type: %s.",
|
188
|
-
self.multiplier,
|
189
|
-
resampled_data.min(),
|
190
|
-
resampled_data.max(),
|
191
|
-
resampled_data.dtype,
|
192
|
-
)
|
193
|
-
|
194
|
-
size_of_resampled_data = asizeof.asizeof(resampled_data) / 1024 / 1024
|
195
|
-
self.logger.debug("Size of resampled data: %s MB.", size_of_resampled_data)
|
196
|
-
|
197
|
-
# Clip values to 16-bit unsigned integer range.
|
198
|
-
resampled_data = np.clip(resampled_data, 0, 65535)
|
199
|
-
resampled_data = resampled_data.astype("uint16")
|
200
|
-
self.logger.debug(
|
201
|
-
"DEM data was multiplied by %s and clipped to 16-bit unsigned integer range. "
|
202
|
-
"Min: %s, max: %s.",
|
203
|
-
self.multiplier,
|
204
|
-
resampled_data.min(),
|
205
|
-
resampled_data.max(),
|
206
|
-
)
|
224
|
+
data = data + (desired_ground_level - current_ground_level)
|
207
225
|
|
208
226
|
self.logger.debug(
|
209
|
-
"
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
resampled_data.max(),
|
227
|
+
"Array was shifted to the ground level %s. Min: %s, max: %s.",
|
228
|
+
desired_ground_level,
|
229
|
+
data.min(),
|
230
|
+
data.max(),
|
214
231
|
)
|
232
|
+
return data
|
215
233
|
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
"Gaussion blur applied to DEM data with kernel size %s.",
|
222
|
-
self.blur_radius,
|
223
|
-
)
|
234
|
+
def apply_multiplier(self, data: np.ndarray) -> np.ndarray:
|
235
|
+
"""Apply multiplier to DEM data.
|
236
|
+
|
237
|
+
Arguments:
|
238
|
+
data (np.ndarray): DEM data.
|
224
239
|
|
240
|
+
Returns:
|
241
|
+
np.ndarray: Multiplied DEM data.
|
242
|
+
"""
|
243
|
+
if not self.multiplier != 1:
|
244
|
+
return data
|
245
|
+
|
246
|
+
multiplied_data = data * self.multiplier
|
225
247
|
self.logger.debug(
|
226
|
-
"DEM data was
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
resampled_data.max(),
|
248
|
+
"DEM data was multiplied by %s. Min: %s, max: %s.",
|
249
|
+
self.multiplier,
|
250
|
+
multiplied_data.min(),
|
251
|
+
multiplied_data.max(),
|
231
252
|
)
|
253
|
+
return multiplied_data
|
232
254
|
|
233
|
-
|
234
|
-
|
235
|
-
# So we just add this value to each pixel of the DEM.
|
236
|
-
# And also need to ensure that there will be no values with height greater than
|
237
|
-
# it's allowed in 16-bit unsigned integer.
|
255
|
+
def resize_to_output(self, data: np.ndarray) -> np.ndarray:
|
256
|
+
"""Resize DEM data to the output resolution.
|
238
257
|
|
239
|
-
|
240
|
-
|
258
|
+
Arguments:
|
259
|
+
data (np.ndarray): DEM data.
|
241
260
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
resampled_data.max(),
|
247
|
-
)
|
261
|
+
Returns:
|
262
|
+
np.ndarray: Resized DEM data.
|
263
|
+
"""
|
264
|
+
resampled_data = cv2.resize(data, self.output_resolution, interpolation=cv2.INTER_LINEAR)
|
248
265
|
|
249
|
-
|
250
|
-
self.logger.debug("
|
266
|
+
size_of_resampled_data = asizeof.asizeof(resampled_data) / 1024 / 1024
|
267
|
+
self.logger.debug("Size of resampled data: %s MB.", size_of_resampled_data)
|
251
268
|
|
252
|
-
|
253
|
-
|
269
|
+
return resampled_data
|
270
|
+
|
271
|
+
def apply_blur(self, data: np.ndarray) -> np.ndarray:
|
272
|
+
"""Apply blur to DEM data.
|
273
|
+
|
274
|
+
Arguments:
|
275
|
+
data (np.ndarray): DEM data.
|
276
|
+
|
277
|
+
Returns:
|
278
|
+
np.ndarray: Blurred DEM data.
|
279
|
+
"""
|
280
|
+
if self.blur_radius == 0:
|
281
|
+
return data
|
282
|
+
|
283
|
+
self.logger.debug(
|
284
|
+
"Applying Gaussion blur to DEM data with kernel size %s.",
|
285
|
+
self.blur_radius,
|
286
|
+
)
|
287
|
+
|
288
|
+
blurred_data = cv2.GaussianBlur(
|
289
|
+
data, (self.blur_radius, self.blur_radius), sigmaX=10, sigmaY=10
|
290
|
+
)
|
291
|
+
self.logger.debug(
|
292
|
+
"DEM data was blurred. Shape: %s, dtype: %s. Min: %s, max: %s.",
|
293
|
+
blurred_data.shape,
|
294
|
+
blurred_data.dtype,
|
295
|
+
blurred_data.min(),
|
296
|
+
blurred_data.max(),
|
297
|
+
)
|
298
|
+
return blurred_data
|
254
299
|
|
255
300
|
def rotate_dem(self) -> None:
|
256
301
|
"""Rotate DEM image."""
|
maps4fs/generator/dtm/dtm.py
CHANGED
@@ -29,9 +29,6 @@ if TYPE_CHECKING:
|
|
29
29
|
class DTMProviderSettings(BaseModel):
|
30
30
|
"""Base class for DTM provider settings models."""
|
31
31
|
|
32
|
-
easy_mode: bool = True
|
33
|
-
power_factor: int = 0
|
34
|
-
|
35
32
|
|
36
33
|
# pylint: disable=too-many-public-methods, too-many-instance-attributes
|
37
34
|
class DTMProvider(ABC):
|
@@ -56,20 +53,7 @@ class DTMProvider(ABC):
|
|
56
53
|
|
57
54
|
_instructions: str | None = None
|
58
55
|
|
59
|
-
_base_instructions =
|
60
|
-
"ℹ️ Using **Easy mode** is recommended, as it automatically adjusts the values in the "
|
61
|
-
"image, so the terrain elevation in Giants Editor will match real world "
|
62
|
-
"elevation in meters. \n"
|
63
|
-
"ℹ️ If the terrain height difference in the real world is bigger than 255 meters, "
|
64
|
-
"the [Height scale](https://github.com/iwatkot/maps4fs/blob/main/docs/dem.md#height-scale)"
|
65
|
-
" parameter in the **map.i3d** file will be automatically adjusted. \n"
|
66
|
-
"⚡ If the **Easy mode** option is disabled, you will probably get completely flat "
|
67
|
-
"terrain, unless you adjust the DEM Multiplier Setting or the Height scale parameter in "
|
68
|
-
"the Giants Editor. \n"
|
69
|
-
"💡 You can use the **Power factor** setting to make the difference between heights "
|
70
|
-
"bigger. Be extremely careful with this setting, and use only low values, otherwise your "
|
71
|
-
"terrain may be completely broken. \n"
|
72
|
-
)
|
56
|
+
_base_instructions = None
|
73
57
|
|
74
58
|
# pylint: disable=R0913, R0917
|
75
59
|
def __init__(
|
@@ -304,64 +288,8 @@ class DTMProvider(ABC):
|
|
304
288
|
# extract region of interest from the tile
|
305
289
|
data = self.extract_roi(tile)
|
306
290
|
|
307
|
-
# process elevation data to be compatible with the game
|
308
|
-
data = self.process_elevation(data)
|
309
|
-
|
310
291
|
return data
|
311
292
|
|
312
|
-
def process_elevation(self, data: np.ndarray) -> np.ndarray:
|
313
|
-
"""Process elevation data.
|
314
|
-
|
315
|
-
Arguments:
|
316
|
-
data (np.ndarray): Elevation data.
|
317
|
-
|
318
|
-
Returns:
|
319
|
-
np.ndarray: Processed elevation data.
|
320
|
-
"""
|
321
|
-
self.data_info = {}
|
322
|
-
self.add_numpy_params(data, "original")
|
323
|
-
|
324
|
-
data = self.ground_height_data(data)
|
325
|
-
self.add_numpy_params(data, "grounded")
|
326
|
-
|
327
|
-
original_deviation = int(self.data_info["original_deviation"])
|
328
|
-
in_game_maximum_height = 65535 // 255
|
329
|
-
if original_deviation > in_game_maximum_height:
|
330
|
-
suggested_height_scale_multiplier = (
|
331
|
-
original_deviation / in_game_maximum_height # type: ignore
|
332
|
-
)
|
333
|
-
suggested_height_scale_value = int(255 * suggested_height_scale_multiplier)
|
334
|
-
else:
|
335
|
-
suggested_height_scale_multiplier = 1
|
336
|
-
suggested_height_scale_value = 255
|
337
|
-
|
338
|
-
self.data_info["suggested_height_scale_multiplier"] = suggested_height_scale_multiplier
|
339
|
-
self.data_info["suggested_height_scale_value"] = suggested_height_scale_value
|
340
|
-
|
341
|
-
self.map.shared_settings.height_scale_multiplier = ( # type: ignore
|
342
|
-
suggested_height_scale_multiplier
|
343
|
-
)
|
344
|
-
self.map.shared_settings.height_scale_value = suggested_height_scale_value # type: ignore
|
345
|
-
|
346
|
-
if self.user_settings.easy_mode: # type: ignore
|
347
|
-
try:
|
348
|
-
data = self.normalize_dem(data)
|
349
|
-
self.add_numpy_params(data, "normalized")
|
350
|
-
|
351
|
-
normalized_deviation = self.data_info["normalized_deviation"]
|
352
|
-
z_scaling_factor = normalized_deviation / original_deviation # type: ignore
|
353
|
-
self.data_info["z_scaling_factor"] = z_scaling_factor
|
354
|
-
|
355
|
-
self.map.shared_settings.mesh_z_scaling_factor = z_scaling_factor # type: ignore
|
356
|
-
self.map.shared_settings.change_height_scale = True # type: ignore
|
357
|
-
|
358
|
-
except Exception as e: # pylint: disable=W0718
|
359
|
-
self.logger.error(
|
360
|
-
"Failed to normalize DEM data. Error: %s. Using original data.", e
|
361
|
-
)
|
362
|
-
|
363
|
-
return data.astype(np.uint16)
|
364
|
-
|
365
293
|
def info_sequence(self) -> dict[str, int | str | float] | None:
|
366
294
|
"""Returns the information sequence for the component. Must be implemented in the child
|
367
295
|
class. If the component does not have an information sequence, an empty dictionary must be
|
@@ -414,6 +342,7 @@ class DTMProvider(ABC):
|
|
414
342
|
desc="Downloading tiles",
|
415
343
|
unit="tile",
|
416
344
|
initial=len(tif_files),
|
345
|
+
total=len(urls),
|
417
346
|
):
|
418
347
|
try:
|
419
348
|
file_name = os.path.basename(url)
|
@@ -570,84 +499,4 @@ class DTMProvider(ABC):
|
|
570
499
|
|
571
500
|
return data
|
572
501
|
|
573
|
-
def normalize_dem(self, data: np.ndarray) -> np.ndarray:
|
574
|
-
"""Normalize DEM data to 16-bit unsigned integer using max height from settings.
|
575
|
-
|
576
|
-
Arguments:
|
577
|
-
data (np.ndarray): DEM data from SRTM file after cropping.
|
578
|
-
|
579
|
-
Returns:
|
580
|
-
np.ndarray: Normalized DEM data.
|
581
|
-
"""
|
582
|
-
maximum_height = int(data.max())
|
583
|
-
minimum_height = int(data.min())
|
584
|
-
deviation = maximum_height - minimum_height
|
585
|
-
self.logger.debug(
|
586
|
-
"Maximum height: %s. Minimum height: %s. Deviation: %s.",
|
587
|
-
maximum_height,
|
588
|
-
minimum_height,
|
589
|
-
deviation,
|
590
|
-
)
|
591
|
-
self.logger.debug("Number of unique values in original DEM data: %s.", np.unique(data).size)
|
592
|
-
|
593
|
-
adjusted_maximum_height = maximum_height * 255
|
594
|
-
adjusted_maximum_height = min(adjusted_maximum_height, 65535)
|
595
|
-
scaling_factor = adjusted_maximum_height / maximum_height
|
596
|
-
self.logger.debug(
|
597
|
-
"Adjusted maximum height: %s. Scaling factor: %s.",
|
598
|
-
adjusted_maximum_height,
|
599
|
-
scaling_factor,
|
600
|
-
)
|
601
|
-
|
602
|
-
if self.user_settings.power_factor: # type: ignore
|
603
|
-
power_factor = 1 + self.user_settings.power_factor / 10 # type: ignore
|
604
|
-
self.logger.debug(
|
605
|
-
"Applying power factor: %s to the DEM data.",
|
606
|
-
power_factor,
|
607
|
-
)
|
608
|
-
data = np.power(data, power_factor)
|
609
|
-
|
610
|
-
normalized_data = np.round(data * scaling_factor).astype(np.uint16)
|
611
|
-
self.logger.debug(
|
612
|
-
"Normalized data maximum height: %s. Minimum height: %s. Number of unique values: %s.",
|
613
|
-
normalized_data.max(),
|
614
|
-
normalized_data.min(),
|
615
|
-
np.unique(normalized_data).size,
|
616
|
-
)
|
617
|
-
return normalized_data
|
618
|
-
|
619
|
-
@staticmethod
|
620
|
-
def ground_height_data(data: np.ndarray, add_one: bool = True) -> np.ndarray:
|
621
|
-
"""Shift the data to ground level (0 meter).
|
622
|
-
Optionally add one meter to the data to leave some room
|
623
|
-
for the water level and pit modifications.
|
624
|
-
|
625
|
-
Arguments:
|
626
|
-
data (np.ndarray): DEM data after cropping.
|
627
|
-
add_one (bool): Add one meter to the data
|
628
|
-
|
629
|
-
Returns:
|
630
|
-
np.ndarray: Unsigned DEM data.
|
631
|
-
"""
|
632
|
-
data = data - data.min()
|
633
|
-
if add_one:
|
634
|
-
data = data + 1
|
635
|
-
return data
|
636
|
-
|
637
|
-
def add_numpy_params(
|
638
|
-
self,
|
639
|
-
data: np.ndarray,
|
640
|
-
prefix: str,
|
641
|
-
) -> None:
|
642
|
-
"""Add numpy array parameters to the data_info dictionary.
|
643
|
-
|
644
|
-
Arguments:
|
645
|
-
data (np.ndarray): Numpy array of the tile.
|
646
|
-
prefix (str): Prefix for the parameters.
|
647
|
-
"""
|
648
|
-
self.data_info[f"{prefix}_minimum_height"] = int(data.min()) # type: ignore
|
649
|
-
self.data_info[f"{prefix}_maximum_height"] = int(data.max()) # type: ignore
|
650
|
-
self.data_info[f"{prefix}_deviation"] = int(data.max() - data.min()) # type: ignore
|
651
|
-
self.data_info[f"{prefix}_unique_values"] = int(np.unique(data).size) # type: ignore
|
652
|
-
|
653
502
|
# endregion
|
maps4fs/generator/dtm/srtm.py
CHANGED
@@ -31,6 +31,8 @@ class SRTM30Provider(DTMProvider):
|
|
31
31
|
|
32
32
|
_settings = SRTM30ProviderSettings
|
33
33
|
|
34
|
+
_instructions = "ℹ️ Recommended settings: \nDEM Settings -> Blur Radius: **35**"
|
35
|
+
|
34
36
|
def __init__(self, *args, **kwargs):
|
35
37
|
super().__init__(*args, **kwargs)
|
36
38
|
self.hgt_directory = os.path.join(self._tile_directory, "hgt")
|
maps4fs/generator/settings.py
CHANGED
@@ -102,18 +102,22 @@ class DEMSettings(SettingsModel):
|
|
102
102
|
"""Represents the advanced settings for DEM component.
|
103
103
|
|
104
104
|
Attributes:
|
105
|
-
|
106
|
-
|
105
|
+
adjust_terrain_to_ground_level (bool): adjust terrain to ground level or not.
|
106
|
+
multiplier (int): multiplier for the heightmap.
|
107
107
|
blur_radius (int): radius of the blur filter.
|
108
|
-
|
109
|
-
|
110
|
-
|
108
|
+
minimum_height_scale (int): minimum height scale for the i3d.
|
109
|
+
plateau (int): plateau height.
|
110
|
+
ceiling (int): ceiling height.
|
111
|
+
water_depth (int): water depth.
|
111
112
|
"""
|
112
113
|
|
114
|
+
adjust_terrain_to_ground_level: bool = True
|
113
115
|
multiplier: int = 1
|
114
|
-
|
116
|
+
minimum_height_scale: int = 255
|
115
117
|
plateau: int = 0
|
118
|
+
ceiling: int = 0
|
116
119
|
water_depth: int = 0
|
120
|
+
blur_radius: int = 3
|
117
121
|
|
118
122
|
|
119
123
|
class BackgroundSettings(SettingsModel):
|
@@ -129,7 +133,7 @@ class BackgroundSettings(SettingsModel):
|
|
129
133
|
generate_background: bool = False
|
130
134
|
generate_water: bool = False
|
131
135
|
resize_factor: int = 8
|
132
|
-
remove_center: bool =
|
136
|
+
remove_center: bool = True
|
133
137
|
apply_decimation: bool = False
|
134
138
|
decimation_percent: int = 25
|
135
139
|
decimation_agression: int = 3
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: maps4fs
|
3
|
-
Version: 1.8.
|
3
|
+
Version: 1.8.173
|
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
|
@@ -562,13 +562,17 @@ You can also apply some advanced settings to the map generation process.<br>
|
|
562
562
|
|
563
563
|
### DEM Advanced settings
|
564
564
|
|
565
|
-
-
|
565
|
+
- Adjust terrain to ground level: Enabling this setting (default) will raise or lower the terrain so that it's lowest point is at ground level (taking into account the plateau and water depth settings).
|
566
566
|
|
567
|
-
-
|
567
|
+
- Multiplier: DEM multiplier can be used to make the terrain more pronounced. By default the DEM file will be exact copy of the real terrain. If you want to make it more steep, you can increase this value. The recommended value of the multiplier is 1.
|
568
568
|
|
569
|
-
-
|
569
|
+
- Minimum height scale: This value is used as the heightScale in your map i3d. It will automatically be set higher if the elevation in your map (plus plateau, ceiling and water depth) is higher than this value.
|
570
570
|
|
571
|
-
-
|
571
|
+
- Plateau: DEM plateau value (in meters) is used to make the whole map higher or lower. This value will be added to each pixel of the DEM image, making it higher. It can be useful if you're working on a plain area and need to add some negative height (to make rivers, for example).
|
572
|
+
|
573
|
+
- Ceiling: DEM ceiling value (in meters) is used to add padding in the DEM above the highest elevation in your map area. It can be useful if you plan to manually add some height to the map by sculpting the terrain in GE.
|
574
|
+
|
575
|
+
- Water depth: Water depth value (in meters) will be subtracted from the DEM image, making the water deeper. The pixel value used for this is calculated based on the heightScale value for your map.
|
572
576
|
|
573
577
|
### Background terrain Advanced settings
|
574
578
|
|
@@ -578,7 +582,7 @@ You can also apply some advanced settings to the map generation process.<br>
|
|
578
582
|
|
579
583
|
- Resize factor - the factor by which the background terrain will be resized. It will be used as 1 / resize_factor while generating the models. Which means that the larger the value the more the terrain will be resized. The lowest value is 1, in this case background terrain will not be resized. Note, than low values will lead to long processing and enormous size of the obj files.
|
580
584
|
|
581
|
-
- Remove center - if enabled, the playable region (map terrain) will be removed from the background terrain.
|
585
|
+
- Remove center - if enabled, the playable region (map terrain) will be removed from the background terrain. By default, it's set to True.
|
582
586
|
|
583
587
|
- Apply decimation - if enabled, the mesh will be simplified to reduce the number of faces.
|
584
588
|
|
@@ -703,4 +707,3 @@ But also, I want to thank the people who helped me with the project in some way,
|
|
703
707
|
- [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.
|
704
708
|
- [Maaslandmods](https://github.com/Maaslandmods) - for the awesome idea to edit the tree schema in UI, images and code snippets on how to do it.
|
705
709
|
- [StrauntMaunt](https://gitlab.com/StrauntMaunt) - for developing procedural generation scripts, providing with the required updates for maps4fs and preparing the docs on how to use procedural generation.
|
706
|
-
|
@@ -1,27 +1,27 @@
|
|
1
1
|
maps4fs/__init__.py,sha256=iq4GcHoQ-by-BK6CzDOfVcdDww6osyi9Wv6pKXub68U,1307
|
2
2
|
maps4fs/logger.py,sha256=B-NEYpMjPAAqlV4VpfTi6nbBFnEABVtQOaYe6nMpidg,1489
|
3
3
|
maps4fs/generator/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
|
4
|
-
maps4fs/generator/dem.py,sha256=
|
4
|
+
maps4fs/generator/dem.py,sha256=oLN02bWNax73HzFsseRBOV47Azl_1L7qdrzuxHh4i_c,11886
|
5
5
|
maps4fs/generator/game.py,sha256=YwtdzqwcBJm1FHtc1ehDd3WbfTTW_gBvxK-pzfU48zs,11015
|
6
6
|
maps4fs/generator/map.py,sha256=c5GMhr5iTRdXvTXoBKyVYa2V1tocR3ZCc7D_hpU21k8,11523
|
7
7
|
maps4fs/generator/qgis.py,sha256=Es8hLuqN_KH8lDfnJE6He2rWYbAKJ3RGPn-o87S6CPI,6116
|
8
8
|
maps4fs/generator/satellite.py,sha256=t33EfaxWTCjcKYnqoppSlFdQQSekiBe7UFmLgTVhFQI,3650
|
9
|
-
maps4fs/generator/settings.py,sha256=
|
9
|
+
maps4fs/generator/settings.py,sha256=cFlN-gK8QcySqyPtcGm-2fLnxQnlmC3Y9kQufJxwI3Y,6270
|
10
10
|
maps4fs/generator/texture.py,sha256=_IfqIuxH4934VJXKtdABHa6ToPWk3T0xknvlu-rZ5Uc,36570
|
11
11
|
maps4fs/generator/component/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
|
12
|
-
maps4fs/generator/component/background.py,sha256=
|
12
|
+
maps4fs/generator/component/background.py,sha256=cBBp-ULrJzuNG9wfE_MaxzAn_mR24neqtL7WjxXEb3I,18749
|
13
13
|
maps4fs/generator/component/config.py,sha256=RitKgFDZPzjA1fi8GcEi1na75qqaueUvpcITHjBvCXc,3674
|
14
14
|
maps4fs/generator/component/grle.py,sha256=aKMjVJNuKHJJ2gsXaH00bz10kWaIbbZXU_JbP-ZAGw4,18990
|
15
15
|
maps4fs/generator/component/i3d.py,sha256=3x38yL-kSJ8ylBwICBb6wPYzRSky4gVj8XCk2jzYSeo,19861
|
16
16
|
maps4fs/generator/component/base/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
|
17
17
|
maps4fs/generator/component/base/component.py,sha256=U3XJae0GUvHN08psv2j24Y4PBAAytSkSK3AmD-VjjXE,21404
|
18
18
|
maps4fs/generator/component/base/component_image.py,sha256=vuiiJqsrKZgYsnK8Qa5yL2Pejqa6nJSTtXNAcBPL29c,3099
|
19
|
-
maps4fs/generator/component/base/component_mesh.py,sha256
|
19
|
+
maps4fs/generator/component/base/component_mesh.py,sha256=jtsEQZkqiCkycYpueq1M1eqgxdShL_bHaJT6Upu8PLI,8412
|
20
20
|
maps4fs/generator/component/base/component_xml.py,sha256=6OO1dKoceO1ACk7-k1oGtnkfNud8ZN3u3ZNjdNMpTqw,3967
|
21
21
|
maps4fs/generator/dtm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
22
|
maps4fs/generator/dtm/bavaria.py,sha256=7njrEvSCYAC8ZVyvS-_84iXHhWA0oHKrEqSzxdnZuGs,4293
|
23
23
|
maps4fs/generator/dtm/canada.py,sha256=lYONwm6aNX5cjVggR3AiZZF9dlCDAWg0M8RMaObog8s,1288
|
24
|
-
maps4fs/generator/dtm/dtm.py,sha256=
|
24
|
+
maps4fs/generator/dtm/dtm.py,sha256=L824mwmd_cNTujwWBsRg7RZLq9KEXf0-xIzKx8M3y1Q,16872
|
25
25
|
maps4fs/generator/dtm/england.py,sha256=YyCYwnNUJuBeeMNUozfKIj_yNjHpGeuH1Mz0NiAJL-U,1122
|
26
26
|
maps4fs/generator/dtm/finland.py,sha256=Chi3-3sanLIYpipjtPpTu9tqnL3DYcnygSDCPm1s24c,1753
|
27
27
|
maps4fs/generator/dtm/flanders.py,sha256=81pKkrM40SeOe1LSlcsTNXSmUNpofC4D454DG6WFSyA,1037
|
@@ -33,7 +33,7 @@ maps4fs/generator/dtm/norway.py,sha256=kSsNZGEqb3IQ3a82UCJ_Iw_0wqdCgEvuRJ4JpEK7Q
|
|
33
33
|
maps4fs/generator/dtm/nrw.py,sha256=1zMa10O0NdQbiTwOa7XXGrx3NhdHUqRXI4yBn_Scb2M,958
|
34
34
|
maps4fs/generator/dtm/scotland.py,sha256=Od4dDMuCM_iteewjGBbmZXJ26S0bDwrhRhXeV4HyyOA,4803
|
35
35
|
maps4fs/generator/dtm/spain.py,sha256=HxN0MvrtRqqeTmhl47q60-1DGaMDb2wq6neMcDlDCl8,1005
|
36
|
-
maps4fs/generator/dtm/srtm.py,sha256=
|
36
|
+
maps4fs/generator/dtm/srtm.py,sha256=ob6AUuEn3G3G9kdqTA2VhT335N65RRBJsqAfHuw0gA8,4502
|
37
37
|
maps4fs/generator/dtm/usgs.py,sha256=1XzLP5GJbe6xcqzkOrEBUtR2SPw7gm6rl1nw5YXmBP8,3253
|
38
38
|
maps4fs/generator/dtm/utils.py,sha256=I-wUSA_J85Xbt8sZCZAVKHSIcrMj5Ng-0adtPVhVmk0,2315
|
39
39
|
maps4fs/generator/dtm/base/wcs.py,sha256=UfkLNHqsYTr2ytjyZjWDocq2hFC_qyFCvl-_tnz6gHM,2510
|
@@ -42,8 +42,8 @@ maps4fs/toolbox/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,4
|
|
42
42
|
maps4fs/toolbox/background.py,sha256=RclEqxEWLbMxuEkkegQP8jybzugwQ1_R3rdfDe0s21U,2104
|
43
43
|
maps4fs/toolbox/custom_osm.py,sha256=X6ZlPqiOhNjkmdD_qVroIfdOl9Rb90cDwVSLDVYgx80,1892
|
44
44
|
maps4fs/toolbox/dem.py,sha256=z9IPFNmYbjiigb3t02ZenI3Mo8odd19c5MZbjDEovTo,3525
|
45
|
-
maps4fs-1.8.
|
46
|
-
maps4fs-1.8.
|
47
|
-
maps4fs-1.8.
|
48
|
-
maps4fs-1.8.
|
49
|
-
maps4fs-1.8.
|
45
|
+
maps4fs-1.8.173.dist-info/LICENSE.md,sha256=pTKD_oUexcn-yccFCTrMeLkZy0ifLRa-VNcDLqLZaIw,10749
|
46
|
+
maps4fs-1.8.173.dist-info/METADATA,sha256=juPm5cwfn3XLlaUemJjP43yHQbW-9NYEljrHscMlGN4,42937
|
47
|
+
maps4fs-1.8.173.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
48
|
+
maps4fs-1.8.173.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
|
49
|
+
maps4fs-1.8.173.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|