maps4fs 2.1.0__tar.gz → 2.1.2__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 (33) hide show
  1. {maps4fs-2.1.0 → maps4fs-2.1.2}/PKG-INFO +6 -1
  2. {maps4fs-2.1.0 → maps4fs-2.1.2}/README.md +5 -0
  3. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs/generator/component/base/component_mesh.py +1 -1
  4. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs/generator/component/grle.py +178 -0
  5. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs/generator/component/i3d.py +90 -10
  6. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs/generator/component/layer.py +9 -0
  7. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs/generator/component/texture.py +30 -0
  8. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs/generator/game.py +32 -0
  9. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs/generator/settings.py +2 -0
  10. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs.egg-info/PKG-INFO +6 -1
  11. {maps4fs-2.1.0 → maps4fs-2.1.2}/pyproject.toml +1 -1
  12. {maps4fs-2.1.0 → maps4fs-2.1.2}/LICENSE.md +0 -0
  13. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs/__init__.py +0 -0
  14. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs/generator/__init__.py +0 -0
  15. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs/generator/component/__init__.py +0 -0
  16. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs/generator/component/background.py +0 -0
  17. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs/generator/component/base/__init__.py +0 -0
  18. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs/generator/component/base/component.py +0 -0
  19. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs/generator/component/base/component_image.py +0 -0
  20. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs/generator/component/base/component_xml.py +0 -0
  21. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs/generator/component/config.py +0 -0
  22. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs/generator/component/dem.py +0 -0
  23. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs/generator/component/satellite.py +0 -0
  24. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs/generator/map.py +0 -0
  25. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs/generator/qgis.py +0 -0
  26. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs/generator/statistics.py +0 -0
  27. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs/logger.py +0 -0
  28. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs.egg-info/SOURCES.txt +0 -0
  29. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs.egg-info/dependency_links.txt +0 -0
  30. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs.egg-info/requires.txt +0 -0
  31. {maps4fs-2.1.0 → maps4fs-2.1.2}/maps4fs.egg-info/top_level.txt +0 -0
  32. {maps4fs-2.1.0 → maps4fs-2.1.2}/setup.cfg +0 -0
  33. {maps4fs-2.1.0 → maps4fs-2.1.2}/tests/test_generator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maps4fs
3
- Version: 2.1.0
3
+ Version: 2.1.2
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
@@ -546,6 +546,9 @@ Let's have a closer look at the fields:
546
546
  - `border` - this value defines the border between the texture and the edge of the map. It's used to prevent the texture from being drawn on the edge of the map. The value is in pixels.
547
547
  - `precise_tags` - can be used for more specific tags, for example instead of `"natural": "wood"` you can use `"leaf_type": "broadleaved"` to draw only broadleaved trees.
548
548
  - `precise_usage` - the same as `usage`, but being used with `precise_tags`.
549
+ - `area_type` - one of the supported by Giants Editor area types, such as: "open_land", "city", "village", "harbor", "industrial", "open_water". It will be reflected in the environment info layer file.
550
+ - `area_water` - whenever this field is set to true, the area will be considered as water, and it will be changed in the environment info layer file.
551
+ - `indoor` - whenever this field is set to true, the area will be considered as indoor, and it will be reflected in the indoorMask info layer.
549
552
 
550
553
  ## Background terrain
551
554
 
@@ -650,6 +653,8 @@ You can also apply some advanced settings to the map generation process.<br>
650
653
 
651
654
  - Forest density - the density of the forest in meters. The lower the value, the lower the distance between the trees, which makes the forest denser. Note, that low values will lead to enormous number of trees, which may cause the Giants Editor to crash or lead to performance issues. By default, it's set to 10.
652
655
 
656
+ - Tree limit - this value will be used to adjust the forest density value. For example, if it's possible to place 100000 trees from OSM data, and the forest density is set to 10, the expected number of trees on map will be 10000. At the same time, if you set the tree limit to 5000, the forest density will be adjusted to 20, which means that the distance between the trees will be doubled. This value is useful to prevent the Giants Editor from crashing due to too many trees on the map. By default, it's set to 0, which means that it's disabled and will use the forest density value as is. Note, that it will not lead to the exact number of trees, but will adjust the forest density value to fit the tree limit so the resulting number of trees will be more or less equal to the tree limit value.
657
+
653
658
  - Trees relative shift - represents the maximum possible shift of the tree from it's original position in percents of the forest density value. The higher the value, the more the trees will be shifted from their original positions. Warning: higher values can lead to overlapping trees.
654
659
 
655
660
  - Splines density - number of points, which will be added (interpolate) between each pair of existing points. The higher the value, the denser the spline will be. It can smooth the splines, but high values can in opposite make the splines look unnatural.
@@ -515,6 +515,9 @@ Let's have a closer look at the fields:
515
515
  - `border` - this value defines the border between the texture and the edge of the map. It's used to prevent the texture from being drawn on the edge of the map. The value is in pixels.
516
516
  - `precise_tags` - can be used for more specific tags, for example instead of `"natural": "wood"` you can use `"leaf_type": "broadleaved"` to draw only broadleaved trees.
517
517
  - `precise_usage` - the same as `usage`, but being used with `precise_tags`.
518
+ - `area_type` - one of the supported by Giants Editor area types, such as: "open_land", "city", "village", "harbor", "industrial", "open_water". It will be reflected in the environment info layer file.
519
+ - `area_water` - whenever this field is set to true, the area will be considered as water, and it will be changed in the environment info layer file.
520
+ - `indoor` - whenever this field is set to true, the area will be considered as indoor, and it will be reflected in the indoorMask info layer.
518
521
 
519
522
  ## Background terrain
520
523
 
@@ -619,6 +622,8 @@ You can also apply some advanced settings to the map generation process.<br>
619
622
 
620
623
  - Forest density - the density of the forest in meters. The lower the value, the lower the distance between the trees, which makes the forest denser. Note, that low values will lead to enormous number of trees, which may cause the Giants Editor to crash or lead to performance issues. By default, it's set to 10.
621
624
 
625
+ - Tree limit - this value will be used to adjust the forest density value. For example, if it's possible to place 100000 trees from OSM data, and the forest density is set to 10, the expected number of trees on map will be 10000. At the same time, if you set the tree limit to 5000, the forest density will be adjusted to 20, which means that the distance between the trees will be doubled. This value is useful to prevent the Giants Editor from crashing due to too many trees on the map. By default, it's set to 0, which means that it's disabled and will use the forest density value as is. Note, that it will not lead to the exact number of trees, but will adjust the forest density value to fit the tree limit so the resulting number of trees will be more or less equal to the tree limit value.
626
+
622
627
  - Trees relative shift - represents the maximum possible shift of the tree from it's original position in percents of the forest density value. The higher the value, the more the trees will be shifted from their original positions. Warning: higher values can lead to overlapping trees.
623
628
 
624
629
  - Splines density - number of points, which will be added (interpolate) between each pair of existing points. The higher the value, the denser the spline will be. It can smooth the splines, but high values can in opposite make the splines look unnatural.
@@ -265,5 +265,5 @@ class MeshComponent(Component):
265
265
  trimesh.Trimesh: A new mesh with inverted faces.
266
266
  """
267
267
  mesh_copy = mesh.copy()
268
- mesh_copy.faces = mesh_copy.faces[:, ::-1]
268
+ mesh_copy.faces = mesh_copy.faces[:, ::-1] # type: ignore
269
269
  return mesh_copy
@@ -11,8 +11,13 @@ from tqdm import tqdm
11
11
 
12
12
  from maps4fs.generator.component.base.component_image import ImageComponent
13
13
  from maps4fs.generator.component.base.component_xml import XMLComponent
14
+ from maps4fs.generator.component.layer import Layer
14
15
  from maps4fs.generator.settings import Parameters
15
16
 
17
+ # This value sums up the pixel value of the basic area type to convert it from "No Water" to "Near Water".
18
+ # For example, if the basic area type is "city" (1), then the pixel value for "near water" will be 9.
19
+ WATER_AREA_PIXEL_VALUE = 8
20
+
16
21
 
17
22
  def plant_to_pixel_value(plant_name: str) -> int | None:
18
23
  """Returns the pixel value representation of the plant.
@@ -31,6 +36,27 @@ def plant_to_pixel_value(plant_name: str) -> int | None:
31
36
  return plants.get(plant_name)
32
37
 
33
38
 
39
+ def area_type_to_pixel_value(area_type: str) -> int | None:
40
+ """Returns the pixel value representation of the area type.
41
+ If not found, returns None.
42
+
43
+ Arguments:
44
+ area_type (str): name of the area type
45
+
46
+ Returns:
47
+ int | None: pixel value of the area type or None if not found.
48
+ """
49
+ area_types = {
50
+ "open_land": 0,
51
+ "city": 1,
52
+ "village": 2,
53
+ "harbor": 3,
54
+ "industrial": 4,
55
+ "open_water": 5,
56
+ }
57
+ return area_types.get(area_type)
58
+
59
+
34
60
  class GRLE(ImageComponent, XMLComponent):
35
61
  """Component for to generate InfoLayer PNG files based on GRLE schema.
36
62
 
@@ -104,6 +130,9 @@ class GRLE(ImageComponent, XMLComponent):
104
130
  self._add_farmlands()
105
131
  if self.game.plants_processing and self.map.grle_settings.add_grass:
106
132
  self._add_plants()
133
+ if self.game.environment_processing:
134
+ self._process_environment()
135
+ self._process_indoor()
107
136
 
108
137
  def previews(self) -> list[str]:
109
138
  """Returns a list of paths to the preview images (empty list).
@@ -130,6 +159,9 @@ class GRLE(ImageComponent, XMLComponent):
130
159
  cv2.imwrite(save_path, image_colored)
131
160
  preview_paths.append(save_path)
132
161
 
162
+ if preview_name != "farmlands":
163
+ continue
164
+
133
165
  with_fields_save_path = os.path.join(
134
166
  self.previews_directory, f"{preview_name}_with_fields.png"
135
167
  )
@@ -462,3 +494,149 @@ class GRLE(ImageComponent, XMLComponent):
462
494
  image_np[:, 0] = 0 # Left side
463
495
  image_np[:, -1] = 0 # Right side
464
496
  return image_np
497
+
498
+ def _process_environment(self) -> None:
499
+ info_layer_environment_path = self.game.get_environment_path(self.map_directory)
500
+ if not info_layer_environment_path or not os.path.isfile(info_layer_environment_path):
501
+ self.logger.warning(
502
+ "Environment InfoLayer PNG file not found in %s.", info_layer_environment_path
503
+ )
504
+ return
505
+
506
+ self.logger.debug(
507
+ "Processing environment InfoLayer PNG file: %s.", info_layer_environment_path
508
+ )
509
+
510
+ environment_image = cv2.imread(info_layer_environment_path, cv2.IMREAD_UNCHANGED)
511
+ if environment_image is None:
512
+ self.logger.error("Failed to read the environment InfoLayer PNG file.")
513
+ return
514
+
515
+ self.logger.debug(
516
+ "Environment InfoLayer PNG file loaded, shape: %s.", environment_image.shape
517
+ )
518
+
519
+ environment_size = int(environment_image.shape[0])
520
+
521
+ # 1. Get the texture layers that contain "area_type" property.
522
+ # 2. Read the corresponding weight image (not the dissolved ones!).
523
+ # 3. Resize it to match the environment image size (probably 1/4 of the texture size).
524
+ # 4. Dilate the texture mask to make it little bit bigger.
525
+ # 5. Set the corresponding pixel values of the environment image to the pixel value of the area type.
526
+ # 6. Get the texture layer that "area_water" is True.
527
+ # 7. Same as resize, dilate, etc.
528
+ # 8. Sum the current pixel value with the WATER_AREA_PIXEL_VALUE.
529
+
530
+ texture_component = self.map.get_texture_component()
531
+ if not texture_component:
532
+ self.logger.warning("Texture component not found in the map.")
533
+ return
534
+
535
+ for layer in texture_component.get_area_type_layers():
536
+ pixel_value = area_type_to_pixel_value(layer.area_type) # type: ignore
537
+ weight_image = self.get_resized_weight(layer, environment_size) # type: ignore
538
+ if weight_image is None:
539
+ self.logger.warning("Weight image for area type layer not found in %s.", layer.name)
540
+ continue
541
+ environment_image[weight_image > 0] = pixel_value # type: ignore
542
+
543
+ for layer in texture_component.get_water_area_layers():
544
+ pixel_value = WATER_AREA_PIXEL_VALUE
545
+ weight_image = self.get_resized_weight(layer, environment_size)
546
+ if weight_image is None:
547
+ self.logger.warning(
548
+ "Weight image for water area layer not found in %s.", layer.name
549
+ )
550
+ continue
551
+ environment_image[weight_image > 0] += pixel_value # type: ignore
552
+
553
+ cv2.imwrite(info_layer_environment_path, environment_image)
554
+ self.logger.debug("Environment InfoLayer PNG file saved: %s.", info_layer_environment_path)
555
+ self.preview_paths["environment"] = info_layer_environment_path
556
+
557
+ def get_resized_weight(
558
+ self, layer: Layer, resize_to: int, dilations: int = 3
559
+ ) -> np.ndarray | None:
560
+ """Get the resized weight image for a given layer.
561
+
562
+ Arguments:
563
+ layer (Layer): The layer for which to get the weight image.
564
+ resize_to (int): The size to which the weight image should be resized.
565
+ dilations (int): The number of dilations to apply to the weight image.
566
+
567
+ Returns:
568
+ np.ndarray | None: The resized and dilated weight image, or None if the image could not be loaded.
569
+ """
570
+ weight_image_path = layer.get_preview_or_path(
571
+ self.game.weights_dir_path(self.map_directory)
572
+ )
573
+ self.logger.debug("Weight image path for area type layer: %s.", weight_image_path)
574
+
575
+ if not weight_image_path or not os.path.isfile(weight_image_path):
576
+ self.logger.warning(
577
+ "Weight image for area type layer not found in %s.", weight_image_path
578
+ )
579
+ return None
580
+
581
+ weight_image = cv2.imread(weight_image_path, cv2.IMREAD_UNCHANGED)
582
+ if weight_image is None:
583
+ self.logger.error("Failed to read the weight image for area type layer.")
584
+ return None
585
+
586
+ self.logger.debug("Weight image for area type layer loaded, shape: %s.", weight_image.shape)
587
+
588
+ # Resize the weight image to match the environment image size.
589
+ weight_image = cv2.resize(
590
+ weight_image,
591
+ (resize_to, resize_to),
592
+ interpolation=cv2.INTER_NEAREST,
593
+ )
594
+
595
+ self.logger.debug(
596
+ "Resized weight image for area type layer, new shape: %s.", weight_image.shape
597
+ )
598
+
599
+ if dilations <= 0:
600
+ return weight_image
601
+
602
+ dilated_weight_image = cv2.dilate(
603
+ weight_image.astype(np.uint8), np.ones((dilations, dilations), np.uint8), iterations=dilations # type: ignore
604
+ )
605
+
606
+ return dilated_weight_image
607
+
608
+ def _process_indoor(self) -> None:
609
+ """Processes the indoor layers."""
610
+ info_layer_indoor_path = self.game.get_indoor_mask_path(self.map_directory)
611
+ if not info_layer_indoor_path or not os.path.isfile(info_layer_indoor_path):
612
+ self.logger.warning(
613
+ "Indoor InfoLayer PNG file not found in %s.", info_layer_indoor_path
614
+ )
615
+ return
616
+
617
+ indoor_mask_image = cv2.imread(info_layer_indoor_path, cv2.IMREAD_UNCHANGED)
618
+ if indoor_mask_image is None:
619
+ self.logger.warning(
620
+ "Failed to read the indoor InfoLayer PNG file %s.", info_layer_indoor_path
621
+ )
622
+ return
623
+
624
+ indoor_mask_size = int(indoor_mask_image.shape[0])
625
+ self.logger.debug("Indoor InfoLayer PNG file loaded, shape: %s.", indoor_mask_image.shape)
626
+
627
+ texture_component = self.map.get_texture_component()
628
+ if not texture_component:
629
+ self.logger.warning("Texture component not found in the map.")
630
+ return
631
+
632
+ for layer in texture_component.get_indoor_layers():
633
+ weight_image = self.get_resized_weight(layer, indoor_mask_size, dilations=0)
634
+ if weight_image is None:
635
+ self.logger.warning("Weight image for indoor layer not found in %s.", layer.name)
636
+ continue
637
+
638
+ indoor_mask_image[weight_image > 0] = 1
639
+
640
+ cv2.imwrite(info_layer_indoor_path, indoor_mask_image)
641
+ self.logger.debug("Indoor InfoLayer PNG file saved: %s.", info_layer_indoor_path)
642
+ self.preview_paths["indoor"] = info_layer_indoor_path
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  import json
6
6
  import os
7
7
  from random import choice, randint, uniform
8
- from typing import Generator
8
+ from typing import Any, Generator
9
9
  from xml.etree import ElementTree as ET
10
10
 
11
11
  import cv2
@@ -48,6 +48,9 @@ class I3d(XMLComponent):
48
48
  attribute. If the game does not support I3D files, the attribute is set to None."""
49
49
  self.xml_path = self.game.i3d_file_path(self.map_directory)
50
50
 
51
+ self.forest_info: dict[str, Any] = {}
52
+ self.field_info: dict[str, Any] = {}
53
+
51
54
  def process(self) -> None:
52
55
  """Updates the map I3D file and creates splines in a separate I3D file."""
53
56
  self.update_height_scale()
@@ -267,6 +270,8 @@ class I3d(XMLComponent):
267
270
 
268
271
  node_id = NODE_ID_STARTING_VALUE
269
272
  field_id = 1
273
+ added_fields = skipped_fields = 0
274
+ skipped_field_ids: list[int] = []
270
275
 
271
276
  for field in tqdm(fields, desc="Adding fields", unit="field"):
272
277
  try:
@@ -279,6 +284,7 @@ class I3d(XMLComponent):
279
284
  field_id,
280
285
  e,
281
286
  )
287
+ skipped_fields += 1
282
288
  continue
283
289
 
284
290
  field_ccs = [self.top_left_coordinates_to_center(point) for point in fitted_field]
@@ -297,6 +303,11 @@ class I3d(XMLComponent):
297
303
 
298
304
  node_id += 1
299
305
  field_id += 1
306
+ added_fields += 1
307
+
308
+ self.field_info["added_fields"] = added_fields
309
+ self.field_info["skipped_fields"] = skipped_fields
310
+ self.field_info["skipped_field_ids"] = skipped_field_ids
300
311
 
301
312
  self.save_tree(tree)
302
313
 
@@ -504,6 +515,8 @@ class I3d(XMLComponent):
504
515
 
505
516
  node_id = TREE_NODE_ID_STARTING_VALUE
506
517
 
518
+ tree_count = 0
519
+
507
520
  for forest_layer in forest_layers:
508
521
  weights_directory = self.game.weights_dir_path(self.map_directory)
509
522
  forest_image_path = forest_layer.get_preview_or_path(weights_directory)
@@ -542,12 +555,17 @@ class I3d(XMLComponent):
542
555
  )
543
556
 
544
557
  forest_image = cv2.imread(forest_image_path, cv2.IMREAD_UNCHANGED)
545
- for x, y in self.non_empty_pixels(
546
- forest_image, step=self.map.i3d_settings.forest_density # type: ignore
547
- ):
558
+
559
+ step = self.get_step_by_limit(
560
+ forest_image, # type: ignore
561
+ self.map.i3d_settings.tree_limit,
562
+ self.map.i3d_settings.forest_density,
563
+ )
564
+
565
+ for x, y in self.non_empty_pixels(forest_image, step=step): # type: ignore
548
566
  shifted_x, shifted_y = self.randomize_coordinates(
549
567
  (x, y),
550
- self.map.i3d_settings.forest_density,
568
+ step,
551
569
  self.map.i3d_settings.trees_relative_shift,
552
570
  )
553
571
 
@@ -573,9 +591,16 @@ class I3d(XMLComponent):
573
591
  }
574
592
  trees_node.append(self.create_element("ReferenceNode", data))
575
593
 
594
+ tree_count += 1
595
+
576
596
  scene_node.append(trees_node)
577
597
  self.save_tree(tree)
578
598
 
599
+ self.forest_info["tree_count"] = tree_count
600
+ self.forest_info["tree_limit"] = self.map.i3d_settings.tree_limit
601
+ self.forest_info["initial_step"] = self.map.i3d_settings.forest_density
602
+ self.forest_info["actual_step"] = step
603
+
579
604
  self.assets.forests = self.xml_path
580
605
 
581
606
  @staticmethod
@@ -606,19 +631,61 @@ class I3d(XMLComponent):
606
631
  image: np.ndarray, step: int = 1
607
632
  ) -> Generator[tuple[int, int], None, None]:
608
633
  """Receives numpy array, which represents single-channeled image of uint8 type.
609
- Yield coordinates of non-empty pixels (pixels with value greater than 0).
634
+ Yield coordinates of non-empty pixels (pixels with value greater than 0), sampling about 1/step of them.
610
635
 
611
636
  Arguments:
612
637
  image (np.ndarray): The image to get non-empty pixels from.
613
- step (int, optional): The step to iterate through the image. Defaults to 1.
638
+ step (int, optional): The step to sample non-empty pixels. Defaults to 1.
614
639
 
615
640
  Yields:
616
641
  tuple[int, int]: The coordinates of non-empty pixels.
617
642
  """
618
- for y, row in enumerate(image[::step]):
619
- for x, value in enumerate(row[::step]):
643
+ count = 0
644
+ for y, row in enumerate(image):
645
+ for x, value in enumerate(row):
620
646
  if value > 0:
621
- yield x * step, y * step
647
+ if count % step == 0:
648
+ yield x, y
649
+ count += 1
650
+
651
+ @staticmethod
652
+ def non_empty_pixels_count(image: np.ndarray) -> int:
653
+ """Counts the number of non-empty pixels in the image.
654
+
655
+ Arguments:
656
+ image (np.ndarray): The image to count non-empty pixels in.
657
+
658
+ Returns:
659
+ int: The number of non-empty pixels in the image.
660
+ """
661
+ result = np.count_nonzero(image > 0)
662
+ return result
663
+
664
+ def get_step_by_limit(
665
+ self, image: np.ndarray, limit: int, current_step: int | None = None
666
+ ) -> int:
667
+ """Calculates the step size for iterating through the image based on the limit based
668
+ on the number of non-empty pixels in the image.
669
+
670
+ Arguments:
671
+ image (np.ndarray): The image to calculate the step size for.
672
+ limit (int): The maximum number of non-empty pixels to process.
673
+ current_step (int | None, optional): The current step size. If provided, the method
674
+ will return the maximum of the recommended step and the current step.
675
+
676
+ Returns:
677
+ int: The recommended step size for iterating through the image.
678
+ """
679
+ available_tree_count = self.non_empty_pixels_count(image)
680
+ self.forest_info["available_tree_count"] = available_tree_count
681
+ if limit <= 0 or available_tree_count <= limit:
682
+ recommended_step = 1
683
+ else:
684
+ recommended_step = int(available_tree_count / limit)
685
+
686
+ self.forest_info["step_by_limit"] = recommended_step
687
+
688
+ return recommended_step if not current_step else max(recommended_step, current_step)
622
689
 
623
690
  def get_not_resized_dem(self) -> np.ndarray | None:
624
691
  """Reads the not resized DEM image from the background component.
@@ -638,3 +705,16 @@ class I3d(XMLComponent):
638
705
  not_resized_dem = cv2.imread(background_component.not_resized_path, cv2.IMREAD_UNCHANGED)
639
706
 
640
707
  return not_resized_dem
708
+
709
+ def info_sequence(self) -> dict[str, dict[str, str | float | int]]:
710
+ """Returns information about the component.
711
+
712
+ Returns:
713
+ dict[str, dict[str, str | float | int]]: Information about the component.
714
+ """
715
+ data = {
716
+ "Forests": self.forest_info,
717
+ "Fields": self.field_info,
718
+ }
719
+
720
+ return data
@@ -46,6 +46,9 @@ class Layer:
46
46
  border: int | None = None,
47
47
  precise_tags: dict[str, str | list[str] | bool] | None = None,
48
48
  precise_usage: str | None = None,
49
+ area_type: str | None = None,
50
+ area_water: bool = False,
51
+ indoor: bool = False,
49
52
  ):
50
53
  self.name = name
51
54
  self.count = count
@@ -62,6 +65,9 @@ class Layer:
62
65
  self.border = border
63
66
  self.precise_tags = precise_tags
64
67
  self.precise_usage = precise_usage
68
+ self.area_type = area_type
69
+ self.area_water = area_water
70
+ self.indoor = indoor
65
71
 
66
72
  def to_json(self) -> dict[str, str | list[str] | bool]: # type: ignore
67
73
  """Returns dictionary with layer data.
@@ -84,6 +90,9 @@ class Layer:
84
90
  "border": self.border,
85
91
  "precise_tags": self.precise_tags,
86
92
  "precise_usage": self.precise_usage,
93
+ "area_type": self.area_type,
94
+ "area_water": self.area_water,
95
+ "indoor": self.indoor,
87
96
  }
88
97
 
89
98
  data = {k: v for k, v in data.items() if v is not None}
@@ -137,6 +137,36 @@ class Texture(ImageComponent):
137
137
  """
138
138
  return [layer for layer in self.layers if layer.usage == usage]
139
139
 
140
+ def get_area_type_layers(self, area_type: str | None = None) -> list[Layer]:
141
+ """Returns layers by area type. If area_type is None, returns all layers
142
+ with area_type set (not None).
143
+
144
+ Arguments:
145
+ area_type (str | None): Area type of the layer.
146
+
147
+ Returns:
148
+ list[Layer]: List of layers.
149
+ """
150
+ if area_type is None:
151
+ return [layer for layer in self.layers if layer.area_type is not None]
152
+ return [layer for layer in self.layers if layer.area_type == area_type]
153
+
154
+ def get_water_area_layers(self) -> list[Layer]:
155
+ """Returns layers which are water areas.
156
+
157
+ Returns:
158
+ list[Layer]: List of layers which are water areas.
159
+ """
160
+ return [layer for layer in self.layers if layer.area_water]
161
+
162
+ def get_indoor_layers(self) -> list[Layer]:
163
+ """Returns layers which are indoor areas.
164
+
165
+ Returns:
166
+ list[Layer]: List of layers which are indoor areas.
167
+ """
168
+ return [layer for layer in self.layers if layer.indoor]
169
+
140
170
  def process(self) -> None:
141
171
  """Processes the data to generate textures."""
142
172
  self._prepare_weights()
@@ -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
+ _environment_processing: bool = True
43
44
  _fog_processing: bool = True
44
45
  _dissolve: bool = True
45
46
 
@@ -180,6 +181,28 @@ class Game:
180
181
  weights_dir = self.weights_dir_path(map_directory)
181
182
  return os.path.join(weights_dir, "infoLayer_farmlands.png")
182
183
 
184
+ def get_environment_path(self, map_directory: str) -> str:
185
+ """Returns the path to the environment file.
186
+
187
+ Arguments:
188
+ map_directory (str): The path to the map directory.
189
+
190
+ Returns:
191
+ str: The path to the environment file."""
192
+ weights_dir = self.weights_dir_path(map_directory)
193
+ return os.path.join(weights_dir, "infoLayer_environment.png")
194
+
195
+ def get_indoor_mask_path(self, map_directory: str) -> str:
196
+ """Returns the path to the indoor mask file.
197
+
198
+ Arguments:
199
+ map_directory (str): The path to the map directory.
200
+
201
+ Returns:
202
+ str: The path to the indoor mask file."""
203
+ weights_dir = self.weights_dir_path(map_directory)
204
+ return os.path.join(weights_dir, "infoLayer_indoorMask.png")
205
+
183
206
  def get_farmlands_xml_path(self, map_directory: str) -> str:
184
207
  """Returns the path to the farmlands xml file.
185
208
 
@@ -218,6 +241,14 @@ class Game:
218
241
  bool: True if the i3d file should be processed, False otherwise."""
219
242
  return self._i3d_processing
220
243
 
244
+ @property
245
+ def environment_processing(self) -> bool:
246
+ """Returns whether the environment should be processed.
247
+
248
+ Returns:
249
+ bool: True if the environment should be processed, False otherwise."""
250
+ return self._environment_processing
251
+
221
252
  @property
222
253
  def fog_processing(self) -> bool:
223
254
  """Returns whether the fog should be processed.
@@ -269,6 +300,7 @@ class FS22(Game):
269
300
  _map_template_path = os.path.join(working_directory, "data", "fs22-map-template.zip")
270
301
  _texture_schema = os.path.join(working_directory, "data", "fs22-texture-schema.json")
271
302
  _i3d_processing = False
303
+ _environment_processing = False
272
304
  _fog_processing = False
273
305
  _plants_processing = False
274
306
  _dissolve = False
@@ -203,6 +203,7 @@ class I3DSettings(SettingsModel):
203
203
  Attributes:
204
204
  add_trees (bool): add trees to the map.
205
205
  forest_density (int): density of the forest (distance between trees).
206
+ tree_limit (int): maximum number of trees to be added to the map.
206
207
  trees_relative_shift (int): relative shift of the trees.
207
208
  spline_density (int): the number of extra points that will be added between each two
208
209
  existing points.
@@ -212,6 +213,7 @@ class I3DSettings(SettingsModel):
212
213
 
213
214
  add_trees: bool = True
214
215
  forest_density: int = 10
216
+ tree_limit: int = 0
215
217
  trees_relative_shift: int = 20
216
218
 
217
219
  spline_density: int = 2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maps4fs
3
- Version: 2.1.0
3
+ Version: 2.1.2
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
@@ -546,6 +546,9 @@ Let's have a closer look at the fields:
546
546
  - `border` - this value defines the border between the texture and the edge of the map. It's used to prevent the texture from being drawn on the edge of the map. The value is in pixels.
547
547
  - `precise_tags` - can be used for more specific tags, for example instead of `"natural": "wood"` you can use `"leaf_type": "broadleaved"` to draw only broadleaved trees.
548
548
  - `precise_usage` - the same as `usage`, but being used with `precise_tags`.
549
+ - `area_type` - one of the supported by Giants Editor area types, such as: "open_land", "city", "village", "harbor", "industrial", "open_water". It will be reflected in the environment info layer file.
550
+ - `area_water` - whenever this field is set to true, the area will be considered as water, and it will be changed in the environment info layer file.
551
+ - `indoor` - whenever this field is set to true, the area will be considered as indoor, and it will be reflected in the indoorMask info layer.
549
552
 
550
553
  ## Background terrain
551
554
 
@@ -650,6 +653,8 @@ You can also apply some advanced settings to the map generation process.<br>
650
653
 
651
654
  - Forest density - the density of the forest in meters. The lower the value, the lower the distance between the trees, which makes the forest denser. Note, that low values will lead to enormous number of trees, which may cause the Giants Editor to crash or lead to performance issues. By default, it's set to 10.
652
655
 
656
+ - Tree limit - this value will be used to adjust the forest density value. For example, if it's possible to place 100000 trees from OSM data, and the forest density is set to 10, the expected number of trees on map will be 10000. At the same time, if you set the tree limit to 5000, the forest density will be adjusted to 20, which means that the distance between the trees will be doubled. This value is useful to prevent the Giants Editor from crashing due to too many trees on the map. By default, it's set to 0, which means that it's disabled and will use the forest density value as is. Note, that it will not lead to the exact number of trees, but will adjust the forest density value to fit the tree limit so the resulting number of trees will be more or less equal to the tree limit value.
657
+
653
658
  - Trees relative shift - represents the maximum possible shift of the tree from it's original position in percents of the forest density value. The higher the value, the more the trees will be shifted from their original positions. Warning: higher values can lead to overlapping trees.
654
659
 
655
660
  - Splines density - number of points, which will be added (interpolate) between each pair of existing points. The higher the value, the denser the spline will be. It can smooth the splines, but high values can in opposite make the splines look unnatural.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "maps4fs"
7
- version = "2.1.0"
7
+ version = "2.1.2"
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