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.
- maps4fs/generator/component/base/component_xml.py +37 -0
- maps4fs/generator/component/config.py +121 -0
- maps4fs/generator/component/i3d.py +1 -1
- maps4fs/generator/game.py +30 -0
- maps4fs/generator/settings.py +2 -0
- {maps4fs-2.0.9.dist-info → maps4fs-2.1.0.dist-info}/METADATA +1 -1
- {maps4fs-2.0.9.dist-info → maps4fs-2.1.0.dist-info}/RECORD +10 -10
- {maps4fs-2.0.9.dist-info → maps4fs-2.1.0.dist-info}/WHEEL +0 -0
- {maps4fs-2.0.9.dist-info → maps4fs-2.1.0.dist-info}/licenses/LICENSE.md +0 -0
- {maps4fs-2.0.9.dist-info → maps4fs-2.1.0.dist-info}/top_level.txt +0 -0
@@ -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 = {
|
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")
|
maps4fs/generator/settings.py
CHANGED
@@ -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=
|
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=
|
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=
|
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=
|
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=
|
23
|
-
maps4fs-2.0.
|
24
|
-
maps4fs-2.0.
|
25
|
-
maps4fs-2.0.
|
26
|
-
maps4fs-2.0.
|
27
|
-
maps4fs-2.0.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|