maps4fs 2.8.9__py3-none-any.whl → 2.9.37__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,118 @@
1
+ """Module for performance monitoring during map generation."""
2
+
3
+ import functools
4
+ import threading
5
+ import uuid
6
+ from collections import defaultdict
7
+ from contextlib import contextmanager
8
+ from time import perf_counter
9
+ from typing import Callable, Generator
10
+
11
+ from maps4fs.generator.utils import Singleton
12
+ from maps4fs.logger import Logger
13
+
14
+ logger = Logger(name="MAPS4FS_MONITOR")
15
+
16
+ _local = threading.local()
17
+
18
+
19
+ def get_current_session() -> str | None:
20
+ """Get the current session name from thread-local storage."""
21
+ return getattr(_local, "current_session", None)
22
+
23
+
24
+ @contextmanager
25
+ def performance_session(session_id: str | None = None) -> Generator[str, None, None]:
26
+ """Context manager for performance monitoring session.
27
+
28
+ Arguments:
29
+ session_id (str, optional): Custom session ID. If None, generates UUID.
30
+ """
31
+ if session_id is None:
32
+ session_id = str(uuid.uuid4())
33
+
34
+ _local.current_session = session_id
35
+
36
+ try:
37
+ yield session_id
38
+ finally:
39
+ _local.current_session = None
40
+
41
+
42
+ class PerformanceMonitor(metaclass=Singleton):
43
+ """Singleton class for monitoring performance metrics."""
44
+
45
+ def __init__(self) -> None:
46
+ self.sessions: dict[str, dict[str, dict[str, float]]] = defaultdict(
47
+ lambda: defaultdict(lambda: defaultdict(float))
48
+ )
49
+
50
+ def add_record(self, session: str, component: str, function: str, time_taken: float) -> None:
51
+ """Add a performance record.
52
+
53
+ Arguments:
54
+ session (str): The session name.
55
+ component (str): The component/class name.
56
+ function (str): The function/method name.
57
+ time_taken (float): Time taken in seconds.
58
+ """
59
+ self.sessions[session][component][function] += time_taken
60
+
61
+ def get_session_json(self, session: str) -> dict[str, dict[str, float]]:
62
+ """Get performance data for a session in JSON-serializable format.
63
+
64
+ Arguments:
65
+ session (str): The session name.
66
+
67
+ Returns:
68
+ dict[str, dict[str, float]]: Performance data.
69
+ """
70
+ return self.sessions.get(session, {})
71
+
72
+
73
+ def monitor_performance(func: Callable) -> Callable:
74
+ """Decorator to monitor performance of methods/functions.
75
+
76
+ Arguments:
77
+ func (callable) -- The function to be monitored.
78
+
79
+ Returns:
80
+ callable -- The wrapped function with performance monitoring.
81
+ """
82
+
83
+ @functools.wraps(func)
84
+ def wrapper(*args, **kwargs):
85
+ if args and hasattr(args[0], "__class__"):
86
+ class_name = args[0].__class__.__name__
87
+ elif args and hasattr(args[0], "__name__"):
88
+ class_name = args[0].__name__
89
+ elif "." in func.__qualname__:
90
+ class_name = func.__qualname__.split(".")[0]
91
+ else:
92
+ class_name = None
93
+
94
+ function_name = func.__name__
95
+
96
+ start = perf_counter()
97
+ result = func(*args, **kwargs)
98
+ end = perf_counter()
99
+ time_taken = round(end - start, 5)
100
+
101
+ session_name = get_current_session()
102
+
103
+ try:
104
+ if session_name and time_taken > 0.001 and class_name:
105
+ PerformanceMonitor().add_record(session_name, class_name, function_name, time_taken)
106
+ logger.debug(
107
+ "[PERFORMANCE] %s | %s | %s | %s",
108
+ session_name,
109
+ class_name,
110
+ function_name,
111
+ time_taken,
112
+ )
113
+ except Exception:
114
+ pass
115
+
116
+ return result
117
+
118
+ return wrapper
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import re
6
6
  from datetime import datetime
7
- from typing import TYPE_CHECKING, Any, NamedTuple
7
+ from typing import TYPE_CHECKING, Any, Literal, NamedTuple
8
8
 
9
9
  from pydantic import BaseModel, ConfigDict
10
10
 
@@ -279,6 +279,21 @@ class SatelliteSettings(SettingsModel):
279
279
  zoom_level: int = 16
280
280
 
281
281
 
282
+ class BuildingSettings(SettingsModel):
283
+ """Represents the advanced settings for building component.
284
+
285
+ Attributes:
286
+ generate_buildings (bool): generate buildings on the map.
287
+ region (Literal["auto", "all", "EU", "US"]): region for the buildings.
288
+ tolerance_factor (float): tolerance factor representing allowed dimension difference
289
+ between OSM building footprint and the building model footprint.
290
+ """
291
+
292
+ generate_buildings: bool = True
293
+ region: Literal["auto", "all", "EU", "US"] = "auto"
294
+ tolerance_factor: float = 0.3
295
+
296
+
282
297
  class GenerationSettings(BaseModel):
283
298
  """Represents the settings for the map generation process."""
284
299
 
@@ -288,6 +303,7 @@ class GenerationSettings(BaseModel):
288
303
  i3d_settings: I3DSettings = I3DSettings()
289
304
  texture_settings: TextureSettings = TextureSettings()
290
305
  satellite_settings: SatelliteSettings = SatelliteSettings()
306
+ building_settings: BuildingSettings = BuildingSettings()
291
307
 
292
308
  def to_json(self) -> dict[str, Any]:
293
309
  """Convert the GenerationSettings instance to JSON format.
@@ -302,6 +318,7 @@ class GenerationSettings(BaseModel):
302
318
  "I3DSettings": self.i3d_settings.model_dump(),
303
319
  "TextureSettings": self.texture_settings.model_dump(),
304
320
  "SatelliteSettings": self.satellite_settings.model_dump(),
321
+ "BuildingSettings": self.building_settings.model_dump(),
305
322
  }
306
323
 
307
324
  @classmethod
@@ -80,3 +80,13 @@ def send_survey(data: dict[str, Any]) -> None:
80
80
  """
81
81
  endpoint = f"{STATS_HOST}/receive_survey"
82
82
  post(endpoint, data)
83
+
84
+
85
+ def send_performance_report(data: dict[str, Any]) -> None:
86
+ """Send performance report to the statistics server.
87
+
88
+ Arguments:
89
+ data (dict[str, Any]): The performance report data to send.
90
+ """
91
+ endpoint = f"{STATS_HOST}/receive_performance_report"
92
+ post(endpoint, data)
@@ -4,7 +4,7 @@ import json
4
4
  import os
5
5
  import shutil
6
6
  from datetime import datetime
7
- from typing import Any
7
+ from typing import Any, Literal
8
8
  from xml.etree import ElementTree as ET
9
9
 
10
10
  import osmnx as ox
@@ -122,6 +122,20 @@ def get_country_by_coordinates(coordinates: tuple[float, float]) -> str:
122
122
  return "Unknown"
123
123
 
124
124
 
125
+ def get_region_by_coordinates(coordinates: tuple[float, float]) -> Literal["EU", "US"]:
126
+ """Get region (EU or US) by coordinates.
127
+
128
+ Arguments:
129
+ coordinates (tuple[float, float]): Latitude and longitude.
130
+
131
+ Returns:
132
+ Literal["EU", "US"]: Region code.
133
+ """
134
+ country = get_country_by_coordinates(coordinates)
135
+ # If country is not US, assume EU for simplicity.
136
+ return "US" if country == "United States" else "EU"
137
+
138
+
125
139
  def get_timestamp() -> str:
126
140
  """Get current underscore-separated timestamp.
127
141
 
@@ -158,3 +172,14 @@ def dump_json(filename: str, directory: str, data: dict[Any, Any] | Any | None)
158
172
  save_path = os.path.join(directory, filename)
159
173
  with open(save_path, "w", encoding="utf-8") as file:
160
174
  json.dump(data, file, indent=4)
175
+
176
+
177
+ class Singleton(type):
178
+ """A metaclass for creating singleton classes."""
179
+
180
+ _instances: dict[Any, Any] = {}
181
+
182
+ def __call__(cls, *args, **kwargs):
183
+ if cls not in cls._instances:
184
+ cls._instances[cls] = super().__call__(*args, **kwargs)
185
+ return cls._instances[cls]
maps4fs/logger.py CHANGED
@@ -14,8 +14,9 @@ class Logger(logging.Logger):
14
14
  self,
15
15
  level: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = "INFO",
16
16
  to_stdout: bool = True,
17
+ **kwargs,
17
18
  ):
18
- super().__init__(LOGGER_NAME)
19
+ super().__init__(kwargs.pop("name", LOGGER_NAME))
19
20
  self.setLevel(level)
20
21
  self.stdout_handler = logging.StreamHandler(sys.stdout)
21
22
  formatter = "%(name)s | %(levelname)s | %(asctime)s | %(message)s"
@@ -1,15 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maps4fs
3
- Version: 2.8.9
3
+ Version: 2.9.37
4
4
  Summary: Generate map templates for Farming Simulator from real places.
5
5
  Author-email: iwatkot <iwatkot@gmail.com>
6
- License: GNU Affero General Public License v3.0
6
+ License-Expression: CC-BY-NC-4.0
7
7
  Project-URL: Homepage, https://github.com/iwatkot/maps4fs
8
8
  Project-URL: Repository, https://github.com/iwatkot/maps4fs
9
9
  Keywords: farmingsimulator,fs,farmingsimulator22,farmingsimulator25,fs22,fs25
10
10
  Classifier: Programming Language :: Python :: 3.11
11
11
  Classifier: Programming Language :: Python :: 3.12
12
- Classifier: License :: OSI Approved :: GNU Affero General Public License v3
13
12
  Classifier: Operating System :: OS Independent
14
13
  Description-Content-Type: text/markdown
15
14
  License-File: LICENSE.md
@@ -76,6 +75,7 @@ Dynamic: license-file
76
75
  🚜 **Farming Simulator 22 & 25** - Generate maps for both game versions<br>
77
76
  🗺️ **Flexible Map Sizes** - 2x2, 4x4, 8x8, 16x16 km + custom sizes<br>
78
77
  ✂️ **Map Scaling & Rotation** - Perfect positioning and sizing control<br>
78
+ 🏘️ **Adding buildings** - Automatic building placement system<br>
79
79
 
80
80
  🌍 **Real-World Foundation** - Built from OpenStreetMap and satellite data<br>
81
81
  🏞️ **Accurate Terrain** - SRTM elevation data with custom DTM support<br>
@@ -86,7 +86,9 @@ Dynamic: license-file
86
86
  🌲 **Natural Forests** - Tree placement with customizable density<br>
87
87
  🌊 **Water Systems** - Rivers, lakes, and water planes<br>
88
88
  🌿 **Decorative Foliage** - Realistic vegetation and grass areas<br>
89
+ 🏘️ **Intelligent Building Placement** - Automatic building placement in appropriate areas<br>
89
90
 
91
+ 🚧 **3D Road Generation** - Automatic road mesh creation with custom textures<br>
90
92
  🚧 **Complete Spline Networks** - Roads and infrastructure<br>
91
93
  🔷 **Background Terrain** - 3D *.obj files for surrounding landscape<br>
92
94
  📦 **Giants Editor Ready** - Import and start building immediately<br>
@@ -96,7 +98,7 @@ Dynamic: license-file
96
98
  📚 **Complete Documentation** - [Detailed guides](https://maps4fs.gitbook.io/docs) and video tutorials<br>
97
99
 
98
100
  <p align="center">
99
- <img src="https://github.com/iwatkot/maps4fsui/releases/download/0.0.2/mfstr.gif"><br>
101
+ <img src="https://github.com/iwatkot/maps4fs/releases/download/2.9.34/mfsg.gif"><br>
100
102
  <i>Example of map generated with Maps4FS with no manual edits.</i>
101
103
  </p>
102
104
 
@@ -0,0 +1,32 @@
1
+ maps4fs/__init__.py,sha256=5ixsCA5vgcIV0OrF9EJBm91Mmc_KfMiDRM-QyifMAvo,386
2
+ maps4fs/logger.py,sha256=aZAa9glzgvH6ySVDLelSPTwHfWZtpGK5YBl-ufNUsPg,801
3
+ maps4fs/generator/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
4
+ maps4fs/generator/config.py,sha256=o_zRT8Nauy1djELsC867yD7GTs1fwz1OtZ2jE4w_7Ts,9797
5
+ maps4fs/generator/game.py,sha256=B7-a9gEBIOKAmC2eAAnEx65Ghx6U-edvNvU4a3WmjIw,18513
6
+ maps4fs/generator/map.py,sha256=kkyMETKteFhnWRgmcR8gjdNBQy4roQzcdlFw1nE5chE,16116
7
+ maps4fs/generator/monitor.py,sha256=Yrc7rClpmJK53SRzrOYZNBlwJmb5l6TkW-laFbyBEno,3524
8
+ maps4fs/generator/qgis.py,sha256=Es8hLuqN_KH8lDfnJE6He2rWYbAKJ3RGPn-o87S6CPI,6116
9
+ maps4fs/generator/settings.py,sha256=jrrIILNRtIpj7hpLrQqLTIagTY8tdJlLZDEN1M4n3Yc,14116
10
+ maps4fs/generator/statistics.py,sha256=ol0MTiehcCbQFfyYA7cKU-M4_cjiLCktnGbid4GYABU,2641
11
+ maps4fs/generator/utils.py,sha256=qaHmS5I30OhDwd213bbctlplQQlX-qkHugyszXGmh0U,5587
12
+ maps4fs/generator/component/__init__.py,sha256=s01yVVVi8R2xxNvflu2D6wTd9I_g73AMM2x7vAC7GX4,490
13
+ maps4fs/generator/component/background.py,sha256=3nzrIENqHVJiZzICqtMdgD-QbGS9125IeTCvO60y7jI,50155
14
+ maps4fs/generator/component/building.py,sha256=VoLjj6mDT-4kVfwxXP-lD0r4vJVYszyWZtciVFwdkIk,27402
15
+ maps4fs/generator/component/config.py,sha256=tI2RQaGIqBgJIi9KjYfMZZ8AWg_YVUm6KKsBHGV241g,31285
16
+ maps4fs/generator/component/dem.py,sha256=vMVJtU2jAS-2lfB9JsqodZsrUvY1h5xr3Dh5qk6txwk,11895
17
+ maps4fs/generator/component/grle.py,sha256=FAcGmG7yq0icOElRoO4QMsVisZMsNrLhfNSWvGKnOHg,29899
18
+ maps4fs/generator/component/i3d.py,sha256=idOixc2UF6RSvQud9GCWmMuTcY1qKVuzijUHakSzWks,28952
19
+ maps4fs/generator/component/layer.py,sha256=-pnKPlZFmsU-OmvG7EX-Nb55eETY0NTiYnnkCIRJuvY,7731
20
+ maps4fs/generator/component/road.py,sha256=YHX3-GcRXEyy9UG5KpUwC75n0FEIysn9PJnC7-tiwkw,27850
21
+ maps4fs/generator/component/satellite.py,sha256=1bPqd8JqAPqU0tEI9m-iuljMW9hXqlaCIxvq7kdpMY0,5219
22
+ maps4fs/generator/component/texture.py,sha256=46eG1EUWqDEppdxkimgu_gCBcNf2KqvZXfkak3GO8EI,38584
23
+ maps4fs/generator/component/base/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
24
+ maps4fs/generator/component/base/component.py,sha256=-7H3donrH19f0_rivNyI3fgLsiZkntXfGywEx4tOnM4,23924
25
+ maps4fs/generator/component/base/component_image.py,sha256=GXFkEFARNRkWkDiGSjvU4WX6f_8s6R1t2ZYqZflv1jk,9626
26
+ maps4fs/generator/component/base/component_mesh.py,sha256=2wGe_-wAZVRljMKzzVJ8jdzIETWg7LjxGj8A3inH5eI,25550
27
+ maps4fs/generator/component/base/component_xml.py,sha256=MT-VhU2dEckLFxAgmxg6V3gnv11di_94Qq6atfpOLdc,5342
28
+ maps4fs-2.9.37.dist-info/licenses/LICENSE.md,sha256=LzOB2xtN1AlHJi4hqoEsBlYLfmfRyXCPC2417miYoBc,19579
29
+ maps4fs-2.9.37.dist-info/METADATA,sha256=rJbXUF1gfOOgila4vS8faBTPMkZSKX0bxCxotgQl940,10204
30
+ maps4fs-2.9.37.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
+ maps4fs-2.9.37.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
32
+ maps4fs-2.9.37.dist-info/RECORD,,