maps4fs 1.8.13__py3-none-any.whl → 1.8.15__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.
@@ -3,17 +3,15 @@
3
3
  import json
4
4
  import os
5
5
  from random import choice, randint
6
- from xml.etree import ElementTree as ET
7
6
 
8
7
  import cv2
9
8
  import numpy as np
10
- from shapely.geometry import Polygon # type: ignore
9
+ from shapely.geometry import Polygon
11
10
  from tqdm import tqdm
12
11
 
13
- from maps4fs.generator.component.base.component import Component
14
- from maps4fs.generator.texture import PREVIEW_MAXIMUM_SIZE, Texture
15
-
16
- ISLAND_DISTORTION = 0.3
12
+ from maps4fs.generator.component.base.component_image import ImageComponent
13
+ from maps4fs.generator.component.base.component_xml import XMLComponent
14
+ from maps4fs.generator.settings import Parameters
17
15
 
18
16
 
19
17
  def plant_to_pixel_value(plant_name: str) -> int | None:
@@ -33,8 +31,7 @@ def plant_to_pixel_value(plant_name: str) -> int | None:
33
31
  return plants.get(plant_name)
34
32
 
35
33
 
36
- # pylint: disable=W0223
37
- class GRLE(Component):
34
+ class GRLE(ImageComponent, XMLComponent):
38
35
  """Component for to generate InfoLayer PNG files based on GRLE schema.
39
36
 
40
37
  Arguments:
@@ -48,34 +45,41 @@ class GRLE(Component):
48
45
  info, warning. If not provided, default logging will be used.
49
46
  """
50
47
 
51
- _grle_schema: dict[str, float | int | str] | None = None
52
-
53
48
  def preprocess(self) -> None:
54
49
  """Gets the path to the map I3D file from the game instance and saves it to the instance
55
50
  attribute. If the game does not support I3D files, the attribute is set to None."""
56
51
  self.preview_paths: dict[str, str] = {}
52
+ try:
53
+ self.xml_path = self.game.get_farmlands_xml_path(self.map_directory)
54
+ except NotImplementedError:
55
+ self.logger.warning("Farmlands XML file processing is not implemented for this game.")
56
+ self.xml_path = None
57
57
 
58
+ def _read_grle_schema(self) -> dict[str, float | int | str] | None:
58
59
  try:
59
60
  grle_schema_path = self.game.grle_schema
60
61
  except ValueError:
61
62
  self.logger.warning("GRLE schema processing is not implemented for this game.")
62
- return
63
+ return None
63
64
 
64
65
  try:
65
66
  with open(grle_schema_path, "r", encoding="utf-8") as file:
66
- self._grle_schema = json.load(file)
67
+ grle_schema = json.load(file)
67
68
  self.logger.debug("GRLE schema loaded from: %s.", grle_schema_path)
68
69
  except (json.JSONDecodeError, FileNotFoundError) as error:
69
70
  self.logger.error("Error loading GRLE schema from %s: %s.", grle_schema_path, error)
70
- self._grle_schema = None
71
+ grle_schema = None
72
+
73
+ return grle_schema
71
74
 
72
75
  def process(self) -> None:
73
76
  """Generates InfoLayer PNG files based on the GRLE schema."""
74
- if not self._grle_schema:
77
+ grle_schema = self._read_grle_schema()
78
+ if not grle_schema:
75
79
  self.logger.debug("GRLE schema is not obtained, skipping the processing.")
76
80
  return
77
81
 
78
- for info_layer in tqdm(self._grle_schema, desc="Preparing GRLE files", unit="layer"):
82
+ for info_layer in tqdm(grle_schema, desc="Preparing GRLE files", unit="layer"):
79
83
  if isinstance(info_layer, dict):
80
84
  file_path = os.path.join(
81
85
  self.game.weights_dir_path(self.map_directory), info_layer["name"]
@@ -98,8 +102,7 @@ class GRLE(Component):
98
102
  self.logger.warning("Invalid InfoLayer schema: %s.", info_layer)
99
103
 
100
104
  self._add_farmlands()
101
- if self.game.code == "FS25":
102
- self.logger.debug("Game is %s, plants will be added.", self.game.code)
105
+ if self.game.plants_processing:
103
106
  self._add_plants()
104
107
  else:
105
108
  self.logger.warning("Adding plants it's not supported for the %s.", self.game.code)
@@ -116,8 +119,13 @@ class GRLE(Component):
116
119
  save_path = os.path.join(self.previews_directory, f"{preview_name}.png")
117
120
  # Resize the preview image to the maximum size allowed for previews.
118
121
  image = cv2.imread(preview_path, cv2.IMREAD_GRAYSCALE)
119
- if image.shape[0] > PREVIEW_MAXIMUM_SIZE or image.shape[1] > PREVIEW_MAXIMUM_SIZE:
120
- image = cv2.resize(image, (PREVIEW_MAXIMUM_SIZE, PREVIEW_MAXIMUM_SIZE))
122
+ if (
123
+ image.shape[0] > Parameters.PREVIEW_MAXIMUM_SIZE
124
+ or image.shape[1] > Parameters.PREVIEW_MAXIMUM_SIZE
125
+ ):
126
+ image = cv2.resize(
127
+ image, (Parameters.PREVIEW_MAXIMUM_SIZE, Parameters.PREVIEW_MAXIMUM_SIZE)
128
+ )
121
129
  image_normalized = np.empty_like(image)
122
130
  cv2.normalize(image, image_normalized, 0, 255, cv2.NORM_MINMAX)
123
131
  image_colored = cv2.applyColorMap(image_normalized, cv2.COLORMAP_JET)
@@ -144,13 +152,12 @@ class GRLE(Component):
144
152
  Returns:
145
153
  np.ndarray | None: The farmlands preview image with fields overlayed on top of it.
146
154
  """
147
- texture_component: Texture | None = self.map.get_component("Texture") # type: ignore
148
- if not texture_component:
149
- self.logger.warning("Texture component not found in the map.")
155
+ fields_layer = self.map.get_texture_layer(by_usage="field")
156
+ if not fields_layer:
157
+ self.logger.warning("Fields layer not found in the texture component.")
150
158
  return None
151
159
 
152
- fields_layer = texture_component.get_layer_by_usage("field")
153
- fields_layer_path = fields_layer.get_preview_or_path( # type: ignore
160
+ fields_layer_path = fields_layer.get_preview_or_path(
154
161
  self.game.weights_dir_path(self.map_directory)
155
162
  )
156
163
  if not fields_layer_path or not os.path.isfile(fields_layer_path):
@@ -163,24 +170,15 @@ class GRLE(Component):
163
170
  # use fields_np as base layer and overlay farmlands_np on top of it with 50% alpha blending.
164
171
  return cv2.addWeighted(fields_np, 0.5, farmlands_np, 0.5, 0)
165
172
 
166
- # pylint: disable=R0801, R0914, R0915
167
173
  def _add_farmlands(self) -> None:
168
174
  """Adds farmlands to the InfoLayer PNG file."""
169
-
170
- textures_info_layer_path = self.get_infolayer_path("textures")
171
- if not textures_info_layer_path:
172
- return
173
-
174
- with open(textures_info_layer_path, "r", encoding="utf-8") as textures_info_layer_file:
175
- textures_info_layer = json.load(textures_info_layer_file)
176
-
177
175
  farmlands = []
178
- farmyards: list[list[tuple[int, int]]] | None = textures_info_layer.get("farmyards")
176
+ farmyards = self.get_infolayer_data(Parameters.TEXTURES, Parameters.FARMYARDS)
179
177
  if farmyards and self.map.grle_settings.add_farmyards:
180
178
  farmlands.extend(farmyards)
181
179
  self.logger.debug("Found %s farmyards in textures info layer.", len(farmyards))
182
180
 
183
- fields: list[list[tuple[int, int]]] | None = textures_info_layer.get("fields")
181
+ fields = self.get_infolayer_data(Parameters.TEXTURES, Parameters.FIELDS)
184
182
  if not fields:
185
183
  self.logger.warning("Fields data not found in textures info layer.")
186
184
  return
@@ -188,9 +186,7 @@ class GRLE(Component):
188
186
 
189
187
  self.logger.debug("Found %s fields in textures info layer.", len(fields))
190
188
 
191
- info_layer_farmlands_path = os.path.join(
192
- self.game.weights_dir_path(self.map_directory), "infoLayer_farmlands.png"
193
- )
189
+ info_layer_farmlands_path = self.game.get_farmlands_path(self.map_directory)
194
190
 
195
191
  self.logger.debug(
196
192
  "Adding farmlands to the InfoLayer PNG file: %s.", info_layer_farmlands_path
@@ -201,22 +197,21 @@ class GRLE(Component):
201
197
  return
202
198
 
203
199
  image = cv2.imread(info_layer_farmlands_path, cv2.IMREAD_UNCHANGED)
204
- farmlands_xml_path = os.path.join(self.map_directory, "map/config/farmlands.xml")
205
- if not os.path.isfile(farmlands_xml_path):
206
- self.logger.warning("Farmlands XML file %s not found.", farmlands_xml_path)
207
- return
208
200
 
209
- tree = ET.parse(farmlands_xml_path)
210
- farmlands_xml = tree.find("farmlands")
201
+ tree = self.get_tree()
202
+ root = tree.getroot()
203
+ farmlands_node = root.find("farmlands")
204
+ if farmlands_node is None:
205
+ raise ValueError("Farmlands XML element not found in the farmlands XML file.")
206
+
207
+ self.update_element(farmlands_node, {"pricePerHa": str(self.map.grle_settings.base_price)})
211
208
 
212
- # Not using enumerate because in case of the error, we do not increment
213
- # the farmland_id. So as a result we do not have a gap in the farmland IDs.
214
209
  farmland_id = 1
215
210
 
216
- for farmland_data in tqdm(farmlands, desc="Adding farmlands", unit="farmland"):
211
+ for farmland in tqdm(farmlands, desc="Adding farmlands", unit="farmland"):
217
212
  try:
218
- fitted_field = self.fit_object_into_bounds(
219
- polygon_points=farmland_data,
213
+ fitted_farmland = self.fit_object_into_bounds(
214
+ polygon_points=farmland,
220
215
  margin=self.map.grle_settings.farmland_margin,
221
216
  angle=self.rotation,
222
217
  )
@@ -228,23 +223,11 @@ class GRLE(Component):
228
223
  )
229
224
  continue
230
225
 
231
- self.logger.debug("Fitted field %s contains %s points.", farmland_id, len(fitted_field))
232
-
233
- field_np = np.array(fitted_field, np.int32)
234
- field_np = field_np.reshape((-1, 1, 2))
235
-
236
- self.logger.debug(
237
- "Created a numpy array and reshaped it. Number of points: %s", len(field_np)
238
- )
239
-
240
- # Infolayer image is 1/2 of the size of the map image, that's why we need to divide
241
- # the coordinates by 2.
242
- field_np = field_np // 2
243
- self.logger.debug("Divided the coordinates by 2.")
226
+ farmland_np = self.polygon_points_to_np(fitted_farmland, divide=2)
244
227
 
245
228
  try:
246
- cv2.fillPoly(image, [field_np], farmland_id) # type: ignore
247
- except Exception as e: # pylint: disable=W0718
229
+ cv2.fillPoly(image, [farmland_np], (float(farmland_id),))
230
+ except Exception as e:
248
231
  self.logger.debug(
249
232
  "Farmland %s could not be added to the InfoLayer PNG file with error: %s",
250
233
  farmland_id,
@@ -252,37 +235,24 @@ class GRLE(Component):
252
235
  )
253
236
  continue
254
237
 
255
- # Add the field to the farmlands XML.
256
- farmland = ET.SubElement(farmlands_xml, "farmland") # type: ignore
257
- farmland.set("id", str(farmland_id))
258
- farmland.set("priceScale", "1")
259
- farmland.set("npcName", "FORESTER")
238
+ data = {
239
+ "id": str(farmland_id),
240
+ "priceScale": "1",
241
+ "npcName": "FORESTER",
242
+ }
243
+ self.create_subelement(farmlands_node, "farmland", data)
260
244
 
261
245
  farmland_id += 1
262
246
 
263
- tree.write(farmlands_xml_path)
264
-
265
- self.logger.debug("Farmlands added to the farmlands XML file: %s.", farmlands_xml_path)
247
+ self.save_tree(tree)
266
248
 
267
249
  cv2.imwrite(info_layer_farmlands_path, image)
268
- self.logger.debug(
269
- "Farmlands added to the InfoLayer PNG file: %s.", info_layer_farmlands_path
270
- )
271
250
 
272
- self.preview_paths["farmlands"] = info_layer_farmlands_path # type: ignore
251
+ self.preview_paths["farmlands"] = info_layer_farmlands_path
273
252
 
274
- # pylint: disable=R0915
275
253
  def _add_plants(self) -> None:
276
254
  """Adds plants to the InfoLayer PNG file."""
277
- # 1. Get the path to the densityMap_fruits.png.
278
- # 2. Get the path to the base layer (grass).
279
- # 3. Detect non-zero areas in the base layer (it's where the plants will be placed).
280
- texture_component: Texture | None = self.map.get_component("Texture") # type: ignore
281
- if not texture_component:
282
- self.logger.warning("Texture component not found in the map.")
283
- return
284
-
285
- grass_layer = texture_component.get_layer_by_usage("grass")
255
+ grass_layer = self.map.get_texture_layer(by_usage="grass")
286
256
  if not grass_layer:
287
257
  self.logger.warning("Grass layer not found in the texture component.")
288
258
  return
@@ -291,7 +261,7 @@ class GRLE(Component):
291
261
  grass_image_path = grass_layer.get_preview_or_path(weights_directory)
292
262
  self.logger.debug("Grass image path: %s.", grass_image_path)
293
263
 
294
- forest_layer = texture_component.get_layer_by_usage("forest")
264
+ forest_layer = self.map.get_texture_layer(by_usage="forest")
295
265
  forest_image = None
296
266
  if forest_layer:
297
267
  forest_image_path = forest_layer.get_preview_or_path(weights_directory)
@@ -304,9 +274,7 @@ class GRLE(Component):
304
274
  self.logger.warning("Base image not found in %s.", grass_image_path)
305
275
  return
306
276
 
307
- density_map_fruit_path = os.path.join(
308
- self.game.weights_dir_path(self.map_directory), "densityMap_fruits.png"
309
- )
277
+ density_map_fruit_path = self.game.get_density_map_fruits_path(self.map_directory)
310
278
 
311
279
  self.logger.debug("Density map for fruits path: %s.", density_map_fruit_path)
312
280
 
@@ -334,88 +302,14 @@ class GRLE(Component):
334
302
  # Add non zero values from the forest image to the grass image.
335
303
  grass_image[forest_image != 0] = 255
336
304
 
337
- # B and G channels remain the same (zeros), while we change the R channel.
338
- possible_R_values = [65, 97, 129, 161, 193, 225] # pylint: disable=C0103
305
+ base_grass = self.map.grle_settings.base_grass
306
+ if isinstance(base_grass, tuple):
307
+ base_grass = base_grass[0]
339
308
 
340
- base_layer_pixel_value = plant_to_pixel_value(
341
- self.map.grle_settings.base_grass # type:ignore
342
- )
309
+ base_layer_pixel_value = plant_to_pixel_value(str(base_grass))
343
310
  if not base_layer_pixel_value:
344
311
  base_layer_pixel_value = 131
345
312
 
346
- def create_island_of_plants(image: np.ndarray, count: int) -> np.ndarray:
347
- """Create an island of plants in the image.
348
-
349
- Arguments:
350
- image (np.ndarray): The image where the island of plants will be created.
351
- count (int): The number of islands of plants to create.
352
-
353
- Returns:
354
- np.ndarray: The image with the islands of plants.
355
- """
356
- for _ in tqdm(range(count), desc="Adding islands of plants", unit="island"):
357
- # Randomly choose the value for the island.
358
- plant_value = choice(possible_R_values)
359
- # Randomly choose the size of the island.
360
- island_size = randint(
361
- self.map.grle_settings.plants_island_minimum_size, # type:ignore
362
- self.map.grle_settings.plants_island_maximum_size, # type:ignore
363
- )
364
- # Randomly choose the position of the island.
365
- x = randint(0, image.shape[1] - island_size)
366
- y = randint(0, image.shape[0] - island_size)
367
-
368
- try:
369
- polygon_points = get_rounded_polygon(
370
- num_vertices=self.map.grle_settings.plants_island_vertex_count,
371
- center=(x + island_size // 2, y + island_size // 2),
372
- radius=island_size // 2,
373
- rounding_radius=self.map.grle_settings.plants_island_rounding_radius,
374
- )
375
- if not polygon_points:
376
- continue
377
-
378
- nodes = np.array(polygon_points, np.int32) # type: ignore
379
- cv2.fillPoly(image, [nodes], plant_value) # type: ignore
380
- except Exception: # pylint: disable=W0703
381
- continue
382
-
383
- return image
384
-
385
- def get_rounded_polygon(
386
- num_vertices: int, center: tuple[int, int], radius: int, rounding_radius: int
387
- ) -> list[tuple[int, int]] | None:
388
- """Get a randomly rounded polygon.
389
-
390
- Arguments:
391
- num_vertices (int): The number of vertices of the polygon.
392
- center (tuple[int, int]): The center of the polygon.
393
- radius (int): The radius of the polygon.
394
- rounding_radius (int): The rounding radius of the polygon.
395
-
396
- Returns:
397
- list[tuple[int, int]] | None: The rounded polygon.
398
- """
399
- angle_offset = np.pi / num_vertices
400
- angles = np.linspace(0, 2 * np.pi, num_vertices, endpoint=False) + angle_offset
401
- random_angles = angles + np.random.uniform(
402
- -ISLAND_DISTORTION, ISLAND_DISTORTION, num_vertices
403
- ) # Add randomness to angles
404
- random_radii = radius + np.random.uniform(
405
- -radius * ISLAND_DISTORTION, radius * ISLAND_DISTORTION, num_vertices
406
- ) # Add randomness to radii
407
-
408
- points = [
409
- (center[0] + np.cos(a) * r, center[1] + np.sin(a) * r)
410
- for a, r in zip(random_angles, random_radii)
411
- ]
412
- polygon = Polygon(points)
413
- buffered_polygon = polygon.buffer(rounding_radius, resolution=16)
414
- rounded_polygon = list(buffered_polygon.exterior.coords)
415
- if not rounded_polygon:
416
- return None
417
- return rounded_polygon
418
-
419
313
  grass_image_copy = grass_image.copy()
420
314
  if forest_image is not None:
421
315
  # Add the forest layer to the base image, to merge the masks.
@@ -427,7 +321,7 @@ class GRLE(Component):
427
321
  island_count = int(self.map_size * self.map.grle_settings.plants_island_percent // 100)
428
322
  self.logger.debug("Adding %s islands of plants to the base image.", island_count)
429
323
  if self.map.grle_settings.random_plants:
430
- grass_image_copy = create_island_of_plants(grass_image_copy, island_count)
324
+ grass_image_copy = self.create_island_of_plants(grass_image_copy, island_count)
431
325
  self.logger.debug("Added %s islands of plants to the base image.", island_count)
432
326
 
433
327
  # Sligtly reduce the size of the grass_image, that we'll use as mask.
@@ -438,15 +332,7 @@ class GRLE(Component):
438
332
  grass_image_copy[grass_image == 0] = 0
439
333
  self.logger.debug("Removed the values where the base image has zeros.")
440
334
 
441
- # Set zeros on all sides of the image
442
- grass_image_copy[0, :] = 0 # Top side
443
- grass_image_copy[-1, :] = 0 # Bottom side
444
- grass_image_copy[:, 0] = 0 # Left side
445
- grass_image_copy[:, -1] = 0 # Right side
446
-
447
- # After painting it with base grass, we'll create multiple islands of different plants.
448
- # On the final step, we'll remove all the values which in pixels
449
- # where zerons in the original base image (so we don't paint grass where it should not be).
335
+ grass_image_copy = self.remove_edge_pixel_values(grass_image_copy)
450
336
 
451
337
  # Three channeled 8-bit image, where non-zero values are the
452
338
  # different types of plants (only in the R channel).
@@ -462,3 +348,99 @@ class GRLE(Component):
462
348
  density_map_fruits = cv2.cvtColor(density_map_fruits, cv2.COLOR_BGR2RGB)
463
349
  cv2.imwrite(density_map_fruit_path, density_map_fruits)
464
350
  self.logger.debug("Updated density map for fruits saved in %s.", density_map_fruit_path)
351
+
352
+ def create_island_of_plants(self, image: np.ndarray, count: int) -> np.ndarray:
353
+ """Create an island of plants in the image.
354
+
355
+ Arguments:
356
+ image (np.ndarray): The image where the island of plants will be created.
357
+ count (int): The number of islands of plants to create.
358
+
359
+ Returns:
360
+ np.ndarray: The image with the islands of plants.
361
+ """
362
+ # B and G channels remain the same (zeros), while we change the R channel.
363
+ possible_r_values = [65, 97, 129, 161, 193, 225]
364
+
365
+ for _ in tqdm(range(count), desc="Adding islands of plants", unit="island"):
366
+ # Randomly choose the value for the island.
367
+ plant_value = choice(possible_r_values)
368
+ # Randomly choose the size of the island.
369
+ island_size = randint(
370
+ self.map.grle_settings.plants_island_minimum_size,
371
+ self.map.grle_settings.plants_island_maximum_size,
372
+ )
373
+ # Randomly choose the position of the island.
374
+ x = randint(0, image.shape[1] - island_size)
375
+ y = randint(0, image.shape[0] - island_size)
376
+
377
+ try:
378
+ polygon_points = self.get_rounded_polygon(
379
+ num_vertices=self.map.grle_settings.plants_island_vertex_count,
380
+ center=(x + island_size // 2, y + island_size // 2),
381
+ radius=island_size // 2,
382
+ rounding_radius=self.map.grle_settings.plants_island_rounding_radius,
383
+ )
384
+ if not polygon_points:
385
+ continue
386
+
387
+ nodes = np.array(polygon_points, np.int32)
388
+ cv2.fillPoly(image, [nodes], (float(plant_value),))
389
+ except Exception:
390
+ continue
391
+
392
+ return image
393
+
394
+ @staticmethod
395
+ def get_rounded_polygon(
396
+ num_vertices: int, center: tuple[int, int], radius: int, rounding_radius: int
397
+ ) -> list[tuple[int, int]] | None:
398
+ """Get a randomly rounded polygon.
399
+
400
+ Arguments:
401
+ num_vertices (int): The number of vertices of the polygon.
402
+ center (tuple[int, int]): The center of the polygon.
403
+ radius (int): The radius of the polygon.
404
+ rounding_radius (int): The rounding radius of the polygon.
405
+
406
+ Returns:
407
+ list[tuple[int, int]] | None: The rounded polygon.
408
+ """
409
+ island_distortion = 0.3
410
+
411
+ angle_offset = np.pi / num_vertices
412
+ angles = np.linspace(0, 2 * np.pi, num_vertices, endpoint=False) + angle_offset
413
+ random_angles = angles + np.random.uniform(
414
+ -island_distortion, island_distortion, num_vertices
415
+ ) # Add randomness to angles
416
+ random_radii = radius + np.random.uniform(
417
+ -radius * island_distortion, radius * island_distortion, num_vertices
418
+ ) # Add randomness to radii
419
+
420
+ points = [
421
+ (center[0] + np.cos(a) * r, center[1] + np.sin(a) * r)
422
+ for a, r in zip(random_angles, random_radii)
423
+ ]
424
+ polygon = Polygon(points)
425
+ buffered_polygon = polygon.buffer(rounding_radius, resolution=16)
426
+ rounded_polygon = list(buffered_polygon.exterior.coords)
427
+ if not rounded_polygon:
428
+ return None
429
+ return rounded_polygon
430
+
431
+ @staticmethod
432
+ def remove_edge_pixel_values(image_np: np.ndarray) -> np.ndarray:
433
+ """Remove the edge pixel values from the image.
434
+
435
+ Arguments:
436
+ image_np (np.ndarray): The image to remove the edge pixel values from.
437
+
438
+ Returns:
439
+ np.ndarray: The image with the edge pixel values removed.
440
+ """
441
+ # Set zeros on all sides of the image
442
+ image_np[0, :] = 0 # Top side
443
+ image_np[-1, :] = 0 # Bottom side
444
+ image_np[:, 0] = 0 # Left side
445
+ image_np[:, -1] = 0 # Right side
446
+ return image_np
@@ -297,8 +297,6 @@ class DTMProvider(ABC):
297
297
  with rasterio.open(tile) as src:
298
298
  crs = src.crs
299
299
  if crs != "EPSG:4326":
300
- print("crs:", crs)
301
- print("reprojecting to EPSG:4326")
302
300
  self.logger.debug(f"Reprojecting GeoTIFF from {crs} to EPSG:4326...")
303
301
  tile = self.reproject_geotiff(tile)
304
302
 
@@ -499,12 +497,10 @@ class DTMProvider(ABC):
499
497
  # Open all input GeoTIFF files as datasets
500
498
  self.logger.debug("Merging tiff files...")
501
499
  datasets = [rasterio.open(file) for file in input_files]
502
- print("datasets:", datasets)
503
500
 
504
501
  # Merge datasets
505
502
  crs = datasets[0].crs
506
503
  mosaic, out_transform = merge(datasets, nodata=0)
507
- print("mosaic:", mosaic)
508
504
 
509
505
  # Get metadata from the first file and update it for the output
510
506
  out_meta = datasets[0].meta.copy()
maps4fs/generator/game.py CHANGED
@@ -6,10 +6,10 @@ from __future__ import annotations
6
6
 
7
7
  import os
8
8
 
9
- from maps4fs.generator.background import Background
9
+ from maps4fs.generator.component.background import Background
10
10
  from maps4fs.generator.component.config import Config
11
+ from maps4fs.generator.component.grle import GRLE
11
12
  from maps4fs.generator.component.i3d import I3d
12
- from maps4fs.generator.grle import GRLE
13
13
  from maps4fs.generator.satellite import Satellite
14
14
  from maps4fs.generator.texture import Texture
15
15
 
@@ -39,6 +39,7 @@ class Game:
39
39
  _grle_schema: str | None = None
40
40
  _tree_schema: str | None = None
41
41
  _i3d_processing: bool = True
42
+ _plants_processing: bool = True
42
43
 
43
44
  # Order matters! Some components depend on others.
44
45
  components = [Texture, Background, GRLE, I3d, Config, Satellite]
@@ -147,6 +148,38 @@ class Game:
147
148
  str: The path to the weights directory."""
148
149
  raise NotImplementedError
149
150
 
151
+ def get_density_map_fruits_path(self, map_directory: str) -> str:
152
+ """Returns the path to the density map fruits file.
153
+
154
+ Arguments:
155
+ map_directory (str): The path to the map directory.
156
+
157
+ Returns:
158
+ str: The path to the density map fruits file."""
159
+ weights_dir = self.weights_dir_path(map_directory)
160
+ return os.path.join(weights_dir, "densityMap_fruits.png")
161
+
162
+ def get_farmlands_path(self, map_directory: str) -> str:
163
+ """Returns the path to the farmlands file.
164
+
165
+ Arguments:
166
+ map_directory (str): The path to the map directory.
167
+
168
+ Returns:
169
+ str: The path to the farmlands file."""
170
+ weights_dir = self.weights_dir_path(map_directory)
171
+ return os.path.join(weights_dir, "infoLayer_farmlands.png")
172
+
173
+ def get_farmlands_xml_path(self, map_directory: str) -> str:
174
+ """Returns the path to the farmlands xml file.
175
+
176
+ Arguments:
177
+ map_directory (str): The path to the map directory.
178
+
179
+ Returns:
180
+ str: The path to the farmlands xml file."""
181
+ raise NotImplementedError
182
+
150
183
  def i3d_file_path(self, map_directory: str) -> str:
151
184
  """Returns the path to the i3d file.
152
185
 
@@ -165,6 +198,14 @@ class Game:
165
198
  bool: True if the i3d file should be processed, False otherwise."""
166
199
  return self._i3d_processing
167
200
 
201
+ @property
202
+ def plants_processing(self) -> bool:
203
+ """Returns whether the plants should be processed.
204
+
205
+ Returns:
206
+ bool: True if the plants should be processed, False otherwise."""
207
+ return self._plants_processing
208
+
168
209
  @property
169
210
  def additional_dem_name(self) -> str | None:
170
211
  """Returns the name of the additional DEM file.
@@ -193,6 +234,7 @@ class FS22(Game):
193
234
  _map_template_path = os.path.join(working_directory, "data", "fs22-map-template.zip")
194
235
  _texture_schema = os.path.join(working_directory, "data", "fs22-texture-schema.json")
195
236
  _i3d_processing = False
237
+ _plants_processing = False
196
238
 
197
239
  def dem_file_path(self, map_directory: str) -> str:
198
240
  """Returns the path to the DEM file.
@@ -276,3 +318,13 @@ class FS25(Game):
276
318
  Returns:
277
319
  str: The path to the i3d file."""
278
320
  return os.path.join(map_directory, "map", "map.i3d")
321
+
322
+ def get_farmlands_xml_path(self, map_directory: str) -> str:
323
+ """Returns the path to the farmlands xml file.
324
+
325
+ Arguments:
326
+ map_directory (str): The path to the map directory.
327
+
328
+ Returns:
329
+ str: The path to the farmlands xml file."""
330
+ return os.path.join(map_directory, "map", "config", "farmlands.xml")
maps4fs/generator/map.py CHANGED
@@ -7,7 +7,7 @@ import os
7
7
  import shutil
8
8
  from typing import Any, Generator
9
9
 
10
- from maps4fs.generator.background import Background
10
+ from maps4fs.generator.component.background import Background
11
11
  from maps4fs.generator.component.base.component import Component
12
12
  from maps4fs.generator.dtm.dtm import DTMProvider, DTMProviderSettings
13
13
  from maps4fs.generator.game import Game
@@ -6,8 +6,8 @@ import os
6
6
  import cv2
7
7
  from pygmdl import save_image # type: ignore
8
8
 
9
- from maps4fs.generator.background import DEFAULT_DISTANCE
10
9
  from maps4fs.generator.component.base.component import Component
10
+ from maps4fs.generator.settings import Parameters
11
11
  from maps4fs.generator.texture import PREVIEW_MAXIMUM_SIZE
12
12
 
13
13
 
@@ -41,7 +41,7 @@ class Satellite(Component):
41
41
  overview_size = (self.map_size + margin) * 2
42
42
  overwiew_path = os.path.join(self.satellite_directory, "satellite_overview.png")
43
43
 
44
- background_size = self.map_size + (DEFAULT_DISTANCE + margin) * 2
44
+ background_size = self.map_size + (Parameters.BACKGROUND_DISTANCE + margin) * 2
45
45
  background_path = os.path.join(self.satellite_directory, "satellite_background.png")
46
46
 
47
47
  sizes = [overview_size, background_size]