maps4fs 2.1.2__tar.gz → 2.1.4__tar.gz
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-2.1.2 → maps4fs-2.1.4}/PKG-INFO +1 -1
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs/generator/component/background.py +101 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs/generator/component/base/component.py +1 -1
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs/generator/component/base/component_image.py +56 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs/generator/component/dem.py +1 -1
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs/generator/settings.py +2 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs.egg-info/PKG-INFO +1 -1
- {maps4fs-2.1.2 → maps4fs-2.1.4}/pyproject.toml +1 -1
- {maps4fs-2.1.2 → maps4fs-2.1.4}/LICENSE.md +0 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/README.md +0 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs/__init__.py +0 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs/generator/__init__.py +0 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs/generator/component/__init__.py +0 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs/generator/component/base/__init__.py +0 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs/generator/component/base/component_mesh.py +0 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs/generator/component/base/component_xml.py +0 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs/generator/component/config.py +0 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs/generator/component/grle.py +0 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs/generator/component/i3d.py +0 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs/generator/component/layer.py +0 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs/generator/component/satellite.py +0 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs/generator/component/texture.py +0 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs/generator/game.py +0 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs/generator/map.py +0 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs/generator/qgis.py +0 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs/generator/statistics.py +0 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs/logger.py +0 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs.egg-info/SOURCES.txt +0 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs.egg-info/dependency_links.txt +0 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs.egg-info/requires.txt +0 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/maps4fs.egg-info/top_level.txt +0 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/setup.cfg +0 -0
- {maps4fs-2.1.2 → maps4fs-2.1.4}/tests/test_generator.py +0 -0
@@ -21,6 +21,8 @@ from maps4fs.generator.component.dem import DEM
|
|
21
21
|
from maps4fs.generator.component.texture import Texture
|
22
22
|
from maps4fs.generator.settings import Parameters
|
23
23
|
|
24
|
+
SEGMENT_LENGTH = 2
|
25
|
+
|
24
26
|
|
25
27
|
class Background(MeshComponent, ImageComponent):
|
26
28
|
"""Component for creating 3D obj files based on DEM data around the map.
|
@@ -97,6 +99,10 @@ class Background(MeshComponent, ImageComponent):
|
|
97
99
|
cutted_dem_path = self.save_map_dem(self.output_path)
|
98
100
|
shutil.copyfile(self.output_path, self.not_substracted_path)
|
99
101
|
self.save_map_dem(self.output_path, save_path=self.not_resized_path)
|
102
|
+
|
103
|
+
if self.map.background_settings.flatten_roads:
|
104
|
+
self.flatten_roads()
|
105
|
+
|
100
106
|
if self.game.additional_dem_name is not None:
|
101
107
|
self.make_copy(cutted_dem_path, self.game.additional_dem_name)
|
102
108
|
|
@@ -521,6 +527,10 @@ class Background(MeshComponent, ImageComponent):
|
|
521
527
|
int(self.map.dem_settings.water_depth * z_scaling_factor),
|
522
528
|
)
|
523
529
|
|
530
|
+
dem_image = self.blur_edges_by_mask(
|
531
|
+
dem_image, water_resources_image, smaller_kernel=3, iterations=5, bigger_kernel=5 # type: ignore
|
532
|
+
)
|
533
|
+
|
524
534
|
# Save the modified dem_image back to the output path
|
525
535
|
cv2.imwrite(self.output_path, dem_image)
|
526
536
|
self.logger.debug("Water depth subtracted from DEM data: %s", self.output_path)
|
@@ -723,3 +733,94 @@ class Background(MeshComponent, ImageComponent):
|
|
723
733
|
self.assets.water_mesh = elevated_save_path
|
724
734
|
|
725
735
|
self.plane_from_np(elevated_water, elevated_save_path, include_zeros=False)
|
736
|
+
|
737
|
+
def flatten_roads(self) -> None:
|
738
|
+
"""Flattens the roads in the DEM data by averaging the height values along the road polylines."""
|
739
|
+
if not self.not_resized_path or not os.path.isfile(self.not_resized_path):
|
740
|
+
self.logger.warning("No DEM data found for flattening roads.")
|
741
|
+
return
|
742
|
+
|
743
|
+
dem_image = cv2.imread(self.not_resized_path, cv2.IMREAD_UNCHANGED)
|
744
|
+
if dem_image is None:
|
745
|
+
self.logger.warning("Failed to read DEM data.")
|
746
|
+
return
|
747
|
+
|
748
|
+
roads_polylines = self.get_infolayer_data(Parameters.TEXTURES, Parameters.ROADS_POLYLINES)
|
749
|
+
if not roads_polylines:
|
750
|
+
self.logger.warning("No roads polylines found in textures info layer.")
|
751
|
+
return
|
752
|
+
|
753
|
+
self.logger.debug("Found %s roads polylines in textures info layer.", len(roads_polylines))
|
754
|
+
|
755
|
+
full_mask = np.zeros(dem_image.shape, dtype=np.uint8)
|
756
|
+
|
757
|
+
for road_polyline in tqdm(roads_polylines, desc="Flattening roads", unit="road"):
|
758
|
+
points = road_polyline.get("points")
|
759
|
+
width = road_polyline.get("width")
|
760
|
+
if not points or not width:
|
761
|
+
self.logger.warning("Skipping road with insufficient data: %s", road_polyline)
|
762
|
+
continue
|
763
|
+
|
764
|
+
try:
|
765
|
+
fitted_road = self.fit_object_into_bounds(
|
766
|
+
linestring_points=points, angle=self.rotation
|
767
|
+
)
|
768
|
+
except ValueError as e:
|
769
|
+
self.logger.debug(
|
770
|
+
"Road polyline could not be fitted into the map bounds with error: %s",
|
771
|
+
e,
|
772
|
+
)
|
773
|
+
continue
|
774
|
+
|
775
|
+
polyline = shapely.LineString(fitted_road)
|
776
|
+
|
777
|
+
total_length = polyline.length
|
778
|
+
self.logger.debug("Total length of the road polyline: %s", total_length)
|
779
|
+
|
780
|
+
current_distance = 0
|
781
|
+
segments: list[shapely.LineString] = []
|
782
|
+
while current_distance < total_length:
|
783
|
+
start_point = polyline.interpolate(current_distance)
|
784
|
+
end_distance = min(current_distance + SEGMENT_LENGTH, total_length)
|
785
|
+
end_point = polyline.interpolate(end_distance)
|
786
|
+
|
787
|
+
# Create a small segment LineString
|
788
|
+
segment = shapely.LineString([start_point, end_point])
|
789
|
+
segments.append(segment)
|
790
|
+
current_distance += SEGMENT_LENGTH
|
791
|
+
|
792
|
+
self.logger.debug("Number of segments created: %s", len(segments))
|
793
|
+
|
794
|
+
road_polygons: list[shapely.Polygon] = []
|
795
|
+
for segment in segments:
|
796
|
+
polygon = segment.buffer(
|
797
|
+
width * 2, resolution=4, cap_style="flat", join_style="mitre"
|
798
|
+
)
|
799
|
+
road_polygons.append(polygon)
|
800
|
+
|
801
|
+
for polygon in road_polygons:
|
802
|
+
polygon_points = polygon.exterior.coords
|
803
|
+
road_np = self.polygon_points_to_np(polygon_points)
|
804
|
+
mask = np.zeros(dem_image.shape, dtype=np.uint8)
|
805
|
+
|
806
|
+
try:
|
807
|
+
cv2.fillPoly(mask, [road_np], 255) # type: ignore
|
808
|
+
except Exception as e:
|
809
|
+
self.logger.debug("Could not create mask for road with error: %s", e)
|
810
|
+
continue
|
811
|
+
|
812
|
+
mean_value = cv2.mean(dem_image, mask=mask)[0] # type: ignore
|
813
|
+
dem_image[mask == 255] = mean_value
|
814
|
+
full_mask[mask == 255] = 255
|
815
|
+
|
816
|
+
main_dem_path = self.game.dem_file_path(self.map_directory)
|
817
|
+
dem_image = self.blur_by_mask(dem_image, full_mask)
|
818
|
+
dem_image = self.blur_edges_by_mask(dem_image, full_mask)
|
819
|
+
|
820
|
+
output_size = dem_image.shape[0] + 1
|
821
|
+
resized_dem = cv2.resize(
|
822
|
+
dem_image, (output_size, output_size), interpolation=cv2.INTER_NEAREST
|
823
|
+
)
|
824
|
+
|
825
|
+
cv2.imwrite(main_dem_path, resized_dem)
|
826
|
+
self.logger.debug("Flattened roads saved to DEM file: %s", main_dem_path)
|
@@ -417,7 +417,7 @@ class Component:
|
|
417
417
|
fitted_osm_object = osm_object.intersection(bounds)
|
418
418
|
self.logger.debug("Fitted the osm_object into the bounds: %s", bounds)
|
419
419
|
except Exception as e:
|
420
|
-
raise ValueError(f"Could not fit the osm_object into the bounds: {e}")
|
420
|
+
raise ValueError(f"Could not fit the osm_object into the bounds: {e}") from e
|
421
421
|
|
422
422
|
if not isinstance(fitted_osm_object, object_type):
|
423
423
|
raise ValueError("The fitted osm_object is not valid (probably splitted into parts).")
|
@@ -175,3 +175,59 @@ class ImageComponent(Component):
|
|
175
175
|
elif blur_radius % 2 == 0:
|
176
176
|
blur_radius += 1
|
177
177
|
return blur_radius
|
178
|
+
|
179
|
+
def blur_by_mask(self, data: np.ndarray, mask: np.ndarray, blur_radius: int = 3) -> np.ndarray:
|
180
|
+
"""Blurs the provided image only where the mask is set.
|
181
|
+
|
182
|
+
Arguments:
|
183
|
+
data (np.ndarray): The image data to be blurred.
|
184
|
+
mask (np.ndarray): The mask where the blur should be applied.
|
185
|
+
|
186
|
+
Returns:
|
187
|
+
np.ndarray: The image with the blur applied according to the mask.
|
188
|
+
"""
|
189
|
+
if data.shape[:2] != mask.shape[:2]:
|
190
|
+
raise ValueError("Data and mask must have the same dimensions.")
|
191
|
+
|
192
|
+
# Create a blurred version of the data
|
193
|
+
blurred_data = cv2.GaussianBlur(data, (blur_radius, blur_radius), sigmaX=3)
|
194
|
+
|
195
|
+
# Combine the blurred data with the original data using the mask
|
196
|
+
result = np.where(mask == 255, blurred_data, data)
|
197
|
+
|
198
|
+
return result
|
199
|
+
|
200
|
+
def blur_edges_by_mask(
|
201
|
+
self,
|
202
|
+
data: np.ndarray,
|
203
|
+
mask: np.ndarray,
|
204
|
+
bigger_kernel: int = 3,
|
205
|
+
smaller_kernel: int = 1,
|
206
|
+
iterations: int = 1,
|
207
|
+
) -> np.ndarray:
|
208
|
+
"""Blurs the edges of the edge region where changes were made by mask earlier.
|
209
|
+
Creates a slightly bigger mask, a slightly smaller mask, subtract them
|
210
|
+
and obtains the mask for the edges.
|
211
|
+
Then applies blur to the image data only where the mask is set.
|
212
|
+
|
213
|
+
Arguments:
|
214
|
+
data (np.ndarray): The image data to be blurred.
|
215
|
+
mask (np.ndarray): The mask where changes were made.
|
216
|
+
blur_radius (int): The radius of the blur.
|
217
|
+
|
218
|
+
Returns:
|
219
|
+
np.ndarray: The image with the edges blurred according to the mask.
|
220
|
+
"""
|
221
|
+
if data.shape[:2] != mask.shape[:2]:
|
222
|
+
raise ValueError("Data and mask must have the same dimensions.")
|
223
|
+
|
224
|
+
bigger_mask = cv2.dilate(
|
225
|
+
mask, np.ones((bigger_kernel, bigger_kernel), np.uint8), iterations=iterations
|
226
|
+
)
|
227
|
+
smaller_mask = cv2.erode(
|
228
|
+
mask, np.ones((smaller_kernel, smaller_kernel), np.uint8), iterations=iterations
|
229
|
+
)
|
230
|
+
|
231
|
+
edge_mask = cv2.subtract(bigger_mask, smaller_mask)
|
232
|
+
|
233
|
+
return self.blur_by_mask(data, edge_mask)
|
@@ -132,7 +132,7 @@ class DEM(ImageComponent):
|
|
132
132
|
raise ValueError(
|
133
133
|
f"Failed to get DEM data from DTM provider: {e}. "
|
134
134
|
"Try using different DTM provider."
|
135
|
-
)
|
135
|
+
) from e
|
136
136
|
|
137
137
|
if len(data.shape) != 2:
|
138
138
|
self.logger.error("DTM provider returned incorrect data: more than 1 channel.")
|
@@ -163,12 +163,14 @@ class BackgroundSettings(SettingsModel):
|
|
163
163
|
water_blurriness (int): blurriness of the water.
|
164
164
|
remove_center (bool): remove the center of the background terrain.
|
165
165
|
It will be used to remove the center of the map where the player starts.
|
166
|
+
flatten_roads (bool): if True, roads will be flattened in the DEM data.
|
166
167
|
"""
|
167
168
|
|
168
169
|
generate_background: bool = False
|
169
170
|
generate_water: bool = False
|
170
171
|
water_blurriness: int = 20
|
171
172
|
remove_center: bool = True
|
173
|
+
flatten_roads: bool = False
|
172
174
|
|
173
175
|
|
174
176
|
class GRLESettings(SettingsModel):
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "maps4fs"
|
7
|
-
version = "2.1.
|
7
|
+
version = "2.1.4"
|
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 = "Apache License 2.0"}
|
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
|