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.
- maps4fs/generator/background.py +56 -10
- maps4fs/generator/component.py +73 -4
- maps4fs/generator/config.py +11 -0
- maps4fs/generator/map.py +1 -1
- maps4fs/generator/path_steps.py +13 -2
- maps4fs/generator/qgis.py +114 -0
- maps4fs/logger.py +27 -3
- {maps4fs-0.9.1.dist-info → maps4fs-0.9.3.dist-info}/METADATA +12 -6
- maps4fs-0.9.3.dist-info/RECORD +19 -0
- maps4fs-0.9.1.dist-info/RECORD +0 -18
- {maps4fs-0.9.1.dist-info → maps4fs-0.9.3.dist-info}/LICENSE.md +0 -0
- {maps4fs-0.9.1.dist-info → maps4fs-0.9.3.dist-info}/WHEEL +0 -0
- {maps4fs-0.9.1.dist-info → maps4fs-0.9.3.dist-info}/top_level.txt +0 -0
maps4fs/generator/background.py
CHANGED
@@ -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
|
-
|
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
|
-
|
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=
|
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,
|
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
|
-
|
156
|
-
faces.append([top_left, bottom_right,
|
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) //
|
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)
|
maps4fs/generator/component.py
CHANGED
@@ -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
|
-
|
191
|
-
|
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
|
-
|
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
|
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)
|
maps4fs/generator/config.py
CHANGED
@@ -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(
|
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
|
|
maps4fs/generator/path_steps.py
CHANGED
@@ -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
|
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__(
|
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.
|
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
|
63
|
-
📄 Generates
|
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
|
+

|
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,,
|
maps4fs-0.9.1.dist-info/RECORD
DELETED
@@ -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,,
|
File without changes
|
File without changes
|
File without changes
|