maps4fs 2.8.9__tar.gz → 2.9.0__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.
Potentially problematic release.
This version of maps4fs might be problematic. Click here for more details.
- {maps4fs-2.8.9 → maps4fs-2.9.0}/PKG-INFO +1 -1
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs/generator/component/background.py +13 -1
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs/generator/component/config.py +5 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs/generator/component/dem.py +7 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs/generator/component/grle.py +9 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs/generator/component/i3d.py +4 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs/generator/component/satellite.py +3 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs/generator/component/texture.py +35 -14
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs/generator/map.py +70 -39
- maps4fs-2.9.0/maps4fs/generator/monitor.py +108 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs/generator/statistics.py +10 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs/generator/utils.py +11 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs.egg-info/PKG-INFO +1 -1
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs.egg-info/SOURCES.txt +1 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/pyproject.toml +1 -1
- {maps4fs-2.8.9 → maps4fs-2.9.0}/LICENSE.md +0 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/README.md +0 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs/__init__.py +0 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs/generator/__init__.py +0 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs/generator/component/__init__.py +0 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs/generator/component/base/__init__.py +0 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs/generator/component/base/component.py +0 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs/generator/component/base/component_image.py +0 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs/generator/component/base/component_mesh.py +0 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs/generator/component/base/component_xml.py +0 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs/generator/component/layer.py +0 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs/generator/config.py +0 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs/generator/game.py +0 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs/generator/qgis.py +0 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs/generator/settings.py +0 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs/logger.py +0 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs.egg-info/dependency_links.txt +0 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs.egg-info/requires.txt +0 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/maps4fs.egg-info/top_level.txt +0 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/setup.cfg +0 -0
- {maps4fs-2.8.9 → maps4fs-2.9.0}/tests/test_generator.py +0 -0
|
@@ -19,6 +19,7 @@ from maps4fs.generator.component.base.component_image import ImageComponent
|
|
|
19
19
|
from maps4fs.generator.component.base.component_mesh import MeshComponent
|
|
20
20
|
from maps4fs.generator.component.dem import DEM
|
|
21
21
|
from maps4fs.generator.component.texture import Texture
|
|
22
|
+
from maps4fs.generator.monitor import monitor_performance
|
|
22
23
|
from maps4fs.generator.settings import Parameters
|
|
23
24
|
|
|
24
25
|
SEGMENT_LENGTH = 2
|
|
@@ -38,6 +39,7 @@ class Background(MeshComponent, ImageComponent):
|
|
|
38
39
|
info, warning. If not provided, default logging will be used.
|
|
39
40
|
"""
|
|
40
41
|
|
|
42
|
+
@monitor_performance
|
|
41
43
|
def preprocess(self) -> None:
|
|
42
44
|
"""Registers the DEMs for the background terrain."""
|
|
43
45
|
self.stl_preview_path = os.path.join(self.previews_directory, "background_dem.stl")
|
|
@@ -141,6 +143,7 @@ class Background(MeshComponent, ImageComponent):
|
|
|
141
143
|
"Mesh processing is disabled for the game, skipping water mesh processing."
|
|
142
144
|
)
|
|
143
145
|
|
|
146
|
+
@monitor_performance
|
|
144
147
|
def create_foundations(self, dem_image: np.ndarray) -> np.ndarray:
|
|
145
148
|
"""Creates foundations for buildings based on the DEM data.
|
|
146
149
|
|
|
@@ -246,6 +249,7 @@ class Background(MeshComponent, ImageComponent):
|
|
|
246
249
|
)
|
|
247
250
|
self.create_qgis_scripts([qgis_layer, qgis_layer_with_margin])
|
|
248
251
|
|
|
252
|
+
@monitor_performance
|
|
249
253
|
def generate_obj_files(self) -> None:
|
|
250
254
|
"""Iterates over all dems and generates 3D obj files based on DEM data.
|
|
251
255
|
If at least one DEM file is missing, the generation will be stopped at all.
|
|
@@ -328,8 +332,9 @@ class Background(MeshComponent, ImageComponent):
|
|
|
328
332
|
return resolution
|
|
329
333
|
return Parameters.MAXIMUM_BACKGROUND_TEXTURE_SIZE
|
|
330
334
|
|
|
335
|
+
@monitor_performance
|
|
331
336
|
def decimate_background_mesh(self) -> None:
|
|
332
|
-
"""
|
|
337
|
+
"""Decimates the background mesh based on the map size."""
|
|
333
338
|
if not self.assets.background_mesh or not os.path.isfile(self.assets.background_mesh):
|
|
334
339
|
self.logger.warning("Background mesh not found, cannot generate i3d background.")
|
|
335
340
|
return
|
|
@@ -362,6 +367,7 @@ class Background(MeshComponent, ImageComponent):
|
|
|
362
367
|
|
|
363
368
|
self.assets.decimated_background_mesh = decimated_save_path
|
|
364
369
|
|
|
370
|
+
@monitor_performance
|
|
365
371
|
def texture_background_mesh(self) -> None:
|
|
366
372
|
"""Textures the background mesh using satellite imagery."""
|
|
367
373
|
satellite_component = self.map.get_satellite_component()
|
|
@@ -427,6 +433,7 @@ class Background(MeshComponent, ImageComponent):
|
|
|
427
433
|
self.logger.error("Could not texture background mesh: %s", e)
|
|
428
434
|
return
|
|
429
435
|
|
|
436
|
+
@monitor_performance
|
|
430
437
|
def convert_background_mesh_to_i3d(self) -> bool:
|
|
431
438
|
"""Converts the textured background mesh to i3d format.
|
|
432
439
|
|
|
@@ -498,6 +505,7 @@ class Background(MeshComponent, ImageComponent):
|
|
|
498
505
|
with open(file_path, "w", encoding="utf-8") as f:
|
|
499
506
|
f.write(content + "\n\n" + note)
|
|
500
507
|
|
|
508
|
+
@monitor_performance
|
|
501
509
|
def convert_water_mesh_to_i3d(self) -> bool:
|
|
502
510
|
"""Converts the line-based water mesh to i3d format.
|
|
503
511
|
|
|
@@ -730,6 +738,7 @@ class Background(MeshComponent, ImageComponent):
|
|
|
730
738
|
cv2.imwrite(colored_dem_path, dem_data_colored)
|
|
731
739
|
return colored_dem_path
|
|
732
740
|
|
|
741
|
+
@monitor_performance
|
|
733
742
|
def create_background_textures(self) -> None:
|
|
734
743
|
"""Creates background textures for the map."""
|
|
735
744
|
layers_schema = self.map.texture_schema
|
|
@@ -782,6 +791,7 @@ class Background(MeshComponent, ImageComponent):
|
|
|
782
791
|
|
|
783
792
|
cv2.imwrite(self.water_resources_path, background_image)
|
|
784
793
|
|
|
794
|
+
@monitor_performance
|
|
785
795
|
def subtraction(self) -> None:
|
|
786
796
|
"""Subtracts the water depth from the DEM data where the water resources are located."""
|
|
787
797
|
if not self.water_resources_path:
|
|
@@ -967,6 +977,7 @@ class Background(MeshComponent, ImageComponent):
|
|
|
967
977
|
mesh = trimesh.Trimesh(vertices=vertices, faces=faces, process=False)
|
|
968
978
|
return mesh
|
|
969
979
|
|
|
980
|
+
@monitor_performance
|
|
970
981
|
def generate_water_resources_obj(self) -> None:
|
|
971
982
|
"""Generates 3D obj files based on water resources data."""
|
|
972
983
|
self.logger.debug("Starting water resources generation...")
|
|
@@ -1033,6 +1044,7 @@ class Background(MeshComponent, ImageComponent):
|
|
|
1033
1044
|
|
|
1034
1045
|
self.plane_from_np(elevated_water, elevated_save_path, include_zeros=False)
|
|
1035
1046
|
|
|
1047
|
+
@monitor_performance
|
|
1036
1048
|
def flatten_roads(self) -> None:
|
|
1037
1049
|
"""Flattens the roads in the DEM data by averaging the height values along the road polylines."""
|
|
1038
1050
|
if not self.not_resized_path or not os.path.isfile(self.not_resized_path):
|
|
@@ -10,6 +10,7 @@ import numpy as np
|
|
|
10
10
|
import maps4fs.generator.utils as mfsutils
|
|
11
11
|
from maps4fs.generator.component.base.component_image import ImageComponent
|
|
12
12
|
from maps4fs.generator.component.base.component_xml import XMLComponent
|
|
13
|
+
from maps4fs.generator.monitor import monitor_performance
|
|
13
14
|
from maps4fs.generator.settings import Parameters
|
|
14
15
|
|
|
15
16
|
# Defines coordinates for country block on the license plate texture.
|
|
@@ -120,6 +121,7 @@ class Config(XMLComponent, ImageComponent):
|
|
|
120
121
|
|
|
121
122
|
self.create_qgis_scripts(layers)
|
|
122
123
|
|
|
124
|
+
@monitor_performance
|
|
123
125
|
def _adjust_fog(self) -> None:
|
|
124
126
|
"""Adjusts the fog settings in the environment XML file based on the DEM and height scale."""
|
|
125
127
|
self.logger.debug("Adjusting fog settings based on DEM and height scale...")
|
|
@@ -231,6 +233,7 @@ class Config(XMLComponent, ImageComponent):
|
|
|
231
233
|
|
|
232
234
|
return dem_maximum_meter, dem_minimum_meter
|
|
233
235
|
|
|
236
|
+
@monitor_performance
|
|
234
237
|
def _set_overview(self) -> None:
|
|
235
238
|
"""Generates and sets the overview image for the map."""
|
|
236
239
|
try:
|
|
@@ -347,6 +350,7 @@ class Config(XMLComponent, ImageComponent):
|
|
|
347
350
|
"HR",
|
|
348
351
|
}
|
|
349
352
|
|
|
353
|
+
@monitor_performance
|
|
350
354
|
def update_license_plates(self):
|
|
351
355
|
"""Updates license plates for the specified country."""
|
|
352
356
|
try:
|
|
@@ -565,6 +569,7 @@ class Config(XMLComponent, ImageComponent):
|
|
|
565
569
|
self.save_tree(tree, xml_path=i3d_path)
|
|
566
570
|
self.logger.debug("Updated licensePlatesPL.i3d texture reference to: %s", filename)
|
|
567
571
|
|
|
572
|
+
@monitor_performance
|
|
568
573
|
def _generate_license_plate_texture(
|
|
569
574
|
self,
|
|
570
575
|
license_plates_directory: str,
|
|
@@ -9,6 +9,7 @@ from pydtmdl import DTMProvider
|
|
|
9
9
|
|
|
10
10
|
import maps4fs.generator.config as mfscfg
|
|
11
11
|
from maps4fs.generator.component.base.component_image import ImageComponent
|
|
12
|
+
from maps4fs.generator.monitor import monitor_performance
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
# pylint: disable=R0903, R0902
|
|
@@ -115,6 +116,7 @@ class DEM(ImageComponent):
|
|
|
115
116
|
except Exception as e:
|
|
116
117
|
self.logger.warning("Failed to update DEM info: %s.", e)
|
|
117
118
|
|
|
119
|
+
@monitor_performance
|
|
118
120
|
def process(self) -> None:
|
|
119
121
|
"""Reads DTM file, crops it to map size, normalizes and blurs it,
|
|
120
122
|
saves to map directory."""
|
|
@@ -190,6 +192,7 @@ class DEM(ImageComponent):
|
|
|
190
192
|
if self.rotation:
|
|
191
193
|
self.rotate_dem()
|
|
192
194
|
|
|
195
|
+
@monitor_performance
|
|
193
196
|
def normalize_data(self, data: np.ndarray, height_scale_value: int) -> np.ndarray:
|
|
194
197
|
"""Normalize DEM data to 16-bit unsigned integer range (0 to 65535).
|
|
195
198
|
|
|
@@ -209,6 +212,7 @@ class DEM(ImageComponent):
|
|
|
209
212
|
)
|
|
210
213
|
return normalized_data
|
|
211
214
|
|
|
215
|
+
@monitor_performance
|
|
212
216
|
def determine_height_scale(self, data: np.ndarray) -> int:
|
|
213
217
|
"""Determine height scale value using ceiling.
|
|
214
218
|
|
|
@@ -261,6 +265,7 @@ class DEM(ImageComponent):
|
|
|
261
265
|
)
|
|
262
266
|
return data
|
|
263
267
|
|
|
268
|
+
@monitor_performance
|
|
264
269
|
def apply_multiplier(self, data: np.ndarray) -> np.ndarray:
|
|
265
270
|
"""Apply multiplier to DEM data.
|
|
266
271
|
|
|
@@ -283,6 +288,7 @@ class DEM(ImageComponent):
|
|
|
283
288
|
)
|
|
284
289
|
return multiplied_data
|
|
285
290
|
|
|
291
|
+
@monitor_performance
|
|
286
292
|
def resize_to_output(self, data: np.ndarray) -> np.ndarray:
|
|
287
293
|
"""Resize DEM data to the output resolution.
|
|
288
294
|
|
|
@@ -296,6 +302,7 @@ class DEM(ImageComponent):
|
|
|
296
302
|
|
|
297
303
|
return resampled_data
|
|
298
304
|
|
|
305
|
+
@monitor_performance
|
|
299
306
|
def rotate_dem(self) -> None:
|
|
300
307
|
"""Rotate DEM image."""
|
|
301
308
|
self.logger.debug("Rotating DEM image by %s degrees.", self.rotation)
|
|
@@ -12,6 +12,7 @@ from tqdm import tqdm
|
|
|
12
12
|
from maps4fs.generator.component.base.component_image import ImageComponent
|
|
13
13
|
from maps4fs.generator.component.base.component_xml import XMLComponent
|
|
14
14
|
from maps4fs.generator.component.layer import Layer
|
|
15
|
+
from maps4fs.generator.monitor import monitor_performance
|
|
15
16
|
from maps4fs.generator.settings import Parameters
|
|
16
17
|
|
|
17
18
|
# This value sums up the pixel value of the basic area type to convert it from "No Water" to "Near Water".
|
|
@@ -98,6 +99,7 @@ class GRLE(ImageComponent, XMLComponent):
|
|
|
98
99
|
|
|
99
100
|
return grle_schema
|
|
100
101
|
|
|
102
|
+
@monitor_performance
|
|
101
103
|
def process(self) -> None:
|
|
102
104
|
"""Generates InfoLayer PNG files based on the GRLE schema."""
|
|
103
105
|
grle_schema = self._read_grle_schema()
|
|
@@ -134,6 +136,7 @@ class GRLE(ImageComponent, XMLComponent):
|
|
|
134
136
|
self._process_environment()
|
|
135
137
|
self._process_indoor()
|
|
136
138
|
|
|
139
|
+
@monitor_performance
|
|
137
140
|
def previews(self) -> list[str]:
|
|
138
141
|
"""Returns a list of paths to the preview images (empty list).
|
|
139
142
|
The component does not generate any preview images so it returns an empty list.
|
|
@@ -173,6 +176,7 @@ class GRLE(ImageComponent, XMLComponent):
|
|
|
173
176
|
|
|
174
177
|
return preview_paths
|
|
175
178
|
|
|
179
|
+
@monitor_performance
|
|
176
180
|
def overlay_fields(self, farmlands_np: np.ndarray) -> np.ndarray | None:
|
|
177
181
|
"""Overlay fields on the farmlands preview image.
|
|
178
182
|
|
|
@@ -200,6 +204,7 @@ class GRLE(ImageComponent, XMLComponent):
|
|
|
200
204
|
# use fields_np as base layer and overlay farmlands_np on top of it with 50% alpha blending.
|
|
201
205
|
return cv2.addWeighted(fields_np, 0.5, farmlands_np, 0.5, 0)
|
|
202
206
|
|
|
207
|
+
@monitor_performance
|
|
203
208
|
def _add_farmlands(self) -> None:
|
|
204
209
|
"""Adds farmlands to the InfoLayer PNG file."""
|
|
205
210
|
farmlands = []
|
|
@@ -297,6 +302,7 @@ class GRLE(ImageComponent, XMLComponent):
|
|
|
297
302
|
|
|
298
303
|
self.preview_paths["farmlands"] = info_layer_farmlands_path
|
|
299
304
|
|
|
305
|
+
@monitor_performance
|
|
300
306
|
def _add_plants(self) -> None:
|
|
301
307
|
"""Adds plants to the InfoLayer PNG file."""
|
|
302
308
|
grass_layer = self.map.get_texture_layer(by_usage="grass")
|
|
@@ -399,6 +405,7 @@ class GRLE(ImageComponent, XMLComponent):
|
|
|
399
405
|
|
|
400
406
|
self.logger.debug("Updated density map for fruits saved in %s.", density_map_fruit_path)
|
|
401
407
|
|
|
408
|
+
@monitor_performance
|
|
402
409
|
def create_island_of_plants(self, image: np.ndarray, count: int) -> np.ndarray:
|
|
403
410
|
"""Create an island of plants in the image.
|
|
404
411
|
|
|
@@ -495,6 +502,7 @@ class GRLE(ImageComponent, XMLComponent):
|
|
|
495
502
|
image_np[:, -1] = 0 # Right side
|
|
496
503
|
return image_np
|
|
497
504
|
|
|
505
|
+
@monitor_performance
|
|
498
506
|
def _process_environment(self) -> None:
|
|
499
507
|
info_layer_environment_path = self.game.get_environment_path(self.map_directory)
|
|
500
508
|
if not info_layer_environment_path or not os.path.isfile(info_layer_environment_path):
|
|
@@ -554,6 +562,7 @@ class GRLE(ImageComponent, XMLComponent):
|
|
|
554
562
|
self.logger.debug("Environment InfoLayer PNG file saved: %s.", info_layer_environment_path)
|
|
555
563
|
self.preview_paths["environment"] = info_layer_environment_path
|
|
556
564
|
|
|
565
|
+
@monitor_performance
|
|
557
566
|
def get_resized_weight(
|
|
558
567
|
self, layer: Layer, resize_to: int, dilations: int = 3
|
|
559
568
|
) -> np.ndarray | None:
|
|
@@ -13,6 +13,7 @@ import numpy as np
|
|
|
13
13
|
from tqdm import tqdm
|
|
14
14
|
|
|
15
15
|
from maps4fs.generator.component.base.component_xml import XMLComponent
|
|
16
|
+
from maps4fs.generator.monitor import monitor_performance
|
|
16
17
|
from maps4fs.generator.settings import Parameters
|
|
17
18
|
|
|
18
19
|
NODE_ID_STARTING_VALUE = 2000
|
|
@@ -112,6 +113,7 @@ class I3d(XMLComponent):
|
|
|
112
113
|
|
|
113
114
|
self.save_tree(tree)
|
|
114
115
|
|
|
116
|
+
@monitor_performance
|
|
115
117
|
def _add_splines(self) -> None:
|
|
116
118
|
"""Adds splines to the map I3D file."""
|
|
117
119
|
splines_i3d_path = self.game.splines_file_path(self.map_directory)
|
|
@@ -240,6 +242,7 @@ class I3d(XMLComponent):
|
|
|
240
242
|
|
|
241
243
|
self.assets.splines = splines_i3d_path
|
|
242
244
|
|
|
245
|
+
@monitor_performance
|
|
243
246
|
def _add_fields(self) -> None:
|
|
244
247
|
"""Adds fields to the map I3D file."""
|
|
245
248
|
tree = self.get_tree()
|
|
@@ -498,6 +501,7 @@ class I3d(XMLComponent):
|
|
|
498
501
|
|
|
499
502
|
return choice(trees_by_leaf_type)
|
|
500
503
|
|
|
504
|
+
@monitor_performance
|
|
501
505
|
def _add_forests(self) -> None:
|
|
502
506
|
"""Adds forests to the map I3D file."""
|
|
503
507
|
tree_schema = self._read_tree_schema()
|
|
@@ -9,6 +9,7 @@ from pygmdl import save_image
|
|
|
9
9
|
|
|
10
10
|
import maps4fs.generator.config as mfscfg
|
|
11
11
|
from maps4fs.generator.component.base.component_image import ImageComponent
|
|
12
|
+
from maps4fs.generator.monitor import monitor_performance
|
|
12
13
|
from maps4fs.generator.settings import Parameters
|
|
13
14
|
|
|
14
15
|
|
|
@@ -49,6 +50,7 @@ class Satellite(ImageComponent):
|
|
|
49
50
|
info, warning. If not provided, default logging will be used.
|
|
50
51
|
"""
|
|
51
52
|
|
|
53
|
+
@monitor_performance
|
|
52
54
|
def process(self) -> None:
|
|
53
55
|
"""Downloads the satellite images for the map."""
|
|
54
56
|
self.image_paths = []
|
|
@@ -126,6 +128,7 @@ class Satellite(ImageComponent):
|
|
|
126
128
|
|
|
127
129
|
return tasks
|
|
128
130
|
|
|
131
|
+
@monitor_performance
|
|
129
132
|
def previews(self) -> list[str]:
|
|
130
133
|
"""Returns the paths to the preview images.
|
|
131
134
|
|
|
@@ -7,10 +7,10 @@ import os
|
|
|
7
7
|
import shutil
|
|
8
8
|
import warnings
|
|
9
9
|
from collections import defaultdict
|
|
10
|
-
from time import perf_counter
|
|
11
10
|
from typing import Any, Callable, Generator, Optional
|
|
12
11
|
|
|
13
12
|
import cv2
|
|
13
|
+
import geopandas as gpd
|
|
14
14
|
import numpy as np
|
|
15
15
|
import osmnx as ox
|
|
16
16
|
import pandas as pd
|
|
@@ -21,6 +21,7 @@ from tqdm import tqdm
|
|
|
21
21
|
|
|
22
22
|
from maps4fs.generator.component.base.component_image import ImageComponent
|
|
23
23
|
from maps4fs.generator.component.layer import Layer
|
|
24
|
+
from maps4fs.generator.monitor import monitor_performance
|
|
24
25
|
from maps4fs.generator.settings import Parameters
|
|
25
26
|
|
|
26
27
|
|
|
@@ -186,6 +187,7 @@ class Texture(ImageComponent):
|
|
|
186
187
|
for layer in self.layers:
|
|
187
188
|
self.assets[layer.name] = layer.path(self._weights_dir)
|
|
188
189
|
|
|
190
|
+
@monitor_performance
|
|
189
191
|
def add_borders(self) -> None:
|
|
190
192
|
"""Iterates over all the layers and picks the one which have the border propety defined.
|
|
191
193
|
Borders are distance from the edge of the map on each side (top, right, bottom, left).
|
|
@@ -272,6 +274,7 @@ class Texture(ImageComponent):
|
|
|
272
274
|
return layer
|
|
273
275
|
return None
|
|
274
276
|
|
|
277
|
+
@monitor_performance
|
|
275
278
|
def merge_into(self) -> None:
|
|
276
279
|
"""Merges the content of layers into their target layers."""
|
|
277
280
|
for layer in self.layers:
|
|
@@ -298,6 +301,7 @@ class Texture(ImageComponent):
|
|
|
298
301
|
cv2.imwrite(layer.path(self._weights_dir), np.zeros_like(layer_image))
|
|
299
302
|
self.logger.debug("Cleared layer %s.", layer.name)
|
|
300
303
|
|
|
304
|
+
@monitor_performance
|
|
301
305
|
def rotate_textures(self) -> None:
|
|
302
306
|
"""Rotates textures of the layers which have tags."""
|
|
303
307
|
if self.rotation:
|
|
@@ -320,6 +324,7 @@ class Texture(ImageComponent):
|
|
|
320
324
|
"Skipping rotation of layer %s because it has no tags.", layer.name
|
|
321
325
|
)
|
|
322
326
|
|
|
327
|
+
@monitor_performance
|
|
323
328
|
def scale_textures(self) -> None:
|
|
324
329
|
"""Resizes all the textures to the map output size."""
|
|
325
330
|
if not self.map.output_size:
|
|
@@ -364,6 +369,7 @@ class Texture(ImageComponent):
|
|
|
364
369
|
]
|
|
365
370
|
return {attr: getattr(self, attr, None) for attr in useful_attributes}
|
|
366
371
|
|
|
372
|
+
@monitor_performance
|
|
367
373
|
def _prepare_weights(self):
|
|
368
374
|
self.logger.debug("Starting preparing weights from %s layers.", len(self.layers))
|
|
369
375
|
|
|
@@ -428,6 +434,7 @@ class Texture(ImageComponent):
|
|
|
428
434
|
),
|
|
429
435
|
)
|
|
430
436
|
|
|
437
|
+
@monitor_performance
|
|
431
438
|
def draw(self) -> None:
|
|
432
439
|
"""Iterates over layers and fills them with polygons from OSM data."""
|
|
433
440
|
layers = self.layers_by_priority()
|
|
@@ -546,6 +553,7 @@ class Texture(ImageComponent):
|
|
|
546
553
|
}
|
|
547
554
|
info_layer_data[f"{layer.info_layer}_polylines"].append(linestring_entry) # type: ignore
|
|
548
555
|
|
|
556
|
+
@monitor_performance
|
|
549
557
|
def dissolve(self) -> None:
|
|
550
558
|
"""Dissolves textures of the layers with tags into sublayers for them to look more
|
|
551
559
|
natural in the game.
|
|
@@ -791,35 +799,47 @@ class Texture(ImageComponent):
|
|
|
791
799
|
ox_settings.use_cache = self.map.texture_settings.use_cache
|
|
792
800
|
ox_settings.requests_timeout = 30
|
|
793
801
|
|
|
802
|
+
objects = self.fetch_osm_data(tags)
|
|
803
|
+
if objects is None or objects.empty:
|
|
804
|
+
self.logger.debug("No objects found for tags: %s.", tags)
|
|
805
|
+
return
|
|
806
|
+
|
|
807
|
+
self.logger.debug("Fetched %s elements for tags: %s.", len(objects), tags)
|
|
808
|
+
|
|
809
|
+
method = self.linestrings_generator if yield_linestrings else self.polygons_generator
|
|
810
|
+
|
|
811
|
+
yield from method(objects, width, is_fieds)
|
|
812
|
+
|
|
813
|
+
@monitor_performance
|
|
814
|
+
def fetch_osm_data(self, tags: dict[str, str | list[str] | bool]) -> gpd.GeoDataFrame | None:
|
|
815
|
+
"""Fetches OSM data for given tags.
|
|
816
|
+
|
|
817
|
+
Arguments:
|
|
818
|
+
tags (dict[str, str | list[str] | bool]): Dictionary of tags to search for.
|
|
819
|
+
|
|
820
|
+
Returns:
|
|
821
|
+
gpd.GeoDataFrame | None: GeoDataFrame with OSM objects or None if no objects found.
|
|
822
|
+
"""
|
|
794
823
|
try:
|
|
795
824
|
if self.map.custom_osm is not None:
|
|
796
825
|
with warnings.catch_warnings():
|
|
797
826
|
warnings.simplefilter("ignore", FutureWarning)
|
|
798
827
|
objects = ox.features_from_xml(self.map.custom_osm, tags=tags)
|
|
799
828
|
else:
|
|
800
|
-
before_fetch = perf_counter()
|
|
801
829
|
objects = ox.features_from_bbox(bbox=self.new_bbox, tags=tags)
|
|
802
|
-
after_fetch = perf_counter()
|
|
803
|
-
fetched_in = after_fetch - before_fetch
|
|
804
|
-
self.logger.info(
|
|
805
|
-
"Fetched OSMNX objects for tags: %s in %.2f seconds.", tags, fetched_in
|
|
806
|
-
)
|
|
807
830
|
except Exception as e:
|
|
808
831
|
self.logger.debug("Error fetching objects for tags: %s. Error: %s.", tags, e)
|
|
809
|
-
return
|
|
810
|
-
self.logger.debug("Fetched %s elements for tags: %s.", len(objects), tags)
|
|
832
|
+
return None
|
|
811
833
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
yield from method(objects, width, is_fieds)
|
|
834
|
+
return objects
|
|
815
835
|
|
|
816
836
|
def linestrings_generator(
|
|
817
|
-
self, objects:
|
|
837
|
+
self, objects: gpd.GeoDataFrame, *args, **kwargs
|
|
818
838
|
) -> Generator[list[tuple[int, int]], None, None]:
|
|
819
839
|
"""Generator which yields lists of point coordinates which represent LineStrings from OSM.
|
|
820
840
|
|
|
821
841
|
Arguments:
|
|
822
|
-
objects (
|
|
842
|
+
objects (gpd.GeoDataFrame): GeoDataFrame with OSM objects.
|
|
823
843
|
|
|
824
844
|
Yields:
|
|
825
845
|
Generator[list[tuple[int, int]], None, None]: List of point coordinates.
|
|
@@ -865,6 +885,7 @@ class Texture(ImageComponent):
|
|
|
865
885
|
polygon_np = self._to_np(polygon)
|
|
866
886
|
yield polygon_np
|
|
867
887
|
|
|
888
|
+
@monitor_performance
|
|
868
889
|
def previews(self) -> list[str]:
|
|
869
890
|
"""Invokes methods to generate previews. Returns list of paths to previews.
|
|
870
891
|
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
7
|
import shutil
|
|
8
|
+
from time import perf_counter
|
|
8
9
|
from typing import Any, Generator
|
|
9
10
|
|
|
10
11
|
from pydtmdl import DTMProvider
|
|
@@ -14,8 +15,13 @@ import maps4fs.generator.config as mfscfg
|
|
|
14
15
|
import maps4fs.generator.utils as mfsutils
|
|
15
16
|
from maps4fs.generator.component import Background, Component, Layer, Satellite, Texture
|
|
16
17
|
from maps4fs.generator.game import Game
|
|
18
|
+
from maps4fs.generator.monitor import PerformanceMonitor, performance_session
|
|
17
19
|
from maps4fs.generator.settings import GenerationSettings, MainSettings, SharedSettings
|
|
18
|
-
from maps4fs.generator.statistics import
|
|
20
|
+
from maps4fs.generator.statistics import (
|
|
21
|
+
send_advanced_settings,
|
|
22
|
+
send_main_settings,
|
|
23
|
+
send_performance_report,
|
|
24
|
+
)
|
|
19
25
|
from maps4fs.logger import Logger
|
|
20
26
|
|
|
21
27
|
|
|
@@ -203,51 +209,76 @@ class Map:
|
|
|
203
209
|
Yields:
|
|
204
210
|
Generator[str, None, None]: Component names.
|
|
205
211
|
"""
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
self.size,
|
|
211
|
-
self.rotation,
|
|
212
|
-
)
|
|
213
|
-
for game_component in self.game.components:
|
|
214
|
-
component = game_component(
|
|
215
|
-
self.game,
|
|
216
|
-
self,
|
|
212
|
+
with performance_session() as session_id:
|
|
213
|
+
self.logger.info(
|
|
214
|
+
"Starting map generation. Game code: %s. Coordinates: %s, size: %s. Rotation: %s.",
|
|
215
|
+
self.game.code,
|
|
217
216
|
self.coordinates,
|
|
218
217
|
self.size,
|
|
219
|
-
self.rotated_size,
|
|
220
218
|
self.rotation,
|
|
221
|
-
self.map_directory,
|
|
222
|
-
self.logger,
|
|
223
|
-
texture_custom_schema=self.texture_custom_schema, # type: ignore
|
|
224
|
-
tree_custom_schema=self.tree_custom_schema, # type: ignore
|
|
225
219
|
)
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
220
|
+
generation_start = perf_counter()
|
|
221
|
+
|
|
222
|
+
for game_component in self.game.components:
|
|
223
|
+
component = game_component(
|
|
224
|
+
self.game,
|
|
225
|
+
self,
|
|
226
|
+
self.coordinates,
|
|
227
|
+
self.size,
|
|
228
|
+
self.rotated_size,
|
|
229
|
+
self.rotation,
|
|
230
|
+
self.map_directory,
|
|
231
|
+
self.logger,
|
|
232
|
+
texture_custom_schema=self.texture_custom_schema, # type: ignore
|
|
233
|
+
tree_custom_schema=self.tree_custom_schema, # type: ignore
|
|
238
234
|
)
|
|
239
|
-
self.
|
|
240
|
-
|
|
235
|
+
self.components.append(component)
|
|
236
|
+
|
|
237
|
+
yield component.__class__.__name__
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
component_start = perf_counter()
|
|
241
|
+
component.process()
|
|
242
|
+
component_finish = perf_counter()
|
|
243
|
+
self.logger.info(
|
|
244
|
+
"Component %s processed in %.2f seconds.",
|
|
245
|
+
component.__class__.__name__,
|
|
246
|
+
component_finish - component_start,
|
|
247
|
+
)
|
|
248
|
+
component.commit_generation_info()
|
|
249
|
+
except Exception as e:
|
|
250
|
+
self.logger.error(
|
|
251
|
+
"Error processing or committing generation info for component %s: %s",
|
|
252
|
+
component.__class__.__name__,
|
|
253
|
+
e,
|
|
254
|
+
)
|
|
255
|
+
self._update_main_settings({"error": str(e)})
|
|
256
|
+
raise e
|
|
257
|
+
|
|
258
|
+
generation_finish = perf_counter()
|
|
259
|
+
self.logger.info(
|
|
260
|
+
"Map generation completed in %.2f seconds.",
|
|
261
|
+
generation_finish - generation_start,
|
|
262
|
+
)
|
|
241
263
|
|
|
242
|
-
|
|
264
|
+
self._update_main_settings({"completed": True})
|
|
243
265
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
266
|
+
self.logger.info(
|
|
267
|
+
"Map generation completed. Game code: %s. Coordinates: %s, size: %s. Rotation: %s.",
|
|
268
|
+
self.game.code,
|
|
269
|
+
self.coordinates,
|
|
270
|
+
self.size,
|
|
271
|
+
self.rotation,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
session_json = PerformanceMonitor().get_session_json(session_id)
|
|
275
|
+
if session_json:
|
|
276
|
+
report_filename = "performance_report.json"
|
|
277
|
+
with open(
|
|
278
|
+
os.path.join(self.map_directory, report_filename), "w", encoding="utf-8"
|
|
279
|
+
) as file:
|
|
280
|
+
json.dump(session_json, file, indent=4)
|
|
281
|
+
send_performance_report(session_json)
|
|
251
282
|
|
|
252
283
|
def _update_main_settings(self, data: dict[str, Any]) -> None:
|
|
253
284
|
"""Update main settings with provided data.
|
|
@@ -0,0 +1,108 @@
|
|
|
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
|
+
|
|
13
|
+
_local = threading.local()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_current_session() -> str | None:
|
|
17
|
+
"""Get the current session name from thread-local storage."""
|
|
18
|
+
return getattr(_local, "current_session", None)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@contextmanager
|
|
22
|
+
def performance_session(session_id: str | None = None) -> Generator[str, None, None]:
|
|
23
|
+
"""Context manager for performance monitoring session.
|
|
24
|
+
|
|
25
|
+
Arguments:
|
|
26
|
+
session_id (str, optional): Custom session ID. If None, generates UUID.
|
|
27
|
+
"""
|
|
28
|
+
if session_id is None:
|
|
29
|
+
session_id = str(uuid.uuid4())
|
|
30
|
+
|
|
31
|
+
_local.current_session = session_id
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
yield session_id
|
|
35
|
+
finally:
|
|
36
|
+
_local.current_session = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class PerformanceMonitor(metaclass=Singleton):
|
|
40
|
+
"""Singleton class for monitoring performance metrics."""
|
|
41
|
+
|
|
42
|
+
def __init__(self) -> None:
|
|
43
|
+
self.sessions: dict[str, dict[str, dict[str, float]]] = defaultdict(
|
|
44
|
+
lambda: defaultdict(lambda: defaultdict(float))
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def add_record(self, session: str, component: str, function: str, time_taken: float) -> None:
|
|
48
|
+
"""Add a performance record.
|
|
49
|
+
|
|
50
|
+
Arguments:
|
|
51
|
+
session (str): The session name.
|
|
52
|
+
component (str): The component/class name.
|
|
53
|
+
function (str): The function/method name.
|
|
54
|
+
time_taken (float): Time taken in seconds.
|
|
55
|
+
"""
|
|
56
|
+
self.sessions[session][component][function] += time_taken
|
|
57
|
+
|
|
58
|
+
def get_session_json(self, session: str) -> dict[str, dict[str, float]]:
|
|
59
|
+
"""Get performance data for a session in JSON-serializable format.
|
|
60
|
+
|
|
61
|
+
Arguments:
|
|
62
|
+
session (str): The session name.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
dict[str, dict[str, float]]: Performance data.
|
|
66
|
+
"""
|
|
67
|
+
return self.sessions.get(session, {})
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def monitor_performance(func: Callable) -> Callable:
|
|
71
|
+
"""Decorator to monitor performance of methods/functions.
|
|
72
|
+
|
|
73
|
+
Arguments:
|
|
74
|
+
func (callable) -- The function to be monitored.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
callable -- The wrapped function with performance monitoring.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
@functools.wraps(func)
|
|
81
|
+
def wrapper(*args, **kwargs):
|
|
82
|
+
if args and hasattr(args[0], "__class__"):
|
|
83
|
+
class_name = args[0].__class__.__name__
|
|
84
|
+
elif args and hasattr(args[0], "__name__"):
|
|
85
|
+
class_name = args[0].__name__
|
|
86
|
+
elif "." in func.__qualname__:
|
|
87
|
+
class_name = func.__qualname__.split(".")[0]
|
|
88
|
+
else:
|
|
89
|
+
class_name = None
|
|
90
|
+
|
|
91
|
+
function_name = func.__name__
|
|
92
|
+
|
|
93
|
+
start = perf_counter()
|
|
94
|
+
result = func(*args, **kwargs)
|
|
95
|
+
end = perf_counter()
|
|
96
|
+
time_taken = round(end - start, 5)
|
|
97
|
+
|
|
98
|
+
session_name = get_current_session()
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
if session_name and time_taken > 0.001 and class_name:
|
|
102
|
+
PerformanceMonitor().add_record(session_name, class_name, function_name, time_taken)
|
|
103
|
+
except Exception:
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
return result
|
|
107
|
+
|
|
108
|
+
return wrapper
|
|
@@ -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)
|
|
@@ -158,3 +158,14 @@ def dump_json(filename: str, directory: str, data: dict[Any, Any] | Any | None)
|
|
|
158
158
|
save_path = os.path.join(directory, filename)
|
|
159
159
|
with open(save_path, "w", encoding="utf-8") as file:
|
|
160
160
|
json.dump(data, file, indent=4)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class Singleton(type):
|
|
164
|
+
"""A metaclass for creating singleton classes."""
|
|
165
|
+
|
|
166
|
+
_instances: dict[Any, Any] = {}
|
|
167
|
+
|
|
168
|
+
def __call__(cls, *args, **kwargs):
|
|
169
|
+
if cls not in cls._instances:
|
|
170
|
+
cls._instances[cls] = super().__call__(*args, **kwargs)
|
|
171
|
+
return cls._instances[cls]
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "maps4fs"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.9.0"
|
|
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 = "GNU Affero General Public License v3.0"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|