maps4fs 2.1.2__py3-none-any.whl → 2.1.4__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.
@@ -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):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maps4fs
3
- Version: 2.1.2
3
+ Version: 2.1.4
4
4
  Summary: Generate map templates for Farming Simulator from real places.
5
5
  Author-email: iwatkot <iwatkot@gmail.com>
6
6
  License: Apache License 2.0
@@ -4,24 +4,24 @@ maps4fs/generator/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk
4
4
  maps4fs/generator/game.py,sha256=Qqeh1rg0RFghhu9uhBMn_XKXYkTkg7fsjY1xO2eIZRw,13777
5
5
  maps4fs/generator/map.py,sha256=x_NjUwKkDnh2cC61QpTfX88mFHAkQG1sxVwZckoV0mE,16357
6
6
  maps4fs/generator/qgis.py,sha256=Es8hLuqN_KH8lDfnJE6He2rWYbAKJ3RGPn-o87S6CPI,6116
7
- maps4fs/generator/settings.py,sha256=OXgCjpmcqvTENRcKKYcEhES3GD3KPpvrbeJDv8g25yY,7676
7
+ maps4fs/generator/settings.py,sha256=bjEhn1918lxSsIRgmOXVpYWS3E4y72eqx2Ty7f99JKE,7788
8
8
  maps4fs/generator/statistics.py,sha256=aynS3zbAtiwnU_YLKHPTiiaKW98_suvQUhy1SGBA6mc,2448
9
9
  maps4fs/generator/component/__init__.py,sha256=s01yVVVi8R2xxNvflu2D6wTd9I_g73AMM2x7vAC7GX4,490
10
- maps4fs/generator/component/background.py,sha256=wqTgndTfRIGlLMpMXqJMw_6eVdH1JtMei46ZIXlT9X4,29480
10
+ maps4fs/generator/component/background.py,sha256=vYlhJL-kR7i2qkrYnGh_MXstl9RkDVtJ0PzhQz24d-s,33673
11
11
  maps4fs/generator/component/config.py,sha256=uL76h9UwyhZKZmbxz0mBmWtEPN6qYay4epTEqqtej60,8601
12
- maps4fs/generator/component/dem.py,sha256=SH_2Zu5O4dhWtZeOkCwzDF4RU04XhTdpGFYaRYJkdjc,11905
12
+ maps4fs/generator/component/dem.py,sha256=94dxWNAWVCIQ-8ewYmBLRtoeBR9MZ3IrB1frRrZfdgU,11912
13
13
  maps4fs/generator/component/grle.py,sha256=3BKGlR0q0t0NvmqeT81WieS6MIc_UlMQjIDDZiqTiws,27243
14
14
  maps4fs/generator/component/i3d.py,sha256=L-QAbr3Z7Ye5N0BeS_qvY9bqYxYs0eVnRCGWp77etrE,26693
15
15
  maps4fs/generator/component/layer.py,sha256=U_DzJTn1m_yGOtwuvbXxr7oL7YHHBGBcK37lyJNnZDk,6508
16
16
  maps4fs/generator/component/satellite.py,sha256=OsxoNOCgkUtRzL7Geuqubsf6uoKXAIN8jQvrJ7IFeAI,4958
17
17
  maps4fs/generator/component/texture.py,sha256=bpHBwUksEbwPQrdQ8K94m_MIKXnWYnUMvP987JVabAA,34987
18
18
  maps4fs/generator/component/base/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
19
- maps4fs/generator/component/base/component.py,sha256=Vgmdsn1ZC37EwWi4Va4uYVt0RnFLiARTtZ-R5GTSrrM,22877
20
- maps4fs/generator/component/base/component_image.py,sha256=2NYJgCU8deHl7O2FYFYk38WKZVJygFoc2gjBXwH6vjM,5970
19
+ maps4fs/generator/component/base/component.py,sha256=AP7b6rmYV_HdyyHlCTo9s6fyBXyyqGyBv-DYVynBGws,22884
20
+ maps4fs/generator/component/base/component_image.py,sha256=n_tHyq8J1zWnb1_8O6fhNNd_4Wfj2VU2QqSAEzJXf60,8119
21
21
  maps4fs/generator/component/base/component_mesh.py,sha256=_thzgjJDroMn-9SBsBmAWizcSsnV9U5445SD18Tx1kc,9090
22
22
  maps4fs/generator/component/base/component_xml.py,sha256=MT-VhU2dEckLFxAgmxg6V3gnv11di_94Qq6atfpOLdc,5342
23
- maps4fs-2.1.2.dist-info/licenses/LICENSE.md,sha256=pTKD_oUexcn-yccFCTrMeLkZy0ifLRa-VNcDLqLZaIw,10749
24
- maps4fs-2.1.2.dist-info/METADATA,sha256=L6AJvi0CMTG_RfUP9isMXDYTnugeBPWQzWQwwy2NyTg,45431
25
- maps4fs-2.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
26
- maps4fs-2.1.2.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
27
- maps4fs-2.1.2.dist-info/RECORD,,
23
+ maps4fs-2.1.4.dist-info/licenses/LICENSE.md,sha256=pTKD_oUexcn-yccFCTrMeLkZy0ifLRa-VNcDLqLZaIw,10749
24
+ maps4fs-2.1.4.dist-info/METADATA,sha256=VLDO4J4xeTO-iuXEryqQ2qHaDsZ73jJBqP1aX_eq7JQ,45431
25
+ maps4fs-2.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
26
+ maps4fs-2.1.4.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
27
+ maps4fs-2.1.4.dist-info/RECORD,,