maps4fs 2.1.5__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.
Files changed (34) hide show
  1. {maps4fs-2.1.5 → maps4fs-2.1.6}/PKG-INFO +1 -1
  2. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs/generator/component/background.py +1 -1
  3. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs/generator/component/base/component_image.py +1 -1
  4. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs/generator/component/base/component_mesh.py +3 -1
  5. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs/generator/component/dem.py +2 -2
  6. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs/generator/component/satellite.py +2 -0
  7. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs/generator/component/texture.py +2 -3
  8. maps4fs-2.1.6/maps4fs/generator/config.py +52 -0
  9. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs/generator/map.py +91 -19
  10. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs/generator/settings.py +102 -1
  11. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs/logger.py +0 -3
  12. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs.egg-info/PKG-INFO +1 -1
  13. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs.egg-info/SOURCES.txt +1 -0
  14. {maps4fs-2.1.5 → maps4fs-2.1.6}/pyproject.toml +1 -1
  15. {maps4fs-2.1.5 → maps4fs-2.1.6}/LICENSE.md +0 -0
  16. {maps4fs-2.1.5 → maps4fs-2.1.6}/README.md +0 -0
  17. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs/__init__.py +0 -0
  18. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs/generator/__init__.py +0 -0
  19. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs/generator/component/__init__.py +0 -0
  20. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs/generator/component/base/__init__.py +0 -0
  21. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs/generator/component/base/component.py +0 -0
  22. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs/generator/component/base/component_xml.py +0 -0
  23. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs/generator/component/config.py +0 -0
  24. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs/generator/component/grle.py +0 -0
  25. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs/generator/component/i3d.py +0 -0
  26. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs/generator/component/layer.py +0 -0
  27. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs/generator/game.py +0 -0
  28. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs/generator/qgis.py +0 -0
  29. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs/generator/statistics.py +0 -0
  30. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs.egg-info/dependency_links.txt +0 -0
  31. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs.egg-info/requires.txt +0 -0
  32. {maps4fs-2.1.5 → maps4fs-2.1.6}/maps4fs.egg-info/top_level.txt +0 -0
  33. {maps4fs-2.1.5 → maps4fs-2.1.6}/setup.cfg +0 -0
  34. {maps4fs-2.1.5 → maps4fs-2.1.6}/tests/test_generator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maps4fs
3
- Version: 2.1.5
3
+ Version: 2.1.6
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
@@ -833,7 +833,7 @@ class Background(MeshComponent, ImageComponent):
833
833
  full_mask[mask == 255] = 255
834
834
 
835
835
  main_dem_path = self.game.dem_file_path(self.map_directory)
836
- dem_image = self.blur_by_mask(dem_image, full_mask)
836
+ dem_image = self.blur_by_mask(dem_image, full_mask, blur_radius=5)
837
837
  dem_image = self.blur_edges_by_mask(dem_image, full_mask)
838
838
 
839
839
  output_size = dem_image.shape[0] + 1
@@ -196,7 +196,7 @@ class ImageComponent(Component):
196
196
  raise ValueError("Data and mask must have the same dimensions.")
197
197
 
198
198
  # Create a blurred version of the data
199
- blurred_data = cv2.GaussianBlur(data, (blur_radius, blur_radius), sigmaX=3)
199
+ blurred_data = cv2.GaussianBlur(data, (blur_radius, blur_radius), sigmaX=10)
200
200
 
201
201
  # Combine the blurred data with the original data using the mask
202
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([mesh_copy, cube_mesh], check_volume=False)
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=self.temp_dir,
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
- try:
87
- main_settings = {
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
- send_main_settings(main_settings)
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
- settings = [
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
 
@@ -252,3 +252,104 @@ class SatelliteSettings(SettingsModel):
252
252
 
253
253
  download_images: bool = False
254
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):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maps4fs
3
- Version: 2.1.5
3
+ Version: 2.1.6
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
@@ -9,6 +9,7 @@ maps4fs.egg-info/dependency_links.txt
9
9
  maps4fs.egg-info/requires.txt
10
10
  maps4fs.egg-info/top_level.txt
11
11
  maps4fs/generator/__init__.py
12
+ maps4fs/generator/config.py
12
13
  maps4fs/generator/game.py
13
14
  maps4fs/generator/map.py
14
15
  maps4fs/generator/qgis.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "maps4fs"
7
- version = "2.1.5"
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