maps4fs 2.1.2__py3-none-any.whl → 2.1.3__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
 
@@ -723,3 +729,94 @@ class Background(MeshComponent, ImageComponent):
723
729
  self.assets.water_mesh = elevated_save_path
724
730
 
725
731
  self.plane_from_np(elevated_water, elevated_save_path, include_zeros=False)
732
+
733
+ def flatten_roads(self) -> None:
734
+ """Flattens the roads in the DEM data by averaging the height values along the road polylines."""
735
+ if not self.not_resized_path or not os.path.isfile(self.not_resized_path):
736
+ self.logger.warning("No DEM data found for flattening roads.")
737
+ return
738
+
739
+ dem_image = cv2.imread(self.not_resized_path, cv2.IMREAD_UNCHANGED)
740
+ if dem_image is None:
741
+ self.logger.warning("Failed to read DEM data.")
742
+ return
743
+
744
+ roads_polylines = self.get_infolayer_data(Parameters.TEXTURES, Parameters.ROADS_POLYLINES)
745
+ if not roads_polylines:
746
+ self.logger.warning("No roads polylines found in textures info layer.")
747
+ return
748
+
749
+ self.logger.debug("Found %s roads polylines in textures info layer.", len(roads_polylines))
750
+
751
+ full_mask = np.zeros(dem_image.shape, dtype=np.uint8)
752
+
753
+ for road_polyline in tqdm(roads_polylines, desc="Flattening roads", unit="road"):
754
+ points = road_polyline.get("points")
755
+ width = road_polyline.get("width")
756
+ if not points or not width:
757
+ self.logger.warning("Skipping road with insufficient data: %s", road_polyline)
758
+ continue
759
+
760
+ try:
761
+ fitted_road = self.fit_object_into_bounds(
762
+ linestring_points=points, angle=self.rotation
763
+ )
764
+ except ValueError as e:
765
+ self.logger.debug(
766
+ "Road polyline could not be fitted into the map bounds with error: %s",
767
+ e,
768
+ )
769
+ continue
770
+
771
+ polyline = shapely.LineString(fitted_road)
772
+
773
+ total_length = polyline.length
774
+ self.logger.debug("Total length of the road polyline: %s", total_length)
775
+
776
+ current_distance = 0
777
+ segments: list[shapely.LineString] = []
778
+ while current_distance < total_length:
779
+ start_point = polyline.interpolate(current_distance)
780
+ end_distance = min(current_distance + SEGMENT_LENGTH, total_length)
781
+ end_point = polyline.interpolate(end_distance)
782
+
783
+ # Create a small segment LineString
784
+ segment = shapely.LineString([start_point, end_point])
785
+ segments.append(segment)
786
+ current_distance += SEGMENT_LENGTH
787
+
788
+ self.logger.debug("Number of segments created: %s", len(segments))
789
+
790
+ road_polygons: list[shapely.Polygon] = []
791
+ for segment in segments:
792
+ polygon = segment.buffer(
793
+ width * 2, resolution=4, cap_style="flat", join_style="mitre"
794
+ )
795
+ road_polygons.append(polygon)
796
+
797
+ for polygon in road_polygons:
798
+ polygon_points = polygon.exterior.coords
799
+ road_np = self.polygon_points_to_np(polygon_points)
800
+ mask = np.zeros(dem_image.shape, dtype=np.uint8)
801
+
802
+ try:
803
+ cv2.fillPoly(mask, [road_np], 255) # type: ignore
804
+ except Exception as e:
805
+ self.logger.debug("Could not create mask for road with error: %s", e)
806
+ continue
807
+
808
+ mean_value = cv2.mean(dem_image, mask=mask)[0] # type: ignore
809
+ dem_image[mask == 255] = mean_value
810
+ full_mask[mask == 255] = 255
811
+
812
+ main_dem_path = self.game.dem_file_path(self.map_directory)
813
+ dem_image = self.blur_by_mask(dem_image, full_mask)
814
+ dem_image = self.blur_edges_by_mask(dem_image, full_mask)
815
+
816
+ output_size = dem_image.shape[0] + 1
817
+ resized_dem = cv2.resize(
818
+ dem_image, (output_size, output_size), interpolation=cv2.INTER_NEAREST
819
+ )
820
+
821
+ cv2.imwrite(main_dem_path, resized_dem)
822
+ self.logger.debug("Flattened roads saved to DEM file: %s", main_dem_path)
@@ -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)
@@ -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.3
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,10 +4,10 @@ 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=0fTmghJZzmZMqleTmFc5C6A53eZm7nhMpLYmkELAHmQ,33507
11
11
  maps4fs/generator/component/config.py,sha256=uL76h9UwyhZKZmbxz0mBmWtEPN6qYay4epTEqqtej60,8601
12
12
  maps4fs/generator/component/dem.py,sha256=SH_2Zu5O4dhWtZeOkCwzDF4RU04XhTdpGFYaRYJkdjc,11905
13
13
  maps4fs/generator/component/grle.py,sha256=3BKGlR0q0t0NvmqeT81WieS6MIc_UlMQjIDDZiqTiws,27243
@@ -17,11 +17,11 @@ maps4fs/generator/component/satellite.py,sha256=OsxoNOCgkUtRzL7Geuqubsf6uoKXAIN8
17
17
  maps4fs/generator/component/texture.py,sha256=bpHBwUksEbwPQrdQ8K94m_MIKXnWYnUMvP987JVabAA,34987
18
18
  maps4fs/generator/component/base/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
19
19
  maps4fs/generator/component/base/component.py,sha256=Vgmdsn1ZC37EwWi4Va4uYVt0RnFLiARTtZ-R5GTSrrM,22877
20
- maps4fs/generator/component/base/component_image.py,sha256=2NYJgCU8deHl7O2FYFYk38WKZVJygFoc2gjBXwH6vjM,5970
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.3.dist-info/licenses/LICENSE.md,sha256=pTKD_oUexcn-yccFCTrMeLkZy0ifLRa-VNcDLqLZaIw,10749
24
+ maps4fs-2.1.3.dist-info/METADATA,sha256=qJ4vDMMwhoupUdurZjg1aROKauLLhWLx9845zyynngY,45431
25
+ maps4fs-2.1.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
26
+ maps4fs-2.1.3.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
27
+ maps4fs-2.1.3.dist-info/RECORD,,