maps4fs 1.5.7__py3-none-any.whl → 1.7.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.
@@ -5,6 +5,8 @@ from __future__ import annotations
5
5
  import json
6
6
  import os
7
7
  import re
8
+ import shutil
9
+ import warnings
8
10
  from collections import defaultdict
9
11
  from typing import Any, Callable, Generator, Optional
10
12
 
@@ -69,6 +71,7 @@ class Texture(Component):
69
71
  usage: str | None = None,
70
72
  background: bool = False,
71
73
  invisible: bool = False,
74
+ procedural: list[str] | None = None,
72
75
  ):
73
76
  self.name = name
74
77
  self.count = count
@@ -81,6 +84,7 @@ class Texture(Component):
81
84
  self.usage = usage
82
85
  self.background = background
83
86
  self.invisible = invisible
87
+ self.procedural = procedural
84
88
 
85
89
  def to_json(self) -> dict[str, str | list[str] | bool]: # type: ignore
86
90
  """Returns dictionary with layer data.
@@ -99,6 +103,7 @@ class Texture(Component):
99
103
  "usage": self.usage,
100
104
  "background": self.background,
101
105
  "invisible": self.invisible,
106
+ "procedural": self.procedural,
102
107
  }
103
108
 
104
109
  data = {k: v for k, v in data.items() if v is not None}
@@ -187,7 +192,7 @@ class Texture(Component):
187
192
  custom_schema = self.kwargs.get("texture_custom_schema")
188
193
  if custom_schema:
189
194
  layers_schema = custom_schema # type: ignore
190
- self.logger.info("Custom schema loaded with %s layers.", len(layers_schema))
195
+ self.logger.debug("Custom schema loaded with %s layers.", len(layers_schema))
191
196
  else:
192
197
  if not os.path.isfile(self.game.texture_schema):
193
198
  raise FileNotFoundError(
@@ -202,18 +207,20 @@ class Texture(Component):
202
207
 
203
208
  try:
204
209
  self.layers = [self.Layer.from_json(layer) for layer in layers_schema] # type: ignore
205
- self.logger.info("Loaded %s layers.", len(self.layers))
210
+ self.logger.debug("Loaded %s layers.", len(self.layers))
206
211
  except Exception as e: # pylint: disable=W0703
207
212
  raise ValueError(f"Error loading texture layers: {e}") from e
208
213
 
209
214
  base_layer = self.get_base_layer()
210
215
  if base_layer:
211
216
  self.logger.debug("Base layer found: %s.", base_layer.name)
212
- else:
213
- self.logger.warning("No base layer found.")
214
217
 
215
218
  self._weights_dir = self.game.weights_dir_path(self.map_directory)
216
219
  self.logger.debug("Weights directory: %s.", self._weights_dir)
220
+ self.procedural_dir = os.path.join(self._weights_dir, "masks")
221
+ os.makedirs(self.procedural_dir, exist_ok=True)
222
+ self.logger.debug("Procedural directory: %s.", self.procedural_dir)
223
+
217
224
  self.info_save_path = os.path.join(self.map_directory, "generation_info.json")
218
225
  self.logger.debug("Generation info save path: %s.", self.info_save_path)
219
226
 
@@ -253,11 +260,56 @@ class Texture(Component):
253
260
  return layer
254
261
  return None
255
262
 
256
- def process(self):
263
+ def process(self) -> None:
264
+ """Processes the data to generate textures."""
257
265
  self._prepare_weights()
258
266
  self._read_parameters()
259
267
  self.draw()
260
268
  self.rotate_textures()
269
+ self.copy_procedural()
270
+
271
+ def copy_procedural(self) -> None:
272
+ """Copies some of the textures to use them as mask for procedural generation.
273
+ Creates an empty blockmask if it does not exist."""
274
+ blockmask_path = os.path.join(self.procedural_dir, "BLOCKMASK.png")
275
+ if not os.path.isfile(blockmask_path):
276
+ self.logger.debug("BLOCKMASK.png not found, creating an empty file.")
277
+ img = np.zeros((self.map_size, self.map_size), dtype=np.uint8)
278
+ cv2.imwrite(blockmask_path, img) # pylint: disable=no-member
279
+
280
+ pg_layers_by_type = defaultdict(list)
281
+ for layer in self.layers:
282
+ if layer.procedural:
283
+ # Get path to the original file.
284
+ texture_path = layer.get_preview_or_path(self._weights_dir)
285
+ for procedural_layer_name in layer.procedural:
286
+ pg_layers_by_type[procedural_layer_name].append(texture_path)
287
+
288
+ if not pg_layers_by_type:
289
+ self.logger.debug("No procedural layers found.")
290
+ return
291
+
292
+ for procedural_layer_name, texture_paths in pg_layers_by_type.items():
293
+ procedural_save_path = os.path.join(self.procedural_dir, f"{procedural_layer_name}.png")
294
+ if len(texture_paths) > 1:
295
+ # If there are more than one texture, merge them.
296
+ merged_texture = np.zeros((self.map_size, self.map_size), dtype=np.uint8)
297
+ for texture_path in texture_paths:
298
+ # pylint: disable=E1101
299
+ texture = cv2.imread(texture_path, cv2.IMREAD_UNCHANGED)
300
+ merged_texture[texture == 255] = 255
301
+ cv2.imwrite(procedural_save_path, merged_texture) # pylint: disable=no-member
302
+ self.logger.debug(
303
+ "Procedural file %s merged from %s textures.",
304
+ procedural_save_path,
305
+ len(texture_paths),
306
+ )
307
+ elif len(texture_paths) == 1:
308
+ # Otherwise, copy the texture.
309
+ shutil.copyfile(texture_paths[0], procedural_save_path)
310
+ self.logger.debug(
311
+ "Procedural file %s copied from %s.", procedural_save_path, texture_paths[0]
312
+ )
261
313
 
262
314
  def rotate_textures(self) -> None:
263
315
  """Rotates textures of the layers which have tags."""
@@ -276,8 +328,6 @@ class Texture(Component):
276
328
  output_height=self.map_size,
277
329
  output_width=self.map_size,
278
330
  )
279
- else:
280
- self.logger.warning("Layer path %s not found.", layer_path)
281
331
  else:
282
332
  self.logger.debug(
283
333
  "Skipping rotation of layer %s because it has no tags.", layer.name
@@ -286,27 +336,19 @@ class Texture(Component):
286
336
  # pylint: disable=W0201
287
337
  def _read_parameters(self) -> None:
288
338
  """Reads map parameters from OSM data, such as:
289
- - minimum and maximum coordinates in UTM format
339
+ - minimum and maximum coordinates
290
340
  - map dimensions in meters
291
341
  - map coefficients (meters per pixel)
292
342
  """
293
- north, south, east, west = self.get_bbox(project_utm=True)
294
-
295
- # Parameters of the map in UTM format (meters).
296
- self.minimum_x = min(west, east)
297
- self.minimum_y = min(south, north)
298
- self.maximum_x = max(west, east)
299
- self.maximum_y = max(south, north)
300
- self.logger.debug("Map minimum coordinates (XxY): %s x %s.", self.minimum_x, self.minimum_y)
301
- self.logger.debug("Map maximum coordinates (XxY): %s x %s.", self.maximum_x, self.maximum_y)
343
+ bbox = ox.utils_geo.bbox_from_point(self.coordinates, dist=self.map_rotated_size / 2)
344
+ self.minimum_x, self.minimum_y, self.maximum_x, self.maximum_y = bbox
302
345
 
303
346
  def info_sequence(self) -> dict[str, Any]:
304
347
  """Returns the JSON representation of the generation info for textures."""
305
348
  useful_attributes = [
306
349
  "coordinates",
307
350
  "bbox",
308
- "map_height",
309
- "map_width",
351
+ "map_size",
310
352
  "rotation",
311
353
  "minimum_x",
312
354
  "minimum_y",
@@ -510,7 +552,7 @@ class Texture(Component):
510
552
  cv2.imwrite(sublayer_path, sublayer)
511
553
  self.logger.debug("Sublayer %s saved.", sublayer_path)
512
554
 
513
- self.logger.info("Dissolved layer %s.", layer.name)
555
+ self.logger.debug("Dissolved layer %s.", layer.name)
514
556
 
515
557
  def draw_base_layer(self, cumulative_image: np.ndarray) -> None:
516
558
  """Draws base layer and saves it into the png file.
@@ -527,29 +569,19 @@ class Texture(Component):
527
569
  cv2.imwrite(layer_path, img)
528
570
  self.logger.debug("Base texture %s saved.", layer_path)
529
571
 
530
- def get_relative_x(self, x: float) -> int:
531
- """Converts UTM X coordinate to relative X coordinate in map image.
572
+ def latlon_to_pixel(self, lat: float, lon: float) -> tuple[int, int]:
573
+ """Converts latitude and longitude to pixel coordinates.
532
574
 
533
575
  Arguments:
534
- x (float): UTM X coordinate.
576
+ lat (float): Latitude.
577
+ lon (float): Longitude.
535
578
 
536
579
  Returns:
537
- int: Relative X coordinate in map image.
580
+ tuple[int, int]: Pixel coordinates.
538
581
  """
539
- return int(self.map_rotated_size * (x - self.minimum_x) / (self.maximum_x - self.minimum_x))
540
-
541
- def get_relative_y(self, y: float) -> int:
542
- """Converts UTM Y coordinate to relative Y coordinate in map image.
543
-
544
- Arguments:
545
- y (float): UTM Y coordinate.
546
-
547
- Returns:
548
- int: Relative Y coordinate in map image.
549
- """
550
- return int(
551
- self.map_rotated_size * (1 - (y - self.minimum_y) / (self.maximum_y - self.minimum_y))
552
- )
582
+ x = int((lon - self.minimum_x) / (self.maximum_x - self.minimum_x) * self.map_rotated_size)
583
+ y = int((lat - self.maximum_y) / (self.minimum_y - self.maximum_y) * self.map_rotated_size)
584
+ return x, y
553
585
 
554
586
  def np_to_polygon_points(self, np_array: np.ndarray) -> list[tuple[int, int]]:
555
587
  """Converts numpy array of polygon points to list of tuples.
@@ -574,11 +606,13 @@ class Texture(Component):
574
606
  Returns:
575
607
  np.ndarray: Numpy array of polygon points.
576
608
  """
577
- xs, ys = geometry.exterior.coords.xy
578
- xs = [int(self.get_relative_x(x)) for x in xs.tolist()]
579
- ys = [int(self.get_relative_y(y)) for y in ys.tolist()]
580
- pairs = list(zip(xs, ys))
581
- return np.array(pairs, dtype=np.int32).reshape((-1, 1, 2))
609
+ coords = list(geometry.exterior.coords)
610
+ pts = np.array(
611
+ [self.latlon_to_pixel(coord[1], coord[0]) for coord in coords],
612
+ np.int32,
613
+ )
614
+ pts = pts.reshape((-1, 1, 2))
615
+ return pts
582
616
 
583
617
  def _to_polygon(
584
618
  self, obj: pd.core.series.Series, width: int | None
@@ -596,7 +630,7 @@ class Texture(Component):
596
630
  geometry_type = geometry.geom_type
597
631
  converter = self._converters(geometry_type)
598
632
  if not converter:
599
- self.logger.warning("Geometry type %s not supported.", geometry_type)
633
+ self.logger.debug("Geometry type %s not supported.", geometry_type)
600
634
  return None
601
635
  return converter(geometry, width)
602
636
 
@@ -615,9 +649,20 @@ class Texture(Component):
615
649
  Returns:
616
650
  shapely.geometry.polygon.Polygon: Polygon geometry.
617
651
  """
618
- polygon = geometry.buffer(width)
652
+ polygon = geometry.buffer(self.meters_to_degrees(width) if width else 0)
619
653
  return polygon
620
654
 
655
+ def meters_to_degrees(self, meters: int) -> float:
656
+ """Converts meters to degrees.
657
+
658
+ Arguments:
659
+ meters (int): Meters.
660
+
661
+ Returns:
662
+ float: Degrees.
663
+ """
664
+ return meters / 111320
665
+
621
666
  def _skip(
622
667
  self, geometry: shapely.geometry.polygon.Polygon, *args, **kwargs
623
668
  ) -> shapely.geometry.polygon.Polygon:
@@ -667,52 +712,51 @@ class Texture(Component):
667
712
  is_fieds = info_layer == "fields"
668
713
  try:
669
714
  if self.map.custom_osm is not None:
670
- objects = ox.features_from_xml(self.map.custom_osm, tags=tags)
715
+ with warnings.catch_warnings():
716
+ warnings.simplefilter("ignore", FutureWarning)
717
+ objects = ox.features_from_xml(self.map.custom_osm, tags=tags)
671
718
  else:
672
719
  objects = ox.features_from_bbox(bbox=self.new_bbox, tags=tags)
673
720
  except Exception as e: # pylint: disable=W0718
674
721
  self.logger.debug("Error fetching objects for tags: %s. Error: %s.", tags, e)
675
722
  return
676
- objects_utm = ox.projection.project_gdf(objects, to_latlong=False)
677
- self.logger.debug("Fetched %s elements for tags: %s.", len(objects_utm), tags)
723
+ self.logger.debug("Fetched %s elements for tags: %s.", len(objects), tags)
678
724
 
679
725
  method = self.linestrings_generator if yield_linestrings else self.polygons_generator
680
726
 
681
- yield from method(objects_utm, width, is_fieds)
727
+ yield from method(objects, width, is_fieds)
682
728
 
683
729
  def linestrings_generator(
684
- self, objects_utm: pd.core.frame.DataFrame, *args, **kwargs
730
+ self, objects: pd.core.frame.DataFrame, *args, **kwargs
685
731
  ) -> Generator[list[tuple[int, int]], None, None]:
686
732
  """Generator which yields lists of point coordinates which represent LineStrings from OSM.
687
733
 
688
734
  Arguments:
689
- objects_utm (pd.core.frame.DataFrame): Dataframe with OSM objects in UTM format.
735
+ objects (pd.core.frame.DataFrame): Dataframe with OSM objects.
690
736
 
691
737
  Yields:
692
738
  Generator[list[tuple[int, int]], None, None]: List of point coordinates.
693
739
  """
694
- for _, obj in objects_utm.iterrows():
740
+ for _, obj in objects.iterrows():
695
741
  geometry = obj["geometry"]
696
742
  if isinstance(geometry, shapely.geometry.linestring.LineString):
697
- points = [
698
- (self.get_relative_x(x), self.get_relative_y(y)) for x, y in geometry.coords
699
- ]
743
+ points = [self.latlon_to_pixel(x, y) for y, x in geometry.coords]
700
744
  yield points
701
745
 
702
746
  def polygons_generator(
703
- self, objects_utm: pd.core.frame.DataFrame, width: int | None, is_fieds: bool
747
+ self, objects: pd.core.frame.DataFrame, width: int | None, is_fieds: bool
704
748
  ) -> Generator[np.ndarray, None, None]:
705
749
  """Generator which yields numpy arrays of polygons from OSM data.
706
750
 
707
751
  Arguments:
708
- objects_utm (pd.core.frame.DataFrame): Dataframe with OSM objects in UTM format.
752
+ objects (pd.core.frame.DataFrame): Dataframe with OSM objects.
709
753
  width (int | None): Width of the polygon in meters (only for LineString).
710
754
  is_fieds (bool): Flag to determine if the fields should be padded.
711
755
 
712
756
  Yields:
713
757
  Generator[np.ndarray, None, None]: Numpy array of polygon points.
714
758
  """
715
- for _, obj in objects_utm.iterrows():
759
+ for _, obj in objects.iterrows():
716
760
  try:
717
761
  polygon = self._to_polygon(obj, width)
718
762
  except Exception as e: # pylint: disable=W0703
@@ -722,10 +766,14 @@ class Texture(Component):
722
766
  continue
723
767
 
724
768
  if is_fieds and self.map.texture_settings.fields_padding > 0:
725
- padded_polygon = polygon.buffer(-self.map.texture_settings.fields_padding)
769
+ padded_polygon = polygon.buffer(
770
+ -self.meters_to_degrees(self.map.texture_settings.fields_padding)
771
+ )
726
772
 
727
773
  if not isinstance(padded_polygon, shapely.geometry.polygon.Polygon):
728
774
  self.logger.warning("The padding value is too high, field will not padded.")
775
+ elif not list(padded_polygon.exterior.coords):
776
+ self.logger.warning("The padding value is too high, field will not padded.")
729
777
  else:
730
778
  polygon = padded_polygon
731
779
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: maps4fs
3
- Version: 1.5.7
3
+ Version: 1.7.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
@@ -48,6 +48,7 @@ Requires-Dist: pydantic
48
48
  <a href="#Expert-settings">Expert settings</a> •
49
49
  <a href="#Resources">Resources</a> •
50
50
  <a href="#Bugs-and-feature-requests">Bugs and feature requests</a><br>
51
+ <a href="#DTM-Providers">DTM Providers</a> •
51
52
  <a href="#Special-thanks">Special thanks</a>
52
53
  </p>
53
54
 
@@ -68,15 +69,15 @@ Requires-Dist: pydantic
68
69
  </div>
69
70
 
70
71
  🗺️ Supports 2x2, 4x4, 8x8, 16x16 and any custom size maps<br>
71
- 🔄 Support map rotation 🆕<br>
72
- 🌾 Automatically generates fields 🆕<br>
73
- 🌽 Automatically generates farmlands 🆕<br>
74
- 🌿 Automatically generates decorative foliage 🆕<br>
75
- 🌲 Automatically generates forests 🆕<br>
76
- 🌊 Automatically generates water planes 🆕<br>
72
+ 🔄 Support map rotation<br>
73
+ 🌐 Supports custom [DTM Providers](#DTM-Providers) 🆕<br>
74
+ 🌾 Automatically generates fields<br>
75
+ 🌽 Automatically generates farmlands<br>
76
+ 🌿 Automatically generates decorative foliage<br>
77
+ 🌲 Automatically generates forests<br>
78
+ 🌊 Automatically generates water planes<br>
77
79
  📈 Automatically generates splines 🆕<br>
78
80
  🛰️ Automatically downloads high resolution satellite images 🆕<br>
79
- 🏔️ Allows to use multiple DTM providers for elevation models 🆕<br>
80
81
  🌍 Based on real-world data from OpenStreetMap<br>
81
82
  🗺️ Supports [custom OSM maps](/docs/custom_osm.md)<br>
82
83
  🏞️ Generates height map using SRTM dataset<br>
@@ -97,10 +98,14 @@ Requires-Dist: pydantic
97
98
  🌿 Automatically generates decorative foliage.<br><br>
98
99
  <img src="https://github.com/user-attachments/assets/27a5e541-a9f5-4504-b8d2-64aae9fb3e52"><br>
99
100
  🌲 Automatically generates forests.<br><br>
101
+ <img src="https://github.com/user-attachments/assets/891911d7-081d-431e-a677-b4ae96870286"><br>
102
+ 🌲 Allows to select trees for generation.<br><br>
100
103
  <img src="https://github.com/user-attachments/assets/cce7d4e0-cba2-4dd2-b22d-03137fb2e860"><br>
101
104
  🌊 Automatically generates water planes.<br><br>
102
105
  <img src="https://github.com/user-attachments/assets/0b05b511-a595-48e7-a353-8298081314a4"><br>
103
106
  📈 Automatically generates splines.<br><br>
107
+ <img src="https://github.com/user-attachments/assets/0957db9e-7b95-4951-969c-9d1edd9f073b"><br>
108
+ 🖌️ Allows customization of the texture schema.<br><br>
104
109
  <img src="https://github.com/user-attachments/assets/80e5923c-22c7-4dc0-8906-680902511f3a"><br>
105
110
  🗒️ True-to-life blueprints for fast and precise modding.<br><br>
106
111
  <img width="480" src="https://github.com/user-attachments/assets/1a8802d2-6a3b-4bfa-af2b-7c09478e199b"><br>
@@ -129,6 +134,7 @@ docker run -d -p 8501:8501 --name maps4fs iwatkot/maps4fs
129
134
  ```
130
135
  And open [http://localhost:8501](http://localhost:8501) in your browser.<br>
131
136
  If you don't know how to use Docker, navigate to the [Docker version](#option-2-docker-version), it's really simple.<br>
137
+ Check out the [Docker FAQ](docs/FAQ_docker.md) if you have any questions.<br>
132
138
 
133
139
  ### 🤯 For developers
134
140
  **Option 3:** Python package. Install the package using the following command:
@@ -183,6 +189,7 @@ Using it is easy and doesn't require any guides. Enjoy!
183
189
  🗺️ Supported map sizes: 2x2, 4x4, 8x8, 16x16 km and any custom size.
184
190
  ⚙️ Advanced settings: enabled.
185
191
  🖼️ Texture dissolving: enabled.
192
+ Check out the [Docker FAQ](docs/FAQ_docker.md) if you have any questions.<br>
186
193
  You can launch the project with minimalistic UI in your browser using Docker. Follow these steps:
187
194
 
188
195
  1. Install [Docker](https://docs.docker.com/get-docker/) for your OS.
@@ -251,6 +258,12 @@ The tool now has a Modder Toolbox, which is a set of tools to help you with vari
251
258
 
252
259
  ### Tool Categories
253
260
  Tools are divided into categories, which are listed below.
261
+
262
+ #### For custom schemas
263
+ - **Tree Schema Editor** - allows you to view all the supported trees models and select the ones you need on your map. After it, you should click the Show updated schema button and copy the JSON schema to the clipboard. Then you can use it in the Expert settings to generate the map with the selected trees.
264
+
265
+ - **Texture Schema Editor** - allows you to view all the supported textures and edit their parameters, such as priority, OSM tags and so on. After editing, you should click the Show updated schema button and copy the JSON schema to the clipboard. Then you can use it in the Expert settings to generate the map with the updated textures.
266
+
254
267
  #### For Textures and DEM
255
268
  - **GeoTIFF windowing** - allows you to upload your GeoTIFF file and select the region of interest to extract it from the image. It's useful when you have high-resolution DEM data and want to create a height map using it.
256
269
 
@@ -419,6 +432,7 @@ Let's have a closer look at the fields:
419
432
  - `background` - set it to True for the textures, which should have impact on the Background Terrain, by default it's used to subtract the water depth from the DEM and background terrain.
420
433
  - `info_layer` - if the layer is saving some data in JSON format, this section will describe it's name in the JSON file. Used to find the needed JSON data, for example for fields it will be `fields` and as a value - list of polygon coordinates.
421
434
  - `invisible` - set it to True for the textures, which should not be drawn in the files, but only to save the data in the JSON file (related to the previous field).
435
+ - `procedural` - is a list of corresponding files, that will be used for a procedural generation. For example: `"procedural": ["PG_meadow", "PG_acres"]` - means that the texture will be used for two procedural generation files: `masks/PG_meadow.png` and `masks/PG_acres.png`. Note, that the one procuderal name can be applied to multiple textures, in this case they will be merged into one mask.
422
436
 
423
437
  ## Background terrain
424
438
  The tool now supports the generation of the background terrain. If you don't know what it is, here's a brief explanation. The background terrain is the world around the map. It's important to create it because if you don't, the map will look like it's floating in the void. The background terrain is a simple plane that can (and should) be textured to look fine.<br>
@@ -465,17 +479,12 @@ List of the important DDS files:
465
479
  - `mapsUS/overview.dds` - 4096x4096 pixels, the overview image of the map (in-game map)
466
480
 
467
481
  ## Advanced settings
468
- The tool supports the custom size of the map. To use this feature select `Custom` in the `Map size` dropdown and enter the desired size. The tool will generate a map with the size you entered.<br>
469
-
470
- ⛔️ Do not use this feature, if you don't know what you're doing. In most cases, the Giants Editor will just crash on opening the file, because you need to enter specific values for the map size.<br><br>
471
-
472
- ![Advanced settings](https://github.com/user-attachments/assets/9e8e178a-58d9-4aa6-aefd-4ed53408701d)
473
482
 
474
- You can also apply some advanced settings to the map generation process. Note that they're ADVANCED, so you don't need to use them if you're not sure what they do.<br>
483
+ You can also apply some advanced settings to the map generation process.<br>
475
484
 
476
485
  ### DEM Advanced settings
477
486
 
478
- - Multiplier: the height of the map is multiplied by this value. So the DEM map is just a 16-bit grayscale image, which means that the maximum available value there is 65535, while the actual difference between the deepest and the highest point on Earth is about 20 km. Just note that this setting mostly does not matter, because you can always adjust it in the Giants Editor, learn more about the DEM file and the heightScale parameter in [docs](docs/dem.md). By default, it's set to 1.
487
+ - Multiplier: the height of the map is multiplied by this value. So the DEM map is just a 16-bit grayscale image, which means that the maximum available value there is 65535, while the actual difference between the deepest and the highest point on Earth is about 20 km. Just note that this setting mostly does not matter, because you can always adjust it in the Giants Editor, learn more about the DEM file and the heightScale parameter in [docs](docs/dem.md). To match the in-game heights with SRTM Data provider, the recommended value is 255 (if easy mode is disabled), but depending on the place, you will need to play with both multiplier and the height scale in Giants Editor to find the best values.
479
488
 
480
489
  - Blur radius: the radius of the Gaussian blur filter applied to the DEM map. By default, it's set to 21. This filter just makes the DEM map smoother, so the height transitions will be more natural. You can set it to 1 to disable the filter, but it will result in a Minecraft-like map.
481
490
 
@@ -491,6 +500,15 @@ You can also apply some advanced settings to the map generation process. Note th
491
500
 
492
501
  - Resize factor - the factor by which the background terrain will be resized. It will be used as 1 / resize_factor while generating the models. Which means that the larger the value the more the terrain will be resized. The lowest value is 1, in this case background terrain will not be resized. Note, than low values will lead to long processing and enormous size of the obj files.
493
502
 
503
+ - Remove center - if enabled, the playable region (map terrain) will be removed from the background terrain. Note, that it will require low resize factors, to avoid gaps between the map and the background terrain.
504
+
505
+ - Apply decimation - if enabled, the mesh will be simplified to reduce the number of faces.
506
+
507
+ - Decimation percent - the target percentage of decimation. The higher the value, the more simplified the mesh will be. Note, that high values will break the 3D model entirely.
508
+
509
+ - Decimation agression - the aggression of the decimation. The higher the value, the more aggressive the
510
+ decimation will be, which means the higher it will affect the geometry. It's not recommended to make it higher than the default value, otherwise the background terrain will not match the map terrain.
511
+
494
512
  ### GRLE Advanced settings
495
513
 
496
514
  - Farmlands margin - this value (in meters) will be applied to each farmland, making it bigger. You can use the value to adjust how much the farmland should be bigger than the actual field. By default, it's set to 3.
@@ -499,6 +517,18 @@ You can also apply some advanced settings to the map generation process. Note th
499
517
 
500
518
  - Add Farmyards - if enabled, the tool will create farmlands from the regions that are marked as farmyards in the OSM data. Those farmlands will not have fields and also will not be drawn on textures. By default, it's turned off.
501
519
 
520
+ - Base grass - you can select which plant will be used as a base grass on the map.
521
+
522
+ - Plants island minimum size - when random plants are enabled, the generator will add islands of differents plants to the map and choose the random size of those island between the minimum and maximum values. This one is the minimum size of the island in meters.
523
+
524
+ - Plants island maximum size - it's the same as above, but for the maximum size of the island in meters.
525
+
526
+ - Plants island vertex count - the number of vertices in the island. The higher the value, the more detailed the island will be. Note, that high values will turn the smoothed island into geometric madness.
527
+
528
+ - Plants insland rounding radius - used to round the vertices of the island. The higher the value, the more rounded the island will be.
529
+
530
+ - Plants island percent - defines the relation between the map size and the number of islands of plants. For example, if set to 100% for map size of 2048 will be added 2048 islands of plants.
531
+
502
532
  ### I3D Advanced settings
503
533
 
504
534
  - 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.
@@ -532,6 +562,8 @@ The tool also supports the expert settings. Do not use them until you read the d
532
562
 
533
563
  - Show schemas - you'll be able to edit or define your own texture or tree schemas. It's useful if you want to add some custom textures or trees to the map. Refer to the [Texture schema](#texture-schema) section to learn more about the schema structure. Any incorrect value here will lead to the completely broken map.
534
564
 
565
+ - Upload custom background image - if you have an image, which represents the map and background terrain you can use it for generation. Note, that the image should meet the following requirements: 1:1 aspect ratio, size = map size + 2048 * 2, it should be uint16 (unsigned 16-bit integer) grayscale (single channel) image. The image should be in the PNG format. If any of the requirements are not met, the tool raises an error. If you're using rotation, the image should already be rotated.
566
+
535
567
  ## Resources
536
568
  In this section, you'll find a list of the resources that you need to create a map for the Farming Simulator.<br>
537
569
  To create a basic map, you only need the Giants Editor. But if you want to create a background terrain - the world around the map, so it won't look like it's floating in the void - you also need Blender and the Blender Exporter Plugins. To create realistic textures for the background terrain, the QGIS is required to obtain high-resolution satellite images.<br>
@@ -547,6 +579,15 @@ To create a basic map, you only need the Giants Editor. But if you want to creat
547
579
  ➡️ Please, before creating an issue or asking some questions, check the [FAQ](docs/FAQ.md) section.<br>
548
580
  If you find a bug or have an idea for a new feature, please create an issue [here](https://github.com/iwatkot/maps4fs/issues) or contact me directly on [Telegram](https://t.me/iwatkot) or on Discord: `iwatkot`.
549
581
 
582
+ ## DTM Providers
583
+
584
+ The generator supports adding the own DTM providers, please refer to the [DTM Providers](docs/dtm_providers.md) section to learn how to add the custom DTM provider.
585
+
586
+ ### Supported DTM providers
587
+
588
+ - [SRTM 30m](https://dwtkns.com/srtm30m/) - the 30 meters resolution DEM data from the SRTM mission for the whole world.
589
+ - [USGS 1m](https://portal.opentopography.org/raster?opentopoID=OTNED.012021.4269.3) - the 1-meter resolution DEM data from the USGS for the USA. Developed by [ZenJakey](https://github.com/ZenJakey).
590
+
550
591
  ## Special thanks
551
592
 
552
593
  Of course, first of all, thanks to the direct [contributors](https://github.com/iwatkot/maps4fs/graphs/contributors) of the project.
@@ -562,3 +603,4 @@ But also, I want to thank the people who helped me with the project in some way,
562
603
  - [Lucandia](https://github.com/Lucandia) - for the awesome StreamLit [widget to preview STL files](https://github.com/Lucandia/streamlit_stl).
563
604
  - [H4rdB4se](https://github.com/H4rdB4se) - for investigating the issue with custom OSM files and finding a proper way to work with the files in JOSM.
564
605
  - [kbrandwijk](https://github.com/kbrandwijk) - for providing [awesome tool](https://github.com/Paint-a-Farm/satmap_downloader) to download the satellite images from the Google Maps and giving a permission to modify it and create a Python Package.
606
+ - [Maaslandmods](https://github.com/Maaslandmods) - for the awesome idea to edit the tree schema in UI, images and code snippets on how to do it.
@@ -0,0 +1,27 @@
1
+ maps4fs/__init__.py,sha256=LrWSsyWaU28Dzcs7sRycywO_LvM-j34UvtafyBhvdx4,490
2
+ maps4fs/logger.py,sha256=B-NEYpMjPAAqlV4VpfTi6nbBFnEABVtQOaYe6nMpidg,1489
3
+ maps4fs/generator/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
4
+ maps4fs/generator/background.py,sha256=tV4UXvtkNN-OSvv6ujp4jFWRU1xGBgEvSakVGZ1H4nc,24877
5
+ maps4fs/generator/component.py,sha256=pbpGaWy5C0UzxpcJ72HPY2gMol98snDr-bvNZSX4yY0,20823
6
+ maps4fs/generator/config.py,sha256=0QmK052B8bxyHVhg3jzCORLfOBMMmqVfhhbqXKf6OMk,4383
7
+ maps4fs/generator/dem.py,sha256=20gx0dzX0LyO6ipvDitst-BwGfcKogFqgQf9Q2qMH5U,10933
8
+ maps4fs/generator/game.py,sha256=Nf5r2ubV4YVAVHGzJyhbF2GnOC0qV3HlHYIZBCWciHs,7992
9
+ maps4fs/generator/grle.py,sha256=hcbVBJ4j_Zr2QvEVo2cYNh2jARVXp_X3onifBtp9Zxs,20922
10
+ maps4fs/generator/i3d.py,sha256=pUyHKWKcw43xVCf3Y8iabtbQba05LYxMHi8vziGksIA,24843
11
+ maps4fs/generator/map.py,sha256=a50KQEr1XZKjS_WKXywGwh4OC3gyjY6M8FTc0eNcxpg,10183
12
+ maps4fs/generator/qgis.py,sha256=Es8hLuqN_KH8lDfnJE6He2rWYbAKJ3RGPn-o87S6CPI,6116
13
+ maps4fs/generator/satellite.py,sha256=_7RcuNmR1mjxEJWMDsjnzKUIqWxnGUn50XtjB7HmSPg,3661
14
+ maps4fs/generator/settings.py,sha256=9vbXISQrE-aDY7ATpvZ7LVJMqjfwa3-gNl-huI8XLO0,5666
15
+ maps4fs/generator/texture.py,sha256=tDC9lIx2qn8d09Gu6PW_Lbq7EK7s1N4l25p50v92xl8,33548
16
+ maps4fs/generator/dtm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ maps4fs/generator/dtm/dtm.py,sha256=nCQKQygARLxaz4HkREQQd0Yb03oKOf1Iav5_VoZsFWI,9819
18
+ maps4fs/generator/dtm/srtm.py,sha256=2-pX6bWrJX6gr8IM7ueX6mm_PW7_UQ58MtdzDHae2OQ,9030
19
+ maps4fs/generator/dtm/usgs.py,sha256=hwVjoSNTNRU6hwnfwJ2d3rOdtOjadCmx2QESA2REn6s,14493
20
+ maps4fs/toolbox/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
21
+ maps4fs/toolbox/background.py,sha256=9BXWNqs_n3HgqDiPztWylgYk_QM4YgBpe6_ZNQAWtSc,2154
22
+ maps4fs/toolbox/dem.py,sha256=z9IPFNmYbjiigb3t02ZenI3Mo8odd19c5MZbjDEovTo,3525
23
+ maps4fs-1.7.1.dist-info/LICENSE.md,sha256=pTKD_oUexcn-yccFCTrMeLkZy0ifLRa-VNcDLqLZaIw,10749
24
+ maps4fs-1.7.1.dist-info/METADATA,sha256=QMIGgmKUnYvtc-Ln_rxPm7WeF8Q9L82Eo_aY2g2Uc8c,39697
25
+ maps4fs-1.7.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
26
+ maps4fs-1.7.1.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
27
+ maps4fs-1.7.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,24 +0,0 @@
1
- maps4fs/__init__.py,sha256=EJzbqRrSGltSMUI-dHgONODxKt9YvP_ElwFmXV8M_MA,380
2
- maps4fs/logger.py,sha256=B-NEYpMjPAAqlV4VpfTi6nbBFnEABVtQOaYe6nMpidg,1489
3
- maps4fs/generator/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
4
- maps4fs/generator/background.py,sha256=moTsEJM-hZgHQQiBjFVTWBKgPMqxup-58EErh4bq_dE,21342
5
- maps4fs/generator/component.py,sha256=RtXruvT4Fxfr7_xo9Bi-i3IIWcPd5QQOSpYJ_cNC49o,20408
6
- maps4fs/generator/config.py,sha256=0QmK052B8bxyHVhg3jzCORLfOBMMmqVfhhbqXKf6OMk,4383
7
- maps4fs/generator/dem.py,sha256=shyehXYXXog9ZTdi_8Y1WAnmhsL4-YAIZ3EpmGy8qeA,12300
8
- maps4fs/generator/dtm.py,sha256=5_1e-kQcZ7c1Xg3tvuTyumzfTAcUPmDkIyZd5VagyOk,10550
9
- maps4fs/generator/game.py,sha256=QHgVnyGYvEnfwGZ84-u-dpbCRr3UeVVqBbrwr5WG8dE,7992
10
- maps4fs/generator/grle.py,sha256=u8ZwSs313PIOkH_0B_O2tVTaZ-eYNkc30eKGtBxWzTM,17846
11
- maps4fs/generator/i3d.py,sha256=FLVlj0g90IXRuaRARD1HTnufsLpuaa5kHKdiME-LUZY,24329
12
- maps4fs/generator/map.py,sha256=flU0b2TrVYLxj9o3v_YRvNz9YB3s4w6YFSv4Jka5ojM,9283
13
- maps4fs/generator/qgis.py,sha256=Es8hLuqN_KH8lDfnJE6He2rWYbAKJ3RGPn-o87S6CPI,6116
14
- maps4fs/generator/satellite.py,sha256=Qnb6XxmXKnHdHKVMb9mJ3vDGtGkDHCOv_81hrrXdx3k,3660
15
- maps4fs/generator/settings.py,sha256=NWuK76ICr8gURQnzePat4JH9w-iACbQEKQebqu51gBE,4470
16
- maps4fs/generator/texture.py,sha256=sErusfv1AqQfP-veMrZ921Tz8DnGEhfB4ucggMmKrD4,31231
17
- maps4fs/toolbox/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
18
- maps4fs/toolbox/background.py,sha256=9BXWNqs_n3HgqDiPztWylgYk_QM4YgBpe6_ZNQAWtSc,2154
19
- maps4fs/toolbox/dem.py,sha256=z9IPFNmYbjiigb3t02ZenI3Mo8odd19c5MZbjDEovTo,3525
20
- maps4fs-1.5.7.dist-info/LICENSE.md,sha256=pTKD_oUexcn-yccFCTrMeLkZy0ifLRa-VNcDLqLZaIw,10749
21
- maps4fs-1.5.7.dist-info/METADATA,sha256=pIrqQEpHgaljNNzjl297anHnHS2jMruuNeBKyX9iGME,35585
22
- maps4fs-1.5.7.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
23
- maps4fs-1.5.7.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
24
- maps4fs-1.5.7.dist-info/RECORD,,