maps4fs 0.9.2__py3-none-any.whl → 0.9.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.
- maps4fs/generator/background.py +56 -12
- maps4fs/generator/component.py +20 -22
- maps4fs/generator/config.py +7 -1
- maps4fs/generator/map.py +1 -1
- maps4fs/generator/path_steps.py +13 -2
- maps4fs/generator/qgis.py +95 -13
- maps4fs/logger.py +27 -3
- {maps4fs-0.9.2.dist-info → maps4fs-0.9.4.dist-info}/METADATA +11 -2
- maps4fs-0.9.4.dist-info/RECORD +19 -0
- maps4fs-0.9.2.dist-info/RECORD +0 -19
- {maps4fs-0.9.2.dist-info → maps4fs-0.9.4.dist-info}/LICENSE.md +0 -0
- {maps4fs-0.9.2.dist-info → maps4fs-0.9.4.dist-info}/WHEEL +0 -0
- {maps4fs-0.9.2.dist-info → maps4fs-0.9.4.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(
|
@@ -86,11 +95,13 @@ class Background(Component):
|
|
86
95
|
for tile in self.tiles:
|
87
96
|
north, south, east, west = tile.bbox
|
88
97
|
epsg3857_string = tile.get_epsg3857_string()
|
98
|
+
epsg3857_string_with_margin = tile.get_epsg3857_string(add_margin=True)
|
89
99
|
|
90
100
|
tile_entry = {
|
91
101
|
"center_latitude": tile.coordinates[0],
|
92
102
|
"center_longitude": tile.coordinates[1],
|
93
103
|
"epsg3857_string": epsg3857_string,
|
104
|
+
"epsg3857_string_with_margin": epsg3857_string_with_margin,
|
94
105
|
"height": tile.map_height,
|
95
106
|
"width": tile.map_width,
|
96
107
|
"north": north,
|
@@ -106,10 +117,16 @@ class Background(Component):
|
|
106
117
|
def qgis_sequence(self) -> None:
|
107
118
|
"""Generates QGIS scripts for creating bounding box layers and rasterizing them."""
|
108
119
|
qgis_layers = [
|
109
|
-
(f"
|
120
|
+
(f"Background_{tile.code}", *tile.get_espg3857_bbox()) for tile in self.tiles
|
110
121
|
]
|
122
|
+
qgis_layers_with_margin = [
|
123
|
+
(f"Background_{tile.code}_margin", *tile.get_espg3857_bbox(add_margin=True))
|
124
|
+
for tile in self.tiles
|
125
|
+
]
|
126
|
+
|
127
|
+
layers = qgis_layers + qgis_layers_with_margin
|
111
128
|
|
112
|
-
self.create_qgis_scripts(
|
129
|
+
self.create_qgis_scripts(layers)
|
113
130
|
|
114
131
|
def generate_obj_files(self) -> None:
|
115
132
|
"""Iterates over all tiles and generates 3D obj files based on DEM data.
|
@@ -129,29 +146,49 @@ class Background(Component):
|
|
129
146
|
self.logger.debug("Generating obj file for tile %s in path: %s", tile.code, save_path)
|
130
147
|
|
131
148
|
dem_data = cv2.imread(tile.dem_path, cv2.IMREAD_UNCHANGED) # pylint: disable=no-member
|
132
|
-
self.plane_from_np(dem_data, save_path)
|
149
|
+
self.plane_from_np(tile.code, dem_data, save_path)
|
133
150
|
|
134
151
|
# pylint: disable=too-many-locals
|
135
|
-
|
152
|
+
@timeit
|
153
|
+
def plane_from_np(self, tile_code: str, dem_data: np.ndarray, save_path: str) -> None:
|
136
154
|
"""Generates a 3D obj file based on DEM data.
|
137
155
|
|
138
156
|
Arguments:
|
157
|
+
tile_code (str) -- The code of the tile.
|
139
158
|
dem_data (np.ndarray) -- The DEM data as a numpy array.
|
140
159
|
save_path (str) -- The path where the obj file will be saved.
|
141
160
|
"""
|
161
|
+
if tile_code == PATH_FULL_NAME:
|
162
|
+
resize_factor = FULL_RESIZE_FACTOR
|
163
|
+
simplify_factor = FULL_SIMPLIFY_FACTOR
|
164
|
+
self.logger.info("Generating a full map obj file")
|
165
|
+
else:
|
166
|
+
resize_factor = RESIZE_FACTOR
|
167
|
+
simplify_factor = SIMPLIFY_FACTOR
|
142
168
|
dem_data = cv2.resize( # pylint: disable=no-member
|
143
|
-
dem_data, (0, 0), fx=
|
169
|
+
dem_data, (0, 0), fx=resize_factor, fy=resize_factor
|
144
170
|
)
|
145
171
|
self.logger.debug(
|
146
|
-
"DEM data resized to shape: %s with factor: %s", dem_data.shape,
|
172
|
+
"DEM data resized to shape: %s with factor: %s", dem_data.shape, resize_factor
|
147
173
|
)
|
148
174
|
|
175
|
+
# Invert the height values.
|
176
|
+
dem_data = dem_data.max() - dem_data
|
177
|
+
|
149
178
|
rows, cols = dem_data.shape
|
150
179
|
x = np.linspace(0, cols - 1, cols)
|
151
180
|
y = np.linspace(0, rows - 1, rows)
|
152
181
|
x, y = np.meshgrid(x, y)
|
153
182
|
z = dem_data
|
154
183
|
|
184
|
+
self.logger.info(
|
185
|
+
"Starting to generate a mesh for tile %s with shape: %s x %s. "
|
186
|
+
"This may take a while...",
|
187
|
+
tile_code,
|
188
|
+
cols,
|
189
|
+
rows,
|
190
|
+
)
|
191
|
+
|
155
192
|
vertices = np.column_stack([x.ravel(), y.ravel(), z.ravel()])
|
156
193
|
faces = []
|
157
194
|
|
@@ -162,15 +199,22 @@ class Background(Component):
|
|
162
199
|
bottom_left = top_left + cols
|
163
200
|
bottom_right = bottom_left + 1
|
164
201
|
|
165
|
-
|
166
|
-
faces.append([top_left, bottom_right,
|
167
|
-
faces.append([top_left, top_right, bottom_right])
|
202
|
+
faces.append([top_left, bottom_left, bottom_right])
|
203
|
+
faces.append([top_left, bottom_right, top_right])
|
168
204
|
|
169
205
|
faces = np.array(faces) # type: ignore
|
170
206
|
mesh = trimesh.Trimesh(vertices=vertices, faces=faces)
|
171
207
|
|
208
|
+
# Apply rotation: 180 degrees around Y-axis and Z-axis
|
209
|
+
rotation_matrix_y = trimesh.transformations.rotation_matrix(np.pi, [0, 1, 0])
|
210
|
+
rotation_matrix_z = trimesh.transformations.rotation_matrix(np.pi, [0, 0, 1])
|
211
|
+
mesh.apply_transform(rotation_matrix_y)
|
212
|
+
mesh.apply_transform(rotation_matrix_z)
|
213
|
+
|
214
|
+
self.logger.info("Mesh generated with %s faces, will be simplified", len(mesh.faces))
|
215
|
+
|
172
216
|
# Simplify the mesh to reduce the number of faces.
|
173
|
-
mesh = mesh.simplify_quadric_decimation(face_count=len(faces) //
|
217
|
+
mesh = mesh.simplify_quadric_decimation(face_count=len(faces) // simplify_factor)
|
174
218
|
self.logger.debug("Mesh simplified to %s faces", len(mesh.faces))
|
175
219
|
|
176
220
|
mesh.export(save_path)
|
maps4fs/generator/component.py
CHANGED
@@ -10,7 +10,7 @@ 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
|
13
|
+
from maps4fs.generator.qgis import save_scripts
|
14
14
|
|
15
15
|
if TYPE_CHECKING:
|
16
16
|
from maps4fs.generator.game import Game
|
@@ -214,16 +214,17 @@ class Component:
|
|
214
214
|
return west, south, east, north
|
215
215
|
|
216
216
|
def get_espg3857_bbox(
|
217
|
-
self, bbox: tuple[
|
218
|
-
) -> tuple[
|
217
|
+
self, bbox: tuple[float, float, float, float] | None = None, add_margin: bool = False
|
218
|
+
) -> tuple[float, float, float, float]:
|
219
219
|
"""Converts the bounding box to EPSG:3857.
|
220
220
|
If the bounding box is not provided, the instance variable is used.
|
221
221
|
|
222
222
|
Args:
|
223
|
-
bbox (tuple[
|
223
|
+
bbox (tuple[float, float, float, float], optional): The bounding box to convert.
|
224
|
+
add_margin (bool, optional): Whether to add a margin to the bounding box.
|
224
225
|
|
225
226
|
Returns:
|
226
|
-
tuple[
|
227
|
+
tuple[float, float, float, float]: The bounding box in EPSG:3857.
|
227
228
|
"""
|
228
229
|
bbox = bbox or self.bbox
|
229
230
|
north, south, east, west = bbox
|
@@ -231,19 +232,29 @@ class Component:
|
|
231
232
|
epsg3857_north, epsg3857_west = transformer.transform(north, west)
|
232
233
|
epsg3857_south, epsg3857_east = transformer.transform(south, east)
|
233
234
|
|
235
|
+
if add_margin:
|
236
|
+
MARGIN = 500 # pylint: disable=C0103
|
237
|
+
epsg3857_north = int(epsg3857_north - MARGIN)
|
238
|
+
epsg3857_south = int(epsg3857_south + MARGIN)
|
239
|
+
epsg3857_east = int(epsg3857_east - MARGIN)
|
240
|
+
epsg3857_west = int(epsg3857_west + MARGIN)
|
241
|
+
|
234
242
|
return epsg3857_north, epsg3857_south, epsg3857_east, epsg3857_west
|
235
243
|
|
236
|
-
def get_epsg3857_string(
|
244
|
+
def get_epsg3857_string(
|
245
|
+
self, bbox: tuple[float, float, float, float] | None = None, add_margin: bool = False
|
246
|
+
) -> str:
|
237
247
|
"""Converts the bounding box to EPSG:3857 string.
|
238
248
|
If the bounding box is not provided, the instance variable is used.
|
239
249
|
|
240
250
|
Args:
|
241
|
-
bbox (tuple[
|
251
|
+
bbox (tuple[float, float, float, float], optional): The bounding box to convert.
|
252
|
+
add_margin (bool, optional): Whether to add a margin to the bounding box.
|
242
253
|
|
243
254
|
Returns:
|
244
255
|
str: The bounding box in EPSG:3857 string.
|
245
256
|
"""
|
246
|
-
north, south, east, west = self.get_espg3857_bbox(bbox)
|
257
|
+
north, south, east, west = self.get_espg3857_bbox(bbox, add_margin=add_margin)
|
247
258
|
return f"{north},{south},{east},{west} [EPSG:3857]"
|
248
259
|
|
249
260
|
def create_qgis_scripts(
|
@@ -259,17 +270,4 @@ class Component:
|
|
259
270
|
create scripts for.
|
260
271
|
"""
|
261
272
|
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)
|
273
|
+
save_scripts(qgis_layers, class_name, self.scripts_directory)
|
maps4fs/generator/config.py
CHANGED
@@ -70,11 +70,13 @@ class Config(Component):
|
|
70
70
|
bbox = self.get_bbox(height_distance=self.map_height, width_distance=self.map_width)
|
71
71
|
south, west, north, east = bbox
|
72
72
|
epsg3857_string = self.get_epsg3857_string(bbox=bbox)
|
73
|
+
epsg3857_string_with_margin = self.get_epsg3857_string(bbox=bbox, add_margin=True)
|
73
74
|
|
74
75
|
self.qgis_sequence()
|
75
76
|
|
76
77
|
overview_data = {
|
77
78
|
"epsg3857_string": epsg3857_string,
|
79
|
+
"epsg3857_string_with_margin": epsg3857_string_with_margin,
|
78
80
|
"south": south,
|
79
81
|
"west": west,
|
80
82
|
"north": north,
|
@@ -93,7 +95,11 @@ class Config(Component):
|
|
93
95
|
"""Generates QGIS scripts for creating bounding box layers and rasterizing them."""
|
94
96
|
bbox = self.get_bbox(height_distance=self.map_height, width_distance=self.map_width)
|
95
97
|
espg3857_bbox = self.get_espg3857_bbox(bbox=bbox)
|
98
|
+
espg3857_bbox_with_margin = self.get_espg3857_bbox(bbox=bbox, add_margin=True)
|
96
99
|
|
97
100
|
qgis_layers = [("Overview_bbox", *espg3857_bbox)]
|
101
|
+
qgis_layers_with_margin = [("Overview_bbox_with_margin", *espg3857_bbox_with_margin)]
|
98
102
|
|
99
|
-
|
103
|
+
layers = qgis_layers + qgis_layers_with_margin
|
104
|
+
|
105
|
+
self.create_qgis_scripts(layers)
|
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
|
]
|
maps4fs/generator/qgis.py
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
"""This module contains templates for generating QGIS scripts."""
|
2
2
|
|
3
|
+
import os
|
4
|
+
|
3
5
|
BBOX_TEMPLATE = """
|
4
6
|
layers = [
|
5
7
|
{layers}
|
6
8
|
]
|
7
9
|
for layer in layers:
|
8
|
-
name = layer[0]
|
10
|
+
name = "Bounding_Box_" + layer[0]
|
9
11
|
north, south, east, west = layer[1:]
|
10
12
|
|
11
13
|
# Create a rectangle geometry from the bounding box.
|
@@ -34,6 +36,42 @@ for layer in layers:
|
|
34
36
|
layer.triggerRepaint()
|
35
37
|
"""
|
36
38
|
|
39
|
+
POINT_TEMPLATE = """
|
40
|
+
layers = [
|
41
|
+
{layers}
|
42
|
+
]
|
43
|
+
for layer in layers:
|
44
|
+
name = "Points_" + layer[0]
|
45
|
+
north, south, east, west = layer[1:]
|
46
|
+
|
47
|
+
top_left = QgsPointXY(north, west)
|
48
|
+
top_right = QgsPointXY(north, east)
|
49
|
+
bottom_right = QgsPointXY(south, east)
|
50
|
+
bottom_left = QgsPointXY(south, west)
|
51
|
+
|
52
|
+
points = [top_left, top_right, bottom_right, bottom_left, top_left]
|
53
|
+
|
54
|
+
# Create a new layer
|
55
|
+
layer = QgsVectorLayer('Point?crs=EPSG:4326', name, 'memory')
|
56
|
+
provider = layer.dataProvider()
|
57
|
+
|
58
|
+
# Add fields
|
59
|
+
provider.addAttributes([QgsField("id", QVariant.Int)])
|
60
|
+
layer.updateFields()
|
61
|
+
|
62
|
+
# Create and add features for each point
|
63
|
+
for i, point in enumerate(points):
|
64
|
+
feature = QgsFeature()
|
65
|
+
feature.setGeometry(QgsGeometry.fromPointXY(point))
|
66
|
+
feature.setAttributes([i + 1])
|
67
|
+
provider.addFeature(feature)
|
68
|
+
|
69
|
+
layer.updateExtents()
|
70
|
+
|
71
|
+
# Add the layer to the project
|
72
|
+
QgsProject.instance().addMapLayer(layer)
|
73
|
+
"""
|
74
|
+
|
37
75
|
RASTERIZE_TEMPLATE = """
|
38
76
|
import processing
|
39
77
|
|
@@ -74,17 +112,18 @@ for layer in layers:
|
|
74
112
|
"""
|
75
113
|
|
76
114
|
|
77
|
-
def
|
78
|
-
"""Returns a template for creating
|
115
|
+
def _get_template(layers: list[tuple[str, float, float, float, float]], template: str) -> str:
|
116
|
+
"""Returns a template for creating layers in QGIS.
|
79
117
|
|
80
118
|
Args:
|
81
119
|
layers (list[tuple[str, float, float, float, float]]): A list of tuples containing the
|
82
120
|
layer name and the bounding box coordinates.
|
121
|
+
template (str): The template for creating layers in QGIS.
|
83
122
|
|
84
123
|
Returns:
|
85
|
-
str: The template for creating
|
124
|
+
str: The template for creating layers in QGIS.
|
86
125
|
"""
|
87
|
-
return
|
126
|
+
return template.format(
|
88
127
|
layers=",\n ".join(
|
89
128
|
[
|
90
129
|
f'("{name}", {north}, {south}, {east}, {west})'
|
@@ -94,6 +133,32 @@ def get_bbox_template(layers: list[tuple[str, float, float, float, float]]) -> s
|
|
94
133
|
)
|
95
134
|
|
96
135
|
|
136
|
+
def get_bbox_template(layers: list[tuple[str, float, float, float, float]]) -> str:
|
137
|
+
"""Returns a template for creating bounding box layers in QGIS.
|
138
|
+
|
139
|
+
Args:
|
140
|
+
layers (list[tuple[str, float, float, float, float]]): A list of tuples containing the
|
141
|
+
layer name and the bounding box coordinates.
|
142
|
+
|
143
|
+
Returns:
|
144
|
+
str: The template for creating bounding box layers in QGIS.
|
145
|
+
"""
|
146
|
+
return _get_template(layers, BBOX_TEMPLATE)
|
147
|
+
|
148
|
+
|
149
|
+
def get_point_template(layers: list[tuple[str, float, float, float, float]]) -> str:
|
150
|
+
"""Returns a template for creating point layers in QGIS.
|
151
|
+
|
152
|
+
Args:
|
153
|
+
layers (list[tuple[str, float, float, float, float]]): A list of tuples containing the
|
154
|
+
layer name and the bounding box coordinates.
|
155
|
+
|
156
|
+
Returns:
|
157
|
+
str: The template for creating point layers in QGIS.
|
158
|
+
"""
|
159
|
+
return _get_template(layers, POINT_TEMPLATE)
|
160
|
+
|
161
|
+
|
97
162
|
def get_rasterize_template(layers: list[tuple[str, float, float, float, float]]) -> str:
|
98
163
|
"""Returns a template for rasterizing bounding box layers in QGIS.
|
99
164
|
|
@@ -104,11 +169,28 @@ def get_rasterize_template(layers: list[tuple[str, float, float, float, float]])
|
|
104
169
|
Returns:
|
105
170
|
str: The template for rasterizing bounding box layers in QGIS.
|
106
171
|
"""
|
107
|
-
return RASTERIZE_TEMPLATE
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
172
|
+
return _get_template(layers, RASTERIZE_TEMPLATE)
|
173
|
+
|
174
|
+
|
175
|
+
def save_scripts(
|
176
|
+
layers: list[tuple[str, float, float, float, float]], file_prefix: str, save_directory: str
|
177
|
+
) -> None:
|
178
|
+
"""Saves QGIS scripts for creating bounding box, point, and raster layers.
|
179
|
+
|
180
|
+
Args:
|
181
|
+
layers (list[tuple[str, float, float, float, float]]): A list of tuples containing the
|
182
|
+
layer name and the bounding box coordinates.
|
183
|
+
save_dir (str): The directory to save the scripts.
|
184
|
+
"""
|
185
|
+
script_files = [
|
186
|
+
(f"{file_prefix}_bbox.py", get_bbox_template),
|
187
|
+
(f"{file_prefix}_rasterize.py", get_rasterize_template),
|
188
|
+
(f"{file_prefix}_point.py", get_point_template),
|
189
|
+
]
|
190
|
+
|
191
|
+
for script_file, process_function in script_files:
|
192
|
+
script_path = os.path.join(save_directory, script_file)
|
193
|
+
script_content = process_function(layers) # type: ignore
|
194
|
+
|
195
|
+
with open(script_path, "w", encoding="utf-8") as file:
|
196
|
+
file.write(script_content)
|
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.4
|
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
|
@@ -23,7 +23,9 @@ Requires-Dist: imageio
|
|
23
23
|
Requires-Dist: tifffile
|
24
24
|
|
25
25
|
<div align="center" markdown>
|
26
|
-
<
|
26
|
+
<a href="https://discord.gg/Sj5QKKyE42">
|
27
|
+
<img src="https://github.com/user-attachments/assets/37043333-d6ef-4ca3-9f3c-81323d9d0b71">
|
28
|
+
</a>
|
27
29
|
|
28
30
|
<p align="center">
|
29
31
|
<a href="#Quick-Start">Quick Start</a> •
|
@@ -41,6 +43,7 @@ Requires-Dist: tifffile
|
|
41
43
|
</p>
|
42
44
|
|
43
45
|
|
46
|
+
[](https://discord.gg/Sj5QKKyE42)
|
44
47
|
[](https://github.com/iwatkot/maps4fs/releases)
|
45
48
|
[](https://pypi.org/project/maps4fs)
|
46
49
|
[](https://hub.docker.com/repository/docker/iwatkot/maps4fs/general)
|
@@ -360,6 +363,12 @@ Let's have a closer look at the fields:
|
|
360
363
|
## Background terrain
|
361
364
|
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
365
|
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>
|
366
|
+
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>
|
367
|
+
|
368
|
+

|
369
|
+
|
370
|
+
➡️ *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.*
|
371
|
+
|
363
372
|
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
373
|
|
365
374
|
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:
|
@@ -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=6mN2xRg5XEqsLaXPUqdNGgDR1Ats60fBK93WlDeB8EI,13002
|
5
|
+
maps4fs/generator/component.py,sha256=ZEDjChPnvqAsgnBu2f2YBOlwGOlfax4VaAYBcJerLIQ,10684
|
6
|
+
maps4fs/generator/config.py,sha256=ZO5BWDU-S3p0-ndKDSFa8Oin3YcYy0iH8B4lqEA_07Q,4275
|
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=vhV-EwZKK-p1qEV-2H_yckuktx8ezlCMnmJJfUt1HGI,6091
|
13
|
+
maps4fs/generator/texture.py,sha256=CwaXZfAG4e3D3YR7yVR2MC677EHpbUWTboCS3G6jkhk,17723
|
14
|
+
maps4fs/generator/tile.py,sha256=3vmfjQiPiiUZKPuIo5tg2rOKkgcP1NRVkMGK-Vo10-A,2138
|
15
|
+
maps4fs-0.9.4.dist-info/LICENSE.md,sha256=-JY0v7p3dwXze61EbYiK7YEJ2aKrjaFZ8y2xYEOrmRY,1068
|
16
|
+
maps4fs-0.9.4.dist-info/METADATA,sha256=WVUjB4PmrCnuWZNIVurx3_1XPqS2oBEwWdVaU22uX2Y,24130
|
17
|
+
maps4fs-0.9.4.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
18
|
+
maps4fs-0.9.4.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
|
19
|
+
maps4fs-0.9.4.dist-info/RECORD,,
|
maps4fs-0.9.2.dist-info/RECORD
DELETED
@@ -1,19 +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=2BUgn12t-FgijPTY7hvwTaYQrVzYdnZqhoz76KSBcKs,11216
|
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=BS8ylTCRlHRmbImg73wnHMpBB2ODckVyVZq5KJhCMfM,4236
|
11
|
-
maps4fs/generator/path_steps.py,sha256=rKslIiG9mriCVL9_0i8Oet2p0DITrpBWi0pECpvuyUM,2707
|
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.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,,
|
File without changes
|
File without changes
|
File without changes
|