maps4fs 1.7.1__py3-none-any.whl → 1.7.6__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.
Potentially problematic release.
This version of maps4fs might be problematic. Click here for more details.
- maps4fs/__init__.py +5 -2
- maps4fs/generator/dtm/bavaria.py +115 -0
- maps4fs/generator/dtm/dtm.py +335 -47
- maps4fs/generator/dtm/nrw.py +127 -0
- maps4fs/generator/dtm/srtm.py +59 -161
- maps4fs/generator/dtm/usgs.py +6 -268
- maps4fs/generator/texture.py +9 -2
- maps4fs/toolbox/custom_osm.py +67 -0
- {maps4fs-1.7.1.dist-info → maps4fs-1.7.6.dist-info}/METADATA +51 -21
- {maps4fs-1.7.1.dist-info → maps4fs-1.7.6.dist-info}/RECORD +13 -10
- {maps4fs-1.7.1.dist-info → maps4fs-1.7.6.dist-info}/LICENSE.md +0 -0
- {maps4fs-1.7.1.dist-info → maps4fs-1.7.6.dist-info}/WHEEL +0 -0
- {maps4fs-1.7.1.dist-info → maps4fs-1.7.6.dist-info}/top_level.txt +0 -0
maps4fs/generator/dtm/usgs.py
CHANGED
@@ -2,15 +2,9 @@
|
|
2
2
|
|
3
3
|
import os
|
4
4
|
from datetime import datetime
|
5
|
-
from zipfile import ZipFile
|
6
5
|
|
7
6
|
import numpy as np
|
8
|
-
import rasterio
|
9
7
|
import requests
|
10
|
-
from rasterio.enums import Resampling
|
11
|
-
from rasterio.merge import merge
|
12
|
-
from rasterio.warp import calculate_default_transform, reproject
|
13
|
-
from rasterio.windows import from_bounds
|
14
8
|
|
15
9
|
from maps4fs.generator.dtm.dtm import DTMProvider, DTMProviderSettings
|
16
10
|
|
@@ -18,7 +12,6 @@ from maps4fs.generator.dtm.dtm import DTMProvider, DTMProviderSettings
|
|
18
12
|
class USGSProviderSettings(DTMProviderSettings):
|
19
13
|
"""Settings for the USGS provider."""
|
20
14
|
|
21
|
-
max_local_elevation: int = 255
|
22
15
|
dataset: tuple | str = (
|
23
16
|
'Digital Elevation Model (DEM) 1 meter',
|
24
17
|
'Alaska IFSAR 5 meter DEM',
|
@@ -43,17 +36,18 @@ class USGSProvider(DTMProvider):
|
|
43
36
|
_author = "[ZenJakey](https://github.com/ZenJakey)"
|
44
37
|
_contributors = "[kbrandwijk](https://github.com/kbrandwijk)"
|
45
38
|
_is_community = True
|
46
|
-
_instructions =
|
47
|
-
"ℹ️ Set the max local elevation to approx the local max elevation for your area in"
|
48
|
-
" meters. This will allow you to use heightScale 255 in GE with minimal tweaking."
|
49
|
-
" Setting this value too low can cause a flat map!"
|
50
|
-
)
|
39
|
+
_instructions = None
|
51
40
|
|
52
41
|
_url = (
|
53
42
|
"https://tnmaccess.nationalmap.gov/api/v1/products?prodFormats=GeoTIFF,IMG"
|
54
43
|
|
55
44
|
)
|
56
45
|
|
46
|
+
def download_tiles(self):
|
47
|
+
download_urls = self.get_download_urls()
|
48
|
+
all_tif_files = self.download_tif_files(download_urls, self.shared_tiff_path)
|
49
|
+
return all_tif_files
|
50
|
+
|
57
51
|
def __init__(self, *args, **kwargs):
|
58
52
|
super().__init__(*args, **kwargs)
|
59
53
|
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
@@ -93,259 +87,3 @@ class USGSProvider(DTMProvider):
|
|
93
87
|
self.logger.error("Failed to get data. Error: %s", e)
|
94
88
|
self.logger.debug("Received %s urls", len(urls))
|
95
89
|
return urls
|
96
|
-
|
97
|
-
def download_tif_files(self, urls: list[str]) -> list[str]:
|
98
|
-
"""Download GeoTIFF files from the given URLs.
|
99
|
-
|
100
|
-
Arguments:
|
101
|
-
urls (list): List of URLs to download GeoTIFF files from.
|
102
|
-
|
103
|
-
Returns:
|
104
|
-
list: List of paths to the downloaded GeoTIFF files.
|
105
|
-
"""
|
106
|
-
tif_files = []
|
107
|
-
for url in urls:
|
108
|
-
file_name = os.path.basename(url)
|
109
|
-
self.logger.debug("Retrieving TIFF: %s", file_name)
|
110
|
-
file_path = os.path.join(self.shared_tiff_path, file_name)
|
111
|
-
if not os.path.exists(file_path):
|
112
|
-
try:
|
113
|
-
# Send a GET request to the file URL
|
114
|
-
response = requests.get(url, stream=True) # pylint: disable=W3101
|
115
|
-
response.raise_for_status() # Raise an error for HTTP status codes 4xx/5xx
|
116
|
-
|
117
|
-
# Write the content of the response to the file
|
118
|
-
with open(file_path, "wb") as file:
|
119
|
-
for chunk in response.iter_content(chunk_size=8192): # Download in chunks
|
120
|
-
file.write(chunk)
|
121
|
-
self.logger.info("File downloaded successfully: %s", file_path)
|
122
|
-
if file_name.endswith('.zip'):
|
123
|
-
with ZipFile(file_path, "r") as f_in:
|
124
|
-
f_in.extract(file_name.replace('.zip', '.img'), self.shared_tiff_path)
|
125
|
-
tif_files.append(file_path.replace('.zip', '.img'))
|
126
|
-
else:
|
127
|
-
tif_files.append(file_path)
|
128
|
-
except requests.exceptions.RequestException as e:
|
129
|
-
self.logger.error("Failed to download file: %s", e)
|
130
|
-
else:
|
131
|
-
self.logger.debug("File already exists: %s", file_name)
|
132
|
-
if file_name.endswith('.zip'):
|
133
|
-
if not os.path.exists(file_path.replace('.zip', '.img')):
|
134
|
-
with ZipFile(file_path, "r") as f_in:
|
135
|
-
f_in.extract(file_name.replace('.zip', '.img'), self.shared_tiff_path)
|
136
|
-
tif_files.append(file_path.replace('.zip', '.img'))
|
137
|
-
else:
|
138
|
-
tif_files.append(file_path)
|
139
|
-
|
140
|
-
return tif_files
|
141
|
-
|
142
|
-
def merge_geotiff(self, input_files: list[str], output_file: str) -> None:
|
143
|
-
"""Merge multiple GeoTIFF files into a single GeoTIFF file.
|
144
|
-
|
145
|
-
Arguments:
|
146
|
-
input_files (list): List of input GeoTIFF files to merge.
|
147
|
-
output_file (str): Path to save the merged GeoTIFF file.
|
148
|
-
"""
|
149
|
-
# Open all input GeoTIFF files as datasets
|
150
|
-
self.logger.debug("Merging tiff files...")
|
151
|
-
datasets = [rasterio.open(file) for file in input_files]
|
152
|
-
|
153
|
-
# Merge datasets
|
154
|
-
mosaic, out_transform = merge(datasets, nodata=0)
|
155
|
-
|
156
|
-
# Get metadata from the first file and update it for the output
|
157
|
-
out_meta = datasets[0].meta.copy()
|
158
|
-
out_meta.update(
|
159
|
-
{
|
160
|
-
"driver": "GTiff",
|
161
|
-
"height": mosaic.shape[1],
|
162
|
-
"width": mosaic.shape[2],
|
163
|
-
"transform": out_transform,
|
164
|
-
"count": mosaic.shape[0], # Number of bands
|
165
|
-
}
|
166
|
-
)
|
167
|
-
|
168
|
-
# Write merged GeoTIFF to the output file
|
169
|
-
with rasterio.open(output_file, "w", **out_meta) as dest:
|
170
|
-
dest.write(mosaic)
|
171
|
-
|
172
|
-
self.logger.debug("GeoTIFF images merged successfully into %s", output_file)
|
173
|
-
|
174
|
-
def reproject_geotiff(self, input_tiff: str, output_tiff: str, target_crs: str) -> None:
|
175
|
-
"""Reproject a GeoTIFF file to a new coordinate reference system (CRS).
|
176
|
-
|
177
|
-
Arguments:
|
178
|
-
input_tiff (str): Path to the input GeoTIFF file.
|
179
|
-
output_tiff (str): Path to save the reprojected GeoTIFF file.
|
180
|
-
target_crs (str): Target CRS (e.g., EPSG:4326 for CRS:84).
|
181
|
-
"""
|
182
|
-
# Open the source GeoTIFF
|
183
|
-
self.logger.debug("Reprojecting GeoTIFF to %s CRS...", target_crs)
|
184
|
-
with rasterio.open(input_tiff) as src:
|
185
|
-
# Get the transform, width, and height of the target CRS
|
186
|
-
transform, width, height = calculate_default_transform(
|
187
|
-
src.crs, target_crs, src.width, src.height, *src.bounds
|
188
|
-
)
|
189
|
-
|
190
|
-
# Update the metadata for the target GeoTIFF
|
191
|
-
kwargs = src.meta.copy()
|
192
|
-
kwargs.update(
|
193
|
-
{"crs": target_crs, "transform": transform, "width": width, "height": height}
|
194
|
-
)
|
195
|
-
|
196
|
-
# Open the destination GeoTIFF file and reproject
|
197
|
-
with rasterio.open(output_tiff, "w", **kwargs) as dst:
|
198
|
-
for i in range(1, src.count + 1): # Iterate over all raster bands
|
199
|
-
reproject(
|
200
|
-
source=rasterio.band(src, i),
|
201
|
-
destination=rasterio.band(dst, i),
|
202
|
-
src_transform=src.transform,
|
203
|
-
src_crs=src.crs,
|
204
|
-
dst_transform=transform,
|
205
|
-
dst_crs=target_crs,
|
206
|
-
resampling=Resampling.nearest, # Choose resampling method
|
207
|
-
)
|
208
|
-
self.logger.debug("Reprojected GeoTIFF saved to %s", output_tiff)
|
209
|
-
|
210
|
-
def extract_roi(self, input_tiff: str) -> np.ndarray: # pylint: disable=W0237
|
211
|
-
"""
|
212
|
-
Crop a GeoTIFF based on given geographic bounding box and save to a new file.
|
213
|
-
|
214
|
-
Arguments:
|
215
|
-
input_tiff (str): Path to the input GeoTIFF file.
|
216
|
-
|
217
|
-
Returns:
|
218
|
-
np.ndarray: Numpy array of the cropped GeoTIFF.
|
219
|
-
"""
|
220
|
-
self.logger.debug("Extracting ROI...")
|
221
|
-
# Open the input GeoTIFF
|
222
|
-
with rasterio.open(input_tiff) as src:
|
223
|
-
|
224
|
-
# Create a rasterio window from the bounding box
|
225
|
-
(north, south, east, west) = self.get_bbox()
|
226
|
-
window = from_bounds(west, south, east, north, transform=src.transform)
|
227
|
-
|
228
|
-
data = src.read(1, window=window)
|
229
|
-
self.logger.debug("Extracted ROI")
|
230
|
-
return data
|
231
|
-
|
232
|
-
# pylint: disable=R0914, R0917, R0913
|
233
|
-
def convert_geotiff_to_geotiff(
|
234
|
-
self,
|
235
|
-
input_tiff: str,
|
236
|
-
output_tiff: str,
|
237
|
-
min_height: float,
|
238
|
-
max_height: float,
|
239
|
-
target_crs: str,
|
240
|
-
) -> None:
|
241
|
-
"""
|
242
|
-
Convert a GeoTIFF to a scaled GeoTIFF with UInt16 values using a specific coordinate
|
243
|
-
system and output size.
|
244
|
-
|
245
|
-
Arguments:
|
246
|
-
input_tiff (str): Path to the input GeoTIFF file.
|
247
|
-
output_tiff (str): Path to save the output GeoTIFF file.
|
248
|
-
min_height (float): Minimum terrain height (input range).
|
249
|
-
max_height (float): Maximum terrain height (input range).
|
250
|
-
target_crs (str): Target CRS (e.g., EPSG:4326 for CRS:84).
|
251
|
-
"""
|
252
|
-
# Open the input GeoTIFF file
|
253
|
-
self.logger.debug("Converting to uint16")
|
254
|
-
with rasterio.open(input_tiff) as src:
|
255
|
-
# Ensure the input CRS matches the target CRS (reprojection may be required)
|
256
|
-
if str(src.crs) != str(target_crs):
|
257
|
-
raise ValueError(
|
258
|
-
f"The GeoTIFF CRS is {src.crs}, but the target CRS is {target_crs}. "
|
259
|
-
"Reprojection may be required."
|
260
|
-
)
|
261
|
-
|
262
|
-
# Read the data from the first band
|
263
|
-
data = src.read(1) # Assuming the input GeoTIFF has only a single band
|
264
|
-
|
265
|
-
# Identify the input file's NoData value
|
266
|
-
input_nodata = src.nodata
|
267
|
-
if input_nodata is None:
|
268
|
-
input_nodata = -999999.0 # Default fallback if no NoData value is defined
|
269
|
-
nodata_value = 0
|
270
|
-
# Replace NoData values (e.g., -999999.0) with the new NoData value
|
271
|
-
# (e.g., 65535 for UInt16)
|
272
|
-
data[data == input_nodata] = nodata_value
|
273
|
-
|
274
|
-
# Scale the data to the 0–65535 range (UInt16), avoiding NoData areas
|
275
|
-
scaled_data = np.clip(
|
276
|
-
(data - min_height) * (65535 / (max_height - min_height)), 0, 65535
|
277
|
-
).astype(np.uint16)
|
278
|
-
scaled_data[data == nodata_value] = (
|
279
|
-
nodata_value # Preserve NoData value in the scaled array
|
280
|
-
)
|
281
|
-
|
282
|
-
# Compute the proper transform to ensure consistency
|
283
|
-
# Get the original transform, width, and height
|
284
|
-
transform = src.transform
|
285
|
-
width = src.width
|
286
|
-
height = src.height
|
287
|
-
left, bottom, right, top = src.bounds
|
288
|
-
|
289
|
-
# Adjust the transform matrix to make sure bounds and transform align correctly
|
290
|
-
transform = rasterio.transform.from_bounds(left, bottom, right, top, width, height)
|
291
|
-
|
292
|
-
# Prepare metadata for the output GeoTIFF
|
293
|
-
metadata = src.meta.copy()
|
294
|
-
metadata.update(
|
295
|
-
{
|
296
|
-
"dtype": rasterio.uint16, # Update dtype for uint16
|
297
|
-
"crs": target_crs, # Update CRS if needed
|
298
|
-
"nodata": nodata_value, # Set the new NoData value
|
299
|
-
"transform": transform, # Use the updated, consistent transform
|
300
|
-
}
|
301
|
-
)
|
302
|
-
|
303
|
-
# Write the scaled data to the output GeoTIFF
|
304
|
-
with rasterio.open(output_tiff, "w", **metadata) as dst:
|
305
|
-
dst.write(scaled_data, 1) # Write the first band
|
306
|
-
|
307
|
-
self.logger.debug(
|
308
|
-
"GeoTIFF successfully converted and saved to %s, with nodata value: %s.",
|
309
|
-
output_tiff,
|
310
|
-
nodata_value,
|
311
|
-
)
|
312
|
-
|
313
|
-
def generate_data(self) -> np.ndarray:
|
314
|
-
"""Generate data from the USGS 1m provider.
|
315
|
-
|
316
|
-
Returns:
|
317
|
-
np.ndarray: Numpy array of the data.
|
318
|
-
"""
|
319
|
-
download_urls = self.get_download_urls()
|
320
|
-
all_tif_files = self.download_tif_files(download_urls)
|
321
|
-
self.merge_geotiff(all_tif_files, os.path.join(self.output_path, "merged.tif"))
|
322
|
-
self.reproject_geotiff(
|
323
|
-
os.path.join(self.output_path, "merged.tif"),
|
324
|
-
os.path.join(self.output_path, "reprojected.tif"),
|
325
|
-
"EPSG:4326",
|
326
|
-
)
|
327
|
-
self.convert_geotiff_to_geotiff(
|
328
|
-
os.path.join(self.output_path, "reprojected.tif"),
|
329
|
-
os.path.join(self.output_path, "translated.tif"),
|
330
|
-
min_height=0,
|
331
|
-
max_height=self.user_settings.max_local_elevation, # type: ignore
|
332
|
-
target_crs="EPSG:4326",
|
333
|
-
)
|
334
|
-
return self.extract_roi(os.path.join(self.output_path, "translated.tif"))
|
335
|
-
|
336
|
-
def get_numpy(self) -> np.ndarray:
|
337
|
-
"""Get numpy array of the tile.
|
338
|
-
|
339
|
-
Returns:
|
340
|
-
np.ndarray: Numpy array of the tile.
|
341
|
-
"""
|
342
|
-
if not self.user_settings:
|
343
|
-
raise ValueError("user_settings is 'none'")
|
344
|
-
if self.user_settings.max_local_elevation <= 0: # type: ignore
|
345
|
-
raise ValueError(
|
346
|
-
"Entered 'max_local_elevation' value is unable to be used. "
|
347
|
-
"Use a value greater than 0."
|
348
|
-
)
|
349
|
-
if not self._data:
|
350
|
-
self._data = self.generate_data()
|
351
|
-
return self._data
|
maps4fs/generator/texture.py
CHANGED
@@ -421,7 +421,7 @@ class Texture(Component):
|
|
421
421
|
),
|
422
422
|
)
|
423
423
|
|
424
|
-
# pylint: disable=no-member, R0912
|
424
|
+
# pylint: disable=no-member, R0912, R0915
|
425
425
|
def draw(self) -> None:
|
426
426
|
"""Iterates over layers and fills them with polygons from OSM data."""
|
427
427
|
layers = self.layers_by_priority()
|
@@ -461,12 +461,19 @@ class Texture(Component):
|
|
461
461
|
for polygon in self.objects_generator( # type: ignore
|
462
462
|
layer.tags, layer.width, layer.info_layer
|
463
463
|
):
|
464
|
+
if not len(polygon) > 2:
|
465
|
+
self.logger.debug("Skipping polygon with less than 3 points.")
|
466
|
+
continue
|
464
467
|
if layer.info_layer:
|
465
468
|
info_layer_data[layer.info_layer].append(
|
466
469
|
self.np_to_polygon_points(polygon) # type: ignore
|
467
470
|
)
|
468
471
|
if not layer.invisible:
|
469
|
-
|
472
|
+
try:
|
473
|
+
cv2.fillPoly(layer_image, [polygon], color=255) # type: ignore
|
474
|
+
except Exception as e: # pylint: disable=W0718
|
475
|
+
self.logger.warning("Error drawing polygon: %s.", repr(e))
|
476
|
+
continue
|
470
477
|
|
471
478
|
if layer.info_layer == "roads":
|
472
479
|
for linestring in self.objects_generator(
|
@@ -0,0 +1,67 @@
|
|
1
|
+
"""This module contains functions to work with custom OSM files."""
|
2
|
+
|
3
|
+
import json
|
4
|
+
from xml.etree import ElementTree as ET
|
5
|
+
|
6
|
+
import osmnx as ox
|
7
|
+
from osmnx._errors import InsufficientResponseError
|
8
|
+
|
9
|
+
from maps4fs.generator.game import FS25
|
10
|
+
|
11
|
+
|
12
|
+
def check_osm_file(file_path: str) -> bool:
|
13
|
+
"""Tries to read the OSM file using OSMnx and returns True if the file is valid,
|
14
|
+
False otherwise.
|
15
|
+
|
16
|
+
Arguments:
|
17
|
+
file_path (str): Path to the OSM file.
|
18
|
+
|
19
|
+
Returns:
|
20
|
+
bool: True if the file is valid, False otherwise.
|
21
|
+
"""
|
22
|
+
with open(FS25().texture_schema, encoding="utf-8") as f:
|
23
|
+
schema = json.load(f)
|
24
|
+
|
25
|
+
tags = []
|
26
|
+
for element in schema:
|
27
|
+
element_tags = element.get("tags")
|
28
|
+
if element_tags:
|
29
|
+
tags.append(element_tags)
|
30
|
+
|
31
|
+
for tag in tags:
|
32
|
+
try:
|
33
|
+
ox.features_from_xml(file_path, tags=tag)
|
34
|
+
except InsufficientResponseError:
|
35
|
+
continue
|
36
|
+
except Exception: # pylint: disable=W0718
|
37
|
+
return False
|
38
|
+
return True
|
39
|
+
|
40
|
+
|
41
|
+
def fix_osm_file(input_file_path: str, output_file_path: str) -> tuple[bool, int]:
|
42
|
+
"""Fixes the OSM file by removing all the <relation> nodes and all the nodes with
|
43
|
+
action='delete'.
|
44
|
+
|
45
|
+
Arguments:
|
46
|
+
input_file_path (str): Path to the input OSM file.
|
47
|
+
output_file_path (str): Path to the output OSM file.
|
48
|
+
|
49
|
+
Returns:
|
50
|
+
tuple[bool, int]: A tuple containing the result of the check_osm_file function
|
51
|
+
and the number of fixed errors.
|
52
|
+
"""
|
53
|
+
broken_entries = ["relation", ".//*[@action='delete']"]
|
54
|
+
|
55
|
+
tree = ET.parse(input_file_path)
|
56
|
+
root = tree.getroot()
|
57
|
+
|
58
|
+
fixed_errors = 0
|
59
|
+
for entry in broken_entries:
|
60
|
+
for element in root.findall(entry):
|
61
|
+
root.remove(element)
|
62
|
+
fixed_errors += 1
|
63
|
+
|
64
|
+
tree.write(output_file_path)
|
65
|
+
result = check_osm_file(output_file_path)
|
66
|
+
|
67
|
+
return result, fixed_errors
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: maps4fs
|
3
|
-
Version: 1.7.
|
3
|
+
Version: 1.7.6
|
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
|
@@ -36,7 +36,6 @@ Requires-Dist: pydantic
|
|
36
36
|
<a href="#How-To-Run">How-To-Run</a><br>
|
37
37
|
<a href="docs/FAQ.md">FAQ</a> •
|
38
38
|
<a href="docs/map_structure.md">Map Structure</a> •
|
39
|
-
<a href="docs/tips_and_hints.md">Tips and Hints</a> •
|
40
39
|
<a href="#Modder-Toolbox">Modder Toolbox</a><br>
|
41
40
|
<a href="#Supported-objects">Supported objects</a> •
|
42
41
|
<a href="#Generation-info">Generation info</a> •
|
@@ -141,7 +140,7 @@ Check out the [Docker FAQ](docs/FAQ_docker.md) if you have any questions.<br>
|
|
141
140
|
```bash
|
142
141
|
pip install maps4fs
|
143
142
|
```
|
144
|
-
And refer to the [Python package](#option-3-python-package) section to learn how to use it.<br>
|
143
|
+
And refer to the [Python package or run from the source](#option-3-python-package-or-source-code) section to learn how to use it.<br>
|
145
144
|
|
146
145
|
## Overview
|
147
146
|
The core idea is coming from the awesome [maps4cim](https://github.com/klamann/maps4cim) project.<br>
|
@@ -201,7 +200,7 @@ docker run -d -p 8501:8501 --name maps4fs iwatkot/maps4fs
|
|
201
200
|
4. Fill in the required fields and click on the `Generate` button.
|
202
201
|
5. When the map is generated click on the `Download` button to get the map.
|
203
202
|
|
204
|
-
### Option 3: Python package
|
203
|
+
### Option 3: Python package or source code
|
205
204
|
🔴 Recommended for developers.
|
206
205
|
🗺️ Supported map sizes: 2x2, 4x4, 8x8, 16x16 km and any custom size.
|
207
206
|
⚙️ Advanced settings: enabled.
|
@@ -212,11 +211,50 @@ You can use the Python package to generate maps. Follow these steps:
|
|
212
211
|
```bash
|
213
212
|
pip install maps4fs
|
214
213
|
```
|
214
|
+
|
215
|
+
Or clone the repository and install the package from the source code:
|
216
|
+
```bash
|
217
|
+
git clone https://github.com/iwatkot/maps4fs.git
|
218
|
+
cd maps4fs
|
219
|
+
dev/create_venv.ps1 # for Windows
|
220
|
+
sh dev/create_venv.sh # for Linux
|
221
|
+
|
222
|
+
# Activate the virtual environment.
|
223
|
+
./venv/scripts/activate # for Windows
|
224
|
+
source venv/bin/activate # for Linux
|
225
|
+
|
226
|
+
# Edit the demo.py file to set the parameters.
|
227
|
+
python demo.py
|
228
|
+
```
|
229
|
+
|
230
|
+
|
215
231
|
2. Import the Game class and create an instance of it:
|
216
232
|
```python
|
217
233
|
import maps4fs as mfs
|
218
234
|
|
219
|
-
|
235
|
+
game_code = "fs25"
|
236
|
+
game = mfs.Game.from_code(game_code)
|
237
|
+
|
238
|
+
dtm_provider = mfs.SRTM30Provider
|
239
|
+
dtm_provider_settings = mfs.SRTM30ProviderSettings(easy_mode=True, power_factor=0)
|
240
|
+
|
241
|
+
lat, lon = 45.28, 20.23
|
242
|
+
coordinates = (lat, lon)
|
243
|
+
size = 2048
|
244
|
+
rotation = 25
|
245
|
+
|
246
|
+
map_directory = "map_directory"
|
247
|
+
os.makedirs(map_directory, exist_ok=True)
|
248
|
+
|
249
|
+
mp = mfs.Map(
|
250
|
+
game,
|
251
|
+
dtm_provider,
|
252
|
+
dtm_provider_settings,
|
253
|
+
coordinates,
|
254
|
+
size,
|
255
|
+
rotation,
|
256
|
+
map_directory,
|
257
|
+
)
|
220
258
|
```
|
221
259
|
In this case, the library will use the default templates, which should be present in the `data` directory, which should be placed in the current working directory.<br>
|
222
260
|
Structure example:<br>
|
@@ -229,28 +267,17 @@ Structure example:<br>
|
|
229
267
|
|
230
268
|
So it's recommended to download the `data` directory from the repository and place it in the root of your project.<br>
|
231
269
|
|
232
|
-
3.
|
233
|
-
```python
|
234
|
-
import maps4fs as mfs
|
235
|
-
|
236
|
-
map = mfs.Map(
|
237
|
-
game,
|
238
|
-
(52.5200, 13.4050), # Latitude and longitude of the map center.
|
239
|
-
height=1024, # The height of the map in meters.
|
240
|
-
width=1024, # The width of the map in meters.
|
241
|
-
map_directory="path/to/your/map/directory", # The directory where the map will be saved.
|
242
|
-
)
|
243
|
-
```
|
244
|
-
|
245
|
-
4. Generate the map:
|
270
|
+
3. Launch the generation process.
|
246
271
|
The `generate` method returns a generator, which yields the active component of the map. You can use it to track the progress of the generation process.
|
247
272
|
```python
|
248
|
-
for
|
249
|
-
print(
|
273
|
+
for component_name in mp.generate():
|
274
|
+
print(f"Generating {component_name}...")
|
250
275
|
```
|
251
276
|
|
252
277
|
The map will be saved in the `map_directory` directory.
|
253
278
|
|
279
|
+
➡️ Check out the [demo.py](demo.py) file for a complete example.
|
280
|
+
|
254
281
|
## Modder Toolbox
|
255
282
|
The tool now has a Modder Toolbox, which is a set of tools to help you with various tasks. You can open the toolbox by switching to the `🧰 Modder Toolbox` tab in the StreamLit app.<br>
|
256
283
|
|
@@ -265,6 +292,9 @@ Tools are divided into categories, which are listed below.
|
|
265
292
|
- **Texture Schema Editor** - allows you to view all the supported textures and edit their parameters, such as priority, OSM tags and so on. After editing, you should click the Show updated schema button and copy the JSON schema to the clipboard. Then you can use it in the Expert settings to generate the map with the updated textures.
|
266
293
|
|
267
294
|
#### For Textures and DEM
|
295
|
+
|
296
|
+
- **Fix custom OSM file** - this tool fixes the most common errors in the custom OSM file, but it can not guarantee that the file will be fixed completely if some non-common errors are present.
|
297
|
+
|
268
298
|
- **GeoTIFF windowing** - allows you to upload your GeoTIFF file and select the region of interest to extract it from the image. It's useful when you have high-resolution DEM data and want to create a height map using it.
|
269
299
|
|
270
300
|
#### For Background terrain
|
@@ -1,4 +1,4 @@
|
|
1
|
-
maps4fs/__init__.py,sha256=
|
1
|
+
maps4fs/__init__.py,sha256=I-WDbZBb1l6us3QxDajPGzjVJe9PK0QrE5DqZwSsd_w,713
|
2
2
|
maps4fs/logger.py,sha256=B-NEYpMjPAAqlV4VpfTi6nbBFnEABVtQOaYe6nMpidg,1489
|
3
3
|
maps4fs/generator/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
|
4
4
|
maps4fs/generator/background.py,sha256=tV4UXvtkNN-OSvv6ujp4jFWRU1xGBgEvSakVGZ1H4nc,24877
|
@@ -12,16 +12,19 @@ maps4fs/generator/map.py,sha256=a50KQEr1XZKjS_WKXywGwh4OC3gyjY6M8FTc0eNcxpg,1018
|
|
12
12
|
maps4fs/generator/qgis.py,sha256=Es8hLuqN_KH8lDfnJE6He2rWYbAKJ3RGPn-o87S6CPI,6116
|
13
13
|
maps4fs/generator/satellite.py,sha256=_7RcuNmR1mjxEJWMDsjnzKUIqWxnGUn50XtjB7HmSPg,3661
|
14
14
|
maps4fs/generator/settings.py,sha256=9vbXISQrE-aDY7ATpvZ7LVJMqjfwa3-gNl-huI8XLO0,5666
|
15
|
-
maps4fs/generator/texture.py,sha256=
|
15
|
+
maps4fs/generator/texture.py,sha256=L_j5GTTJXbp7OCT4-TWGFcY_zyAI_fNzDFLvXYiyKPI,33921
|
16
16
|
maps4fs/generator/dtm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
|
-
maps4fs/generator/dtm/
|
18
|
-
maps4fs/generator/dtm/
|
19
|
-
maps4fs/generator/dtm/
|
17
|
+
maps4fs/generator/dtm/bavaria.py,sha256=r6-3DcKY4JeFQ-8jbf0xxtB5SPbbZc7KSGYxllqOCo4,4412
|
18
|
+
maps4fs/generator/dtm/dtm.py,sha256=DXRxpRuzy5l00yGVMkxamJE2zRygwpi6xcEe29y7LsA,21714
|
19
|
+
maps4fs/generator/dtm/nrw.py,sha256=8h33743_FvfScnABSFWgV7zPE5L1i6vL2uwpIMZey6g,4610
|
20
|
+
maps4fs/generator/dtm/srtm.py,sha256=RsvVa7ErajPwXoetG7mO_rldji9GR97HFaazH-PkdHw,4399
|
21
|
+
maps4fs/generator/dtm/usgs.py,sha256=dyy2NT0USlRkYL2qfXQzFT_q3VfkVZUSmKBzPkDNvV4,3202
|
20
22
|
maps4fs/toolbox/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
|
21
23
|
maps4fs/toolbox/background.py,sha256=9BXWNqs_n3HgqDiPztWylgYk_QM4YgBpe6_ZNQAWtSc,2154
|
24
|
+
maps4fs/toolbox/custom_osm.py,sha256=X6ZlPqiOhNjkmdD_qVroIfdOl9Rb90cDwVSLDVYgx80,1892
|
22
25
|
maps4fs/toolbox/dem.py,sha256=z9IPFNmYbjiigb3t02ZenI3Mo8odd19c5MZbjDEovTo,3525
|
23
|
-
maps4fs-1.7.
|
24
|
-
maps4fs-1.7.
|
25
|
-
maps4fs-1.7.
|
26
|
-
maps4fs-1.7.
|
27
|
-
maps4fs-1.7.
|
26
|
+
maps4fs-1.7.6.dist-info/LICENSE.md,sha256=pTKD_oUexcn-yccFCTrMeLkZy0ifLRa-VNcDLqLZaIw,10749
|
27
|
+
maps4fs-1.7.6.dist-info/METADATA,sha256=JJrc4bG4NmytUf6tI4QdMF2_a2GJjKLzUnHOGLIuE28,40436
|
28
|
+
maps4fs-1.7.6.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
29
|
+
maps4fs-1.7.6.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
|
30
|
+
maps4fs-1.7.6.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|