maps4fs 1.2.4__py3-none-any.whl → 1.5.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/__init__.py CHANGED
@@ -1,4 +1,13 @@
1
1
  # pylint: disable=missing-module-docstring
2
2
  from maps4fs.generator.game import Game
3
- from maps4fs.generator.map import Map
3
+ from maps4fs.generator.map import (
4
+ BackgroundSettings,
5
+ DEMSettings,
6
+ GRLESettings,
7
+ I3DSettings,
8
+ Map,
9
+ SettingsModel,
10
+ SplineSettings,
11
+ TextureSettings,
12
+ )
4
13
  from maps4fs.logger import Logger
@@ -13,16 +13,10 @@ import numpy as np
13
13
  import trimesh # type: ignore
14
14
 
15
15
  from maps4fs.generator.component import Component
16
- from maps4fs.generator.dem import (
17
- DEFAULT_BLUR_RADIUS,
18
- DEFAULT_MULTIPLIER,
19
- DEFAULT_PLATEAU,
20
- DEM,
21
- )
16
+ from maps4fs.generator.dem import DEM
22
17
  from maps4fs.generator.texture import Texture
23
18
 
24
19
  DEFAULT_DISTANCE = 2048
25
- RESIZE_FACTOR = 1 / 8
26
20
  FULL_NAME = "FULL"
27
21
  FULL_PREVIEW_NAME = "PREVIEW"
28
22
  ELEMENTS = [FULL_NAME, FULL_PREVIEW_NAME]
@@ -46,8 +40,6 @@ class Background(Component):
46
40
  # pylint: disable=R0801
47
41
  def preprocess(self) -> None:
48
42
  """Registers the DEMs for the background terrain."""
49
- self.light_version = self.kwargs.get("light_version", False)
50
- self.water_depth = self.kwargs.get("water_depth", 0)
51
43
  self.stl_preview_path: str | None = None
52
44
  self.water_resources_path: str | None = None
53
45
 
@@ -65,11 +57,12 @@ class Background(Component):
65
57
  os.makedirs(self.background_directory, exist_ok=True)
66
58
  os.makedirs(self.water_directory, exist_ok=True)
67
59
 
68
- autoprocesses = [self.kwargs.get("auto_process", False), False]
60
+ autoprocesses = [self.map.dem_settings.auto_process, False]
69
61
  self.output_paths = [
70
62
  os.path.join(self.background_directory, f"{name}.png") for name in ELEMENTS
71
63
  ]
72
64
  self.not_substracted_path = os.path.join(self.background_directory, "not_substracted.png")
65
+ self.not_resized_path = os.path.join(self.background_directory, "not_resized.png")
73
66
 
74
67
  dems = []
75
68
 
@@ -83,13 +76,12 @@ class Background(Component):
83
76
  self.rotation,
84
77
  self.map_directory,
85
78
  self.logger,
86
- auto_process=autoprocess,
87
- blur_radius=self.kwargs.get("blur_radius", DEFAULT_BLUR_RADIUS),
88
- multiplier=self.kwargs.get("multiplier", DEFAULT_MULTIPLIER),
89
- plateau=self.kwargs.get("plateau", DEFAULT_PLATEAU),
90
79
  )
91
80
  dem.preprocess()
92
81
  dem.is_preview = self.is_preview(name) # type: ignore
82
+ if dem.is_preview: # type: ignore
83
+ dem.multiplier = 1
84
+ dem.auto_process = autoprocess
93
85
  dem.set_output_resolution((self.rotated_size, self.rotated_size))
94
86
  dem.set_dem_path(output_path)
95
87
  dems.append(dem)
@@ -117,8 +109,9 @@ class Background(Component):
117
109
  dem.process()
118
110
  if not dem.is_preview: # type: ignore
119
111
  shutil.copyfile(dem.dem_path, self.not_substracted_path)
112
+ self.cutout(dem.dem_path, save_path=self.not_resized_path)
120
113
 
121
- if self.water_depth:
114
+ if self.map.dem_settings.water_depth:
122
115
  self.subtraction()
123
116
 
124
117
  for dem in self.dems:
@@ -127,11 +120,10 @@ class Background(Component):
127
120
  if self.game.additional_dem_name is not None:
128
121
  self.make_copy(cutted_dem_path, self.game.additional_dem_name)
129
122
 
130
- if not self.light_version:
123
+ if self.map.background_settings.generate_background:
131
124
  self.generate_obj_files()
125
+ if self.map.background_settings.generate_water:
132
126
  self.generate_water_resources_obj()
133
- else:
134
- self.logger.info("Light version is enabled, obj files will not be generated.")
135
127
 
136
128
  def make_copy(self, dem_path: str, dem_name: str) -> None:
137
129
  """Copies DEM data to additional DEM file.
@@ -145,7 +137,7 @@ class Background(Component):
145
137
  additional_dem_path = os.path.join(dem_directory, dem_name)
146
138
 
147
139
  shutil.copyfile(dem_path, additional_dem_path)
148
- self.logger.info("Additional DEM data was copied to %s.", additional_dem_path)
140
+ self.logger.debug("Additional DEM data was copied to %s.", additional_dem_path)
149
141
 
150
142
  def info_sequence(self) -> dict[str, str | float | int]:
151
143
  """Returns a dictionary with information about the background terrain.
@@ -207,11 +199,12 @@ class Background(Component):
207
199
  self.plane_from_np(dem_data, save_path, is_preview=dem.is_preview) # type: ignore
208
200
 
209
201
  # pylint: disable=too-many-locals
210
- def cutout(self, dem_path: str) -> str:
202
+ def cutout(self, dem_path: str, save_path: str | None = None) -> str:
211
203
  """Cuts out the center of the DEM (the actual map) and saves it as a separate file.
212
204
 
213
205
  Arguments:
214
206
  dem_path (str): The path to the DEM file.
207
+ save_path (str, optional): The path where the cutout DEM file will be saved.
215
208
 
216
209
  Returns:
217
210
  str -- The path to the cutout DEM file.
@@ -226,6 +219,11 @@ class Background(Component):
226
219
  y2 = center[1] + half_size
227
220
  dem_data = dem_data[x1:x2, y1:y2]
228
221
 
222
+ if save_path:
223
+ cv2.imwrite(save_path, dem_data) # pylint: disable=no-member
224
+ self.logger.debug("Not resized DEM saved: %s", save_path)
225
+ return save_path
226
+
229
227
  output_size = self.map_size + 1
230
228
 
231
229
  main_dem_path = self.game.dem_file_path(self.map_directory)
@@ -261,11 +259,12 @@ class Background(Component):
261
259
  is_preview (bool, optional) -- If True, the preview mesh will be generated.
262
260
  include_zeros (bool, optional) -- If True, the mesh will include the zero height values.
263
261
  """
262
+ resize_factor = 1 / self.map.background_settings.resize_factor
264
263
  dem_data = cv2.resize( # pylint: disable=no-member
265
- dem_data, (0, 0), fx=RESIZE_FACTOR, fy=RESIZE_FACTOR
264
+ dem_data, (0, 0), fx=resize_factor, fy=resize_factor
266
265
  )
267
266
  self.logger.debug(
268
- "DEM data resized to shape: %s with factor: %s", dem_data.shape, RESIZE_FACTOR
267
+ "DEM data resized to shape: %s with factor: %s", dem_data.shape, resize_factor
269
268
  )
270
269
 
271
270
  # Invert the height values.
@@ -325,13 +324,13 @@ class Background(Component):
325
324
  self.mesh_to_stl(mesh)
326
325
  else:
327
326
  if not include_zeros:
328
- multiplier = self.kwargs.get("multiplier", DEFAULT_MULTIPLIER)
327
+ multiplier = self.map.dem_settings.multiplier
329
328
  if multiplier != 1:
330
329
  z_scaling_factor = 1 / multiplier
331
330
  else:
332
331
  z_scaling_factor = 1 / 2**5
333
332
  self.logger.debug("Z scaling factor: %s", z_scaling_factor)
334
- mesh.apply_scale([1 / RESIZE_FACTOR, 1 / RESIZE_FACTOR, z_scaling_factor])
333
+ mesh.apply_scale([1 / resize_factor, 1 / resize_factor, z_scaling_factor])
335
334
 
336
335
  mesh.export(save_path)
337
336
  self.logger.debug("Obj file saved: %s", save_path)
@@ -347,7 +346,7 @@ class Background(Component):
347
346
  preview_path = os.path.join(self.previews_directory, "background_dem.stl")
348
347
  mesh.export(preview_path)
349
348
 
350
- self.logger.info("STL file saved: %s", preview_path)
349
+ self.logger.debug("STL file saved: %s", preview_path)
351
350
 
352
351
  self.stl_preview_path = preview_path # pylint: disable=attribute-defined-outside-init
353
352
 
@@ -485,8 +484,7 @@ class Background(Component):
485
484
  rotation=self.rotation,
486
485
  map_directory=self.map_directory,
487
486
  logger=self.logger,
488
- light_version=self.light_version,
489
- custom_schema=background_layers,
487
+ texture_custom_schema=background_layers, # type: ignore
490
488
  )
491
489
 
492
490
  self.background_texture.preprocess()
@@ -509,7 +507,7 @@ class Background(Component):
509
507
 
510
508
  background_save_path = os.path.join(self.water_directory, "water_resources.png")
511
509
  cv2.imwrite(background_save_path, background_image)
512
- self.logger.info("Background texture saved: %s", background_save_path)
510
+ self.logger.debug("Background texture saved: %s", background_save_path)
513
511
  self.water_resources_path = background_save_path # pylint: disable=W0201
514
512
 
515
513
  def subtraction(self) -> None:
@@ -534,7 +532,7 @@ class Background(Component):
534
532
 
535
533
  # Create a mask where water_resources_image is 255 (or not 0)
536
534
  # Subtract water_depth from dem_image where mask is True
537
- dem_image[mask] = dem_image[mask] - self.water_depth
535
+ dem_image[mask] = dem_image[mask] - self.map.dem_settings.water_depth
538
536
 
539
537
  # Save the modified dem_image back to the output path
540
538
  cv2.imwrite(output_path, dem_image)
@@ -7,11 +7,11 @@ import os
7
7
  from copy import deepcopy
8
8
  from typing import TYPE_CHECKING, Any
9
9
 
10
- import cv2
10
+ import cv2 # type: ignore
11
11
  import osmnx as ox # type: ignore
12
12
  from pyproj import Transformer
13
13
  from shapely.affinity import rotate, translate # type: ignore
14
- from shapely.geometry import Polygon, box # type: ignore
14
+ from shapely.geometry import LineString, Polygon, box # type: ignore
15
15
 
16
16
  from maps4fs.generator.qgis import save_scripts
17
17
 
@@ -20,7 +20,7 @@ if TYPE_CHECKING:
20
20
  from maps4fs.generator.map import Map
21
21
 
22
22
 
23
- # pylint: disable=R0801, R0903, R0902, R0904
23
+ # pylint: disable=R0801, R0903, R0902, R0904, R0913, R0917
24
24
  class Component:
25
25
  """Base class for all map generation components.
26
26
 
@@ -46,7 +46,7 @@ class Component:
46
46
  rotation: int,
47
47
  map_directory: str,
48
48
  logger: Any = None,
49
- **kwargs, # pylint: disable=W0613, R0913, R0917
49
+ **kwargs: dict[str, Any],
50
50
  ):
51
51
  self.game = game
52
52
  self.map = map
@@ -58,6 +58,13 @@ class Component:
58
58
  self.logger = logger
59
59
  self.kwargs = kwargs
60
60
 
61
+ self.logger.info(
62
+ "Component %s initialized. Map size: %s, map rotated size: %s", # type: ignore
63
+ self.__class__.__name__,
64
+ self.map_size,
65
+ self.map_rotated_size,
66
+ )
67
+
61
68
  os.makedirs(self.previews_directory, exist_ok=True)
62
69
  os.makedirs(self.scripts_directory, exist_ok=True)
63
70
  os.makedirs(self.info_layers_directory, exist_ok=True)
@@ -331,62 +338,79 @@ class Component:
331
338
  return cs_x, cs_y
332
339
 
333
340
  # pylint: disable=R0914
334
- def fit_polygon_into_bounds(
335
- self, polygon_points: list[tuple[int, int]], margin: int = 0, angle: int = 0
341
+ def fit_object_into_bounds(
342
+ self,
343
+ polygon_points: list[tuple[int, int]] | None = None,
344
+ linestring_points: list[tuple[int, int]] | None = None,
345
+ margin: int = 0,
346
+ angle: int = 0,
336
347
  ) -> list[tuple[int, int]]:
337
348
  """Fits a polygon into the bounds of the map.
338
349
 
339
350
  Arguments:
340
351
  polygon_points (list[tuple[int, int]]): The points of the polygon.
352
+ linestring_points (list[tuple[int, int]]): The points of the linestring.
341
353
  margin (int, optional): The margin to add to the polygon. Defaults to 0.
342
354
  angle (int, optional): The angle to rotate the polygon by. Defaults to 0.
343
355
 
344
356
  Returns:
345
357
  list[tuple[int, int]]: The points of the polygon fitted into the map bounds.
346
358
  """
359
+ if polygon_points is None and linestring_points is None:
360
+ raise ValueError("Either polygon or linestring points must be provided.")
361
+
347
362
  min_x = min_y = 0
348
363
  max_x = max_y = self.map_size
349
364
 
350
- polygon = Polygon(polygon_points)
365
+ object_type = Polygon if polygon_points else LineString
366
+
367
+ # polygon = Polygon(polygon_points)
368
+ osm_object = object_type(polygon_points or linestring_points)
351
369
 
352
370
  if angle:
353
371
  center_x = center_y = self.map_rotated_size // 2
354
372
  self.logger.debug(
355
- "Rotating the polygon by %s degrees with center at %sx%s",
373
+ "Rotating the osm_object by %s degrees with center at %sx%s",
356
374
  angle,
357
375
  center_x,
358
376
  center_y,
359
377
  )
360
- polygon = rotate(polygon, -angle, origin=(center_x, center_y))
378
+ osm_object = rotate(osm_object, -angle, origin=(center_x, center_y))
361
379
  offset = (self.map_size / 2) - (self.map_rotated_size / 2)
362
- self.logger.debug("Translating the polygon by %s", offset)
363
- polygon = translate(polygon, xoff=offset, yoff=offset)
364
- self.logger.debug("Rotated and translated polygon.")
380
+ self.logger.debug("Translating the osm_object by %s", offset)
381
+ osm_object = translate(osm_object, xoff=offset, yoff=offset)
382
+ self.logger.debug("Rotated and translated the osm_object.")
365
383
 
366
- if margin:
367
- polygon = polygon.buffer(margin, join_style="mitre")
368
- if polygon.is_empty:
369
- raise ValueError("The polygon is empty after adding the margin.")
384
+ if margin and object_type is Polygon:
385
+ osm_object = osm_object.buffer(margin, join_style="mitre")
386
+ if osm_object.is_empty:
387
+ raise ValueError("The osm_object is empty after adding the margin.")
370
388
 
371
389
  # Create a bounding box for the map bounds
372
390
  bounds = box(min_x, min_y, max_x, max_y)
373
391
 
374
- # Intersect the polygon with the bounds to fit it within the map
392
+ # Intersect the osm_object with the bounds to fit it within the map
375
393
  try:
376
- fitted_polygon = polygon.intersection(bounds)
377
- self.logger.debug("Fitted the polygon into the bounds: %s", bounds)
394
+ fitted_osm_object = osm_object.intersection(bounds)
395
+ self.logger.debug("Fitted the osm_object into the bounds: %s", bounds)
378
396
  except Exception as e:
379
397
  raise ValueError( # pylint: disable=W0707
380
- f"Could not fit the polygon into the bounds: {e}"
398
+ f"Could not fit the osm_object into the bounds: {e}"
381
399
  )
382
400
 
383
- if not isinstance(fitted_polygon, Polygon):
384
- raise ValueError("The fitted polygon is not a valid polygon.")
401
+ if not isinstance(fitted_osm_object, object_type):
402
+ raise ValueError("The fitted osm_object is not valid (probably splitted into parts).")
385
403
 
386
404
  # Return the fitted polygon points
387
- as_list = list(fitted_polygon.exterior.coords)
405
+ if object_type is Polygon:
406
+ as_list = list(fitted_osm_object.exterior.coords)
407
+ elif object_type is LineString:
408
+ as_list = list(fitted_osm_object.coords)
409
+ else:
410
+ raise ValueError("The object type is not supported.")
411
+
388
412
  if not as_list:
389
- raise ValueError("The fitted polygon has no points.")
413
+ raise ValueError("The fitted osm_object has no points.")
390
414
  return as_list
391
415
 
392
416
  def get_infolayer_path(self, layer_name: str) -> str | None:
@@ -469,3 +493,35 @@ class Component:
469
493
  self.logger.debug("Shape of the cropped image: %s", cropped.shape)
470
494
 
471
495
  cv2.imwrite(output_path, cropped)
496
+
497
+ @staticmethod
498
+ def interpolate_points(
499
+ polyline: list[tuple[int, int]], num_points: int = 4
500
+ ) -> list[tuple[int, int]]:
501
+ """Receives a list of tuples, which represents a polyline. Add additional points
502
+ between the existing points to make the polyline smoother.
503
+
504
+ Arguments:
505
+ polyline (list[tuple[int, int]]): The list of points to interpolate.
506
+ num_points (int): The number of additional points to add between each pair of points.
507
+
508
+ Returns:
509
+ list[tuple[int, int]]: The list of points with additional points.
510
+ """
511
+ if not polyline or num_points < 1:
512
+ return polyline
513
+
514
+ interpolated_polyline = []
515
+ for i in range(len(polyline) - 1):
516
+ p1 = polyline[i]
517
+ p2 = polyline[i + 1]
518
+ interpolated_polyline.append(p1)
519
+ for j in range(1, num_points + 1):
520
+ new_point = (
521
+ p1[0] + (p2[0] - p1[0]) * j / (num_points + 1),
522
+ p1[1] + (p2[1] - p1[1]) * j / (num_points + 1),
523
+ )
524
+ interpolated_polyline.append((int(new_point[0]), int(new_point[1])))
525
+ interpolated_polyline.append(polyline[-1])
526
+
527
+ return interpolated_polyline
@@ -38,7 +38,7 @@ class Config(Component):
38
38
  self.logger.warning("Map XML file not found: %s.", self._map_xml_path)
39
39
  return
40
40
  tree = ET.parse(self._map_xml_path)
41
- self.logger.info("Map XML file loaded from: %s.", self._map_xml_path)
41
+ self.logger.debug("Map XML file loaded from: %s.", self._map_xml_path)
42
42
  root = tree.getroot()
43
43
  for map_elem in root.iter("map"):
44
44
  map_elem.set("width", str(self.map_size))
maps4fs/generator/dem.py CHANGED
@@ -14,9 +14,6 @@ from pympler import asizeof # type: ignore
14
14
  from maps4fs.generator.component import Component
15
15
 
16
16
  SRTM = "https://elevation-tiles-prod.s3.amazonaws.com/skadi/{latitude_band}/{tile_name}.hgt.gz"
17
- DEFAULT_MULTIPLIER = 1
18
- DEFAULT_BLUR_RADIUS = 35
19
- DEFAULT_PLATEAU = 0
20
17
 
21
18
 
22
19
  # pylint: disable=R0903, R0902
@@ -50,20 +47,21 @@ class DEM(Component):
50
47
  self.output_resolution = self.get_output_resolution()
51
48
  self.logger.debug("Output resolution for DEM data: %s.", self.output_resolution)
52
49
 
53
- self.multiplier = self.kwargs.get("multiplier", DEFAULT_MULTIPLIER)
54
- blur_radius = self.kwargs.get("blur_radius", DEFAULT_BLUR_RADIUS)
50
+ blur_radius = self.map.dem_settings.blur_radius
55
51
  if blur_radius is None or blur_radius <= 0:
56
52
  # We'll disable blur if the radius is 0 or negative.
57
53
  blur_radius = 0
58
54
  elif blur_radius % 2 == 0:
59
55
  blur_radius += 1
60
56
  self.blur_radius = blur_radius
57
+ self.multiplier = self.map.dem_settings.multiplier
61
58
  self.logger.debug(
62
- "DEM value multiplier is %s, blur radius is %s.", self.multiplier, self.blur_radius
59
+ "DEM value multiplier is %s, blur radius is %s.",
60
+ self.multiplier,
61
+ self.blur_radius,
63
62
  )
64
63
 
65
- self.auto_process = self.kwargs.get("auto_process", False)
66
- self.plateau = self.kwargs.get("plateau", False)
64
+ self.auto_process = self.map.dem_settings.auto_process
67
65
 
68
66
  @property
69
67
  def dem_path(self) -> str:
@@ -240,24 +238,24 @@ class DEM(Component):
240
238
  resampled_data.max(),
241
239
  )
242
240
 
243
- if self.plateau:
241
+ if self.map.dem_settings.plateau:
244
242
  # Plateau is a flat area with a constant height.
245
243
  # So we just add this value to each pixel of the DEM.
246
244
  # And also need to ensure that there will be no values with height greater than
247
245
  # it's allowed in 16-bit unsigned integer.
248
246
 
249
- resampled_data += self.plateau
247
+ resampled_data += self.map.dem_settings.plateau
250
248
  resampled_data = np.clip(resampled_data, 0, 65535)
251
249
 
252
250
  self.logger.debug(
253
251
  "Plateau with height %s was added to DEM data. Min: %s, max: %s.",
254
- self.plateau,
252
+ self.map.dem_settings.plateau,
255
253
  resampled_data.min(),
256
254
  resampled_data.max(),
257
255
  )
258
256
 
259
257
  cv2.imwrite(self._dem_path, resampled_data)
260
- self.logger.info("DEM data was saved to %s.", self._dem_path)
258
+ self.logger.debug("DEM data was saved to %s.", self._dem_path)
261
259
 
262
260
  if self.rotation:
263
261
  self.rotate_dem()
@@ -403,7 +401,7 @@ class DEM(Component):
403
401
 
404
402
  scaling_factor = self._get_scaling_factor(max_dev)
405
403
  adjusted_max_height = int(65535 * scaling_factor)
406
- self.logger.info(
404
+ self.logger.debug(
407
405
  "Maximum deviation: %s. Scaling factor: %s. Adjusted max height: %s.",
408
406
  max_dev,
409
407
  scaling_factor,
maps4fs/generator/game.py CHANGED
@@ -39,7 +39,7 @@ class Game:
39
39
  _tree_schema: str | None = None
40
40
 
41
41
  # Order matters! Some components depend on others.
42
- components = [Texture, I3d, GRLE, Background, Config]
42
+ components = [Texture, GRLE, Background, I3d, Config]
43
43
 
44
44
  def __init__(self, map_template_path: str | None = None):
45
45
  if map_template_path:
maps4fs/generator/grle.py CHANGED
@@ -40,13 +40,10 @@ class GRLE(Component):
40
40
  """Gets the path to the map I3D file from the game instance and saves it to the instance
41
41
  attribute. If the game does not support I3D files, the attribute is set to None."""
42
42
 
43
- self.farmland_margin = self.kwargs.get("farmland_margin", 0)
44
- self.randomize_plants = self.kwargs.get("randomize_plants", True)
45
-
46
43
  try:
47
44
  grle_schema_path = self.game.grle_schema
48
45
  except ValueError:
49
- self.logger.info("GRLE schema processing is not implemented for this game.")
46
+ self.logger.warning("GRLE schema processing is not implemented for this game.")
50
47
  return
51
48
 
52
49
  try:
@@ -60,7 +57,7 @@ class GRLE(Component):
60
57
  def process(self) -> None:
61
58
  """Generates InfoLayer PNG files based on the GRLE schema."""
62
59
  if not self._grle_schema:
63
- self.logger.info("GRLE schema is not obtained, skipping the processing.")
60
+ self.logger.debug("GRLE schema is not obtained, skipping the processing.")
64
61
  return
65
62
 
66
63
  for info_layer in self._grle_schema:
@@ -87,7 +84,7 @@ class GRLE(Component):
87
84
 
88
85
  self._add_farmlands()
89
86
  if self.game.code == "FS25":
90
- self.logger.info("Game is %s, plants will be added.", self.game.code)
87
+ self.logger.debug("Game is %s, plants will be added.", self.game.code)
91
88
  self._add_plants()
92
89
  else:
93
90
  self.logger.warning("Adding plants it's not supported for the %s.", self.game.code)
@@ -119,11 +116,16 @@ class GRLE(Component):
119
116
 
120
117
  self.logger.info("Found %s fields in textures info layer.", len(fields))
121
118
 
119
+ farmyards: list[list[tuple[int, int]]] | None = textures_info_layer.get("farmyards")
120
+ if farmyards and self.map.grle_settings.add_farmyards:
121
+ fields.extend(farmyards)
122
+ self.logger.info("Found %s farmyards in textures info layer.", len(farmyards))
123
+
122
124
  info_layer_farmlands_path = os.path.join(
123
125
  self.game.weights_dir_path(self.map_directory), "infoLayer_farmlands.png"
124
126
  )
125
127
 
126
- self.logger.info(
128
+ self.logger.debug(
127
129
  "Adding farmlands to the InfoLayer PNG file: %s.", info_layer_farmlands_path
128
130
  )
129
131
 
@@ -147,8 +149,10 @@ class GRLE(Component):
147
149
 
148
150
  for field in fields:
149
151
  try:
150
- fitted_field = self.fit_polygon_into_bounds(
151
- field, self.farmland_margin, angle=self.rotation
152
+ fitted_field = self.fit_object_into_bounds(
153
+ polygon_points=field,
154
+ margin=self.map.grle_settings.farmland_margin,
155
+ angle=self.rotation,
152
156
  )
153
157
  except ValueError as e:
154
158
  self.logger.warning(
@@ -193,10 +197,10 @@ class GRLE(Component):
193
197
 
194
198
  tree.write(farmlands_xml_path)
195
199
 
196
- self.logger.info("Farmlands added to the farmlands XML file: %s.", farmlands_xml_path)
200
+ self.logger.debug("Farmlands added to the farmlands XML file: %s.", farmlands_xml_path)
197
201
 
198
202
  cv2.imwrite(info_layer_farmlands_path, image) # pylint: disable=no-member
199
- self.logger.info(
203
+ self.logger.debug(
200
204
  "Farmlands added to the InfoLayer PNG file: %s.", info_layer_farmlands_path
201
205
  )
202
206
 
@@ -357,8 +361,8 @@ class GRLE(Component):
357
361
 
358
362
  # Add islands of plants to the base image.
359
363
  island_count = self.map_size
360
- self.logger.info("Adding %s islands of plants to the base image.", island_count)
361
- if self.randomize_plants:
364
+ self.logger.debug("Adding %s islands of plants to the base image.", island_count)
365
+ if self.map.grle_settings.random_plants:
362
366
  grass_image_copy = create_island_of_plants(grass_image_copy, island_count)
363
367
  self.logger.debug("Islands of plants added to the base image.")
364
368
 
@@ -394,4 +398,4 @@ class GRLE(Component):
394
398
  # Ensure that order of channels is correct because CV2 uses BGR and we need RGB.
395
399
  density_map_fruits = cv2.cvtColor(density_map_fruits, cv2.COLOR_BGR2RGB)
396
400
  cv2.imwrite(density_map_fruit_path, density_map_fruits)
397
- self.logger.info("Updated density map for fruits saved in %s.", density_map_fruit_path)
401
+ self.logger.debug("Updated density map for fruits saved in %s.", density_map_fruit_path)