maps4fs 1.0.9__py3-none-any.whl → 1.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
maps4fs/generator/map.py CHANGED
@@ -18,8 +18,7 @@ class Map:
18
18
  Arguments:
19
19
  game (Type[Game]): Game for which the map is generated.
20
20
  coordinates (tuple[float, float]): Coordinates of the center of the map.
21
- height (int): Height of the map in pixels.
22
- width (int): Width of the map in pixels.
21
+ size (int): Height and width of the map in pixels (it's a square).
23
22
  map_directory (str): Path to the directory where map files will be stored.
24
23
  logger (Any): Logger instance
25
24
  """
@@ -28,22 +27,30 @@ class Map:
28
27
  self,
29
28
  game: Game,
30
29
  coordinates: tuple[float, float],
31
- height: int,
32
- width: int,
30
+ size: int,
31
+ rotation: int,
33
32
  map_directory: str,
34
33
  logger: Any = None,
35
34
  **kwargs,
36
35
  ):
36
+ if not logger:
37
+ logger = Logger(to_stdout=True, to_file=False)
38
+ self.logger = logger
39
+ self.size = size
40
+
41
+ if rotation:
42
+ rotation_multiplier = 1.5
43
+ else:
44
+ rotation_multiplier = 1
45
+
46
+ self.rotation = rotation
47
+ self.rotated_size = int(size * rotation_multiplier)
48
+
37
49
  self.game = game
38
50
  self.components: list[Component] = []
39
51
  self.coordinates = coordinates
40
- self.height = height
41
- self.width = width
42
52
  self.map_directory = map_directory
43
53
 
44
- if not logger:
45
- logger = Logger(to_stdout=True, to_file=False)
46
- self.logger = logger
47
54
  self.logger.info("Game was set to %s", game.code)
48
55
 
49
56
  self.kwargs = kwargs
@@ -68,8 +75,9 @@ class Map:
68
75
  component = game_component(
69
76
  self.game,
70
77
  self.coordinates,
71
- self.height,
72
- self.width,
78
+ self.size,
79
+ self.rotated_size,
80
+ self.rotation,
73
81
  self.map_directory,
74
82
  self.logger,
75
83
  **self.kwargs,
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import json
6
6
  import os
7
+ import re
7
8
  from collections import defaultdict
8
9
  from typing import Any, Callable, Generator, Optional
9
10
 
@@ -151,10 +152,22 @@ class Texture(Component):
151
152
  list[str]: List of paths to the textures.
152
153
  """
153
154
  weight_files = os.listdir(weights_directory)
155
+
156
+ # Inconsistent names are the name of textures that are not following the pattern
157
+ # of texture_name{idx}_weight.png.
158
+ inconsistent_names = ["forestRockRoot", "waterPuddle"]
159
+
160
+ if self.name in inconsistent_names:
161
+ return [
162
+ os.path.join(weights_directory, weight_file)
163
+ for weight_file in weight_files
164
+ if weight_file.startswith(self.name)
165
+ ]
166
+
154
167
  return [
155
168
  os.path.join(weights_directory, weight_file)
156
169
  for weight_file in weight_files
157
- if weight_file.startswith(self.name)
170
+ if re.match(rf"{self.name}\d{{2}}_weight.png", weight_file)
158
171
  ]
159
172
 
160
173
  def preprocess(self) -> None:
@@ -203,6 +216,31 @@ class Texture(Component):
203
216
  self._prepare_weights()
204
217
  self._read_parameters()
205
218
  self.draw()
219
+ self.rotate_textures()
220
+
221
+ def rotate_textures(self) -> None:
222
+ """Rotates textures of the layers which have tags."""
223
+ if self.rotation:
224
+ # Iterate over the layers which have tags and rotate them.
225
+ for layer in self.layers:
226
+ if layer.tags:
227
+ self.logger.debug("Rotating layer %s.", layer.name)
228
+ layer_paths = layer.paths(self._weights_dir)
229
+ layer_paths += [layer.path_preview(self._weights_dir)]
230
+ for layer_path in layer_paths:
231
+ if os.path.isfile(layer_path):
232
+ self.rotate_image(
233
+ layer_path,
234
+ self.rotation,
235
+ output_height=self.map_size,
236
+ output_width=self.map_size,
237
+ )
238
+ else:
239
+ self.logger.warning("Layer path %s not found.", layer_path)
240
+ else:
241
+ self.logger.debug(
242
+ "Skipping rotation of layer %s because it has no tags.", layer.name
243
+ )
206
244
 
207
245
  # pylint: disable=W0201
208
246
  def _read_parameters(self) -> None:
@@ -249,7 +287,7 @@ class Texture(Component):
249
287
  Arguments:
250
288
  layer (Layer): Layer with textures and tags.
251
289
  """
252
- size = (self.map_height, self.map_width)
290
+ size = (self.map_rotated_size, self.map_rotated_size)
253
291
  postfix = "_weight.png" if not layer.exclude_weight else ".png"
254
292
  if layer.count == 0:
255
293
  filepaths = [os.path.join(self._weights_dir, layer.name + postfix)]
@@ -396,12 +434,6 @@ class Texture(Component):
396
434
  for coord in non_zero_coords:
397
435
  sublayers[np.random.randint(0, layer.count)][coord[0], coord[1]] = 255
398
436
 
399
- # # ! Debug test if the sublayers are correct.
400
- # if True:
401
- # combined = cv2.bitwise_or(*sublayers)
402
- # # Match the original image with the combined sublayers.
403
- # assert np.array_equal(layer_image, combined), "Sublayers are not correct."
404
-
405
437
  # Save the sublayers.
406
438
  for sublayer, sublayer_path in zip(sublayers, layer_paths):
407
439
  cv2.imwrite(sublayer_path, sublayer)
@@ -435,7 +467,7 @@ class Texture(Component):
435
467
  Returns:
436
468
  int: Relative X coordinate in map image.
437
469
  """
438
- return int(self.map_width * (x - self.minimum_x) / (self.maximum_x - self.minimum_x))
470
+ return int(self.map_rotated_size * (x - self.minimum_x) / (self.maximum_x - self.minimum_x))
439
471
 
440
472
  def get_relative_y(self, y: float) -> int:
441
473
  """Converts UTM Y coordinate to relative Y coordinate in map image.
@@ -446,7 +478,9 @@ class Texture(Component):
446
478
  Returns:
447
479
  int: Relative Y coordinate in map image.
448
480
  """
449
- return int(self.map_height * (1 - (y - self.minimum_y) / (self.maximum_y - self.minimum_y)))
481
+ return int(
482
+ self.map_rotated_size * (1 - (y - self.minimum_y) / (self.maximum_y - self.minimum_y))
483
+ )
450
484
 
451
485
  def np_to_polygon_points(self, np_array: np.ndarray) -> list[tuple[int, int]]:
452
486
  """Converts numpy array of polygon points to list of tuples.
@@ -597,13 +631,11 @@ class Texture(Component):
597
631
  Returns:
598
632
  str: Path to the preview.
599
633
  """
600
- scaling_factor = min(
601
- PREVIEW_MAXIMUM_SIZE / self.map_width, PREVIEW_MAXIMUM_SIZE / self.map_height
602
- )
634
+ scaling_factor = PREVIEW_MAXIMUM_SIZE / self.map_size
603
635
 
604
636
  preview_size = (
605
- int(self.map_width * scaling_factor),
606
- int(self.map_height * scaling_factor),
637
+ int(self.map_size * scaling_factor),
638
+ int(self.map_size * scaling_factor),
607
639
  )
608
640
  self.logger.debug(
609
641
  "Scaling factor: %s. Preview size: %s.",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: maps4fs
3
- Version: 1.0.9
3
+ Version: 1.1.1
4
4
  Summary: Generate map templates for Farming Simulator from real places.
5
5
  Author-email: iwatkot <iwatkot@gmail.com>
6
6
  License: MIT License
@@ -63,15 +63,17 @@ Requires-Dist: pympler
63
63
  </div>
64
64
 
65
65
  🗺️ Supports 2x2, 4x4, 8x8, 16x16 and any custom size maps<br>
66
+ 🔄 Support map rotation 🆕<br>
67
+ 🌾 Automatically generates fields 🆕<br>
68
+ 🌽 Automatically generates farmlands 🆕<br>
66
69
  🌍 Based on real-world data from OpenStreetMap<br>
67
- 🏞️ Generates height using SRTM dataset<br>
70
+ 🏞️ Generates height map using SRTM dataset<br>
68
71
  📦 Provides a ready-to-use map template for the Giants Editor<br>
69
72
  🚜 Supports Farming Simulator 22 and 25<br>
70
73
  🔷 Generates *.obj files for background terrain based on the real-world height map<br>
71
74
  📄 Generates scripts to download high-resolution satellite images from [QGIS](https://qgis.org/download/) in one click<br>
75
+ 📕 Detailed [documentation](/docs) and tutorials <br>
72
76
  🧰 Modder Toolbox to help you with various tasks <br>
73
- 🌾 Automatically generates fields 🆕<br>
74
- 🌽 Automatically generates farmlands 🆕<br>
75
77
 
76
78
  <p align="center">
77
79
  <img src="https://github.com/user-attachments/assets/cf8f5752-9c69-4018-bead-290f59ba6976"><br>
@@ -467,10 +469,6 @@ You can also apply some advanced settings to the map generation process. Note th
467
469
 
468
470
  - Plateau height: this value will be added to each pixel of the DEM image, making it "higher". It's useful when you want to add some negative heights on the map, that appear to be in a "low" place. By default, it's set to 0.
469
471
 
470
- ### Background Terrain Advanced settings
471
-
472
- - Background Terrain Generate only full tiles: if checked (by default) the small tiles (N, NE, E, and so on) will not be generated, only the full tile will be created. It's useful when you don't want to work with separate tiles, but with one big file. Since the new method of cutting the map from the background terrain added to the documentation, and now it's possible to perfectly align the map with the background terrain, this option will remain just as a legacy one.
473
-
474
472
  ### Texture Advanced settings
475
473
 
476
474
  - Fields padding - this value (in meters) will be applied to each field, making it smaller. It's useful when the fields are too close to each other and you want to make them smaller. By default, it's set to 0.
@@ -0,0 +1,21 @@
1
+ maps4fs/__init__.py,sha256=da4jmND2Ths9AffnkAKgzLHNkvKFOc_l21gJisPXqWY,155
2
+ maps4fs/logger.py,sha256=B-NEYpMjPAAqlV4VpfTi6nbBFnEABVtQOaYe6nMpidg,1489
3
+ maps4fs/generator/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
4
+ maps4fs/generator/background.py,sha256=dnUkS1atEqqz_ryKkcfP_K9ZcwMHZVs97y-twZMpD44,15881
5
+ maps4fs/generator/component.py,sha256=iYQgw2l_rvVpZ-VIhoZELdAe8rPVBwCVKwRsYejDCb0,17094
6
+ maps4fs/generator/config.py,sha256=b7qY0luC-_WM_c72Ohtlf4FrB37X5cALInbestSdUsw,4382
7
+ maps4fs/generator/dem.py,sha256=rc7ADzjvlZzStOqagsWW0Vrm9-X86aPpoR1RhBF_-OE,16025
8
+ maps4fs/generator/game.py,sha256=H4msVHoDlJD5_9KoRvr3lCv2ZbLqJPqPDCEAEIDaCB4,7407
9
+ maps4fs/generator/grle.py,sha256=HFOH07RZfkCkIpSj2VSMoBUjhmYDVCFWQQYd4tn6O5o,7162
10
+ maps4fs/generator/i3d.py,sha256=GcGyawpE70yVjH9WdgpbNXESZoHYHmCKncl7pp5G1zo,11741
11
+ maps4fs/generator/map.py,sha256=7UqLjDZgoY6M0ZxX5Q4Rjee2UGWZ64a3tGyr8B24UO0,4863
12
+ maps4fs/generator/qgis.py,sha256=Es8hLuqN_KH8lDfnJE6He2rWYbAKJ3RGPn-o87S6CPI,6116
13
+ maps4fs/generator/texture.py,sha256=uSt563KomSVUndl25IgEIi0YuhBQbnhPIoQKa-4A3_E,26016
14
+ maps4fs/toolbox/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
15
+ maps4fs/toolbox/background.py,sha256=9BXWNqs_n3HgqDiPztWylgYk_QM4YgBpe6_ZNQAWtSc,2154
16
+ maps4fs/toolbox/dem.py,sha256=z9IPFNmYbjiigb3t02ZenI3Mo8odd19c5MZbjDEovTo,3525
17
+ maps4fs-1.1.1.dist-info/LICENSE.md,sha256=pTKD_oUexcn-yccFCTrMeLkZy0ifLRa-VNcDLqLZaIw,10749
18
+ maps4fs-1.1.1.dist-info/METADATA,sha256=V59zXt_h8KNG1R6KssJ8ZMDiQOt5stAwsYbjvzJaY9U,27907
19
+ maps4fs-1.1.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
20
+ maps4fs-1.1.1.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
21
+ maps4fs-1.1.1.dist-info/RECORD,,
@@ -1,97 +0,0 @@
1
- """This module contains functions and clas for generating path steps."""
2
-
3
- from typing import NamedTuple
4
-
5
- from geopy.distance import distance # type: ignore
6
-
7
- DEFAULT_DISTANCE = 2048
8
- PATH_FULL_NAME = "FULL"
9
- PATH_FULL_PREVIEW_NAME = "FULL_PREVIEW"
10
-
11
-
12
- class PathStep(NamedTuple):
13
- """Represents parameters of one step in the path.
14
-
15
- Attributes:
16
- code {str} -- Tile code (N, NE, E, SE, S, SW, W, NW).
17
- angle {int} -- Angle in degrees (for example 0 for North, 90 for East).
18
- If None, the step is a full map with a center at the same coordinates as the
19
- map itself.
20
- distance {int} -- Distance in meters from previous step.
21
- If None, the step is a full map with a center at the same coordinates as the
22
- map itself.
23
- size {tuple[int, int]} -- Size of the tile in pixels (width, height).
24
- """
25
-
26
- code: str
27
- angle: int | None
28
- distance: int | None
29
- size: tuple[int, int]
30
-
31
- def get_destination(self, origin: tuple[float, float]) -> tuple[float, float]:
32
- """Calculate destination coordinates based on origin and step parameters.
33
-
34
- Arguments:
35
- origin {tuple[float, float]} -- Origin coordinates (latitude, longitude)
36
-
37
- Returns:
38
- tuple[float, float] -- Destination coordinates (latitude, longitude)
39
- """
40
- destination = distance(meters=self.distance).destination(origin, self.angle)
41
- return destination.latitude, destination.longitude
42
-
43
-
44
- def get_steps(map_height: int, map_width: int, only_full_tiles: bool = True) -> list[PathStep]:
45
- """Return a list of PathStep objects for each tile, which represent a step in the path.
46
- Moving from the center of the map to North, then clockwise.
47
-
48
- Arguments:
49
- map_height {int} -- Height of the map in pixels
50
- map_width {int} -- Width of the map in pixels
51
- only_full_tiles {bool} -- Whether to return only full tiles or all tiles
52
-
53
- Returns:
54
- list[PathStep] -- List of PathStep objects
55
- """
56
- # Move clockwise from N and calculate coordinates and sizes for each tile.
57
- half_width = int(map_width / 2)
58
- half_height = int(map_height / 2)
59
-
60
- half_default_distance = int(DEFAULT_DISTANCE / 2)
61
-
62
- tiles = [
63
- PathStep("N", 0, half_height + half_default_distance, (map_width, DEFAULT_DISTANCE)),
64
- PathStep(
65
- "NE", 90, half_width + half_default_distance, (DEFAULT_DISTANCE, DEFAULT_DISTANCE)
66
- ),
67
- PathStep("E", 180, half_height + half_default_distance, (DEFAULT_DISTANCE, map_height)),
68
- PathStep(
69
- "SE", 180, half_height + half_default_distance, (DEFAULT_DISTANCE, DEFAULT_DISTANCE)
70
- ),
71
- PathStep("S", 270, half_width + half_default_distance, (map_width, DEFAULT_DISTANCE)),
72
- PathStep(
73
- "SW", 270, half_width + half_default_distance, (DEFAULT_DISTANCE, DEFAULT_DISTANCE)
74
- ),
75
- PathStep("W", 0, half_height + half_default_distance, (DEFAULT_DISTANCE, map_height)),
76
- PathStep(
77
- "NW", 0, half_height + half_default_distance, (DEFAULT_DISTANCE, DEFAULT_DISTANCE)
78
- ),
79
- ]
80
- full_tiles = [
81
- PathStep(
82
- PATH_FULL_NAME,
83
- None,
84
- None,
85
- (map_width + DEFAULT_DISTANCE * 2, map_height + DEFAULT_DISTANCE * 2),
86
- ),
87
- PathStep(
88
- PATH_FULL_PREVIEW_NAME,
89
- None,
90
- None,
91
- (map_width + DEFAULT_DISTANCE * 2, map_height + DEFAULT_DISTANCE * 2),
92
- ),
93
- ]
94
-
95
- if only_full_tiles:
96
- return full_tiles
97
- return tiles + full_tiles
maps4fs/generator/tile.py DELETED
@@ -1,51 +0,0 @@
1
- """This module contains the Tile component, which is used to generate a tile of DEM data around
2
- the map."""
3
-
4
- import os
5
-
6
- from maps4fs.generator.dem import DEM
7
-
8
-
9
- class Tile(DEM):
10
- """Component for creating a tile of DEM data around the map.
11
-
12
- Arguments:
13
- coordinates (tuple[float, float]): The latitude and longitude of the center of the map.
14
- map_height (int): The height of the map in pixels.
15
- map_width (int): The width of the map in pixels.
16
- map_directory (str): The directory where the map files are stored.
17
- logger (Any, optional): The logger to use. Must have at least three basic methods: debug,
18
- info, warning. If not provided, default logging will be used.
19
-
20
- Keyword Arguments:
21
- tile_code (str): The code of the tile (N, NE, E, SE, S, SW, W, NW).
22
-
23
- Public Methods:
24
- get_output_resolution: Return the resolution of the output image.
25
- process: Launch the component processing.
26
- make_copy: Override the method to prevent copying the tile.
27
- """
28
-
29
- def preprocess(self) -> None:
30
- """Prepares the component for processing. Reads the tile code from the kwargs and sets the
31
- DEM path for the tile."""
32
- super().preprocess()
33
- self.code = self.kwargs.get("tile_code")
34
- if not self.code:
35
- raise ValueError("Tile code was not provided")
36
-
37
- self.logger.debug("Generating tile for code %s", self.code)
38
-
39
- background_directory = os.path.join(self.map_directory, "background")
40
- os.makedirs(background_directory, exist_ok=True)
41
-
42
- self._dem_path = os.path.join(background_directory, f"{self.code}.png")
43
- self.logger.debug("DEM path for tile %s is %s", self.code, self._dem_path)
44
-
45
- def get_output_resolution(self) -> tuple[int, int]:
46
- """Return the resolution of the output image.
47
-
48
- Returns:
49
- tuple[int, int]: The width and height of the output image.
50
- """
51
- return self.map_width, self.map_height
@@ -1,23 +0,0 @@
1
- maps4fs/__init__.py,sha256=da4jmND2Ths9AffnkAKgzLHNkvKFOc_l21gJisPXqWY,155
2
- maps4fs/logger.py,sha256=B-NEYpMjPAAqlV4VpfTi6nbBFnEABVtQOaYe6nMpidg,1489
3
- maps4fs/generator/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
4
- maps4fs/generator/background.py,sha256=ogd5TmAWL5zhZtTCOH8YHGKqc0SGQqOsWuVg3AaZO0I,14015
5
- maps4fs/generator/component.py,sha256=ac0l1nUeRnMejNgJ26PO0Jjb2ELYSaagM0mzNjM_w5o,14582
6
- maps4fs/generator/config.py,sha256=kspXIT2o-_28EU0RQsROHCjkgQdqQnvreAKP5QAC5Ws,4279
7
- maps4fs/generator/dem.py,sha256=cCJLE20-XKaQx5lwIFNEgmQ5kfhE24QmVrAyMVwsU_A,16459
8
- maps4fs/generator/game.py,sha256=4I6edxTeZf41Vgvx6BaucEflMEHomRRvdMZRJAPm0d4,7450
9
- maps4fs/generator/grle.py,sha256=5Ck44CKI-yxnxgy8K_18L0pFAfln5e9e7DVkpNZVwC0,6404
10
- maps4fs/generator/i3d.py,sha256=CPscvhuoBRfGJSmcCGHyjM5FKlDXAbYALJkvhGv4UrA,11091
11
- maps4fs/generator/map.py,sha256=gDZUZ2wimoeA8mHVOCnZvrIBeK7b99OIWFd_LjruqBc,4677
12
- maps4fs/generator/path_steps.py,sha256=twhoP0KOYWOpOJfYrSWPHygtIeM-r5cIlePg1SHVyHk,3589
13
- maps4fs/generator/qgis.py,sha256=Es8hLuqN_KH8lDfnJE6He2rWYbAKJ3RGPn-o87S6CPI,6116
14
- maps4fs/generator/texture.py,sha256=2c2x99xnqKZoXDB4fdQBESFMPMiGrbx_fADFTdx4ZGY,24638
15
- maps4fs/generator/tile.py,sha256=z1-xEVjgFNf2WzLkgwoGGq8nREJpjPljeC9lmb5xPKA,1997
16
- maps4fs/toolbox/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
17
- maps4fs/toolbox/background.py,sha256=9BXWNqs_n3HgqDiPztWylgYk_QM4YgBpe6_ZNQAWtSc,2154
18
- maps4fs/toolbox/dem.py,sha256=z9IPFNmYbjiigb3t02ZenI3Mo8odd19c5MZbjDEovTo,3525
19
- maps4fs-1.0.9.dist-info/LICENSE.md,sha256=pTKD_oUexcn-yccFCTrMeLkZy0ifLRa-VNcDLqLZaIw,10749
20
- maps4fs-1.0.9.dist-info/METADATA,sha256=2cnIMdJN45eVlg9tBVPAwJsoBWVz1qavS6U9B4-iVAU,28327
21
- maps4fs-1.0.9.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
22
- maps4fs-1.0.9.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
23
- maps4fs-1.0.9.dist-info/RECORD,,