maps4fs 1.2.3__py3-none-any.whl → 1.2.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.
@@ -3,8 +3,10 @@ around the map."""
3
3
 
4
4
  from __future__ import annotations
5
5
 
6
+ import json
6
7
  import os
7
8
  import shutil
9
+ from copy import deepcopy
8
10
 
9
11
  import cv2
10
12
  import numpy as np
@@ -17,6 +19,7 @@ from maps4fs.generator.dem import (
17
19
  DEFAULT_PLATEAU,
18
20
  DEM,
19
21
  )
22
+ from maps4fs.generator.texture import Texture
20
23
 
21
24
  DEFAULT_DISTANCE = 2048
22
25
  RESIZE_FACTOR = 1 / 8
@@ -25,6 +28,7 @@ FULL_PREVIEW_NAME = "PREVIEW"
25
28
  ELEMENTS = [FULL_NAME, FULL_PREVIEW_NAME]
26
29
 
27
30
 
31
+ # pylint: disable=R0902
28
32
  class Background(Component):
29
33
  """Component for creating 3D obj files based on DEM data around the map.
30
34
 
@@ -43,7 +47,9 @@ class Background(Component):
43
47
  def preprocess(self) -> None:
44
48
  """Registers the DEMs for the background terrain."""
45
49
  self.light_version = self.kwargs.get("light_version", False)
50
+ self.water_depth = self.kwargs.get("water_depth", 0)
46
51
  self.stl_preview_path: str | None = None
52
+ self.water_resources_path: str | None = None
47
53
 
48
54
  if self.rotation:
49
55
  self.logger.debug("Rotation is enabled: %s.", self.rotation)
@@ -51,22 +57,29 @@ class Background(Component):
51
57
  else:
52
58
  output_size_multiplier = 1
53
59
 
54
- background_size = self.map_size + DEFAULT_DISTANCE * 2
55
- rotated_size = int(background_size * output_size_multiplier)
60
+ self.background_size = self.map_size + DEFAULT_DISTANCE * 2
61
+ self.rotated_size = int(self.background_size * output_size_multiplier)
56
62
 
57
63
  self.background_directory = os.path.join(self.map_directory, "background")
64
+ self.water_directory = os.path.join(self.map_directory, "water")
58
65
  os.makedirs(self.background_directory, exist_ok=True)
66
+ os.makedirs(self.water_directory, exist_ok=True)
59
67
 
60
68
  autoprocesses = [self.kwargs.get("auto_process", False), False]
69
+ self.output_paths = [
70
+ os.path.join(self.background_directory, f"{name}.png") for name in ELEMENTS
71
+ ]
72
+ self.not_substracted_path = os.path.join(self.background_directory, "not_substracted.png")
73
+
61
74
  dems = []
62
75
 
63
- for name, autoprocess in zip(ELEMENTS, autoprocesses):
76
+ for name, autoprocess, output_path in zip(ELEMENTS, autoprocesses, self.output_paths):
64
77
  dem = DEM(
65
78
  self.game,
66
79
  self.map,
67
80
  self.coordinates,
68
- background_size,
69
- rotated_size,
81
+ self.background_size,
82
+ self.rotated_size,
70
83
  self.rotation,
71
84
  self.map_directory,
72
85
  self.logger,
@@ -77,8 +90,8 @@ class Background(Component):
77
90
  )
78
91
  dem.preprocess()
79
92
  dem.is_preview = self.is_preview(name) # type: ignore
80
- dem.set_output_resolution((rotated_size, rotated_size))
81
- dem.set_dem_path(os.path.join(self.background_directory, f"{name}.png"))
93
+ dem.set_output_resolution((self.rotated_size, self.rotated_size))
94
+ dem.set_dem_path(output_path)
82
95
  dems.append(dem)
83
96
 
84
97
  self.dems = dems
@@ -98,8 +111,17 @@ class Background(Component):
98
111
  """Launches the component processing. Iterates over all tiles and processes them
99
112
  as a result the DEM files will be saved, then based on them the obj files will be
100
113
  generated."""
114
+ self.create_background_textures()
115
+
101
116
  for dem in self.dems:
102
117
  dem.process()
118
+ if not dem.is_preview: # type: ignore
119
+ shutil.copyfile(dem.dem_path, self.not_substracted_path)
120
+
121
+ if self.water_depth:
122
+ self.subtraction()
123
+
124
+ for dem in self.dems:
103
125
  if not dem.is_preview: # type: ignore
104
126
  cutted_dem_path = self.cutout(dem.dem_path)
105
127
  if self.game.additional_dem_name is not None:
@@ -107,6 +129,7 @@ class Background(Component):
107
129
 
108
130
  if not self.light_version:
109
131
  self.generate_obj_files()
132
+ self.generate_water_resources_obj()
110
133
  else:
111
134
  self.logger.info("Light version is enabled, obj files will not be generated.")
112
135
 
@@ -223,13 +246,20 @@ class Background(Component):
223
246
  return main_dem_path
224
247
 
225
248
  # pylint: disable=too-many-locals
226
- def plane_from_np(self, dem_data: np.ndarray, save_path: str, is_preview: bool = False) -> None:
249
+ def plane_from_np(
250
+ self,
251
+ dem_data: np.ndarray,
252
+ save_path: str,
253
+ is_preview: bool = False,
254
+ include_zeros: bool = True,
255
+ ) -> None:
227
256
  """Generates a 3D obj file based on DEM data.
228
257
 
229
258
  Arguments:
230
259
  dem_data (np.ndarray) -- The DEM data as a numpy array.
231
260
  save_path (str) -- The path where the obj file will be saved.
232
261
  is_preview (bool, optional) -- If True, the preview mesh will be generated.
262
+ include_zeros (bool, optional) -- If True, the mesh will include the zero height values.
233
263
  """
234
264
  dem_data = cv2.resize( # pylint: disable=no-member
235
265
  dem_data, (0, 0), fx=RESIZE_FACTOR, fy=RESIZE_FACTOR
@@ -247,6 +277,9 @@ class Background(Component):
247
277
  x, y = np.meshgrid(x, y)
248
278
  z = dem_data
249
279
 
280
+ ground = z.max()
281
+ self.logger.debug("Ground level: %s", ground)
282
+
250
283
  self.logger.debug(
251
284
  "Starting to generate a mesh for with shape: %s x %s. This may take a while...",
252
285
  cols,
@@ -256,6 +289,8 @@ class Background(Component):
256
289
  vertices = np.column_stack([x.ravel(), y.ravel(), z.ravel()])
257
290
  faces = []
258
291
 
292
+ skipped = 0
293
+
259
294
  for i in range(rows - 1):
260
295
  for j in range(cols - 1):
261
296
  top_left = i * cols + j
@@ -263,9 +298,15 @@ class Background(Component):
263
298
  bottom_left = top_left + cols
264
299
  bottom_right = bottom_left + 1
265
300
 
301
+ if ground in [z[i, j], z[i, j + 1], z[i + 1, j], z[i + 1, j + 1]]:
302
+ skipped += 1
303
+ continue
304
+
266
305
  faces.append([top_left, bottom_left, bottom_right])
267
306
  faces.append([top_left, bottom_right, top_right])
268
307
 
308
+ self.logger.debug("Skipped faces: %s", skipped)
309
+
269
310
  faces = np.array(faces) # type: ignore
270
311
  mesh = trimesh.Trimesh(vertices=vertices, faces=faces)
271
312
 
@@ -283,13 +324,14 @@ class Background(Component):
283
324
  mesh.apply_scale([0.5, 0.5, 0.5])
284
325
  self.mesh_to_stl(mesh)
285
326
  else:
286
- multiplier = self.kwargs.get("multiplier", DEFAULT_MULTIPLIER)
287
- if multiplier != 1:
288
- z_scaling_factor = 1 / multiplier
289
- else:
290
- z_scaling_factor = 1 / 2**5
291
- self.logger.debug("Z scaling factor: %s", z_scaling_factor)
292
- mesh.apply_scale([1 / RESIZE_FACTOR, 1 / RESIZE_FACTOR, z_scaling_factor])
327
+ if not include_zeros:
328
+ multiplier = self.kwargs.get("multiplier", DEFAULT_MULTIPLIER)
329
+ if multiplier != 1:
330
+ z_scaling_factor = 1 / multiplier
331
+ else:
332
+ z_scaling_factor = 1 / 2**5
333
+ self.logger.debug("Z scaling factor: %s", z_scaling_factor)
334
+ mesh.apply_scale([1 / RESIZE_FACTOR, 1 / RESIZE_FACTOR, z_scaling_factor])
293
335
 
294
336
  mesh.export(save_path)
295
337
  self.logger.debug("Obj file saved: %s", save_path)
@@ -413,3 +455,123 @@ class Background(Component):
413
455
 
414
456
  cv2.imwrite(colored_dem_path, dem_data_colored)
415
457
  return colored_dem_path
458
+
459
+ def create_background_textures(self) -> None:
460
+ """Creates background textures for the map."""
461
+ if not os.path.isfile(self.game.texture_schema):
462
+ self.logger.warning("Texture schema file not found: %s", self.game.texture_schema)
463
+ return
464
+
465
+ with open(self.game.texture_schema, "r", encoding="utf-8") as f:
466
+ layers_schema = json.load(f)
467
+
468
+ background_layers = []
469
+ for layer in layers_schema:
470
+ if layer.get("background") is True:
471
+ layer_copy = deepcopy(layer)
472
+ layer_copy["count"] = 1
473
+ layer_copy["name"] = f"{layer['name']}_background"
474
+ background_layers.append(layer_copy)
475
+
476
+ if not background_layers:
477
+ return
478
+
479
+ self.background_texture = Texture( # pylint: disable=W0201
480
+ self.game,
481
+ self.map,
482
+ self.coordinates,
483
+ self.background_size,
484
+ self.rotated_size,
485
+ rotation=self.rotation,
486
+ map_directory=self.map_directory,
487
+ logger=self.logger,
488
+ light_version=self.light_version,
489
+ custom_schema=background_layers,
490
+ )
491
+
492
+ self.background_texture.preprocess()
493
+ self.background_texture.process()
494
+
495
+ processed_layers = self.background_texture.get_background_layers()
496
+ weights_directory = self.game.weights_dir_path(self.map_directory)
497
+ background_paths = [layer.path(weights_directory) for layer in processed_layers]
498
+ self.logger.debug("Found %s background textures.", len(background_paths))
499
+
500
+ if not background_paths:
501
+ self.logger.warning("No background textures found.")
502
+ return
503
+
504
+ # Merge all images into one.
505
+ background_image = np.zeros((self.background_size, self.background_size), dtype=np.uint8)
506
+ for path in background_paths:
507
+ layer = cv2.imread(path, cv2.IMREAD_GRAYSCALE)
508
+ background_image = cv2.add(background_image, layer) # type: ignore
509
+
510
+ background_save_path = os.path.join(self.water_directory, "water_resources.png")
511
+ cv2.imwrite(background_save_path, background_image)
512
+ self.logger.info("Background texture saved: %s", background_save_path)
513
+ self.water_resources_path = background_save_path # pylint: disable=W0201
514
+
515
+ def subtraction(self) -> None:
516
+ """Subtracts the water depth from the DEM data where the water resources are located."""
517
+ if not self.water_resources_path:
518
+ self.logger.warning("Water resources texture not found.")
519
+ return
520
+
521
+ # Single channeled 8 bit image, where the water have values of 255, and the rest 0.
522
+ water_resources_image = cv2.imread(self.water_resources_path, cv2.IMREAD_UNCHANGED)
523
+ mask = water_resources_image == 255
524
+
525
+ # Make mask a little bit smaller (1 pixel).
526
+ mask = cv2.erode(mask.astype(np.uint8), np.ones((3, 3), np.uint8), iterations=1).astype(
527
+ bool
528
+ )
529
+
530
+ for output_path in self.output_paths:
531
+ if FULL_PREVIEW_NAME in output_path:
532
+ continue
533
+ dem_image = cv2.imread(output_path, cv2.IMREAD_UNCHANGED)
534
+
535
+ # Create a mask where water_resources_image is 255 (or not 0)
536
+ # Subtract water_depth from dem_image where mask is True
537
+ dem_image[mask] = dem_image[mask] - self.water_depth
538
+
539
+ # Save the modified dem_image back to the output path
540
+ cv2.imwrite(output_path, dem_image)
541
+ self.logger.debug("Water depth subtracted from DEM data: %s", output_path)
542
+
543
+ def generate_water_resources_obj(self) -> None:
544
+ """Generates 3D obj files based on water resources data."""
545
+ if not self.water_resources_path:
546
+ self.logger.warning("Water resources texture not found.")
547
+ return
548
+
549
+ # Single channeled 8 bit image, where the water have values of 255, and the rest 0.
550
+ plane_water = cv2.imread(self.water_resources_path, cv2.IMREAD_UNCHANGED)
551
+ dilated_plane_water = cv2.dilate(
552
+ plane_water.astype(np.uint8), np.ones((5, 5), np.uint8), iterations=5
553
+ ).astype(np.uint8)
554
+ plane_save_path = os.path.join(self.water_directory, "plane_water.obj")
555
+ self.plane_from_np(
556
+ dilated_plane_water, plane_save_path, is_preview=False, include_zeros=False
557
+ )
558
+
559
+ # Single channeled 16 bit DEM image of terrain.
560
+ background_dem = cv2.imread(self.not_substracted_path, cv2.IMREAD_UNCHANGED)
561
+
562
+ # Remove all the values from the background dem where the plane_water is 0.
563
+ background_dem[plane_water == 0] = 0
564
+
565
+ # Dilate the background dem to make the water more smooth.
566
+ elevated_water = cv2.dilate(background_dem, np.ones((3, 3), np.uint16), iterations=10)
567
+
568
+ # Use the background dem as a mask to prevent the original values from being overwritten.
569
+ mask = background_dem > 0
570
+
571
+ # Combine the dilated background dem with non-dilated background dem.
572
+ elevated_water = np.where(mask, background_dem, elevated_water)
573
+ elevated_save_path = os.path.join(self.water_directory, "elevated_water.obj")
574
+
575
+ self.plane_from_np(
576
+ elevated_water, elevated_save_path, is_preview=False, include_zeros=False
577
+ )
@@ -330,6 +330,7 @@ class Component:
330
330
 
331
331
  return cs_x, cs_y
332
332
 
333
+ # pylint: disable=R0914
333
334
  def fit_polygon_into_bounds(
334
335
  self, polygon_points: list[tuple[int, int]], margin: int = 0, angle: int = 0
335
336
  ) -> list[tuple[int, int]]:
@@ -371,8 +372,13 @@ class Component:
371
372
  bounds = box(min_x, min_y, max_x, max_y)
372
373
 
373
374
  # Intersect the polygon with the bounds to fit it within the map
374
- fitted_polygon = polygon.intersection(bounds)
375
- self.logger.debug("Fitted the polygon into the bounds: %s", bounds)
375
+ try:
376
+ fitted_polygon = polygon.intersection(bounds)
377
+ self.logger.debug("Fitted the polygon into the bounds: %s", bounds)
378
+ except Exception as e:
379
+ raise ValueError( # pylint: disable=W0707
380
+ f"Could not fit the polygon into the bounds: {e}"
381
+ )
376
382
 
377
383
  if not isinstance(fitted_polygon, Polygon):
378
384
  raise ValueError("The fitted polygon is not a valid polygon.")
@@ -64,6 +64,7 @@ class Texture(Component):
64
64
  priority: int | None = None,
65
65
  info_layer: str | None = None,
66
66
  usage: str | None = None,
67
+ background: bool = False,
67
68
  ):
68
69
  self.name = name
69
70
  self.count = count
@@ -74,6 +75,7 @@ class Texture(Component):
74
75
  self.priority = priority
75
76
  self.info_layer = info_layer
76
77
  self.usage = usage
78
+ self.background = background
77
79
 
78
80
  def to_json(self) -> dict[str, str | list[str] | bool]: # type: ignore
79
81
  """Returns dictionary with layer data.
@@ -90,6 +92,7 @@ class Texture(Component):
90
92
  "priority": self.priority,
91
93
  "info_layer": self.info_layer,
92
94
  "usage": self.usage,
95
+ "background": self.background,
93
96
  }
94
97
 
95
98
  data = {k: v for k, v in data.items() if v is not None}
@@ -178,17 +181,30 @@ class Texture(Component):
178
181
  self.fields_padding = self.kwargs.get("fields_padding", 0)
179
182
  self.logger.debug("Light version: %s.", self.light_version)
180
183
 
181
- if not os.path.isfile(self.game.texture_schema):
182
- raise FileNotFoundError(f"Texture layers schema not found: {self.game.texture_schema}")
184
+ self.custom_schema: list[dict[str, str | dict[str, str] | int]] | None = self.kwargs.get(
185
+ "custom_schema"
186
+ )
183
187
 
184
- try:
185
- with open(self.game.texture_schema, "r", encoding="utf-8") as f:
186
- layers_schema = json.load(f)
187
- except json.JSONDecodeError as e:
188
- raise ValueError(f"Error loading texture layers schema: {e}") from e
188
+ if self.custom_schema:
189
+ layers_schema = self.custom_schema
190
+ self.logger.info("Custom schema loaded with %s layers.", len(layers_schema))
191
+ else:
192
+ if not os.path.isfile(self.game.texture_schema):
193
+ raise FileNotFoundError(
194
+ f"Texture layers schema not found: {self.game.texture_schema}"
195
+ )
196
+
197
+ try:
198
+ with open(self.game.texture_schema, "r", encoding="utf-8") as f:
199
+ layers_schema = json.load(f)
200
+ except json.JSONDecodeError as e:
201
+ raise ValueError(f"Error loading texture layers schema: {e}") from e
189
202
 
190
- self.layers = [self.Layer.from_json(layer) for layer in layers_schema]
191
- self.logger.info("Loaded %s layers.", len(self.layers))
203
+ try:
204
+ self.layers = [self.Layer.from_json(layer) for layer in layers_schema]
205
+ self.logger.info("Loaded %s layers.", len(self.layers))
206
+ except Exception as e: # pylint: disable=W0703
207
+ raise ValueError(f"Error loading texture layers: {e}") from e
192
208
 
193
209
  base_layer = self.get_base_layer()
194
210
  if base_layer:
@@ -215,6 +231,14 @@ class Texture(Component):
215
231
  return layer
216
232
  return None
217
233
 
234
+ def get_background_layers(self) -> list[Layer]:
235
+ """Returns list of background layers.
236
+
237
+ Returns:
238
+ list[Layer]: List of background layers.
239
+ """
240
+ return [layer for layer in self.layers if layer.background]
241
+
218
242
  def get_layer_by_usage(self, usage: str) -> Layer | None:
219
243
  """Returns layer by usage.
220
244
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: maps4fs
3
- Version: 1.2.3
3
+ Version: 1.2.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
@@ -70,6 +70,7 @@ Requires-Dist: pympler
70
70
  🌽 Automatically generates farmlands 🆕<br>
71
71
  🌿 Automatically generates decorative foliage 🆕<br>
72
72
  🌲 Automatically generates forests 🆕<br>
73
+ 🌊 Automatically generates water planes 🆕<br>
73
74
  🌍 Based on real-world data from OpenStreetMap<br>
74
75
  🏞️ Generates height map using SRTM dataset<br>
75
76
  📦 Provides a ready-to-use map template for the Giants Editor<br>
@@ -90,6 +91,8 @@ Requires-Dist: pympler
90
91
  🌿 Automatically generates decorative foliage.<br><br>
91
92
  <img src="https://github.com/user-attachments/assets/27a5e541-a9f5-4504-b8d2-64aae9fb3e52"><br>
92
93
  🌲 Automatically generates forests.<br><br>
94
+ <img src="https://github.com/user-attachments/assets/cce7d4e0-cba2-4dd2-b22d-03137fb2e860"><br>
95
+ 🌊 Automatically generates water planes.<br><br>
93
96
  <img src="https://github.com/user-attachments/assets/80e5923c-22c7-4dc0-8906-680902511f3a"><br>
94
97
  🗒️ True-to-life blueprints for fast and precise modding.<br><br>
95
98
  <img width="480" src="https://github.com/user-attachments/assets/1a8802d2-6a3b-4bfa-af2b-7c09478e199b"><br>
@@ -101,6 +104,9 @@ Requires-Dist: pympler
101
104
  <a href="https://www.youtube.com/watch?v=Nl_aqXJ5nAk" target="_blank"><img src="https://github.com/user-attachments/assets/4845e030-0e73-47ab-a5a3-430308913060"/></a>
102
105
  <p align="center"><i>How to Generate a Map for Farming Simulator 25 and 22 from a real place using maps4FS.</i></p>
103
106
 
107
+ ![Map example](https://github.com/user-attachments/assets/c46a3581-dd17-462f-b815-e36d4f724947)
108
+ <p align="center"><i>Map example generated with maps4fs.</i></p>
109
+
104
110
  ## Quick Start
105
111
  There are several ways to use the tool. You obviously need the **first one**, but you can choose any of the others depending on your needs.<br>
106
112
  ### 🚜 For most users
@@ -476,6 +482,8 @@ You can also apply some advanced settings to the map generation process. Note th
476
482
 
477
483
  - Plateau height: this value will be added to each pixel of the DEM image, making it "higher". It's useful when you want to add some negative heights on the map, that appear to be in a "low" place. By default, it's set to 0.
478
484
 
485
+ - Water depth: this value will be subtracted from each pixel of the DEM image, where water resources are located. Pay attention that it's not in meters, instead it in the pixel value of DEM, which is 16 bit image with possible values from 0 to 65535. When this value is set, the same value will be added to the plateau setting to avoid negative heights.
486
+
479
487
  ### Texture Advanced settings
480
488
 
481
489
  - Fields padding - this value (in meters) will be applied to each field, making it smaller. It's useful when the fields are too close to each other and you want to make them smaller. By default, it's set to 0.
@@ -1,8 +1,8 @@
1
1
  maps4fs/__init__.py,sha256=da4jmND2Ths9AffnkAKgzLHNkvKFOc_l21gJisPXqWY,155
2
2
  maps4fs/logger.py,sha256=B-NEYpMjPAAqlV4VpfTi6nbBFnEABVtQOaYe6nMpidg,1489
3
3
  maps4fs/generator/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
4
- maps4fs/generator/background.py,sha256=fLWk7FSNL08gk3skHfi0iVchnKrYjnLLKAT1g_7sRzc,15907
5
- maps4fs/generator/component.py,sha256=_d9rHmGh348KOMrLWR8rRDVsbZ2xwJQwZGIGvMIYXPM,17533
4
+ maps4fs/generator/background.py,sha256=21xB-xn2A6QGdX9UVWqvzoW-L6JWPAZOqCcIhR8nxKU,22689
5
+ maps4fs/generator/component.py,sha256=SeI1xfwo9I4lrkcOcHyjxMffHsG8OXc80-mNsR2zpPw,17748
6
6
  maps4fs/generator/config.py,sha256=b7qY0luC-_WM_c72Ohtlf4FrB37X5cALInbestSdUsw,4382
7
7
  maps4fs/generator/dem.py,sha256=rc7ADzjvlZzStOqagsWW0Vrm9-X86aPpoR1RhBF_-OE,16025
8
8
  maps4fs/generator/game.py,sha256=ZQeYzPzPB3CG41avdhNCyTZpHEeedqNBuAbNevTZuXg,7931
@@ -10,12 +10,12 @@ maps4fs/generator/grle.py,sha256=3hcr5e2YLXemFi-_x2cLHWbMVb06591k0PZxaBVovH8,176
10
10
  maps4fs/generator/i3d.py,sha256=oK5pKjzvT-gydma5Q6CcDYTVODGxK7MIGajLrAV9JkU,18370
11
11
  maps4fs/generator/map.py,sha256=lA1MNAcMwsDtsYxbwwm7DjwP3zraHKnri_xnLUu30j0,5326
12
12
  maps4fs/generator/qgis.py,sha256=Es8hLuqN_KH8lDfnJE6He2rWYbAKJ3RGPn-o87S6CPI,6116
13
- maps4fs/generator/texture.py,sha256=MSkM-rH_836l8zgq1WVcYJeYrUofWBpC8OKJglSmGGQ,26558
13
+ maps4fs/generator/texture.py,sha256=vgiwJNIl14JABhNOBGh_W8SBkAUNQN3TjNJayR76va0,27468
14
14
  maps4fs/toolbox/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
15
15
  maps4fs/toolbox/background.py,sha256=9BXWNqs_n3HgqDiPztWylgYk_QM4YgBpe6_ZNQAWtSc,2154
16
16
  maps4fs/toolbox/dem.py,sha256=z9IPFNmYbjiigb3t02ZenI3Mo8odd19c5MZbjDEovTo,3525
17
- maps4fs-1.2.3.dist-info/LICENSE.md,sha256=pTKD_oUexcn-yccFCTrMeLkZy0ifLRa-VNcDLqLZaIw,10749
18
- maps4fs-1.2.3.dist-info/METADATA,sha256=F3YcAPR8CA5160fvsWtnxEq3CtObEpiAG5B1DS6iSkg,29885
19
- maps4fs-1.2.3.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
20
- maps4fs-1.2.3.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
21
- maps4fs-1.2.3.dist-info/RECORD,,
17
+ maps4fs-1.2.4.dist-info/LICENSE.md,sha256=pTKD_oUexcn-yccFCTrMeLkZy0ifLRa-VNcDLqLZaIw,10749
18
+ maps4fs-1.2.4.dist-info/METADATA,sha256=cR3704tYCx9AaL8NW5cLrkMijvDNyWdxT7CpCaTv9NE,30600
19
+ maps4fs-1.2.4.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
20
+ maps4fs-1.2.4.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
21
+ maps4fs-1.2.4.dist-info/RECORD,,