maps4fs 0.9.0__py3-none-any.whl → 0.9.2__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.
@@ -50,7 +50,8 @@ class Background(Component):
50
50
  tile_code=path_step.code,
51
51
  auto_process=False,
52
52
  blur_radius=self.kwargs.get("blur_radius"),
53
- multiplier=1,
53
+ multiplier=self.kwargs.get("multiplier", 1),
54
+ plateau=self.kwargs.get("plateau", 0),
54
55
  )
55
56
 
56
57
  # Update the origin for the next tile.
@@ -80,6 +81,8 @@ class Background(Component):
80
81
  dict[str, dict[str, float | int]] -- A dictionary with information about the tiles.
81
82
  """
82
83
  data = {}
84
+ self.qgis_sequence()
85
+
83
86
  for tile in self.tiles:
84
87
  north, south, east, west = tile.bbox
85
88
  epsg3857_string = tile.get_epsg3857_string()
@@ -100,6 +103,14 @@ class Background(Component):
100
103
 
101
104
  return data # type: ignore
102
105
 
106
+ def qgis_sequence(self) -> None:
107
+ """Generates QGIS scripts for creating bounding box layers and rasterizing them."""
108
+ qgis_layers = [
109
+ (f"Background_bbox_{tile.code}", *tile.get_espg3857_bbox()) for tile in self.tiles
110
+ ]
111
+
112
+ self.create_qgis_scripts(qgis_layers) # type: ignore
113
+
103
114
  def generate_obj_files(self) -> None:
104
115
  """Iterates over all tiles and generates 3D obj files based on DEM data.
105
116
  If at least one DEM file is missing, the generation will be stopped at all.
@@ -10,6 +10,8 @@ from typing import TYPE_CHECKING, Any
10
10
  import osmnx as ox # type: ignore
11
11
  from pyproj import Transformer
12
12
 
13
+ from maps4fs.generator.qgis import get_bbox_template, get_rasterize_template
14
+
13
15
  if TYPE_CHECKING:
14
16
  from maps4fs.generator.game import Game
15
17
 
@@ -47,6 +49,7 @@ class Component:
47
49
  self.kwargs = kwargs
48
50
 
49
51
  os.makedirs(self.previews_directory, exist_ok=True)
52
+ os.makedirs(self.scripts_directory, exist_ok=True)
50
53
 
51
54
  self.save_bbox()
52
55
  self.preprocess()
@@ -84,6 +87,15 @@ class Component:
84
87
  """
85
88
  return os.path.join(self.map_directory, "previews")
86
89
 
90
+ @property
91
+ def scripts_directory(self) -> str:
92
+ """The directory where the scripts are stored.
93
+
94
+ Returns:
95
+ str: The directory where the scripts are stored.
96
+ """
97
+ return os.path.join(self.map_directory, "scripts")
98
+
87
99
  @property
88
100
  def generation_info_path(self) -> str:
89
101
  """The path to the generation info JSON file.
@@ -187,15 +199,31 @@ class Component:
187
199
  self.bbox = self.get_bbox(project_utm=False)
188
200
  self.logger.debug("Saved bounding box: %s", self.bbox)
189
201
 
190
- def get_epsg3857_string(self, bbox: tuple[int, int, int, int] | None = None) -> str:
191
- """Converts the bounding box to EPSG:3857 string.
202
+ @property
203
+ def new_bbox(self) -> tuple[float, float, float, float]:
204
+ """This property is used for a new version of osmnx library, where the order of coordinates
205
+ has been changed to (left, bottom, right, top).
206
+
207
+ Returns:
208
+ tuple[float, float, float, float]: The bounding box of the map in the new order:
209
+ (left, bottom, right, top).
210
+ """
211
+ # FutureWarning: The expected order of coordinates in `bbox`
212
+ # will change in the v2.0.0 release to `(left, bottom, right, top)`.
213
+ north, south, east, west = self.bbox
214
+ return west, south, east, north
215
+
216
+ def get_espg3857_bbox(
217
+ self, bbox: tuple[int, int, int, int] | None = None
218
+ ) -> tuple[int, int, int, int]:
219
+ """Converts the bounding box to EPSG:3857.
192
220
  If the bounding box is not provided, the instance variable is used.
193
221
 
194
222
  Args:
195
223
  bbox (tuple[int, int, int, int], optional): The bounding box to convert.
196
224
 
197
225
  Returns:
198
- str: The bounding box in EPSG:3857 string.
226
+ tuple[int, int, int, int]: The bounding box in EPSG:3857.
199
227
  """
200
228
  bbox = bbox or self.bbox
201
229
  north, south, east, west = bbox
@@ -203,4 +231,45 @@ class Component:
203
231
  epsg3857_north, epsg3857_west = transformer.transform(north, west)
204
232
  epsg3857_south, epsg3857_east = transformer.transform(south, east)
205
233
 
206
- return f"{epsg3857_north},{epsg3857_south},{epsg3857_east},{epsg3857_west} [EPSG:3857]"
234
+ return epsg3857_north, epsg3857_south, epsg3857_east, epsg3857_west
235
+
236
+ def get_epsg3857_string(self, bbox: tuple[int, int, int, int] | None = None) -> str:
237
+ """Converts the bounding box to EPSG:3857 string.
238
+ If the bounding box is not provided, the instance variable is used.
239
+
240
+ Args:
241
+ bbox (tuple[int, int, int, int], optional): The bounding box to convert.
242
+
243
+ Returns:
244
+ str: The bounding box in EPSG:3857 string.
245
+ """
246
+ north, south, east, west = self.get_espg3857_bbox(bbox)
247
+ return f"{north},{south},{east},{west} [EPSG:3857]"
248
+
249
+ def create_qgis_scripts(
250
+ self, qgis_layers: list[tuple[str, float, float, float, float]]
251
+ ) -> None:
252
+ """Creates QGIS scripts from the given layers.
253
+ Each layer is a tuple where the first element is a name of the layer and the rest are the
254
+ bounding box coordinates in EPSG:3857.
255
+ For filenames, the class name is used as a prefix.
256
+
257
+ Args:
258
+ qgis_layers (list[tuple[str, float, float, float, float]]): The list of layers to
259
+ create scripts for.
260
+ """
261
+ class_name = self.__class__.__name__.lower()
262
+
263
+ script_files = [
264
+ (f"{class_name}_bbox.py", get_bbox_template),
265
+ (f"{class_name}_rasterize.py", get_rasterize_template),
266
+ ]
267
+
268
+ for script_file, process_function in script_files:
269
+ script_path = os.path.join(self.scripts_directory, script_file)
270
+ script_content = process_function(qgis_layers) # type: ignore
271
+
272
+ with open(script_path, "w", encoding="utf-8") as file:
273
+ file.write(script_content)
274
+
275
+ self.logger.info("QGIS script saved: %s", script_path)
@@ -71,6 +71,8 @@ class Config(Component):
71
71
  south, west, north, east = bbox
72
72
  epsg3857_string = self.get_epsg3857_string(bbox=bbox)
73
73
 
74
+ self.qgis_sequence()
75
+
74
76
  overview_data = {
75
77
  "epsg3857_string": epsg3857_string,
76
78
  "south": south,
@@ -86,3 +88,12 @@ class Config(Component):
86
88
  }
87
89
 
88
90
  return data # type: ignore
91
+
92
+ def qgis_sequence(self) -> None:
93
+ """Generates QGIS scripts for creating bounding box layers and rasterizing them."""
94
+ bbox = self.get_bbox(height_distance=self.map_height, width_distance=self.map_width)
95
+ espg3857_bbox = self.get_espg3857_bbox(bbox=bbox)
96
+
97
+ qgis_layers = [("Overview_bbox", *espg3857_bbox)]
98
+
99
+ self.create_qgis_scripts(qgis_layers) # type: ignore
@@ -0,0 +1,114 @@
1
+ """This module contains templates for generating QGIS scripts."""
2
+
3
+ BBOX_TEMPLATE = """
4
+ layers = [
5
+ {layers}
6
+ ]
7
+ for layer in layers:
8
+ name = layer[0]
9
+ north, south, east, west = layer[1:]
10
+
11
+ # Create a rectangle geometry from the bounding box.
12
+ rect = QgsRectangle(north, east, south, west)
13
+
14
+ # Create a new memory layer to hold the bounding box.
15
+ layer = QgsVectorLayer("Polygon?crs=EPSG:3857", name, "memory")
16
+ provider = layer.dataProvider()
17
+
18
+ # Add the rectangle as a feature to the layer.
19
+ feature = QgsFeature()
20
+ feature.setGeometry(QgsGeometry.fromRect(rect))
21
+ provider.addFeatures([feature])
22
+
23
+ # Add the layer to the map.
24
+ QgsProject.instance().addMapLayer(layer)
25
+
26
+ # Set the fill opacity.
27
+ symbol = layer.renderer().symbol()
28
+ symbol_layer = symbol.symbolLayer(0)
29
+
30
+ # Set the stroke color and width.
31
+ symbol_layer.setStrokeColor(QColor(0, 255, 0))
32
+ symbol_layer.setStrokeWidth(0.2)
33
+ symbol_layer.setFillColor(QColor(0, 0, 255, 0))
34
+ layer.triggerRepaint()
35
+ """
36
+
37
+ RASTERIZE_TEMPLATE = """
38
+ import processing
39
+
40
+ ############################################################
41
+ ####### ADD THE DIRECTORY FOR THE FILES TO SAVE HERE #######
42
+ ############################################################
43
+ ############### IT MUST END WITH A SLASH (/) ###############
44
+ ############################################################
45
+
46
+ SAVE_DIR = "C:/Users/iwatk/OneDrive/Desktop/"
47
+
48
+ ############################################################
49
+
50
+ layers = [
51
+ {layers}
52
+ ]
53
+
54
+ for layer in layers:
55
+ name = layer[0]
56
+ north, south, east, west = layer[1:]
57
+
58
+ epsg3857_string = str(north) + "," + str(south) + "," + str(east) + "," + str(west) + " [EPSG:3857]"
59
+ file_path = SAVE_DIR + name + ".tif"
60
+
61
+ processing.run(
62
+ "native:rasterize",
63
+ {{
64
+ "EXTENT": epsg3857_string,
65
+ "EXTENT_BUFFER": 0,
66
+ "TILE_SIZE": 64,
67
+ "MAP_UNITS_PER_PIXEL": 1,
68
+ "MAKE_BACKGROUND_TRANSPARENT": False,
69
+ "MAP_THEME": None,
70
+ "LAYERS": None,
71
+ "OUTPUT": file_path,
72
+ }},
73
+ )
74
+ """
75
+
76
+
77
+ def get_bbox_template(layers: list[tuple[str, float, float, float, float]]) -> str:
78
+ """Returns a template for creating bounding box layers in QGIS.
79
+
80
+ Args:
81
+ layers (list[tuple[str, float, float, float, float]]): A list of tuples containing the
82
+ layer name and the bounding box coordinates.
83
+
84
+ Returns:
85
+ str: The template for creating bounding box layers in QGIS.
86
+ """
87
+ return BBOX_TEMPLATE.format(
88
+ layers=",\n ".join(
89
+ [
90
+ f'("{name}", {north}, {south}, {east}, {west})'
91
+ for name, north, south, east, west in layers
92
+ ]
93
+ )
94
+ )
95
+
96
+
97
+ def get_rasterize_template(layers: list[tuple[str, float, float, float, float]]) -> str:
98
+ """Returns a template for rasterizing bounding box layers in QGIS.
99
+
100
+ Args:
101
+ layers (list[tuple[str, float, float, float, float]]): A list of tuples containing the
102
+ layer name and the bounding box coordinates.
103
+
104
+ Returns:
105
+ str: The template for rasterizing bounding box layers in QGIS.
106
+ """
107
+ return RASTERIZE_TEMPLATE.format(
108
+ layers=",\n ".join(
109
+ [
110
+ f'("{name}", {north}, {south}, {east}, {west})'
111
+ for name, north, south, east, west in layers
112
+ ]
113
+ )
114
+ )
@@ -167,14 +167,6 @@ class Texture(Component):
167
167
  self.logger.debug("Map minimum coordinates (XxY): %s x %s.", self.minimum_x, self.minimum_y)
168
168
  self.logger.debug("Map maximum coordinates (XxY): %s x %s.", self.maximum_x, self.maximum_y)
169
169
 
170
- self.height = abs(north - south)
171
- self.width = abs(east - west)
172
- self.logger.info("Map dimensions (HxW): %s x %s.", self.height, self.width)
173
-
174
- self.height_coef = self.height / self.map_height
175
- self.width_coef = self.width / self.map_width
176
- self.logger.debug("Map coefficients (HxW): %s x %s.", self.height_coef, self.width_coef)
177
-
178
170
  def info_sequence(self) -> dict[str, Any]:
179
171
  """Returns the JSON representation of the generation info for textures."""
180
172
  useful_attributes = [
@@ -186,10 +178,6 @@ class Texture(Component):
186
178
  "minimum_y",
187
179
  "maximum_x",
188
180
  "maximum_y",
189
- "height",
190
- "width",
191
- "height_coef",
192
- "width_coef",
193
181
  ]
194
182
  return {attr: getattr(self, attr, None) for attr in useful_attributes}
195
183
 
@@ -321,8 +309,7 @@ class Texture(Component):
321
309
  Returns:
322
310
  int: Relative X coordinate in map image.
323
311
  """
324
- raw_x = x - self.minimum_x
325
- return int(raw_x * self.height_coef)
312
+ return int(self.map_width * (x - self.minimum_x) / (self.maximum_x - self.minimum_x))
326
313
 
327
314
  def get_relative_y(self, y: float) -> int:
328
315
  """Converts UTM Y coordinate to relative Y coordinate in map image.
@@ -333,8 +320,7 @@ class Texture(Component):
333
320
  Returns:
334
321
  int: Relative Y coordinate in map image.
335
322
  """
336
- raw_y = y - self.minimum_y
337
- return self.height - int(raw_y * self.width_coef)
323
+ return int(self.map_height * (1 - (y - self.minimum_y) / (self.maximum_y - self.minimum_y)))
338
324
 
339
325
  # pylint: disable=W0613
340
326
  def _to_np(self, geometry: shapely.geometry.polygon.Polygon, *args) -> np.ndarray:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: maps4fs
3
- Version: 0.9.0
3
+ Version: 0.9.2
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
@@ -59,8 +59,8 @@ Requires-Dist: tifffile
59
59
  🏞️ Generates height using SRTM dataset<br>
60
60
  📦 Provides a ready-to-use map template for the Giants Editor<br>
61
61
  🚜 Supports Farming Simulator 22 and 25<br>
62
- 🔷 Generates *.obj files for background terrain based on the real-world height map 🆕<br>
63
- 📄 Generates commands to obtain high-resolution satellite images from [QGIS](https://qgis.org/download/) 🆕<br>
62
+ 🔷 Generates *.obj files for background terrain based on the real-world height map<br>
63
+ 📄 Generates scripts to download high-resolution satellite images from [QGIS](https://qgis.org/download/) in one click<br>
64
64
 
65
65
  <p align="center">
66
66
  <img src="https://github.com/user-attachments/assets/cf8f5752-9c69-4018-bead-290f59ba6976"><br>
@@ -266,10 +266,6 @@ Example of the `Texture` component:
266
266
  "minimum_y": 5013940.540089059,
267
267
  "maximum_x": 441233.5397821935,
268
268
  "maximum_y": 5016006.074349126,
269
- "height": 2065.5342600671574,
270
- "width": 2072.295804702677,
271
- "height_coef": 1.0085616504234167,
272
- "width_coef": 1.011863185889979
273
269
  },
274
270
  ```
275
271
 
@@ -282,12 +278,7 @@ And here's the list of the fields:
282
278
  - `"minimum_x"` - the minimum x coordinate of the map (UTM projection),<br>
283
279
  - `"minimum_y"` - the minimum y coordinate of the map (UTM projection),<br>
284
280
  - `"maximum_x"` - the maximum x coordinate of the map (UTM projection),<br>
285
- - `"maximum_y"` - the maximum y coordinate of the map (UTM projection),<br>
286
- - `"height"` - the height of the map in meters (it won't be equal to the parameters above since the Earth is not flat, sorry flat-earthers),<br>
287
- - `"width"` - the width of the map in meters (same as above),<br>
288
- - `"height_coef"` - since we need a texture of exact size, the height of the map is multiplied by this coefficient,<br>
289
- - `"width_coef"` - same as above but for the width,<br>
290
- - `"tile_name"` - the name of the SRTM tile which was used to generate the height map, e.g. "N52E013"<br>
281
+ - `"maximum_y"` - the maximum y coordinate of the map (UTM projection),
291
282
 
292
283
  ### Background
293
284
 
@@ -373,9 +364,9 @@ If you're willing to create a background terrain, you will need: Blender, the Bl
373
364
 
374
365
  If you're afraid of this task, please don't be. It's really simple and I've prepaired detailed step-by-step instructions for you, you'll find them in the separate README files. Here are the steps you need to follow:
375
366
 
376
- 1. [Download high-resolution satellite images](README_satellite_images.md).
377
- 2. [Prepare the i3d files](README_i3d.md).
378
- 3. [Import the i3d files to Giants Editor](README_giants_editor.md).
367
+ 1. [Download high-resolution satellite images](tutorials/README_satellite_images.md).
368
+ 2. [Prepare the i3d files](tutorials/README_i3d.md).
369
+ 3. [Import the i3d files to Giants Editor](tutorials/README_giants_editor.md).
379
370
 
380
371
  ## Overview image
381
372
  The overview image is an image that is used as in-game map. No matter what the size of the map, this file is always `4096x4096 pixels`, while the region of your map is `2048x2048 pixels` in center of this file. The rest of the image is just here for nice view, but you still may add satellite pictures to this region.<br>
@@ -1,18 +1,19 @@
1
1
  maps4fs/__init__.py,sha256=da4jmND2Ths9AffnkAKgzLHNkvKFOc_l21gJisPXqWY,155
2
2
  maps4fs/logger.py,sha256=CneeHxQywjNUJXqQrUUSeiDxu95FfrfyK_Si1v0gMZ8,1477
3
3
  maps4fs/generator/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
4
- maps4fs/generator/background.py,sha256=YZbTSijKgNQOvpWJHqCJNHeoV5GxUjp79b8PoQwRQH0,10778
5
- maps4fs/generator/component.py,sha256=g3IBC8ul9zmcG9tHvyPJIg8hxBHFR8kB8Smw9yMW3qA,7864
6
- maps4fs/generator/config.py,sha256=JkRexT_ZclRa_x0w6ojgG-Tsu4NoshFTUGycRFdfrVk,3463
4
+ maps4fs/generator/background.py,sha256=2BUgn12t-FgijPTY7hvwTaYQrVzYdnZqhoz76KSBcKs,11216
5
+ maps4fs/generator/component.py,sha256=tLjjbrFn3v4CrUrUOgH9s8NuJhmQcBs74ShefkNg1VE,10584
6
+ maps4fs/generator/config.py,sha256=JL7leQv8C06JQOXIbgQ-jve2re7cNsx8vKa8dfbnxPM,3896
7
7
  maps4fs/generator/dem.py,sha256=p7THncPUdYWtNR2HrTXe0bCuJ8psUV8rRpYPJkzL2gA,15220
8
8
  maps4fs/generator/game.py,sha256=94HjPNOyy6XSSOnBp-uxrkBglKyC-X3ULIrrucfdlKw,6825
9
9
  maps4fs/generator/i3d.py,sha256=UuQiQRusPQ2f6PvGKxJ_UZeXsx3uG15efmy8Ysnm3F8,3597
10
10
  maps4fs/generator/map.py,sha256=BS8ylTCRlHRmbImg73wnHMpBB2ODckVyVZq5KJhCMfM,4236
11
11
  maps4fs/generator/path_steps.py,sha256=rKslIiG9mriCVL9_0i8Oet2p0DITrpBWi0pECpvuyUM,2707
12
- maps4fs/generator/texture.py,sha256=0gkohGmamZbmqc9VpPqt8pli_x9w9SnUh2JjYGaiI3I,18170
12
+ maps4fs/generator/qgis.py,sha256=k19miPEFyOdu_ogBFHmFVfYQ-IgF38ufJZ5DM5NSHBQ,3400
13
+ maps4fs/generator/texture.py,sha256=CwaXZfAG4e3D3YR7yVR2MC677EHpbUWTboCS3G6jkhk,17723
13
14
  maps4fs/generator/tile.py,sha256=3vmfjQiPiiUZKPuIo5tg2rOKkgcP1NRVkMGK-Vo10-A,2138
14
- maps4fs-0.9.0.dist-info/LICENSE.md,sha256=-JY0v7p3dwXze61EbYiK7YEJ2aKrjaFZ8y2xYEOrmRY,1068
15
- maps4fs-0.9.0.dist-info/METADATA,sha256=HZffexpblrgCEHi3ThLJ3FbmCT8HDxbetbJZUFnPToI,24031
16
- maps4fs-0.9.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
17
- maps4fs-0.9.0.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
18
- maps4fs-0.9.0.dist-info/RECORD,,
15
+ maps4fs-0.9.2.dist-info/LICENSE.md,sha256=-JY0v7p3dwXze61EbYiK7YEJ2aKrjaFZ8y2xYEOrmRY,1068
16
+ maps4fs-0.9.2.dist-info/METADATA,sha256=J9SVncLGsj5Le0QQu-mfSyrsQJc0EkO7tdDs1HFd8vQ,23427
17
+ maps4fs-0.9.2.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
18
+ maps4fs-0.9.2.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
19
+ maps4fs-0.9.2.dist-info/RECORD,,