maps4fs 2.2.7__tar.gz → 2.2.72__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.2.7 → maps4fs-2.2.72}/PKG-INFO +2 -4
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/__init__.py +1 -0
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/background.py +1 -1
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/dem.py +0 -6
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/grle.py +1 -1
- maps4fs-2.2.72/maps4fs/generator/config.py +154 -0
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/map.py +76 -258
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/settings.py +60 -17
- maps4fs-2.2.72/maps4fs/generator/statistics.py +72 -0
- maps4fs-2.2.72/maps4fs/generator/utils.py +160 -0
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs.egg-info/PKG-INFO +2 -4
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs.egg-info/SOURCES.txt +1 -0
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs.egg-info/requires.txt +1 -3
- {maps4fs-2.2.7 → maps4fs-2.2.72}/pyproject.toml +2 -4
- {maps4fs-2.2.7 → maps4fs-2.2.72}/tests/test_generator.py +13 -6
- maps4fs-2.2.7/maps4fs/generator/config.py +0 -93
- maps4fs-2.2.7/maps4fs/generator/statistics.py +0 -85
- {maps4fs-2.2.7 → maps4fs-2.2.72}/LICENSE.md +0 -0
- {maps4fs-2.2.7 → maps4fs-2.2.72}/README.md +0 -0
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/__init__.py +0 -0
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/__init__.py +0 -0
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/base/__init__.py +0 -0
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/base/component.py +0 -0
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/base/component_image.py +0 -0
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/base/component_mesh.py +0 -0
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/base/component_xml.py +0 -0
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/config.py +0 -0
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/i3d.py +0 -0
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/layer.py +0 -0
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/satellite.py +0 -0
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/texture.py +0 -0
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/game.py +0 -0
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/qgis.py +0 -0
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/logger.py +0 -0
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs.egg-info/dependency_links.txt +0 -0
- {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs.egg-info/top_level.txt +0 -0
- {maps4fs-2.2.7 → maps4fs-2.2.72}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: maps4fs
|
3
|
-
Version: 2.2.
|
3
|
+
Version: 2.2.72
|
4
4
|
Summary: Generate map templates for Farming Simulator from real places.
|
5
5
|
Author-email: iwatkot <iwatkot@gmail.com>
|
6
6
|
License: Apache License 2.0
|
@@ -18,15 +18,13 @@ Requires-Dist: osmnx>=2.0.0
|
|
18
18
|
Requires-Dist: rasterio
|
19
19
|
Requires-Dist: geopy
|
20
20
|
Requires-Dist: trimesh
|
21
|
-
Requires-Dist: imageio
|
22
|
-
Requires-Dist: tifffile
|
23
|
-
Requires-Dist: pympler
|
24
21
|
Requires-Dist: pydantic
|
25
22
|
Requires-Dist: pygmdl
|
26
23
|
Requires-Dist: owslib
|
27
24
|
Requires-Dist: tqdm
|
28
25
|
Requires-Dist: scipy
|
29
26
|
Requires-Dist: pydtmdl
|
27
|
+
Requires-Dist: manifold3d
|
30
28
|
Dynamic: license-file
|
31
29
|
|
32
30
|
⚠️ Learn more about the 2.0 changes in the [migration guide](docs/migration.md).
|
@@ -6,4 +6,5 @@ import maps4fs.generator.component as component
|
|
6
6
|
import maps4fs.generator.settings as settings
|
7
7
|
from maps4fs.generator.game import Game
|
8
8
|
from maps4fs.generator.map import Map
|
9
|
+
from maps4fs.generator.settings import GenerationSettings, MainSettings
|
9
10
|
from maps4fs.logger import Logger
|
@@ -586,7 +586,7 @@ class Background(MeshComponent, ImageComponent):
|
|
586
586
|
continue
|
587
587
|
|
588
588
|
# Make Polygon a little bit bigger to hide under the terrain when creating water planes.
|
589
|
-
polygon = polygon.buffer(Parameters.WATER_ADD_WIDTH,
|
589
|
+
polygon = polygon.buffer(Parameters.WATER_ADD_WIDTH, quad_segs=4)
|
590
590
|
|
591
591
|
polygons.append(polygon)
|
592
592
|
|
@@ -7,9 +7,6 @@ import cv2
|
|
7
7
|
import numpy as np
|
8
8
|
from pydtmdl import DTMProvider
|
9
9
|
|
10
|
-
# import rasterio # type: ignore
|
11
|
-
from pympler import asizeof # type: ignore
|
12
|
-
|
13
10
|
import maps4fs.generator.config as mfscfg
|
14
11
|
from maps4fs.generator.component.base.component_image import ImageComponent
|
15
12
|
|
@@ -297,9 +294,6 @@ class DEM(ImageComponent):
|
|
297
294
|
"""
|
298
295
|
resampled_data = cv2.resize(data, self.output_resolution, interpolation=cv2.INTER_LINEAR)
|
299
296
|
|
300
|
-
size_of_resampled_data = asizeof.asizeof(resampled_data) / 1024 / 1024
|
301
|
-
self.logger.debug("Size of resampled data: %s MB.", size_of_resampled_data)
|
302
|
-
|
303
297
|
return resampled_data
|
304
298
|
|
305
299
|
def rotate_dem(self) -> None:
|
@@ -472,7 +472,7 @@ class GRLE(ImageComponent, XMLComponent):
|
|
472
472
|
for a, r in zip(random_angles, random_radii)
|
473
473
|
]
|
474
474
|
polygon = Polygon(points)
|
475
|
-
buffered_polygon = polygon.buffer(rounding_radius,
|
475
|
+
buffered_polygon = polygon.buffer(rounding_radius, quad_segs=16)
|
476
476
|
rounded_polygon = list(buffered_polygon.exterior.coords)
|
477
477
|
if not rounded_polygon:
|
478
478
|
return None
|
@@ -0,0 +1,154 @@
|
|
1
|
+
"""This module contains configuration files for the maps4fs generator."""
|
2
|
+
|
3
|
+
import os
|
4
|
+
import shutil
|
5
|
+
import subprocess
|
6
|
+
import tempfile
|
7
|
+
|
8
|
+
from osmnx import settings as ox_settings
|
9
|
+
|
10
|
+
from maps4fs.logger import Logger
|
11
|
+
|
12
|
+
logger = Logger()
|
13
|
+
|
14
|
+
MFS_TEMPLATES_DIR = os.path.join(os.getcwd(), "data")
|
15
|
+
|
16
|
+
|
17
|
+
def ensure_templates():
|
18
|
+
"""Ensure templates directory exists and is populated with data.
|
19
|
+
|
20
|
+
If MFS_TEMPLATES_DIR is empty or doesn't exist, clone the maps4fsdata
|
21
|
+
repository and run the preparation script to populate it.
|
22
|
+
"""
|
23
|
+
|
24
|
+
# Check if templates directory exists and has content
|
25
|
+
if os.path.exists(MFS_TEMPLATES_DIR) and os.listdir(MFS_TEMPLATES_DIR):
|
26
|
+
logger.info("Templates directory already exists and contains data: %s", MFS_TEMPLATES_DIR)
|
27
|
+
return
|
28
|
+
|
29
|
+
logger.info("Templates directory is empty or missing, preparing data...")
|
30
|
+
|
31
|
+
# Create templates directory if it doesn't exist
|
32
|
+
os.makedirs(MFS_TEMPLATES_DIR, exist_ok=True)
|
33
|
+
|
34
|
+
try:
|
35
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
36
|
+
clone_dir = os.path.join(temp_dir, "maps4fsdata")
|
37
|
+
|
38
|
+
logger.info("Cloning maps4fsdata repository to temporary directory...")
|
39
|
+
# Clone the repository with depth 1 (shallow clone)
|
40
|
+
subprocess.run(
|
41
|
+
[
|
42
|
+
"git",
|
43
|
+
"clone",
|
44
|
+
"--depth",
|
45
|
+
"1",
|
46
|
+
"https://github.com/iwatkot/maps4fsdata.git",
|
47
|
+
clone_dir,
|
48
|
+
],
|
49
|
+
check=True,
|
50
|
+
capture_output=True,
|
51
|
+
text=True,
|
52
|
+
)
|
53
|
+
|
54
|
+
# Make the preparation script executable
|
55
|
+
prep_script = os.path.join(clone_dir, "prepare_data.sh")
|
56
|
+
if os.path.exists(prep_script):
|
57
|
+
os.chmod(prep_script, 0o755)
|
58
|
+
|
59
|
+
logger.info("Running data preparation script...")
|
60
|
+
# Run the preparation script from the cloned directory
|
61
|
+
subprocess.run(
|
62
|
+
["./prepare_data.sh"], cwd=clone_dir, check=True, capture_output=True, text=True
|
63
|
+
)
|
64
|
+
|
65
|
+
# Copy the generated data directory to templates directory
|
66
|
+
data_src = os.path.join(clone_dir, "data")
|
67
|
+
if os.path.exists(data_src):
|
68
|
+
logger.info(
|
69
|
+
"Copying prepared data to templates directory: %s", MFS_TEMPLATES_DIR
|
70
|
+
)
|
71
|
+
# Copy all files from data directory to MFS_TEMPLATES_DIR
|
72
|
+
for item in os.listdir(data_src):
|
73
|
+
src_path = os.path.join(data_src, item)
|
74
|
+
dst_path = os.path.join(MFS_TEMPLATES_DIR, item)
|
75
|
+
if os.path.isdir(src_path):
|
76
|
+
shutil.copytree(src_path, dst_path, dirs_exist_ok=True)
|
77
|
+
else:
|
78
|
+
shutil.copy2(src_path, dst_path)
|
79
|
+
logger.info("Templates data prepared successfully")
|
80
|
+
else:
|
81
|
+
logger.error("Data directory not found after running preparation script")
|
82
|
+
raise FileNotFoundError(
|
83
|
+
"Data preparation script did not create expected data directory"
|
84
|
+
)
|
85
|
+
else:
|
86
|
+
logger.error("Preparation script not found: %s", prep_script)
|
87
|
+
raise FileNotFoundError("prepare_data.sh not found in cloned repository")
|
88
|
+
|
89
|
+
except subprocess.CalledProcessError as e:
|
90
|
+
logger.error("Failed to prepare templates data: %s", str(e))
|
91
|
+
if e.stdout:
|
92
|
+
logger.error("Script stdout: %s", e.stdout)
|
93
|
+
if e.stderr:
|
94
|
+
logger.error("Script stderr: %s", e.stderr)
|
95
|
+
raise
|
96
|
+
except Exception as e:
|
97
|
+
logger.error("Error preparing templates: %s", str(e))
|
98
|
+
raise
|
99
|
+
|
100
|
+
|
101
|
+
ensure_templates()
|
102
|
+
|
103
|
+
MFS_ROOT_DIR = os.getenv("MFS_ROOT_DIRECTORY", os.path.join(os.getcwd(), "mfsrootdir"))
|
104
|
+
MFS_CACHE_DIR = os.path.join(MFS_ROOT_DIR, "cache")
|
105
|
+
MFS_DATA_DIR = os.path.join(MFS_ROOT_DIR, "data")
|
106
|
+
os.makedirs(MFS_CACHE_DIR, exist_ok=True)
|
107
|
+
os.makedirs(MFS_DATA_DIR, exist_ok=True)
|
108
|
+
logger.info(
|
109
|
+
"MFS_ROOT_DIR: %s. MFS_CACHE_DIR: %s. MFS_DATA_DIR: %s.",
|
110
|
+
MFS_ROOT_DIR,
|
111
|
+
MFS_CACHE_DIR,
|
112
|
+
MFS_DATA_DIR,
|
113
|
+
)
|
114
|
+
|
115
|
+
DTM_CACHE_DIR = os.path.join(MFS_CACHE_DIR, "dtm")
|
116
|
+
SAT_CACHE_DIR = os.path.join(MFS_CACHE_DIR, "sat")
|
117
|
+
|
118
|
+
osmnx_cache = os.path.join(MFS_CACHE_DIR, "osmnx")
|
119
|
+
osmnx_data = os.path.join(MFS_CACHE_DIR, "odata")
|
120
|
+
os.makedirs(osmnx_cache, exist_ok=True)
|
121
|
+
os.makedirs(osmnx_data, exist_ok=True)
|
122
|
+
|
123
|
+
|
124
|
+
ox_settings.cache_folder = osmnx_cache
|
125
|
+
ox_settings.data_folder = osmnx_data
|
126
|
+
|
127
|
+
|
128
|
+
def get_package_version(package_name: str) -> str:
|
129
|
+
"""Get the package version.
|
130
|
+
|
131
|
+
Arguments:
|
132
|
+
package_name (str): The name of the package to check.
|
133
|
+
|
134
|
+
Returns:
|
135
|
+
str: The version of the package, or "unknown" if it cannot be determined.
|
136
|
+
"""
|
137
|
+
try:
|
138
|
+
result = subprocess.run(
|
139
|
+
[os.sys.executable, "-m", "pip", "show", package_name], # type: ignore
|
140
|
+
stdout=subprocess.PIPE,
|
141
|
+
stderr=subprocess.PIPE,
|
142
|
+
text=True,
|
143
|
+
check=True,
|
144
|
+
)
|
145
|
+
for line in result.stdout.splitlines():
|
146
|
+
if line.startswith("Version:"):
|
147
|
+
return line.split(":", 1)[1].strip()
|
148
|
+
return "unknown"
|
149
|
+
except Exception:
|
150
|
+
return "unknown"
|
151
|
+
|
152
|
+
|
153
|
+
PACKAGE_VERSION = get_package_version("maps4fs")
|
154
|
+
logger.info("maps4fs version: %s", PACKAGE_VERSION)
|
@@ -5,30 +5,16 @@ from __future__ import annotations
|
|
5
5
|
import json
|
6
6
|
import os
|
7
7
|
import shutil
|
8
|
-
from datetime import datetime
|
9
8
|
from typing import Any, Generator
|
10
|
-
from xml.etree import ElementTree as ET
|
11
9
|
|
12
|
-
import osmnx as ox
|
13
|
-
from geopy.geocoders import Nominatim
|
14
|
-
from osmnx._errors import InsufficientResponseError
|
15
10
|
from pydtmdl import DTMProvider
|
16
11
|
from pydtmdl.base.dtm import DTMProviderSettings
|
17
12
|
|
18
13
|
import maps4fs.generator.config as mfscfg
|
14
|
+
import maps4fs.generator.utils as mfsutils
|
19
15
|
from maps4fs.generator.component import Background, Component, Layer, Texture
|
20
|
-
from maps4fs.generator.game import
|
21
|
-
from maps4fs.generator.settings import
|
22
|
-
BackgroundSettings,
|
23
|
-
DEMSettings,
|
24
|
-
GenerationSettings,
|
25
|
-
GRLESettings,
|
26
|
-
I3DSettings,
|
27
|
-
MainSettings,
|
28
|
-
SatelliteSettings,
|
29
|
-
SharedSettings,
|
30
|
-
TextureSettings,
|
31
|
-
)
|
16
|
+
from maps4fs.generator.game import Game
|
17
|
+
from maps4fs.generator.settings import GenerationSettings, MainSettings, SharedSettings
|
32
18
|
from maps4fs.generator.statistics import send_advanced_settings, send_main_settings
|
33
19
|
from maps4fs.logger import Logger
|
34
20
|
|
@@ -55,174 +41,98 @@ class Map:
|
|
55
41
|
map_directory: str | None = None,
|
56
42
|
logger: Any = None,
|
57
43
|
custom_osm: str | None = None,
|
58
|
-
|
59
|
-
background_settings: BackgroundSettings = BackgroundSettings(),
|
60
|
-
grle_settings: GRLESettings = GRLESettings(),
|
61
|
-
i3d_settings: I3DSettings = I3DSettings(),
|
62
|
-
texture_settings: TextureSettings = TextureSettings(),
|
63
|
-
satellite_settings: SatelliteSettings = SatelliteSettings(),
|
44
|
+
generation_settings: GenerationSettings = GenerationSettings(),
|
64
45
|
**kwargs,
|
65
46
|
):
|
66
|
-
if not logger:
|
67
|
-
logger = Logger()
|
68
|
-
self.logger = logger
|
69
|
-
self.size = size
|
70
|
-
|
71
|
-
if rotation:
|
72
|
-
rotation_multiplier = 1.5
|
73
|
-
else:
|
74
|
-
rotation_multiplier = 1
|
75
|
-
|
76
|
-
self.rotation = rotation
|
77
|
-
self.rotated_size = int(size * rotation_multiplier)
|
78
|
-
self.output_size = kwargs.get("output_size", None)
|
79
|
-
self.size_scale = 1.0
|
80
|
-
if self.output_size:
|
81
|
-
self.size_scale = self.output_size / self.size
|
82
47
|
|
48
|
+
# region main properties
|
83
49
|
self.game = game
|
84
50
|
self.dtm_provider = dtm_provider
|
85
51
|
self.dtm_provider_settings = dtm_provider_settings
|
86
|
-
self.components: list[Component] = []
|
87
52
|
self.coordinates = coordinates
|
88
53
|
self.map_directory = map_directory or self.suggest_map_directory(
|
89
54
|
coordinates=coordinates, game_code=game.code # type: ignore
|
90
55
|
)
|
56
|
+
self.rotation = rotation
|
57
|
+
self.kwargs = kwargs
|
58
|
+
# endregion
|
91
59
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
"output_size": self.output_size,
|
100
|
-
"rotation": rotation,
|
101
|
-
"dtm_provider": dtm_provider.name(),
|
102
|
-
"custom_osm": bool(custom_osm),
|
103
|
-
"is_public": kwargs.get("is_public", False),
|
104
|
-
"api_request": kwargs.get("api_request", False),
|
105
|
-
"date": datetime.now().strftime("%Y-%m-%d"),
|
106
|
-
"time": datetime.now().strftime("%H:%M:%S"),
|
107
|
-
"version": mfscfg.PACKAGE_VERSION,
|
108
|
-
"completed": False,
|
109
|
-
"error": None,
|
110
|
-
}
|
111
|
-
)
|
112
|
-
main_settings_json = main_settings.to_json()
|
113
|
-
|
114
|
-
try:
|
115
|
-
send_main_settings(main_settings_json)
|
116
|
-
except Exception as e:
|
117
|
-
self.logger.error("Error sending main settings: %s", e)
|
118
|
-
|
119
|
-
self.main_settings_path = os.path.join(self.map_directory, "main_settings.json")
|
120
|
-
with open(self.main_settings_path, "w", encoding="utf-8") as file:
|
121
|
-
json.dump(main_settings_json, file, indent=4)
|
122
|
-
|
123
|
-
log_entry = ""
|
124
|
-
log_entry += f"Map instance created for Game: {game.code}. "
|
125
|
-
log_entry += f"Coordinates: {coordinates}. Size: {size}. Rotation: {rotation}. "
|
126
|
-
if self.output_size:
|
127
|
-
log_entry += f"Output size: {self.output_size}. Scaling: {self.size_scale}. "
|
128
|
-
log_entry += f"DTM provider is {dtm_provider.name()}. "
|
60
|
+
# region size properties
|
61
|
+
self.size = size
|
62
|
+
rotation_multiplier = 1.5 if rotation else 1
|
63
|
+
self.rotated_size = int(size * rotation_multiplier)
|
64
|
+
self.output_size = kwargs.get("output_size", None)
|
65
|
+
self.size_scale = 1.0 if not self.output_size else self.output_size / size
|
66
|
+
# endregion
|
129
67
|
|
68
|
+
# region custom OSM properties
|
130
69
|
self.custom_osm = custom_osm
|
131
|
-
|
70
|
+
if custom_osm and not os.path.isfile(custom_osm):
|
71
|
+
raise FileNotFoundError(f"Custom OSM file {custom_osm} does not exist.")
|
72
|
+
mfsutils.check_and_fix_osm(self.custom_osm, save_directory=self.map_directory)
|
73
|
+
# endregion
|
132
74
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
fixed, fixed_errors = fix_osm_file(self.custom_osm)
|
140
|
-
if not fixed:
|
141
|
-
raise ValueError(
|
142
|
-
f"Custom OSM file {custom_osm} is not valid and cannot be fixed."
|
143
|
-
)
|
144
|
-
self.logger.info(
|
145
|
-
"Custom OSM file %s fixed. Fixed errors: %d", custom_osm, fixed_errors
|
146
|
-
)
|
147
|
-
|
148
|
-
# Make a copy of a custom osm file to the map directory, so it will be
|
149
|
-
# included in the output archive.
|
150
|
-
if custom_osm:
|
151
|
-
copy_path = os.path.join(self.map_directory, "custom_osm.osm")
|
152
|
-
shutil.copyfile(custom_osm, copy_path)
|
153
|
-
self.logger.debug("Custom OSM file copied to %s", copy_path)
|
154
|
-
|
155
|
-
self.dem_settings = dem_settings
|
156
|
-
log_entry += f"DEM settings: {dem_settings}. "
|
157
|
-
if self.dem_settings.water_depth > 0:
|
158
|
-
# Make sure that the plateau value is >= water_depth
|
159
|
-
self.dem_settings.plateau = max(
|
160
|
-
self.dem_settings.plateau, self.dem_settings.water_depth
|
161
|
-
)
|
75
|
+
# region main settings
|
76
|
+
main_settings = MainSettings.from_map(self)
|
77
|
+
main_settings_json = main_settings.to_json()
|
78
|
+
self.main_settings_path = os.path.join(self.map_directory, "main_settings.json")
|
79
|
+
self._update_main_settings(main_settings_json)
|
80
|
+
# endregion
|
162
81
|
|
163
|
-
|
164
|
-
|
165
|
-
self.
|
166
|
-
|
167
|
-
self.i3d_settings = i3d_settings
|
168
|
-
|
169
|
-
self.
|
170
|
-
|
171
|
-
self.satellite_settings = satellite_settings
|
172
|
-
|
173
|
-
self.logger.info(log_entry)
|
174
|
-
os.makedirs(self.map_directory, exist_ok=True)
|
175
|
-
self.logger.debug("Map directory created: %s", self.map_directory)
|
82
|
+
# region generation settings
|
83
|
+
self.dem_settings = generation_settings.dem_settings
|
84
|
+
self.background_settings = generation_settings.background_settings
|
85
|
+
self.grle_settings = generation_settings.grle_settings
|
86
|
+
self.i3d_settings = generation_settings.i3d_settings
|
87
|
+
self.texture_settings = generation_settings.texture_settings
|
88
|
+
self.satellite_settings = generation_settings.satellite_settings
|
89
|
+
self.process_settings()
|
176
90
|
|
177
|
-
|
178
|
-
|
179
|
-
background_settings=background_settings,
|
180
|
-
grle_settings=grle_settings,
|
181
|
-
i3d_settings=i3d_settings,
|
182
|
-
texture_settings=texture_settings,
|
183
|
-
satellite_settings=satellite_settings,
|
184
|
-
).to_json()
|
91
|
+
self.logger = logger if logger else Logger()
|
92
|
+
generation_settings_json = generation_settings.to_json()
|
185
93
|
|
186
94
|
try:
|
187
|
-
|
95
|
+
send_main_settings(main_settings_json)
|
96
|
+
send_advanced_settings(generation_settings_json)
|
97
|
+
self.logger.info("Settings sent successfully.")
|
188
98
|
except Exception as e:
|
189
|
-
self.logger.
|
190
|
-
|
191
|
-
save_path = os.path.join(self.map_directory, "generation_settings.json")
|
192
|
-
|
193
|
-
with open(save_path, "w", encoding="utf-8") as file:
|
194
|
-
json.dump(settings_json, file, indent=4)
|
195
|
-
|
196
|
-
self.shared_settings = SharedSettings()
|
99
|
+
self.logger.warning("Error sending settings: %s", e)
|
100
|
+
# endregion
|
197
101
|
|
102
|
+
# region JSON data saving
|
103
|
+
os.makedirs(self.map_directory, exist_ok=True)
|
198
104
|
self.texture_custom_schema = kwargs.get("texture_custom_schema", None)
|
199
|
-
if self.texture_custom_schema:
|
200
|
-
save_path = os.path.join(self.map_directory, "texture_custom_schema.json")
|
201
|
-
with open(save_path, "w", encoding="utf-8") as file:
|
202
|
-
json.dump(self.texture_custom_schema, file, indent=4)
|
203
|
-
self.logger.debug("Texture custom schema saved to %s", save_path)
|
204
|
-
|
205
105
|
self.tree_custom_schema = kwargs.get("tree_custom_schema", None)
|
206
|
-
if self.tree_custom_schema:
|
207
|
-
save_path = os.path.join(self.map_directory, "tree_custom_schema.json")
|
208
|
-
with open(save_path, "w", encoding="utf-8") as file:
|
209
|
-
json.dump(self.tree_custom_schema, file, indent=4)
|
210
|
-
self.logger.debug("Tree custom schema saved to %s", save_path)
|
211
106
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
107
|
+
json_data = {
|
108
|
+
"generation_settings.json": generation_settings_json,
|
109
|
+
"texture_custom_schema.json": self.texture_custom_schema,
|
110
|
+
"tree_custom_schema.json": self.tree_custom_schema,
|
111
|
+
}
|
112
|
+
|
113
|
+
for filename, data in json_data.items():
|
114
|
+
mfsutils.dump_json(filename, self.map_directory, data)
|
115
|
+
# endregion
|
216
116
|
|
117
|
+
# region prepare map working directory
|
217
118
|
try:
|
218
119
|
shutil.unpack_archive(game.template_path, self.map_directory)
|
219
120
|
self.logger.debug("Map template unpacked to %s", self.map_directory)
|
220
121
|
except Exception as e:
|
221
122
|
raise RuntimeError(f"Can not unpack map template due to error: {e}") from e
|
123
|
+
# endregion
|
222
124
|
|
223
|
-
self.
|
224
|
-
|
225
|
-
)
|
125
|
+
self.shared_settings = SharedSettings()
|
126
|
+
self.components: list[Component] = []
|
127
|
+
self.custom_background_path = kwargs.get("custom_background_path", None)
|
128
|
+
|
129
|
+
def process_settings(self) -> None:
|
130
|
+
"""Checks the settings by predefined rules and updates them accordingly."""
|
131
|
+
if self.dem_settings.water_depth > 0:
|
132
|
+
# Make sure that the plateau value is >= water_depth
|
133
|
+
self.dem_settings.plateau = max(
|
134
|
+
self.dem_settings.plateau, self.dem_settings.water_depth
|
135
|
+
)
|
226
136
|
|
227
137
|
@staticmethod
|
228
138
|
def suggest_map_directory(coordinates: tuple[float, float], game_code: str) -> str:
|
@@ -241,30 +151,9 @@ class Map:
|
|
241
151
|
str: Directory name.
|
242
152
|
"""
|
243
153
|
lat, lon = coordinates
|
244
|
-
latr =
|
245
|
-
lonr =
|
246
|
-
return f"{
|
247
|
-
|
248
|
-
@staticmethod
|
249
|
-
def get_timestamp() -> str:
|
250
|
-
"""Get current underscore-separated timestamp.
|
251
|
-
|
252
|
-
Returns:
|
253
|
-
str: Current timestamp.
|
254
|
-
"""
|
255
|
-
return datetime.now().strftime("%Y%m%d_%H%M%S")
|
256
|
-
|
257
|
-
@staticmethod
|
258
|
-
def coordinate_to_string(coordinate: float) -> str:
|
259
|
-
"""Convert coordinate to string with 3 decimal places.
|
260
|
-
|
261
|
-
Arguments:
|
262
|
-
coordinate (float): Coordinate value.
|
263
|
-
|
264
|
-
Returns:
|
265
|
-
str: Coordinate as string.
|
266
|
-
"""
|
267
|
-
return f"{coordinate:.3f}".replace(".", "_")
|
154
|
+
latr = mfsutils.coordinate_to_string(lat)
|
155
|
+
lonr = mfsutils.coordinate_to_string(lon)
|
156
|
+
return f"{mfsutils.get_timestamp()}_{game_code}_{latr}_{lonr}".lower()
|
268
157
|
|
269
158
|
@property
|
270
159
|
def texture_schema(self) -> list[dict[str, Any]] | None:
|
@@ -332,14 +221,19 @@ class Map:
|
|
332
221
|
|
333
222
|
def _update_main_settings(self, data: dict[str, Any]) -> None:
|
334
223
|
"""Update main settings with provided data.
|
224
|
+
If the main settings file exists, it will be updated with the new data.
|
225
|
+
If it does not exist, a new file will be created.
|
335
226
|
|
336
227
|
Arguments:
|
337
228
|
data (dict[str, Any]): Data to update main settings.
|
338
229
|
"""
|
339
|
-
|
340
|
-
|
230
|
+
if os.path.exists(self.main_settings_path):
|
231
|
+
with open(self.main_settings_path, "r", encoding="utf-8") as file:
|
232
|
+
main_settings_json = json.load(file)
|
341
233
|
|
342
|
-
|
234
|
+
main_settings_json.update(data)
|
235
|
+
else:
|
236
|
+
main_settings_json = data
|
343
237
|
|
344
238
|
with open(self.main_settings_path, "w", encoding="utf-8") as file:
|
345
239
|
json.dump(main_settings_json, file, indent=4)
|
@@ -452,79 +346,3 @@ class Map:
|
|
452
346
|
except Exception as e:
|
453
347
|
self.logger.debug("Error removing map directory %s: %s", self.map_directory, e)
|
454
348
|
return archive_path
|
455
|
-
|
456
|
-
def get_country_by_coordinates(self) -> str:
|
457
|
-
"""Get country name by coordinates.
|
458
|
-
|
459
|
-
Returns:
|
460
|
-
str: Country name.
|
461
|
-
"""
|
462
|
-
try:
|
463
|
-
geolocator = Nominatim(user_agent="maps4fs")
|
464
|
-
location = geolocator.reverse(self.coordinates, language="en")
|
465
|
-
if location and "country" in location.raw["address"]:
|
466
|
-
return location.raw["address"]["country"]
|
467
|
-
except Exception as e:
|
468
|
-
self.logger.error("Error getting country name by coordinates: %s", e)
|
469
|
-
return "Unknown"
|
470
|
-
return "Unknown"
|
471
|
-
|
472
|
-
|
473
|
-
def check_osm_file(file_path: str) -> bool:
|
474
|
-
"""Tries to read the OSM file using OSMnx and returns True if the file is valid,
|
475
|
-
False otherwise.
|
476
|
-
|
477
|
-
Arguments:
|
478
|
-
file_path (str): Path to the OSM file.
|
479
|
-
|
480
|
-
Returns:
|
481
|
-
bool: True if the file is valid, False otherwise.
|
482
|
-
"""
|
483
|
-
with open(FS25().texture_schema, encoding="utf-8") as f:
|
484
|
-
schema = json.load(f)
|
485
|
-
|
486
|
-
tags = []
|
487
|
-
for element in schema:
|
488
|
-
element_tags = element.get("tags")
|
489
|
-
if element_tags:
|
490
|
-
tags.append(element_tags)
|
491
|
-
|
492
|
-
for tag in tags:
|
493
|
-
try:
|
494
|
-
ox.features_from_xml(file_path, tags=tag)
|
495
|
-
except InsufficientResponseError:
|
496
|
-
continue
|
497
|
-
except Exception: # pylint: disable=W0718
|
498
|
-
return False
|
499
|
-
return True
|
500
|
-
|
501
|
-
|
502
|
-
def fix_osm_file(input_file_path: str, output_file_path: str | None = None) -> tuple[bool, int]:
|
503
|
-
"""Fixes the OSM file by removing all the <relation> nodes and all the nodes with
|
504
|
-
action='delete'.
|
505
|
-
|
506
|
-
Arguments:
|
507
|
-
input_file_path (str): Path to the input OSM file.
|
508
|
-
output_file_path (str | None): Path to the output OSM file. If None, the input file
|
509
|
-
will be overwritten.
|
510
|
-
|
511
|
-
Returns:
|
512
|
-
tuple[bool, int]: A tuple containing the result of the check_osm_file function
|
513
|
-
and the number of fixed errors.
|
514
|
-
"""
|
515
|
-
broken_entries = ["relation", ".//*[@action='delete']"]
|
516
|
-
output_file_path = output_file_path or input_file_path
|
517
|
-
|
518
|
-
tree = ET.parse(input_file_path)
|
519
|
-
root = tree.getroot()
|
520
|
-
|
521
|
-
fixed_errors = 0
|
522
|
-
for entry in broken_entries:
|
523
|
-
for element in root.findall(entry):
|
524
|
-
root.remove(element)
|
525
|
-
fixed_errors += 1
|
526
|
-
|
527
|
-
tree.write(output_file_path) # type: ignore
|
528
|
-
result = check_osm_file(output_file_path) # type: ignore
|
529
|
-
|
530
|
-
return result, fixed_errors
|