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.
Files changed (37) hide show
  1. {maps4fs-2.2.7 → maps4fs-2.2.72}/PKG-INFO +2 -4
  2. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/__init__.py +1 -0
  3. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/background.py +1 -1
  4. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/dem.py +0 -6
  5. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/grle.py +1 -1
  6. maps4fs-2.2.72/maps4fs/generator/config.py +154 -0
  7. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/map.py +76 -258
  8. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/settings.py +60 -17
  9. maps4fs-2.2.72/maps4fs/generator/statistics.py +72 -0
  10. maps4fs-2.2.72/maps4fs/generator/utils.py +160 -0
  11. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs.egg-info/PKG-INFO +2 -4
  12. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs.egg-info/SOURCES.txt +1 -0
  13. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs.egg-info/requires.txt +1 -3
  14. {maps4fs-2.2.7 → maps4fs-2.2.72}/pyproject.toml +2 -4
  15. {maps4fs-2.2.7 → maps4fs-2.2.72}/tests/test_generator.py +13 -6
  16. maps4fs-2.2.7/maps4fs/generator/config.py +0 -93
  17. maps4fs-2.2.7/maps4fs/generator/statistics.py +0 -85
  18. {maps4fs-2.2.7 → maps4fs-2.2.72}/LICENSE.md +0 -0
  19. {maps4fs-2.2.7 → maps4fs-2.2.72}/README.md +0 -0
  20. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/__init__.py +0 -0
  21. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/__init__.py +0 -0
  22. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/base/__init__.py +0 -0
  23. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/base/component.py +0 -0
  24. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/base/component_image.py +0 -0
  25. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/base/component_mesh.py +0 -0
  26. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/base/component_xml.py +0 -0
  27. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/config.py +0 -0
  28. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/i3d.py +0 -0
  29. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/layer.py +0 -0
  30. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/satellite.py +0 -0
  31. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/component/texture.py +0 -0
  32. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/game.py +0 -0
  33. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/generator/qgis.py +0 -0
  34. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs/logger.py +0 -0
  35. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs.egg-info/dependency_links.txt +0 -0
  36. {maps4fs-2.2.7 → maps4fs-2.2.72}/maps4fs.egg-info/top_level.txt +0 -0
  37. {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.7
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, resolution=4)
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, resolution=16)
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 FS25, Game
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
- dem_settings: DEMSettings = DEMSettings(),
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
- main_settings = MainSettings.from_json(
93
- {
94
- "game": game.code, # type: ignore
95
- "latitude": coordinates[0],
96
- "longitude": coordinates[1],
97
- "country": self.get_country_by_coordinates(),
98
- "size": size,
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
- log_entry += f"Custom OSM file: {custom_osm}. "
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
- if self.custom_osm:
134
- osm_is_valid = check_osm_file(self.custom_osm)
135
- if not osm_is_valid:
136
- self.logger.warning(
137
- "Custom OSM file %s is not valid. Attempting to fix it.", custom_osm
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
- self.background_settings = background_settings
164
- log_entry += f"Background settings: {background_settings}. "
165
- self.grle_settings = grle_settings
166
- log_entry += f"GRLE settings: {grle_settings}. "
167
- self.i3d_settings = i3d_settings
168
- log_entry += f"I3D settings: {i3d_settings}. "
169
- self.texture_settings = texture_settings
170
- log_entry += f"Texture settings: {texture_settings}. "
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
- settings_json = GenerationSettings(
178
- dem_settings=dem_settings,
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
- send_advanced_settings(settings_json)
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.error("Error sending advanced settings: %s", e)
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
- self.custom_background_path = kwargs.get("custom_background_path", None)
213
- if self.custom_background_path:
214
- save_path = os.path.join(self.map_directory, "custom_background.png")
215
- shutil.copyfile(self.custom_background_path, save_path)
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.logger.debug(
224
- "MFS_DATA_DIR: %s. MFS_CACHE_DIR %s", mfscfg.MFS_DATA_DIR, mfscfg.MFS_CACHE_DIR
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 = Map.coordinate_to_string(lat)
245
- lonr = Map.coordinate_to_string(lon)
246
- return f"{Map.get_timestamp()}_{game_code}_{latr}_{lonr}".lower()
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
- with open(self.main_settings_path, "r", encoding="utf-8") as file:
340
- main_settings_json = json.load(file)
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
- main_settings_json.update(data)
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