maps4fs 0.9.1__py3-none-any.whl → 0.9.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.
@@ -10,10 +10,14 @@ import numpy as np
10
10
  import trimesh # type: ignore
11
11
 
12
12
  from maps4fs.generator.component import Component
13
- from maps4fs.generator.path_steps import DEFAULT_DISTANCE, get_steps
13
+ from maps4fs.generator.path_steps import DEFAULT_DISTANCE, PATH_FULL_NAME, get_steps
14
14
  from maps4fs.generator.tile import Tile
15
+ from maps4fs.logger import timeit
15
16
 
16
17
  RESIZE_FACTOR = 1 / 4
18
+ SIMPLIFY_FACTOR = 10
19
+ FULL_RESIZE_FACTOR = 1 / 4
20
+ FULL_SIMPLIFY_FACTOR = 20
17
21
 
18
22
 
19
23
  class Background(Component):
@@ -37,7 +41,12 @@ class Background(Component):
37
41
  # Getting a list of 8 tiles around the map starting from the N(North) tile.
38
42
  for path_step in get_steps(self.map_height, self.map_width):
39
43
  # Getting the destination coordinates for the current tile.
40
- tile_coordinates = path_step.get_destination(origin)
44
+ if path_step.angle is None:
45
+ # For the case when generating the overview map, which has the same
46
+ # center as the main map.
47
+ tile_coordinates = self.coordinates
48
+ else:
49
+ tile_coordinates = path_step.get_destination(origin)
41
50
 
42
51
  # Create a Tile component, which is needed to save the DEM image.
43
52
  tile = Tile(
@@ -81,6 +90,8 @@ class Background(Component):
81
90
  dict[str, dict[str, float | int]] -- A dictionary with information about the tiles.
82
91
  """
83
92
  data = {}
93
+ self.qgis_sequence()
94
+
84
95
  for tile in self.tiles:
85
96
  north, south, east, west = tile.bbox
86
97
  epsg3857_string = tile.get_epsg3857_string()
@@ -101,6 +112,14 @@ class Background(Component):
101
112
 
102
113
  return data # type: ignore
103
114
 
115
+ def qgis_sequence(self) -> None:
116
+ """Generates QGIS scripts for creating bounding box layers and rasterizing them."""
117
+ qgis_layers = [
118
+ (f"Background_bbox_{tile.code}", *tile.get_espg3857_bbox()) for tile in self.tiles
119
+ ]
120
+
121
+ self.create_qgis_scripts(qgis_layers) # type: ignore
122
+
104
123
  def generate_obj_files(self) -> None:
105
124
  """Iterates over all tiles and generates 3D obj files based on DEM data.
106
125
  If at least one DEM file is missing, the generation will be stopped at all.
@@ -119,29 +138,49 @@ class Background(Component):
119
138
  self.logger.debug("Generating obj file for tile %s in path: %s", tile.code, save_path)
120
139
 
121
140
  dem_data = cv2.imread(tile.dem_path, cv2.IMREAD_UNCHANGED) # pylint: disable=no-member
122
- self.plane_from_np(dem_data, save_path)
141
+ self.plane_from_np(tile.code, dem_data, save_path)
123
142
 
124
143
  # pylint: disable=too-many-locals
125
- def plane_from_np(self, dem_data: np.ndarray, save_path: str) -> None:
144
+ @timeit
145
+ def plane_from_np(self, tile_code: str, dem_data: np.ndarray, save_path: str) -> None:
126
146
  """Generates a 3D obj file based on DEM data.
127
147
 
128
148
  Arguments:
149
+ tile_code (str) -- The code of the tile.
129
150
  dem_data (np.ndarray) -- The DEM data as a numpy array.
130
151
  save_path (str) -- The path where the obj file will be saved.
131
152
  """
153
+ if tile_code == PATH_FULL_NAME:
154
+ resize_factor = FULL_RESIZE_FACTOR
155
+ simplify_factor = FULL_SIMPLIFY_FACTOR
156
+ self.logger.info("Generating a full map obj file")
157
+ else:
158
+ resize_factor = RESIZE_FACTOR
159
+ simplify_factor = SIMPLIFY_FACTOR
132
160
  dem_data = cv2.resize( # pylint: disable=no-member
133
- dem_data, (0, 0), fx=RESIZE_FACTOR, fy=RESIZE_FACTOR
161
+ dem_data, (0, 0), fx=resize_factor, fy=resize_factor
134
162
  )
135
163
  self.logger.debug(
136
- "DEM data resized to shape: %s with factor: %s", dem_data.shape, RESIZE_FACTOR
164
+ "DEM data resized to shape: %s with factor: %s", dem_data.shape, resize_factor
137
165
  )
138
166
 
167
+ # Invert the height values.
168
+ dem_data = dem_data.max() - dem_data
169
+
139
170
  rows, cols = dem_data.shape
140
171
  x = np.linspace(0, cols - 1, cols)
141
172
  y = np.linspace(0, rows - 1, rows)
142
173
  x, y = np.meshgrid(x, y)
143
174
  z = dem_data
144
175
 
176
+ self.logger.info(
177
+ "Starting to generate a mesh for tile %s with shape: %s x %s. "
178
+ "This may take a while...",
179
+ tile_code,
180
+ cols,
181
+ rows,
182
+ )
183
+
145
184
  vertices = np.column_stack([x.ravel(), y.ravel(), z.ravel()])
146
185
  faces = []
147
186
 
@@ -152,15 +191,22 @@ class Background(Component):
152
191
  bottom_left = top_left + cols
153
192
  bottom_right = bottom_left + 1
154
193
 
155
- # Invert the order of vertices to flip the normals
156
- faces.append([top_left, bottom_right, bottom_left])
157
- faces.append([top_left, top_right, bottom_right])
194
+ faces.append([top_left, bottom_left, bottom_right])
195
+ faces.append([top_left, bottom_right, top_right])
158
196
 
159
197
  faces = np.array(faces) # type: ignore
160
198
  mesh = trimesh.Trimesh(vertices=vertices, faces=faces)
161
199
 
200
+ # Apply rotation: 180 degrees around Y-axis and Z-axis
201
+ rotation_matrix_y = trimesh.transformations.rotation_matrix(np.pi, [0, 1, 0])
202
+ rotation_matrix_z = trimesh.transformations.rotation_matrix(np.pi, [0, 0, 1])
203
+ mesh.apply_transform(rotation_matrix_y)
204
+ mesh.apply_transform(rotation_matrix_z)
205
+
206
+ self.logger.info("Mesh generated with %s faces, will be simplified", len(mesh.faces))
207
+
162
208
  # Simplify the mesh to reduce the number of faces.
163
- mesh = mesh.simplify_quadric_decimation(face_count=len(faces) // 10)
209
+ mesh = mesh.simplify_quadric_decimation(face_count=len(faces) // simplify_factor)
164
210
  self.logger.debug("Mesh simplified to %s faces", len(mesh.faces))
165
211
 
166
212
  mesh.export(save_path)
@@ -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
maps4fs/generator/map.py CHANGED
@@ -42,7 +42,7 @@ class Map:
42
42
  self.map_directory = map_directory
43
43
 
44
44
  if not logger:
45
- logger = Logger(__name__, to_stdout=True, to_file=False)
45
+ logger = Logger(to_stdout=True, to_file=False)
46
46
  self.logger = logger
47
47
  self.logger.debug("Game was set to %s", game.code)
48
48
 
@@ -5,6 +5,7 @@ from typing import NamedTuple
5
5
  from geopy.distance import distance # type: ignore
6
6
 
7
7
  DEFAULT_DISTANCE = 2048
8
+ PATH_FULL_NAME = "FULL"
8
9
 
9
10
 
10
11
  class PathStep(NamedTuple):
@@ -13,13 +14,17 @@ class PathStep(NamedTuple):
13
14
  Attributes:
14
15
  code {str} -- Tile code (N, NE, E, SE, S, SW, W, NW).
15
16
  angle {int} -- Angle in degrees (for example 0 for North, 90 for East).
17
+ If None, the step is a full map with a center at the same coordinates as the
18
+ map itself.
16
19
  distance {int} -- Distance in meters from previous step.
20
+ If None, the step is a full map with a center at the same coordinates as the
21
+ map itself.
17
22
  size {tuple[int, int]} -- Size of the tile in pixels (width, height).
18
23
  """
19
24
 
20
25
  code: str
21
- angle: int
22
- distance: int
26
+ angle: int | None
27
+ distance: int | None
23
28
  size: tuple[int, int]
24
29
 
25
30
  def get_destination(self, origin: tuple[float, float]) -> tuple[float, float]:
@@ -69,4 +74,10 @@ def get_steps(map_height: int, map_width: int) -> list[PathStep]:
69
74
  PathStep(
70
75
  "NW", 0, half_height + half_default_distance, (DEFAULT_DISTANCE, DEFAULT_DISTANCE)
71
76
  ),
77
+ PathStep(
78
+ PATH_FULL_NAME,
79
+ None,
80
+ None,
81
+ (map_width + DEFAULT_DISTANCE * 2, map_height + DEFAULT_DISTANCE * 2),
82
+ ),
72
83
  ]
@@ -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
+ )
maps4fs/logger.py CHANGED
@@ -4,8 +4,11 @@ import logging
4
4
  import os
5
5
  import sys
6
6
  from datetime import datetime
7
- from typing import Literal
7
+ from logging import getLogger
8
+ from time import perf_counter
9
+ from typing import Any, Callable, Literal
8
10
 
11
+ LOGGER_NAME = "maps4fs"
9
12
  log_directory = os.path.join(os.getcwd(), "logs")
10
13
  os.makedirs(log_directory, exist_ok=True)
11
14
 
@@ -15,12 +18,11 @@ class Logger(logging.Logger):
15
18
 
16
19
  def __init__(
17
20
  self,
18
- name: str,
19
21
  level: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = "ERROR",
20
22
  to_stdout: bool = True,
21
23
  to_file: bool = True,
22
24
  ):
23
- super().__init__(name)
25
+ super().__init__(LOGGER_NAME)
24
26
  self.setLevel(level)
25
27
  self.stdout_handler = logging.StreamHandler(sys.stdout)
26
28
  self.file_handler = logging.FileHandler(
@@ -44,3 +46,25 @@ class Logger(logging.Logger):
44
46
  today = datetime.now().strftime("%Y-%m-%d")
45
47
  log_file = os.path.join(log_directory, f"{today}.txt")
46
48
  return log_file
49
+
50
+
51
+ def timeit(func: Callable[..., Any]) -> Callable[..., Any]:
52
+ """Decorator to log the time taken by a function to execute.
53
+
54
+ Args:
55
+ func (function): The function to be timed.
56
+
57
+ Returns:
58
+ function: The timed function.
59
+ """
60
+
61
+ def timed(*args, **kwargs):
62
+ logger = getLogger("maps4fs")
63
+ start = perf_counter()
64
+ result = func(*args, **kwargs)
65
+ end = perf_counter()
66
+ if logger is not None:
67
+ logger.info("Function %s took %s seconds to execute", func.__name__, end - start)
68
+ return result
69
+
70
+ return timed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: maps4fs
3
- Version: 0.9.1
3
+ Version: 0.9.3
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>
@@ -360,13 +360,19 @@ Let's have a closer look at the fields:
360
360
  ## Background terrain
361
361
  The tool now supports the generation of the background terrain. If you don't know what it is, here's a brief explanation. The background terrain is the world around the map. It's important to create it, because if you don't, the map will look like it's floating in the void. The background terrain is a simple plane which can (and should) be texture to look fine.<br>
362
362
  So, the tool generates the background terrain in the form of the 8 tiles, which surround the map. The tiles are named as the cardinal points, e.g. "N", "NE", "E" and so on. All those tiles will be saved in the `objects/tiles` directory with corresponding names: `N.obj`, `NE.obj`, `E.obj` and so on.<br>
363
+ If you don't want to work with separate tiles, the tool also generates the `FULL.obj` file, which includes everything around the map and the map itself. It may be a convinient approach to work with one file, one texture and then just cut the map from it.<br>
364
+
365
+ ![Complete background terrain in Blender](https://github.com/user-attachments/assets/7266b8f1-bfa2-4c14-a740-1c84b1030a66)
366
+
367
+ ➡️ *No matter which approach you choose, you still need to adjust the background terrain to connect it to the map without any gaps. But with a sinlge file it's much easier to do.*
368
+
363
369
  If you're willing to create a background terrain, you will need: Blender, the Blender Exporter Plugins and the QGIS. You'll find the download links in the [Resources](#resources) section.<br>
364
370
 
365
371
  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:
366
372
 
367
- 1. [Download high-resolution satellite images](README_satellite_images.md).
368
- 2. [Prepare the i3d files](README_i3d.md).
369
- 3. [Import the i3d files to Giants Editor](README_giants_editor.md).
373
+ 1. [Download high-resolution satellite images](tutorials/README_satellite_images.md).
374
+ 2. [Prepare the i3d files](tutorials/README_i3d.md).
375
+ 3. [Import the i3d files to Giants Editor](tutorials/README_giants_editor.md).
370
376
 
371
377
  ## Overview image
372
378
  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>
@@ -0,0 +1,19 @@
1
+ maps4fs/__init__.py,sha256=da4jmND2Ths9AffnkAKgzLHNkvKFOc_l21gJisPXqWY,155
2
+ maps4fs/logger.py,sha256=8oZzAKJllilYrVp452LX0zx-dNFwpS6UngbTrI6KfwA,2148
3
+ maps4fs/generator/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
4
+ maps4fs/generator/background.py,sha256=M43VNwiAnL4-1iUGAFwwwl5lV5EVy4luvZZmsuGO_Co,12642
5
+ maps4fs/generator/component.py,sha256=tLjjbrFn3v4CrUrUOgH9s8NuJhmQcBs74ShefkNg1VE,10584
6
+ maps4fs/generator/config.py,sha256=JL7leQv8C06JQOXIbgQ-jve2re7cNsx8vKa8dfbnxPM,3896
7
+ maps4fs/generator/dem.py,sha256=p7THncPUdYWtNR2HrTXe0bCuJ8psUV8rRpYPJkzL2gA,15220
8
+ maps4fs/generator/game.py,sha256=94HjPNOyy6XSSOnBp-uxrkBglKyC-X3ULIrrucfdlKw,6825
9
+ maps4fs/generator/i3d.py,sha256=UuQiQRusPQ2f6PvGKxJ_UZeXsx3uG15efmy8Ysnm3F8,3597
10
+ maps4fs/generator/map.py,sha256=v8OOLmhAkgqq64tQgEDbV6DmbgOVm3NXJBDDy0nJf60,4226
11
+ maps4fs/generator/path_steps.py,sha256=yeN6hmOD8O2LMozUf4HcQMIy8WJ5sHF5pGQT_s2FfOw,3147
12
+ maps4fs/generator/qgis.py,sha256=k19miPEFyOdu_ogBFHmFVfYQ-IgF38ufJZ5DM5NSHBQ,3400
13
+ maps4fs/generator/texture.py,sha256=CwaXZfAG4e3D3YR7yVR2MC677EHpbUWTboCS3G6jkhk,17723
14
+ maps4fs/generator/tile.py,sha256=3vmfjQiPiiUZKPuIo5tg2rOKkgcP1NRVkMGK-Vo10-A,2138
15
+ maps4fs-0.9.3.dist-info/LICENSE.md,sha256=-JY0v7p3dwXze61EbYiK7YEJ2aKrjaFZ8y2xYEOrmRY,1068
16
+ maps4fs-0.9.3.dist-info/METADATA,sha256=0iPJEUJXeSqgNXqhcD2BM5lqkvX7YBK2O-UFWY5HxvM,23996
17
+ maps4fs-0.9.3.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
18
+ maps4fs-0.9.3.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
19
+ maps4fs-0.9.3.dist-info/RECORD,,
@@ -1,18 +0,0 @@
1
- maps4fs/__init__.py,sha256=da4jmND2Ths9AffnkAKgzLHNkvKFOc_l21gJisPXqWY,155
2
- maps4fs/logger.py,sha256=CneeHxQywjNUJXqQrUUSeiDxu95FfrfyK_Si1v0gMZ8,1477
3
- maps4fs/generator/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
4
- maps4fs/generator/background.py,sha256=ThYwjk7XJuqMDf9ITh22clvE5wVq_b1lSRH4Ha8REC8,10864
5
- maps4fs/generator/component.py,sha256=g3IBC8ul9zmcG9tHvyPJIg8hxBHFR8kB8Smw9yMW3qA,7864
6
- maps4fs/generator/config.py,sha256=JkRexT_ZclRa_x0w6ojgG-Tsu4NoshFTUGycRFdfrVk,3463
7
- maps4fs/generator/dem.py,sha256=p7THncPUdYWtNR2HrTXe0bCuJ8psUV8rRpYPJkzL2gA,15220
8
- maps4fs/generator/game.py,sha256=94HjPNOyy6XSSOnBp-uxrkBglKyC-X3ULIrrucfdlKw,6825
9
- maps4fs/generator/i3d.py,sha256=UuQiQRusPQ2f6PvGKxJ_UZeXsx3uG15efmy8Ysnm3F8,3597
10
- maps4fs/generator/map.py,sha256=BS8ylTCRlHRmbImg73wnHMpBB2ODckVyVZq5KJhCMfM,4236
11
- maps4fs/generator/path_steps.py,sha256=rKslIiG9mriCVL9_0i8Oet2p0DITrpBWi0pECpvuyUM,2707
12
- maps4fs/generator/texture.py,sha256=CwaXZfAG4e3D3YR7yVR2MC677EHpbUWTboCS3G6jkhk,17723
13
- maps4fs/generator/tile.py,sha256=3vmfjQiPiiUZKPuIo5tg2rOKkgcP1NRVkMGK-Vo10-A,2138
14
- maps4fs-0.9.1.dist-info/LICENSE.md,sha256=-JY0v7p3dwXze61EbYiK7YEJ2aKrjaFZ8y2xYEOrmRY,1068
15
- maps4fs-0.9.1.dist-info/METADATA,sha256=XTu4AwG_x6F8n_7ooxJMRCBXv7RwuR2Jf5Po8i1zRGs,23393
16
- maps4fs-0.9.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
17
- maps4fs-0.9.1.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
18
- maps4fs-0.9.1.dist-info/RECORD,,