maps4fs 1.8.13__tar.gz → 1.8.14__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {maps4fs-1.8.13 → maps4fs-1.8.14}/PKG-INFO +7 -1
- {maps4fs-1.8.13 → maps4fs-1.8.14}/README.md +6 -0
- maps4fs-1.8.14/maps4fs/generator/component/base/component_image.py +27 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/component/base/component_xml.py +13 -0
- {maps4fs-1.8.13/maps4fs/generator → maps4fs-1.8.14/maps4fs/generator/component}/grle.py +160 -178
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/dtm/dtm.py +0 -4
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/game.py +53 -1
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/settings.py +6 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/texture.py +6 -1
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs.egg-info/PKG-INFO +7 -1
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs.egg-info/SOURCES.txt +2 -1
- {maps4fs-1.8.13 → maps4fs-1.8.14}/pyproject.toml +1 -1
- {maps4fs-1.8.13 → maps4fs-1.8.14}/LICENSE.md +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/__init__.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/__init__.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/background.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/component/__init__.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/component/base/__init__.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/component/base/component.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/component/config.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/component/i3d.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/dem.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/dtm/__init__.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/dtm/base/wcs.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/dtm/base/wms.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/dtm/bavaria.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/dtm/canada.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/dtm/england.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/dtm/hessen.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/dtm/niedersachsen.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/dtm/nrw.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/dtm/srtm.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/dtm/usgs.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/dtm/utils.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/map.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/qgis.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/generator/satellite.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/logger.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/toolbox/__init__.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/toolbox/background.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/toolbox/custom_osm.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs/toolbox/dem.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs.egg-info/dependency_links.txt +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs.egg-info/requires.txt +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/maps4fs.egg-info/top_level.txt +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/setup.cfg +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.14}/tests/test_generator.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: maps4fs
|
3
|
-
Version: 1.8.
|
3
|
+
Version: 1.8.14
|
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
|
@@ -595,6 +595,10 @@ You can also apply some advanced settings to the map generation process.<br>
|
|
595
595
|
|
596
596
|
- 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.
|
597
597
|
|
598
|
+
- Base price - the base price of the farmland. It's used to calculate the price of the farmland in the game. In default in-game maps this value equals to 60000.
|
599
|
+
|
600
|
+
- Price scale - is a value in percent which will be applied to all farmnlands. The price per Ha will be calculated as `base_price * price_scale / 100`. By default, it's set to 100%.
|
601
|
+
|
598
602
|
- Base grass - you can select which plant will be used as a base grass on the map.
|
599
603
|
|
600
604
|
- 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.
|
@@ -621,6 +625,8 @@ You can also apply some advanced settings to the map generation process.<br>
|
|
621
625
|
|
622
626
|
- Skip drains - if enabled, the tool will not generate the drains and ditches on the map. By default, it's set to False. Use this if you don't need the drains on the map.
|
623
627
|
|
628
|
+
- Use cache - if enabled, the tool will use the cached OSM data for generating the map. It's useful when you're generating the same map multiple times and don't want to download the OSM data each time. But if you've made some changes to the OSM data, you should disable this option to get the updated data. By default, it's set to True. This option has no effect when you're using the custom OSM file.
|
629
|
+
|
624
630
|
### Splines Advanced settings
|
625
631
|
|
626
632
|
- Splines density - number of points, which will be added (interpolate) between each pair of existing points. The higher the value, the denser the spline will be. It can smooth the splines, but high values can in opposite make the splines look unnatural.
|
@@ -569,6 +569,10 @@ You can also apply some advanced settings to the map generation process.<br>
|
|
569
569
|
|
570
570
|
- 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.
|
571
571
|
|
572
|
+
- Base price - the base price of the farmland. It's used to calculate the price of the farmland in the game. In default in-game maps this value equals to 60000.
|
573
|
+
|
574
|
+
- Price scale - is a value in percent which will be applied to all farmnlands. The price per Ha will be calculated as `base_price * price_scale / 100`. By default, it's set to 100%.
|
575
|
+
|
572
576
|
- Base grass - you can select which plant will be used as a base grass on the map.
|
573
577
|
|
574
578
|
- 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.
|
@@ -595,6 +599,8 @@ You can also apply some advanced settings to the map generation process.<br>
|
|
595
599
|
|
596
600
|
- Skip drains - if enabled, the tool will not generate the drains and ditches on the map. By default, it's set to False. Use this if you don't need the drains on the map.
|
597
601
|
|
602
|
+
- Use cache - if enabled, the tool will use the cached OSM data for generating the map. It's useful when you're generating the same map multiple times and don't want to download the OSM data each time. But if you've made some changes to the OSM data, you should disable this option to get the updated data. By default, it's set to True. This option has no effect when you're using the custom OSM file.
|
603
|
+
|
598
604
|
### Splines Advanced settings
|
599
605
|
|
600
606
|
- Splines density - number of points, which will be added (interpolate) between each pair of existing points. The higher the value, the denser the spline will be. It can smooth the splines, but high values can in opposite make the splines look unnatural.
|
@@ -0,0 +1,27 @@
|
|
1
|
+
"""Base class for all components that primarily used to work with images."""
|
2
|
+
|
3
|
+
import numpy as np
|
4
|
+
|
5
|
+
from maps4fs.generator.component.base.component import Component
|
6
|
+
|
7
|
+
|
8
|
+
class ImageComponent(Component):
|
9
|
+
"""Base class for all components that primarily used to work with images."""
|
10
|
+
|
11
|
+
@staticmethod
|
12
|
+
def polygon_points_to_np(
|
13
|
+
polygon_points: list[tuple[int, int]], divide: int | None = None
|
14
|
+
) -> np.ndarray:
|
15
|
+
"""Converts the polygon points to a NumPy array.
|
16
|
+
|
17
|
+
Arguments:
|
18
|
+
polygon_points (list[tuple[int, int]]): The polygon points.
|
19
|
+
divide (int, optional): The number to divide the points by. Defaults to None.
|
20
|
+
|
21
|
+
Returns:
|
22
|
+
np.array: The NumPy array of the polygon points.
|
23
|
+
"""
|
24
|
+
array = np.array(polygon_points, dtype=np.int32).reshape((-1, 1, 2))
|
25
|
+
if divide:
|
26
|
+
return array // divide
|
27
|
+
return array
|
@@ -93,3 +93,16 @@ class XMLComponent(Component):
|
|
93
93
|
for key, value in data.items():
|
94
94
|
element.set(key, value)
|
95
95
|
return element
|
96
|
+
|
97
|
+
def create_subelement(
|
98
|
+
self, parent: ET.Element, element_name: str, data: dict[str, str]
|
99
|
+
) -> None:
|
100
|
+
"""Creates a subelement under the parent element with the provided data.
|
101
|
+
|
102
|
+
Arguments:
|
103
|
+
parent (ET.Element): The parent element.
|
104
|
+
element_name (str): The name of the subelement.
|
105
|
+
data (dict[str, str]): The data to set the subelement attributes to.
|
106
|
+
"""
|
107
|
+
element = ET.SubElement(parent, element_name)
|
108
|
+
self.update_element(element, data)
|
@@ -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
|
9
|
+
from shapely.geometry import Polygon
|
11
10
|
from tqdm import tqdm
|
12
11
|
|
13
|
-
from maps4fs.generator.component.base.
|
14
|
-
from maps4fs.generator.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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(
|
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.
|
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
|
120
|
-
image
|
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
|
-
|
148
|
-
if not
|
149
|
-
self.logger.warning("
|
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
|
-
|
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
|
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
|
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 =
|
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 =
|
210
|
-
|
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
|
211
|
+
for farmland in tqdm(farmlands, desc="Adding farmlands", unit="farmland"):
|
217
212
|
try:
|
218
|
-
|
219
|
-
polygon_points=
|
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.
|
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, [
|
247
|
-
except Exception as e:
|
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
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
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
|
-
|
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
|
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
|
-
|
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 =
|
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 =
|
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
|
-
|
338
|
-
|
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
|
-
|
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()
|
@@ -8,8 +8,8 @@ import os
|
|
8
8
|
|
9
9
|
from maps4fs.generator.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")
|
@@ -15,6 +15,9 @@ class Parameters:
|
|
15
15
|
TEXTURES = "textures"
|
16
16
|
FOREST = "forest"
|
17
17
|
ROADS_POLYLINES = "roads_polylines"
|
18
|
+
FARMYARDS = "farmyards"
|
19
|
+
|
20
|
+
PREVIEW_MAXIMUM_SIZE = 2048
|
18
21
|
|
19
22
|
|
20
23
|
class SharedSettings(BaseModel):
|
@@ -141,6 +144,8 @@ class GRLESettings(SettingsModel):
|
|
141
144
|
farmland_margin: int = 0
|
142
145
|
random_plants: bool = True
|
143
146
|
add_farmyards: bool = False
|
147
|
+
base_price: int = 60000
|
148
|
+
price_scale: int = 100
|
144
149
|
base_grass: tuple | str = ("smallDenseMix", "meadow")
|
145
150
|
plants_island_minimum_size: int = 10
|
146
151
|
plants_island_maximum_size: int = 200
|
@@ -172,6 +177,7 @@ class TextureSettings(SettingsModel):
|
|
172
177
|
dissolve: bool = False
|
173
178
|
fields_padding: int = 0
|
174
179
|
skip_drains: bool = False
|
180
|
+
use_cache: bool = True
|
175
181
|
|
176
182
|
|
177
183
|
class SplineSettings(SettingsModel):
|
@@ -12,9 +12,10 @@ from typing import Any, Callable, Generator, Optional
|
|
12
12
|
|
13
13
|
import cv2
|
14
14
|
import numpy as np
|
15
|
-
import osmnx as ox
|
15
|
+
import osmnx as ox
|
16
16
|
import pandas as pd
|
17
17
|
import shapely.geometry # type: ignore
|
18
|
+
from osmnx import settings as ox_settings
|
18
19
|
from shapely.geometry.base import BaseGeometry # type: ignore
|
19
20
|
from tqdm import tqdm
|
20
21
|
|
@@ -764,6 +765,10 @@ class Texture(Component):
|
|
764
765
|
Numpy array of polygon points or list of point coordinates.
|
765
766
|
"""
|
766
767
|
is_fieds = info_layer == "fields"
|
768
|
+
|
769
|
+
ox_settings.use_cache = self.map.texture_settings.use_cache
|
770
|
+
ox_settings.requests_timeout = 30
|
771
|
+
|
767
772
|
try:
|
768
773
|
if self.map.custom_osm is not None:
|
769
774
|
with warnings.catch_warnings():
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: maps4fs
|
3
|
-
Version: 1.8.
|
3
|
+
Version: 1.8.14
|
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
|
@@ -595,6 +595,10 @@ You can also apply some advanced settings to the map generation process.<br>
|
|
595
595
|
|
596
596
|
- 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.
|
597
597
|
|
598
|
+
- Base price - the base price of the farmland. It's used to calculate the price of the farmland in the game. In default in-game maps this value equals to 60000.
|
599
|
+
|
600
|
+
- Price scale - is a value in percent which will be applied to all farmnlands. The price per Ha will be calculated as `base_price * price_scale / 100`. By default, it's set to 100%.
|
601
|
+
|
598
602
|
- Base grass - you can select which plant will be used as a base grass on the map.
|
599
603
|
|
600
604
|
- 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.
|
@@ -621,6 +625,8 @@ You can also apply some advanced settings to the map generation process.<br>
|
|
621
625
|
|
622
626
|
- Skip drains - if enabled, the tool will not generate the drains and ditches on the map. By default, it's set to False. Use this if you don't need the drains on the map.
|
623
627
|
|
628
|
+
- Use cache - if enabled, the tool will use the cached OSM data for generating the map. It's useful when you're generating the same map multiple times and don't want to download the OSM data each time. But if you've made some changes to the OSM data, you should disable this option to get the updated data. By default, it's set to True. This option has no effect when you're using the custom OSM file.
|
629
|
+
|
624
630
|
### Splines Advanced settings
|
625
631
|
|
626
632
|
- Splines density - number of points, which will be added (interpolate) between each pair of existing points. The higher the value, the denser the spline will be. It can smooth the splines, but high values can in opposite make the splines look unnatural.
|
@@ -12,7 +12,6 @@ maps4fs/generator/__init__.py
|
|
12
12
|
maps4fs/generator/background.py
|
13
13
|
maps4fs/generator/dem.py
|
14
14
|
maps4fs/generator/game.py
|
15
|
-
maps4fs/generator/grle.py
|
16
15
|
maps4fs/generator/map.py
|
17
16
|
maps4fs/generator/qgis.py
|
18
17
|
maps4fs/generator/satellite.py
|
@@ -20,9 +19,11 @@ maps4fs/generator/settings.py
|
|
20
19
|
maps4fs/generator/texture.py
|
21
20
|
maps4fs/generator/component/__init__.py
|
22
21
|
maps4fs/generator/component/config.py
|
22
|
+
maps4fs/generator/component/grle.py
|
23
23
|
maps4fs/generator/component/i3d.py
|
24
24
|
maps4fs/generator/component/base/__init__.py
|
25
25
|
maps4fs/generator/component/base/component.py
|
26
|
+
maps4fs/generator/component/base/component_image.py
|
26
27
|
maps4fs/generator/component/base/component_xml.py
|
27
28
|
maps4fs/generator/dtm/__init__.py
|
28
29
|
maps4fs/generator/dtm/bavaria.py
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "maps4fs"
|
7
|
-
version = "1.8.
|
7
|
+
version = "1.8.14"
|
8
8
|
description = "Generate map templates for Farming Simulator from real places."
|
9
9
|
authors = [{name = "iwatkot", email = "iwatkot@gmail.com"}]
|
10
10
|
license = {text = "MIT License"}
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|