maps4fs 2.1.4__tar.gz → 2.1.6__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-2.1.4 → maps4fs-2.1.6}/PKG-INFO +1 -1
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs/generator/component/background.py +26 -7
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs/generator/component/base/component_image.py +7 -1
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs/generator/component/base/component_mesh.py +3 -1
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs/generator/component/dem.py +2 -2
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs/generator/component/satellite.py +2 -0
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs/generator/component/texture.py +2 -3
- maps4fs-2.1.6/maps4fs/generator/config.py +52 -0
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs/generator/map.py +91 -19
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs/generator/settings.py +105 -1
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs/logger.py +0 -3
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs.egg-info/PKG-INFO +1 -1
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs.egg-info/SOURCES.txt +1 -0
- {maps4fs-2.1.4 → maps4fs-2.1.6}/pyproject.toml +1 -1
- {maps4fs-2.1.4 → maps4fs-2.1.6}/LICENSE.md +0 -0
- {maps4fs-2.1.4 → maps4fs-2.1.6}/README.md +0 -0
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs/__init__.py +0 -0
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs/generator/__init__.py +0 -0
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs/generator/component/__init__.py +0 -0
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs/generator/component/base/__init__.py +0 -0
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs/generator/component/base/component.py +0 -0
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs/generator/component/base/component_xml.py +0 -0
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs/generator/component/config.py +0 -0
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs/generator/component/grle.py +0 -0
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs/generator/component/i3d.py +0 -0
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs/generator/component/layer.py +0 -0
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs/generator/game.py +0 -0
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs/generator/qgis.py +0 -0
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs/generator/statistics.py +0 -0
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs.egg-info/dependency_links.txt +0 -0
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs.egg-info/requires.txt +0 -0
- {maps4fs-2.1.4 → maps4fs-2.1.6}/maps4fs.egg-info/top_level.txt +0 -0
- {maps4fs-2.1.4 → maps4fs-2.1.6}/setup.cfg +0 -0
- {maps4fs-2.1.4 → maps4fs-2.1.6}/tests/test_generator.py +0 -0
@@ -69,6 +69,8 @@ class Background(MeshComponent, ImageComponent):
|
|
69
69
|
)
|
70
70
|
self.not_resized_path: str = os.path.join(self.background_directory, "not_resized.png")
|
71
71
|
|
72
|
+
self.flatten_water_to: int | None = None
|
73
|
+
|
72
74
|
self.dem = DEM(
|
73
75
|
self.game,
|
74
76
|
self.map,
|
@@ -520,11 +522,22 @@ class Background(MeshComponent, ImageComponent):
|
|
520
522
|
if self.map.shared_settings.mesh_z_scaling_factor is not None
|
521
523
|
else 257
|
522
524
|
)
|
525
|
+
flatten_to = None
|
526
|
+
subtract_by = int(self.map.dem_settings.water_depth * z_scaling_factor)
|
527
|
+
|
528
|
+
if self.map.background_settings.flatten_water:
|
529
|
+
try:
|
530
|
+
mask = water_resources_image == 255
|
531
|
+
flatten_to = int(np.mean(dem_image[mask]) - subtract_by) # type: ignore
|
532
|
+
self.flatten_water_to = flatten_to # type: ignore
|
533
|
+
except Exception as e:
|
534
|
+
self.logger.warning("Error occurred while flattening water: %s", e)
|
523
535
|
|
524
536
|
dem_image = self.subtract_by_mask(
|
525
537
|
dem_image, # type: ignore
|
526
538
|
water_resources_image, # type: ignore
|
527
|
-
|
539
|
+
subtract_by=subtract_by,
|
540
|
+
flatten_to=flatten_to,
|
528
541
|
)
|
529
542
|
|
530
543
|
dem_image = self.blur_edges_by_mask(
|
@@ -598,7 +611,7 @@ class Background(MeshComponent, ImageComponent):
|
|
598
611
|
return
|
599
612
|
|
600
613
|
# Create a mesh from the 3D polygons
|
601
|
-
mesh = self.mesh_from_3d_polygons(fitted_polygons)
|
614
|
+
mesh = self.mesh_from_3d_polygons(fitted_polygons, single_z_value=self.flatten_water_to)
|
602
615
|
if mesh is None:
|
603
616
|
self.logger.warning("No mesh could be created from the water polygons.")
|
604
617
|
return
|
@@ -611,13 +624,16 @@ class Background(MeshComponent, ImageComponent):
|
|
611
624
|
mesh.export(line_based_save_path)
|
612
625
|
self.logger.debug("Line-based water mesh saved to %s", line_based_save_path)
|
613
626
|
|
614
|
-
def mesh_from_3d_polygons(
|
627
|
+
def mesh_from_3d_polygons(
|
628
|
+
self, polygons: list[shapely.Polygon], single_z_value: int | None = None
|
629
|
+
) -> Trimesh | None:
|
615
630
|
"""Create a simple mesh from a list of 3D shapely Polygons.
|
616
631
|
Each polygon must be flat (all Z the same or nearly the same for each polygon).
|
617
632
|
Returns a single Trimesh mesh.
|
618
633
|
|
619
634
|
Arguments:
|
620
635
|
polygons (list[shapely.Polygon]): List of 3D shapely Polygons to create the mesh from.
|
636
|
+
single_z_value (int | None): The Z value to use for all vertices in the mesh.
|
621
637
|
|
622
638
|
Returns:
|
623
639
|
Trimesh: A single Trimesh object containing the mesh created from the polygons.
|
@@ -649,9 +665,12 @@ class Background(MeshComponent, ImageComponent):
|
|
649
665
|
dists = np.linalg.norm(exterior_2d - v[:2], axis=1)
|
650
666
|
idx = np.argmin(dists)
|
651
667
|
# z = exterior_coords[idx, 2]
|
652
|
-
|
653
|
-
|
654
|
-
|
668
|
+
if not single_z_value:
|
669
|
+
z = self.get_z_coordinate_from_dem(
|
670
|
+
not_resized_dem, exterior_coords[idx, 0], exterior_coords[idx, 1] # type: ignore
|
671
|
+
)
|
672
|
+
else:
|
673
|
+
z = single_z_value
|
655
674
|
vertices_3d.append([v[0], v[1], z])
|
656
675
|
vertices_3d = np.array(vertices_3d) # type: ignore
|
657
676
|
|
@@ -814,7 +833,7 @@ class Background(MeshComponent, ImageComponent):
|
|
814
833
|
full_mask[mask == 255] = 255
|
815
834
|
|
816
835
|
main_dem_path = self.game.dem_file_path(self.map_directory)
|
817
|
-
dem_image = self.blur_by_mask(dem_image, full_mask)
|
836
|
+
dem_image = self.blur_by_mask(dem_image, full_mask, blur_radius=5)
|
818
837
|
dem_image = self.blur_edges_by_mask(dem_image, full_mask)
|
819
838
|
|
820
839
|
output_size = dem_image.shape[0] + 1
|
@@ -67,6 +67,7 @@ class ImageComponent(Component):
|
|
67
67
|
mask_by: int = 255,
|
68
68
|
erode_kernel: int | None = 3,
|
69
69
|
erode_iter: int | None = 1,
|
70
|
+
flatten_to: int | None = None,
|
70
71
|
) -> np.ndarray:
|
71
72
|
"""Subtracts a value from the image where the mask is equal to the mask by value.
|
72
73
|
|
@@ -77,6 +78,7 @@ class ImageComponent(Component):
|
|
77
78
|
mask_by (int, optional): The value to mask by. Defaults to 255.
|
78
79
|
erode_kernel (int, optional): The kernel size for the erosion. Defaults to 3.
|
79
80
|
erode_iter (int, optional): The number of iterations for the erosion. Defaults to 1.
|
81
|
+
flatten_to_mean (bool, optional): Whether to flatten the image to the mean value.
|
80
82
|
|
81
83
|
Returns:
|
82
84
|
np.ndarray: The image with the subtracted value.
|
@@ -90,6 +92,10 @@ class ImageComponent(Component):
|
|
90
92
|
).astype(bool)
|
91
93
|
|
92
94
|
image[mask] = image[mask] - subtract_by
|
95
|
+
|
96
|
+
if flatten_to:
|
97
|
+
image[mask] = flatten_to
|
98
|
+
|
93
99
|
return image
|
94
100
|
|
95
101
|
@staticmethod
|
@@ -190,7 +196,7 @@ class ImageComponent(Component):
|
|
190
196
|
raise ValueError("Data and mask must have the same dimensions.")
|
191
197
|
|
192
198
|
# Create a blurred version of the data
|
193
|
-
blurred_data = cv2.GaussianBlur(data, (blur_radius, blur_radius), sigmaX=
|
199
|
+
blurred_data = cv2.GaussianBlur(data, (blur_radius, blur_radius), sigmaX=10)
|
194
200
|
|
195
201
|
# Combine the blurred data with the original data using the mask
|
196
202
|
result = np.where(mask == 255, blurred_data, data)
|
@@ -235,7 +235,9 @@ class MeshComponent(Component):
|
|
235
235
|
pass
|
236
236
|
cube_mesh = trimesh.creation.box([remove_size, remove_size, z_size * 4])
|
237
237
|
|
238
|
-
return trimesh.boolean.difference(
|
238
|
+
return trimesh.boolean.difference(
|
239
|
+
[mesh_copy, cube_mesh], check_volume=False, engine="blender"
|
240
|
+
)
|
239
241
|
|
240
242
|
@staticmethod
|
241
243
|
def mesh_to_origin(mesh: trimesh.Trimesh) -> trimesh.Trimesh:
|
@@ -10,6 +10,7 @@ from pydtmdl import DTMProvider
|
|
10
10
|
# import rasterio # type: ignore
|
11
11
|
from pympler import asizeof # type: ignore
|
12
12
|
|
13
|
+
import maps4fs.generator.config as mfscfg
|
13
14
|
from maps4fs.generator.component.base.component_image import ImageComponent
|
14
15
|
|
15
16
|
|
@@ -30,7 +31,6 @@ class DEM(ImageComponent):
|
|
30
31
|
|
31
32
|
def preprocess(self) -> None:
|
32
33
|
self._dem_path = self.game.dem_file_path(self.map_directory)
|
33
|
-
self.temp_dir = "temp"
|
34
34
|
|
35
35
|
self.logger.debug("Map size: %s x %s.", self.map_size, self.map_size)
|
36
36
|
self.logger.debug(
|
@@ -44,7 +44,7 @@ class DEM(ImageComponent):
|
|
44
44
|
coordinates=self.coordinates,
|
45
45
|
user_settings=self.map.dtm_provider_settings,
|
46
46
|
size=self.map_rotated_size,
|
47
|
-
directory=
|
47
|
+
directory=mfscfg.DTM_CACHE_DIR,
|
48
48
|
logger=self.logger,
|
49
49
|
)
|
50
50
|
|
@@ -7,6 +7,7 @@ from typing import NamedTuple
|
|
7
7
|
|
8
8
|
from pygmdl import save_image
|
9
9
|
|
10
|
+
import maps4fs.generator.config as mfscfg
|
10
11
|
from maps4fs.generator.component.base.component_image import ImageComponent
|
11
12
|
from maps4fs.generator.settings import Parameters
|
12
13
|
|
@@ -85,6 +86,7 @@ class Satellite(ImageComponent):
|
|
85
86
|
zoom=task.zoom,
|
86
87
|
from_center=True,
|
87
88
|
logger=self.logger,
|
89
|
+
tiles_dir=mfscfg.SAT_CACHE_DIR,
|
88
90
|
)
|
89
91
|
|
90
92
|
except Exception as e:
|
@@ -20,8 +20,7 @@ from tqdm import tqdm
|
|
20
20
|
|
21
21
|
from maps4fs.generator.component.base.component_image import ImageComponent
|
22
22
|
from maps4fs.generator.component.layer import Layer
|
23
|
-
|
24
|
-
PREVIEW_MAXIMUM_SIZE = 2048
|
23
|
+
from maps4fs.generator.settings import Parameters
|
25
24
|
|
26
25
|
|
27
26
|
class Texture(ImageComponent):
|
@@ -834,7 +833,7 @@ class Texture(ImageComponent):
|
|
834
833
|
Returns:
|
835
834
|
str: Path to the preview.
|
836
835
|
"""
|
837
|
-
scaling_factor = PREVIEW_MAXIMUM_SIZE / self.map_size
|
836
|
+
scaling_factor = Parameters.PREVIEW_MAXIMUM_SIZE / self.map_size
|
838
837
|
|
839
838
|
preview_size = (
|
840
839
|
int(self.map_size * scaling_factor),
|
@@ -0,0 +1,52 @@
|
|
1
|
+
"""This module contains configuration files for the maps4fs generator."""
|
2
|
+
|
3
|
+
import os
|
4
|
+
import subprocess
|
5
|
+
|
6
|
+
from osmnx import settings as ox_settings
|
7
|
+
|
8
|
+
MFS_ROOT_DIR = os.getenv("MFS_ROOT_DIRECTORY", os.path.join(os.getcwd(), "mfsrootdir"))
|
9
|
+
MFS_CACHE_DIR = os.path.join(MFS_ROOT_DIR, "cache")
|
10
|
+
MFS_DATA_DIR = os.path.join(MFS_ROOT_DIR, "data")
|
11
|
+
os.makedirs(MFS_CACHE_DIR, exist_ok=True)
|
12
|
+
os.makedirs(MFS_DATA_DIR, exist_ok=True)
|
13
|
+
|
14
|
+
DTM_CACHE_DIR = os.path.join(MFS_CACHE_DIR, "dtm")
|
15
|
+
SAT_CACHE_DIR = os.path.join(MFS_CACHE_DIR, "sat")
|
16
|
+
|
17
|
+
osmnx_cache = os.path.join(MFS_CACHE_DIR, "osmnx")
|
18
|
+
osmnx_data = os.path.join(MFS_CACHE_DIR, "odata")
|
19
|
+
os.makedirs(osmnx_cache, exist_ok=True)
|
20
|
+
os.makedirs(osmnx_data, exist_ok=True)
|
21
|
+
|
22
|
+
|
23
|
+
ox_settings.cache_folder = osmnx_cache
|
24
|
+
ox_settings.data_folder = osmnx_data
|
25
|
+
|
26
|
+
|
27
|
+
def get_package_version(package_name: str) -> str:
|
28
|
+
"""Get the package version.
|
29
|
+
|
30
|
+
Arguments:
|
31
|
+
package_name (str): The name of the package to check.
|
32
|
+
|
33
|
+
Returns:
|
34
|
+
str: The version of the package, or "unknown" if it cannot be determined.
|
35
|
+
"""
|
36
|
+
try:
|
37
|
+
result = subprocess.run(
|
38
|
+
[os.sys.executable, "-m", "pip", "show", package_name], # type: ignore
|
39
|
+
stdout=subprocess.PIPE,
|
40
|
+
stderr=subprocess.PIPE,
|
41
|
+
text=True,
|
42
|
+
check=True,
|
43
|
+
)
|
44
|
+
for line in result.stdout.splitlines():
|
45
|
+
if line.startswith("Version:"):
|
46
|
+
return line.split(":", 1)[1].strip()
|
47
|
+
return "unknown"
|
48
|
+
except Exception:
|
49
|
+
return "unknown"
|
50
|
+
|
51
|
+
|
52
|
+
PACKAGE_VERSION = get_package_version("maps4fs")
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
5
5
|
import json
|
6
6
|
import os
|
7
7
|
import shutil
|
8
|
+
from datetime import datetime
|
8
9
|
from typing import Any, Generator
|
9
10
|
from xml.etree import ElementTree as ET
|
10
11
|
|
@@ -14,13 +15,16 @@ from osmnx._errors import InsufficientResponseError
|
|
14
15
|
from pydtmdl import DTMProvider
|
15
16
|
from pydtmdl.base.dtm import DTMProviderSettings
|
16
17
|
|
18
|
+
import maps4fs.generator.config as mfscfg
|
17
19
|
from maps4fs.generator.component import Background, Component, Layer, Texture
|
18
20
|
from maps4fs.generator.game import FS25, Game
|
19
21
|
from maps4fs.generator.settings import (
|
20
22
|
BackgroundSettings,
|
21
23
|
DEMSettings,
|
24
|
+
GenerationSettings,
|
22
25
|
GRLESettings,
|
23
26
|
I3DSettings,
|
27
|
+
MainSettings,
|
24
28
|
SatelliteSettings,
|
25
29
|
SharedSettings,
|
26
30
|
TextureSettings,
|
@@ -48,7 +52,7 @@ class Map:
|
|
48
52
|
coordinates: tuple[float, float],
|
49
53
|
size: int,
|
50
54
|
rotation: int,
|
51
|
-
map_directory: str,
|
55
|
+
map_directory: str | None = None,
|
52
56
|
logger: Any = None,
|
53
57
|
custom_osm: str | None = None,
|
54
58
|
dem_settings: DEMSettings = DEMSettings(),
|
@@ -81,11 +85,13 @@ class Map:
|
|
81
85
|
self.dtm_provider_settings = dtm_provider_settings
|
82
86
|
self.components: list[Component] = []
|
83
87
|
self.coordinates = coordinates
|
84
|
-
self.map_directory = map_directory
|
88
|
+
self.map_directory = map_directory or self.suggest_map_directory(
|
89
|
+
coordinates=coordinates, game_code=game.code # type: ignore
|
90
|
+
)
|
85
91
|
|
86
|
-
|
87
|
-
|
88
|
-
"game": game.code,
|
92
|
+
main_settings = MainSettings.from_json(
|
93
|
+
{
|
94
|
+
"game": game.code, # type: ignore
|
89
95
|
"latitude": coordinates[0],
|
90
96
|
"longitude": coordinates[1],
|
91
97
|
"country": self.get_country_by_coordinates(),
|
@@ -95,11 +101,24 @@ class Map:
|
|
95
101
|
"custom_osm": bool(custom_osm),
|
96
102
|
"is_public": kwargs.get("is_public", False),
|
97
103
|
"api_request": kwargs.get("api_request", False),
|
104
|
+
"date": datetime.now().strftime("%Y-%m-%d"),
|
105
|
+
"time": datetime.now().strftime("%H:%M:%S"),
|
106
|
+
"version": mfscfg.PACKAGE_VERSION,
|
107
|
+
"completed": False,
|
108
|
+
"error": None,
|
98
109
|
}
|
99
|
-
|
110
|
+
)
|
111
|
+
main_settings_json = main_settings.to_json()
|
112
|
+
|
113
|
+
try:
|
114
|
+
send_main_settings(main_settings_json)
|
100
115
|
except Exception as e:
|
101
116
|
self.logger.error("Error sending main settings: %s", e)
|
102
117
|
|
118
|
+
self.main_settings_path = os.path.join(self.map_directory, "main_settings.json")
|
119
|
+
with open(self.main_settings_path, "w", encoding="utf-8") as file:
|
120
|
+
json.dump(main_settings_json, file, indent=4)
|
121
|
+
|
103
122
|
log_entry = ""
|
104
123
|
log_entry += f"Map instance created for Game: {game.code}. "
|
105
124
|
log_entry += f"Coordinates: {coordinates}. Size: {size}. Rotation: {rotation}. "
|
@@ -154,19 +173,14 @@ class Map:
|
|
154
173
|
os.makedirs(self.map_directory, exist_ok=True)
|
155
174
|
self.logger.debug("Map directory created: %s", self.map_directory)
|
156
175
|
|
157
|
-
|
158
|
-
dem_settings,
|
159
|
-
background_settings,
|
160
|
-
grle_settings,
|
161
|
-
i3d_settings,
|
162
|
-
texture_settings,
|
163
|
-
satellite_settings,
|
164
|
-
|
165
|
-
|
166
|
-
settings_json = {}
|
167
|
-
|
168
|
-
for setting in settings:
|
169
|
-
settings_json[setting.__class__.__name__] = setting.model_dump()
|
176
|
+
settings_json = GenerationSettings(
|
177
|
+
dem_settings=dem_settings,
|
178
|
+
background_settings=background_settings,
|
179
|
+
grle_settings=grle_settings,
|
180
|
+
i3d_settings=i3d_settings,
|
181
|
+
texture_settings=texture_settings,
|
182
|
+
satellite_settings=satellite_settings,
|
183
|
+
).to_json()
|
170
184
|
|
171
185
|
try:
|
172
186
|
send_advanced_settings(settings_json)
|
@@ -205,6 +219,52 @@ class Map:
|
|
205
219
|
except Exception as e:
|
206
220
|
raise RuntimeError(f"Can not unpack map template due to error: {e}") from e
|
207
221
|
|
222
|
+
self.logger.debug(
|
223
|
+
"MFS_DATA_DIR: %s. MFS_CACHE_DIR %s", mfscfg.MFS_DATA_DIR, mfscfg.MFS_CACHE_DIR
|
224
|
+
)
|
225
|
+
|
226
|
+
@staticmethod
|
227
|
+
def suggest_map_directory(coordinates: tuple[float, float], game_code: str) -> str:
|
228
|
+
"""Generate map directory path from coordinates and game code.
|
229
|
+
|
230
|
+
Returns:
|
231
|
+
str: Map directory path.
|
232
|
+
"""
|
233
|
+
return os.path.join(mfscfg.MFS_DATA_DIR, Map.suggest_directory_name(coordinates, game_code))
|
234
|
+
|
235
|
+
@staticmethod
|
236
|
+
def suggest_directory_name(coordinates: tuple[float, float], game_code: str) -> str:
|
237
|
+
"""Generate directory name from coordinates and game code.
|
238
|
+
|
239
|
+
Returns:
|
240
|
+
str: Directory name.
|
241
|
+
"""
|
242
|
+
lat, lon = coordinates
|
243
|
+
latr = Map.coordinate_to_string(lat)
|
244
|
+
lonr = Map.coordinate_to_string(lon)
|
245
|
+
return f"{Map.get_timestamp()}_{game_code}_{latr}_{lonr}".lower()
|
246
|
+
|
247
|
+
@staticmethod
|
248
|
+
def get_timestamp() -> str:
|
249
|
+
"""Get current underscore-separated timestamp.
|
250
|
+
|
251
|
+
Returns:
|
252
|
+
str: Current timestamp.
|
253
|
+
"""
|
254
|
+
return datetime.now().strftime("%Y%m%d_%H%M%S")
|
255
|
+
|
256
|
+
@staticmethod
|
257
|
+
def coordinate_to_string(coordinate: float) -> str:
|
258
|
+
"""Convert coordinate to string with 3 decimal places.
|
259
|
+
|
260
|
+
Arguments:
|
261
|
+
coordinate (float): Coordinate value.
|
262
|
+
|
263
|
+
Returns:
|
264
|
+
str: Coordinate as string.
|
265
|
+
"""
|
266
|
+
return f"{coordinate:.3f}".replace(".", "_")
|
267
|
+
|
208
268
|
@property
|
209
269
|
def texture_schema(self) -> list[dict[str, Any]] | None:
|
210
270
|
"""Return texture schema (custom if provided, default otherwise).
|
@@ -230,6 +290,7 @@ class Map:
|
|
230
290
|
self.size,
|
231
291
|
self.rotation,
|
232
292
|
)
|
293
|
+
error_text = None
|
233
294
|
|
234
295
|
for game_component in self.game.components:
|
235
296
|
component = game_component(
|
@@ -256,8 +317,19 @@ class Map:
|
|
256
317
|
component.__class__.__name__,
|
257
318
|
e,
|
258
319
|
)
|
320
|
+
error_text = str(e)
|
259
321
|
raise e
|
260
322
|
|
323
|
+
finally:
|
324
|
+
with open(self.main_settings_path, "r", encoding="utf-8") as file:
|
325
|
+
main_settings_json = json.load(file)
|
326
|
+
|
327
|
+
main_settings_json["completed"] = True
|
328
|
+
main_settings_json["error"] = error_text
|
329
|
+
|
330
|
+
with open(self.main_settings_path, "w", encoding="utf-8") as file:
|
331
|
+
json.dump(main_settings_json, file, indent=4)
|
332
|
+
|
261
333
|
try:
|
262
334
|
component.commit_generation_info()
|
263
335
|
except Exception as e:
|
@@ -3,7 +3,7 @@
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
5
|
import re
|
6
|
-
from typing import Any
|
6
|
+
from typing import Any, NamedTuple
|
7
7
|
|
8
8
|
from pydantic import BaseModel, ConfigDict
|
9
9
|
|
@@ -164,6 +164,8 @@ class BackgroundSettings(SettingsModel):
|
|
164
164
|
remove_center (bool): remove the center of the background terrain.
|
165
165
|
It will be used to remove the center of the map where the player starts.
|
166
166
|
flatten_roads (bool): if True, roads will be flattened in the DEM data.
|
167
|
+
flatten_water (bool): if True, the bottom of the water resources will be flattened
|
168
|
+
to the average height of the water resources.
|
167
169
|
"""
|
168
170
|
|
169
171
|
generate_background: bool = False
|
@@ -171,6 +173,7 @@ class BackgroundSettings(SettingsModel):
|
|
171
173
|
water_blurriness: int = 20
|
172
174
|
remove_center: bool = True
|
173
175
|
flatten_roads: bool = False
|
176
|
+
flatten_water: bool = False
|
174
177
|
|
175
178
|
|
176
179
|
class GRLESettings(SettingsModel):
|
@@ -249,3 +252,104 @@ class SatelliteSettings(SettingsModel):
|
|
249
252
|
|
250
253
|
download_images: bool = False
|
251
254
|
zoom_level: int = 16
|
255
|
+
|
256
|
+
|
257
|
+
class GenerationSettings(BaseModel):
|
258
|
+
"""Represents the settings for the map generation process."""
|
259
|
+
|
260
|
+
dem_settings: DEMSettings
|
261
|
+
background_settings: BackgroundSettings
|
262
|
+
grle_settings: GRLESettings
|
263
|
+
i3d_settings: I3DSettings
|
264
|
+
texture_settings: TextureSettings
|
265
|
+
satellite_settings: SatelliteSettings
|
266
|
+
|
267
|
+
def to_json(self) -> dict[str, Any]:
|
268
|
+
"""Convert the GenerationSettings instance to JSON format.
|
269
|
+
|
270
|
+
Returns:
|
271
|
+
dict[str, Any]: JSON representation of the GenerationSettings.
|
272
|
+
"""
|
273
|
+
return {
|
274
|
+
"DEMSettings": self.dem_settings.model_dump(),
|
275
|
+
"BackgroundSettings": self.background_settings.model_dump(),
|
276
|
+
"GRLESettings": self.grle_settings.model_dump(),
|
277
|
+
"I3DSettings": self.i3d_settings.model_dump(),
|
278
|
+
"TextureSettings": self.texture_settings.model_dump(),
|
279
|
+
"SatelliteSettings": self.satellite_settings.model_dump(),
|
280
|
+
}
|
281
|
+
|
282
|
+
@classmethod
|
283
|
+
def from_json(cls, data: dict[str, Any]) -> GenerationSettings:
|
284
|
+
"""Create a GenerationSettings instance from JSON data.
|
285
|
+
|
286
|
+
Arguments:
|
287
|
+
data (dict[str, Any]): JSON data.
|
288
|
+
|
289
|
+
Returns:
|
290
|
+
GenerationSettings: Instance of GenerationSettings.
|
291
|
+
"""
|
292
|
+
return cls(
|
293
|
+
dem_settings=DEMSettings(**data["DEMSettings"]),
|
294
|
+
background_settings=BackgroundSettings(**data["BackgroundSettings"]),
|
295
|
+
grle_settings=GRLESettings(**data["GRLESettings"]),
|
296
|
+
i3d_settings=I3DSettings(**data["I3DSettings"]),
|
297
|
+
texture_settings=TextureSettings(**data["TextureSettings"]),
|
298
|
+
satellite_settings=SatelliteSettings(**data["SatelliteSettings"]),
|
299
|
+
)
|
300
|
+
|
301
|
+
|
302
|
+
class MainSettings(NamedTuple):
|
303
|
+
"""Represents the main settings for the map generation."""
|
304
|
+
|
305
|
+
game: str
|
306
|
+
latitude: float
|
307
|
+
longitude: float
|
308
|
+
country: str
|
309
|
+
size: int
|
310
|
+
rotation: int
|
311
|
+
dtm_provider: str
|
312
|
+
custom_osm: bool
|
313
|
+
is_public: bool
|
314
|
+
api_request: bool
|
315
|
+
date: str
|
316
|
+
time: str
|
317
|
+
version: str
|
318
|
+
completed: bool
|
319
|
+
error: str | None = None
|
320
|
+
|
321
|
+
@classmethod
|
322
|
+
def from_json(cls, data: dict[str, str | float | int | bool | None]) -> MainSettings:
|
323
|
+
"""Create a MainSettings instance from JSON data.
|
324
|
+
|
325
|
+
Arguments:
|
326
|
+
data (dict[str, str | float | int | bool | None]): JSON data.
|
327
|
+
|
328
|
+
Returns:
|
329
|
+
MainSettings: Instance of MainSettings.
|
330
|
+
"""
|
331
|
+
return cls(**data) # type: ignore
|
332
|
+
|
333
|
+
def to_json(self) -> dict[str, str | float | int | bool | None]:
|
334
|
+
"""Convert the MainSettings instance to JSON format.
|
335
|
+
|
336
|
+
Returns:
|
337
|
+
dict[str, str | float | int | bool | None]: JSON representation of the MainSettings.
|
338
|
+
"""
|
339
|
+
return {
|
340
|
+
"game": self.game,
|
341
|
+
"latitude": self.latitude,
|
342
|
+
"longitude": self.longitude,
|
343
|
+
"country": self.country,
|
344
|
+
"size": self.size,
|
345
|
+
"rotation": self.rotation,
|
346
|
+
"dtm_provider": self.dtm_provider,
|
347
|
+
"custom_osm": self.custom_osm,
|
348
|
+
"is_public": self.is_public,
|
349
|
+
"api_request": self.api_request,
|
350
|
+
"date": self.date,
|
351
|
+
"time": self.time,
|
352
|
+
"version": self.version,
|
353
|
+
"completed": self.completed,
|
354
|
+
"error": self.error,
|
355
|
+
}
|
@@ -1,13 +1,10 @@
|
|
1
1
|
"""This module contains the Logger class for logging to the file and stdout."""
|
2
2
|
|
3
3
|
import logging
|
4
|
-
import os
|
5
4
|
import sys
|
6
5
|
from typing import Literal
|
7
6
|
|
8
7
|
LOGGER_NAME = "maps4fs"
|
9
|
-
log_directory = os.path.join(os.getcwd(), "logs")
|
10
|
-
os.makedirs(log_directory, exist_ok=True)
|
11
8
|
|
12
9
|
|
13
10
|
class Logger(logging.Logger):
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "maps4fs"
|
7
|
-
version = "2.1.
|
7
|
+
version = "2.1.6"
|
8
8
|
description = "Generate map templates for Farming Simulator from real places."
|
9
9
|
authors = [{name = "iwatkot", email = "iwatkot@gmail.com"}]
|
10
10
|
license = {text = "Apache License 2.0"}
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|