maps4fs 2.0.9__py3-none-any.whl → 2.1.0__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.
@@ -4,6 +4,7 @@ import os
4
4
  from xml.etree import ElementTree as ET
5
5
 
6
6
  from maps4fs.generator.component.base.component import Component
7
+ from maps4fs.generator.settings import Parameters
7
8
 
8
9
 
9
10
  class XMLComponent(Component):
@@ -57,6 +58,20 @@ class XMLComponent(Component):
57
58
 
58
59
  tree.write(xml_path, encoding="utf-8", xml_declaration=True)
59
60
 
61
+ def get_element_from_tree(self, path: str, xml_path: str | None = None) -> ET.Element | None:
62
+ """Finds an element in the XML tree by the path.
63
+
64
+ Arguments:
65
+ path (str): The path to the element.
66
+ xml_path (str, optional): The path to the XML file. Defaults to None.
67
+
68
+ Returns:
69
+ ET.Element | None: The found element or None if not found.
70
+ """
71
+ tree = self.get_tree(xml_path)
72
+ root = tree.getroot()
73
+ return root.find(path) # type: ignore
74
+
60
75
  def get_and_update_element(self, root: ET.Element, path: str, data: dict[str, str]) -> None:
61
76
  """Finds the element by the path and updates it with the provided data.
62
77
 
@@ -106,3 +121,25 @@ class XMLComponent(Component):
106
121
  """
107
122
  element = ET.SubElement(parent, element_name)
108
123
  self.update_element(element, data)
124
+
125
+ def get_height_scale(self) -> int:
126
+ """Returns the height scale from the I3D file.
127
+
128
+ Returns:
129
+ int: The height scale value.
130
+
131
+ Raises:
132
+ ValueError: If the height scale element is not found in the I3D file.
133
+ """
134
+ height_scale_element = self.get_element_from_tree(
135
+ path=".//Scene/TerrainTransformGroup",
136
+ xml_path=self.game.i3d_file_path(self.map_directory),
137
+ )
138
+ if height_scale_element is None:
139
+ raise ValueError("Height scale element not found in the I3D file.")
140
+
141
+ height_scale = height_scale_element.get(Parameters.HEIGHT_SCALE)
142
+ if height_scale is None:
143
+ raise ValueError("Height scale not found in the I3D file.")
144
+
145
+ return int(height_scale)
@@ -2,6 +2,10 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import os
6
+
7
+ import cv2
8
+
5
9
  from maps4fs.generator.component.base.component_xml import XMLComponent
6
10
 
7
11
 
@@ -23,11 +27,15 @@ class Config(XMLComponent):
23
27
  def preprocess(self) -> None:
24
28
  """Gets the path to the map XML file and saves it to the instance variable."""
25
29
  self.xml_path = self.game.map_xml_path(self.map_directory)
30
+ self.fog_parameters: dict[str, int] = {}
26
31
 
27
32
  def process(self) -> None:
28
33
  """Sets the map size in the map.xml file."""
29
34
  self._set_map_size()
30
35
 
36
+ if self.game.fog_processing:
37
+ self._adjust_fog()
38
+
31
39
  def _set_map_size(self) -> None:
32
40
  """Edits map.xml file to set correct map size."""
33
41
  tree = self.get_tree()
@@ -77,6 +85,8 @@ class Config(XMLComponent):
77
85
  data = {
78
86
  "Overview": overview_data,
79
87
  }
88
+ if self.fog_parameters:
89
+ data["Fog"] = self.fog_parameters # type: ignore
80
90
 
81
91
  return data # type: ignore
82
92
 
@@ -92,3 +102,114 @@ class Config(XMLComponent):
92
102
  layers = qgis_layers + qgis_layers_with_margin
93
103
 
94
104
  self.create_qgis_scripts(layers)
105
+
106
+ def _adjust_fog(self) -> None:
107
+ """Adjusts the fog settings in the environment XML file based on the DEM and height scale."""
108
+ self.logger.debug("Adjusting fog settings based on DEM and height scale...")
109
+ try:
110
+ environment_xml_path = self.game.get_environment_xml_path(self.map_directory)
111
+ except NotImplementedError:
112
+ self.logger.warning(
113
+ "Game does not support environment XML file, fog adjustment will not be applied."
114
+ )
115
+ return
116
+
117
+ if not environment_xml_path or not os.path.isfile(environment_xml_path):
118
+ self.logger.warning(
119
+ "Environment XML file not found, fog adjustment will not be applied."
120
+ )
121
+ return
122
+
123
+ self.logger.debug("Will work with environment XML file: %s", environment_xml_path)
124
+
125
+ dem_params = self._get_dem_meter_params()
126
+ if not dem_params:
127
+ return
128
+ maximum_height, minimum_height = dem_params
129
+
130
+ tree = self.get_tree(xml_path=environment_xml_path)
131
+ root = tree.getroot()
132
+
133
+ # Find the <latitude>40.6</latitude> element in the XML file.
134
+ latitude_element = root.find("./latitude") # type: ignore
135
+ if latitude_element is not None:
136
+ map_latitude = round(self.map.coordinates[0], 1)
137
+ latitude_element.text = str(map_latitude)
138
+ self.logger.debug(
139
+ "Found latitude element and set it to: %s",
140
+ latitude_element.text,
141
+ )
142
+
143
+ # The XML file contains 4 <fog> entries in different sections of <weather> representing
144
+ # different seasons, such as <season name="spring">, <season name="summer">, etc.
145
+ # We need to find them all and adjust the parameters accordingly.
146
+ for season in root.findall(".//weather/season"): # type: ignore
147
+ # Example of the <heightFog> element:
148
+ # <heightFog>
149
+ # <groundLevelDensity min="0.05" max="0.2" />
150
+ # <maxHeight min="420" max="600" />
151
+ # </heightFog>
152
+ # We need to adjust the maxheight min and max attributes.
153
+ max_height_element = season.find("./fog/heightFog/maxHeight")
154
+ data = {
155
+ "min": str(minimum_height),
156
+ "max": str(maximum_height),
157
+ }
158
+ self.update_element(max_height_element, data) # type: ignore
159
+ self.logger.debug(
160
+ "Adjusted fog settings for season '%s': min=%s, max=%s",
161
+ season.get("name", "unknown"),
162
+ minimum_height,
163
+ maximum_height,
164
+ )
165
+
166
+ self.logger.debug("Fog adjusted and file will be saved to %s", environment_xml_path)
167
+ self.save_tree(tree, xml_path=environment_xml_path)
168
+
169
+ self.fog_parameters = {
170
+ "minimum_height": minimum_height,
171
+ "maximum_height": maximum_height,
172
+ }
173
+
174
+ def _get_dem_meter_params(self) -> tuple[int, int] | None:
175
+ """Reads the DEM file and returns the maximum and minimum height in meters.
176
+
177
+ Returns:
178
+ tuple[int, int] | None: Maximum and minimum height in meters or None if the DEM file
179
+ is not found or cannot be read.
180
+ """
181
+ self.logger.debug("Reading DEM meter parameters...")
182
+ dem_path = self.game.dem_file_path(self.map_directory)
183
+ if not dem_path or not os.path.isfile(dem_path):
184
+ self.logger.warning("DEM file not found, fog adjustment will not be applied.")
185
+ return None
186
+
187
+ dem_image = cv2.imread(dem_path, cv2.IMREAD_UNCHANGED)
188
+ if dem_image is None:
189
+ self.logger.warning("Failed to read DEM image, fog adjustment will not be applied.")
190
+ return None
191
+ dem_maximum_pixel = dem_image.max()
192
+ dem_minimum_pixel = dem_image.min()
193
+
194
+ self.logger.debug(
195
+ "DEM read successfully. Max pixel: %d, Min pixel: %d",
196
+ dem_maximum_pixel,
197
+ dem_minimum_pixel,
198
+ )
199
+
200
+ try:
201
+ height_scale = self.get_height_scale()
202
+ except ValueError as e:
203
+ self.logger.warning("Error getting height scale from I3D file: %s", e)
204
+ return None
205
+ self.logger.debug("Height scale from I3D file: %d", height_scale)
206
+
207
+ dem_maximum_meter = int(dem_maximum_pixel / height_scale)
208
+ dem_minimum_meter = int(dem_minimum_pixel / height_scale)
209
+ self.logger.debug(
210
+ "DEM maximum height in meters: %d, minimum height in meters: %d",
211
+ dem_maximum_meter,
212
+ dem_minimum_meter,
213
+ )
214
+
215
+ return dem_maximum_meter, dem_minimum_meter
@@ -83,7 +83,7 @@ class I3d(XMLComponent):
83
83
  root = tree.getroot()
84
84
  path = ".//Scene/TerrainTransformGroup"
85
85
 
86
- data = {"heightScale": str(value)}
86
+ data = {Parameters.HEIGHT_SCALE: str(value)}
87
87
 
88
88
  self.get_and_update_element(root, path, data) # type: ignore
89
89
  self.save_tree(tree) # type: ignore
maps4fs/generator/game.py CHANGED
@@ -40,6 +40,7 @@ class Game:
40
40
  _tree_schema: str | None = None
41
41
  _i3d_processing: bool = True
42
42
  _plants_processing: bool = True
43
+ _fog_processing: bool = True
43
44
  _dissolve: bool = True
44
45
 
45
46
  # Order matters! Some components depend on others.
@@ -189,6 +190,16 @@ class Game:
189
190
  str: The path to the farmlands xml file."""
190
191
  raise NotImplementedError
191
192
 
193
+ def get_environment_xml_path(self, map_directory: str) -> str:
194
+ """Returns the path to the environment xml file.
195
+
196
+ Arguments:
197
+ map_directory (str): The path to the map directory.
198
+
199
+ Returns:
200
+ str: The path to the environment xml file."""
201
+ raise NotImplementedError
202
+
192
203
  def i3d_file_path(self, map_directory: str) -> str:
193
204
  """Returns the path to the i3d file.
194
205
 
@@ -207,6 +218,14 @@ class Game:
207
218
  bool: True if the i3d file should be processed, False otherwise."""
208
219
  return self._i3d_processing
209
220
 
221
+ @property
222
+ def fog_processing(self) -> bool:
223
+ """Returns whether the fog should be processed.
224
+
225
+ Returns:
226
+ bool: True if the fog should be processed, False otherwise."""
227
+ return self._fog_processing
228
+
210
229
  @property
211
230
  def plants_processing(self) -> bool:
212
231
  """Returns whether the plants should be processed.
@@ -250,6 +269,7 @@ class FS22(Game):
250
269
  _map_template_path = os.path.join(working_directory, "data", "fs22-map-template.zip")
251
270
  _texture_schema = os.path.join(working_directory, "data", "fs22-texture-schema.json")
252
271
  _i3d_processing = False
272
+ _fog_processing = False
253
273
  _plants_processing = False
254
274
  _dissolve = False
255
275
 
@@ -345,3 +365,13 @@ class FS25(Game):
345
365
  Returns:
346
366
  str: The path to the farmlands xml file."""
347
367
  return os.path.join(map_directory, "map", "config", "farmlands.xml")
368
+
369
+ def get_environment_xml_path(self, map_directory: str) -> str:
370
+ """Returns the path to the environment xml file.
371
+
372
+ Arguments:
373
+ map_directory (str): The path to the map directory.
374
+
375
+ Returns:
376
+ str: The path to the environment xml file."""
377
+ return os.path.join(map_directory, "map", "config", "environment.xml")
@@ -40,6 +40,8 @@ class Parameters:
40
40
 
41
41
  WATER_ADD_WIDTH = 2
42
42
 
43
+ HEIGHT_SCALE = "heightScale"
44
+
43
45
 
44
46
  class SharedSettings(BaseModel):
45
47
  """Represents the shared settings for all components."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maps4fs
3
- Version: 2.0.9
3
+ Version: 2.1.0
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
@@ -1,17 +1,17 @@
1
1
  maps4fs/__init__.py,sha256=Fy521EmVAWnhu6OvOInc97yrtJotFzcV0YfRB2b9O4s,314
2
2
  maps4fs/logger.py,sha256=WDfR14hxqy8b6xtwL6YIu2LGzFO1sbt0LxMgfsDTOkA,865
3
3
  maps4fs/generator/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
4
- maps4fs/generator/game.py,sha256=g8lMHuuRRmJLSDsQTAMv8p_-qntYMiZKnAqn7ru96i0,11645
4
+ maps4fs/generator/game.py,sha256=Nl8xqEwW-E04l2D3e9ff7kkzPcolr1ndnXxQJlLMBDM,12627
5
5
  maps4fs/generator/map.py,sha256=x_NjUwKkDnh2cC61QpTfX88mFHAkQG1sxVwZckoV0mE,16357
6
6
  maps4fs/generator/qgis.py,sha256=Es8hLuqN_KH8lDfnJE6He2rWYbAKJ3RGPn-o87S6CPI,6116
7
- maps4fs/generator/settings.py,sha256=iEazY2goQlyYgJA6-jhCvDOh7EZ4bDzBDGocJ5K3cjc,7544
7
+ maps4fs/generator/settings.py,sha256=FIE02u2gWwo1WC3rUIZmQziC994dhDH38HflLrXT_DM,7578
8
8
  maps4fs/generator/statistics.py,sha256=aynS3zbAtiwnU_YLKHPTiiaKW98_suvQUhy1SGBA6mc,2448
9
9
  maps4fs/generator/component/__init__.py,sha256=s01yVVVi8R2xxNvflu2D6wTd9I_g73AMM2x7vAC7GX4,490
10
10
  maps4fs/generator/component/background.py,sha256=wqTgndTfRIGlLMpMXqJMw_6eVdH1JtMei46ZIXlT9X4,29480
11
- maps4fs/generator/component/config.py,sha256=IP530sapLofFskSnBEB96G0aUSd6Sno0G9ET3ca0ZOQ,3696
11
+ maps4fs/generator/component/config.py,sha256=uL76h9UwyhZKZmbxz0mBmWtEPN6qYay4epTEqqtej60,8601
12
12
  maps4fs/generator/component/dem.py,sha256=SH_2Zu5O4dhWtZeOkCwzDF4RU04XhTdpGFYaRYJkdjc,11905
13
13
  maps4fs/generator/component/grle.py,sha256=8K32pC_ar9CR6p0EhCe2X--wEoIxFzJCPcN9ydHQ1LE,19747
14
- maps4fs/generator/component/i3d.py,sha256=iGBDaCPC-EqoJ7C4PrKSel6mW4U1DPfgSp6pPNR-Xr8,23788
14
+ maps4fs/generator/component/i3d.py,sha256=B0-YTeiIkon2v7OLFNMEw38ouuuu4G54WbYIQm6I5a8,23798
15
15
  maps4fs/generator/component/layer.py,sha256=-br4gAGcGeBNb3ldch9XFEK0lhXqb1IbArhFB5Owu54,6186
16
16
  maps4fs/generator/component/satellite.py,sha256=OsxoNOCgkUtRzL7Geuqubsf6uoKXAIN8jQvrJ7IFeAI,4958
17
17
  maps4fs/generator/component/texture.py,sha256=krtvOS0hH8BTzfxd2jsTo3bIJYRkIVbaz6FGhWY8L1o,33921
@@ -19,9 +19,9 @@ maps4fs/generator/component/base/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX5
19
19
  maps4fs/generator/component/base/component.py,sha256=Vgmdsn1ZC37EwWi4Va4uYVt0RnFLiARTtZ-R5GTSrrM,22877
20
20
  maps4fs/generator/component/base/component_image.py,sha256=2NYJgCU8deHl7O2FYFYk38WKZVJygFoc2gjBXwH6vjM,5970
21
21
  maps4fs/generator/component/base/component_mesh.py,sha256=lZuF-9hzDYodL4Ub4RgvFjsozxTPZJrOaLQmlNTrZ7I,9074
22
- maps4fs/generator/component/base/component_xml.py,sha256=V9pGUvHh6UF6BP0qFARqDq9vquoAgq1zJqhOgBoeS_Y,3983
23
- maps4fs-2.0.9.dist-info/licenses/LICENSE.md,sha256=pTKD_oUexcn-yccFCTrMeLkZy0ifLRa-VNcDLqLZaIw,10749
24
- maps4fs-2.0.9.dist-info/METADATA,sha256=JhBYuvnO_3NuZlvzWafXt94qffCQPdO5GXotONW69PE,44135
25
- maps4fs-2.0.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
26
- maps4fs-2.0.9.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
27
- maps4fs-2.0.9.dist-info/RECORD,,
22
+ maps4fs/generator/component/base/component_xml.py,sha256=MT-VhU2dEckLFxAgmxg6V3gnv11di_94Qq6atfpOLdc,5342
23
+ maps4fs-2.1.0.dist-info/licenses/LICENSE.md,sha256=pTKD_oUexcn-yccFCTrMeLkZy0ifLRa-VNcDLqLZaIw,10749
24
+ maps4fs-2.1.0.dist-info/METADATA,sha256=-_6ei79gfvcNStmIBB3iu8zlf4Y39EOZWp1pvvIQdwI,44135
25
+ maps4fs-2.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
26
+ maps4fs-2.1.0.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
27
+ maps4fs-2.1.0.dist-info/RECORD,,