maps4fs 1.8.185__py3-none-any.whl → 1.8.187__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/component/background.py +1 -1
- maps4fs/generator/component/base/component_image.py +22 -0
- maps4fs/generator/component/layer.py +162 -0
- maps4fs/generator/{texture.py → component/texture.py} +161 -286
- maps4fs/generator/game.py +11 -2
- maps4fs/generator/map.py +4 -3
- {maps4fs-1.8.185.dist-info → maps4fs-1.8.187.dist-info}/METADATA +1 -1
- {maps4fs-1.8.185.dist-info → maps4fs-1.8.187.dist-info}/RECORD +11 -10
- {maps4fs-1.8.185.dist-info → maps4fs-1.8.187.dist-info}/LICENSE.md +0 -0
- {maps4fs-1.8.185.dist-info → maps4fs-1.8.187.dist-info}/WHEEL +0 -0
- {maps4fs-1.8.185.dist-info → maps4fs-1.8.187.dist-info}/top_level.txt +0 -0
@@ -13,9 +13,9 @@ import numpy as np
|
|
13
13
|
|
14
14
|
from maps4fs.generator.component.base.component_image import ImageComponent
|
15
15
|
from maps4fs.generator.component.base.component_mesh import MeshComponent
|
16
|
+
from maps4fs.generator.component.texture import Texture
|
16
17
|
from maps4fs.generator.dem import DEM
|
17
18
|
from maps4fs.generator.settings import Parameters
|
18
|
-
from maps4fs.generator.texture import Texture
|
19
19
|
|
20
20
|
|
21
21
|
class Background(MeshComponent, ImageComponent):
|
@@ -116,3 +116,25 @@ class ImageComponent(Component):
|
|
116
116
|
)
|
117
117
|
|
118
118
|
cv2.imwrite(save_path, image)
|
119
|
+
|
120
|
+
@staticmethod
|
121
|
+
def transfer_border(src_image: np.ndarray, dst_image: np.ndarray | None, border: int) -> None:
|
122
|
+
"""Transfers the border of the source image to the destination image.
|
123
|
+
|
124
|
+
Arguments:
|
125
|
+
src_image (np.ndarray): The source image.
|
126
|
+
dst_image (np.ndarray, optional): The destination image.
|
127
|
+
border (int): The border size.
|
128
|
+
"""
|
129
|
+
borders = [
|
130
|
+
(slice(None, border), slice(None)),
|
131
|
+
(slice(None), slice(-border, None)),
|
132
|
+
(slice(-border, None), slice(None)),
|
133
|
+
(slice(None), slice(None, border)),
|
134
|
+
]
|
135
|
+
|
136
|
+
for row_slice, col_slice in borders:
|
137
|
+
border_slice = (row_slice, col_slice)
|
138
|
+
if dst_image is not None:
|
139
|
+
dst_image[border_slice][src_image[border_slice] != 0] = 255
|
140
|
+
src_image[border_slice] = 0
|
@@ -0,0 +1,162 @@
|
|
1
|
+
"""This module contains the class representing a layer with textures and tags."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import os
|
6
|
+
import re
|
7
|
+
|
8
|
+
|
9
|
+
class Layer:
|
10
|
+
"""Class which represents a layer with textures and tags.
|
11
|
+
It's using to obtain data from OSM using tags and make changes into corresponding textures.
|
12
|
+
|
13
|
+
Arguments:
|
14
|
+
name (str): Name of the layer.
|
15
|
+
tags (dict[str, str | list[str]]): Dictionary of tags to search for.
|
16
|
+
width (int | None): Width of the polygon in meters (only for LineString).
|
17
|
+
color (tuple[int, int, int]): Color of the layer in BGR format.
|
18
|
+
exclude_weight (bool): Flag to exclude weight from the texture.
|
19
|
+
priority (int | None): Priority of the layer.
|
20
|
+
info_layer (str | None): Name of the corresnponding info layer.
|
21
|
+
usage (str | None): Usage of the layer.
|
22
|
+
background (bool): Flag to determine if the layer is a background.
|
23
|
+
invisible (bool): Flag to determine if the layer is invisible.
|
24
|
+
|
25
|
+
Attributes:
|
26
|
+
name (str): Name of the layer.
|
27
|
+
tags (dict[str, str | list[str]]): Dictionary of tags to search for.
|
28
|
+
width (int | None): Width of the polygon in meters (only for LineString).
|
29
|
+
"""
|
30
|
+
|
31
|
+
# pylint: disable=R0913
|
32
|
+
def __init__( # pylint: disable=R0917
|
33
|
+
self,
|
34
|
+
name: str,
|
35
|
+
count: int,
|
36
|
+
tags: dict[str, str | list[str] | bool] | None = None,
|
37
|
+
width: int | None = None,
|
38
|
+
color: tuple[int, int, int] | list[int] | None = None,
|
39
|
+
exclude_weight: bool = False,
|
40
|
+
priority: int | None = None,
|
41
|
+
info_layer: str | None = None,
|
42
|
+
usage: str | None = None,
|
43
|
+
background: bool = False,
|
44
|
+
invisible: bool = False,
|
45
|
+
procedural: list[str] | None = None,
|
46
|
+
border: int | None = None,
|
47
|
+
):
|
48
|
+
self.name = name
|
49
|
+
self.count = count
|
50
|
+
self.tags = tags
|
51
|
+
self.width = width
|
52
|
+
self.color = color if color else (255, 255, 255)
|
53
|
+
self.exclude_weight = exclude_weight
|
54
|
+
self.priority = priority
|
55
|
+
self.info_layer = info_layer
|
56
|
+
self.usage = usage
|
57
|
+
self.background = background
|
58
|
+
self.invisible = invisible
|
59
|
+
self.procedural = procedural
|
60
|
+
self.border = border
|
61
|
+
|
62
|
+
def to_json(self) -> dict[str, str | list[str] | bool]: # type: ignore
|
63
|
+
"""Returns dictionary with layer data.
|
64
|
+
|
65
|
+
Returns:
|
66
|
+
dict: Dictionary with layer data."""
|
67
|
+
data = {
|
68
|
+
"name": self.name,
|
69
|
+
"count": self.count,
|
70
|
+
"tags": self.tags,
|
71
|
+
"width": self.width,
|
72
|
+
"color": list(self.color),
|
73
|
+
"exclude_weight": self.exclude_weight,
|
74
|
+
"priority": self.priority,
|
75
|
+
"info_layer": self.info_layer,
|
76
|
+
"usage": self.usage,
|
77
|
+
"background": self.background,
|
78
|
+
"invisible": self.invisible,
|
79
|
+
"procedural": self.procedural,
|
80
|
+
"border": self.border,
|
81
|
+
}
|
82
|
+
|
83
|
+
data = {k: v for k, v in data.items() if v is not None}
|
84
|
+
return data # type: ignore
|
85
|
+
|
86
|
+
@classmethod
|
87
|
+
def from_json(cls, data: dict[str, str | list[str] | bool]) -> Layer:
|
88
|
+
"""Creates a new instance of the class from dictionary.
|
89
|
+
|
90
|
+
Arguments:
|
91
|
+
data (dict[str, str | list[str] | bool]): Dictionary with layer data.
|
92
|
+
|
93
|
+
Returns:
|
94
|
+
Layer: New instance of the class.
|
95
|
+
"""
|
96
|
+
return cls(**data) # type: ignore
|
97
|
+
|
98
|
+
def path(self, weights_directory: str) -> str:
|
99
|
+
"""Returns path to the first texture of the layer.
|
100
|
+
|
101
|
+
Arguments:
|
102
|
+
weights_directory (str): Path to the directory with weights.
|
103
|
+
|
104
|
+
Returns:
|
105
|
+
str: Path to the texture.
|
106
|
+
"""
|
107
|
+
idx = "01" if self.count > 0 else ""
|
108
|
+
weight_postfix = "_weight" if not self.exclude_weight else ""
|
109
|
+
return os.path.join(weights_directory, f"{self.name}{idx}{weight_postfix}.png")
|
110
|
+
|
111
|
+
def path_preview(self, weights_directory: str) -> str:
|
112
|
+
"""Returns path to the preview of the first texture of the layer.
|
113
|
+
|
114
|
+
Arguments:
|
115
|
+
weights_directory (str): Path to the directory with weights.
|
116
|
+
|
117
|
+
Returns:
|
118
|
+
str: Path to the preview.
|
119
|
+
"""
|
120
|
+
return self.path(weights_directory).replace(".png", "_preview.png")
|
121
|
+
|
122
|
+
def get_preview_or_path(self, weights_directory: str) -> str:
|
123
|
+
"""Returns path to the preview of the first texture of the layer if it exists,
|
124
|
+
otherwise returns path to the texture.
|
125
|
+
|
126
|
+
Arguments:
|
127
|
+
weights_directory (str): Path to the directory with weights.
|
128
|
+
|
129
|
+
Returns:
|
130
|
+
str: Path to the preview or texture.
|
131
|
+
"""
|
132
|
+
preview_path = self.path_preview(weights_directory)
|
133
|
+
return preview_path if os.path.isfile(preview_path) else self.path(weights_directory)
|
134
|
+
|
135
|
+
def paths(self, weights_directory: str) -> list[str]:
|
136
|
+
"""Returns a list of paths to the textures of the layer.
|
137
|
+
NOTE: Works only after the textures are generated, since it just lists the directory.
|
138
|
+
|
139
|
+
Arguments:
|
140
|
+
weights_directory (str): Path to the directory with weights.
|
141
|
+
|
142
|
+
Returns:
|
143
|
+
list[str]: List of paths to the textures.
|
144
|
+
"""
|
145
|
+
weight_files = os.listdir(weights_directory)
|
146
|
+
|
147
|
+
# Inconsistent names are the name of textures that are not following the pattern
|
148
|
+
# of texture_name{idx}_weight.png.
|
149
|
+
inconsistent_names = ["forestRockRoot", "waterPuddle"]
|
150
|
+
|
151
|
+
if self.name in inconsistent_names:
|
152
|
+
return [
|
153
|
+
os.path.join(weights_directory, weight_file)
|
154
|
+
for weight_file in weight_files
|
155
|
+
if weight_file.startswith(self.name)
|
156
|
+
]
|
157
|
+
|
158
|
+
return [
|
159
|
+
os.path.join(weights_directory, weight_file)
|
160
|
+
for weight_file in weight_files
|
161
|
+
if re.match(rf"{self.name}\d{{2}}_weight.png", weight_file)
|
162
|
+
]
|
@@ -4,7 +4,6 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
import json
|
6
6
|
import os
|
7
|
-
import re
|
8
7
|
import shutil
|
9
8
|
import warnings
|
10
9
|
from collections import defaultdict
|
@@ -14,18 +13,18 @@ import cv2
|
|
14
13
|
import numpy as np
|
15
14
|
import osmnx as ox
|
16
15
|
import pandas as pd
|
17
|
-
import shapely.geometry # type: ignore
|
18
16
|
from osmnx import settings as ox_settings
|
19
|
-
from shapely
|
17
|
+
from shapely import LineString, Point, Polygon
|
18
|
+
from shapely.geometry.base import BaseGeometry
|
20
19
|
from tqdm import tqdm
|
21
20
|
|
22
|
-
from maps4fs.generator.component.base.
|
21
|
+
from maps4fs.generator.component.base.component_image import ImageComponent
|
22
|
+
from maps4fs.generator.component.layer import Layer
|
23
23
|
|
24
24
|
PREVIEW_MAXIMUM_SIZE = 2048
|
25
25
|
|
26
26
|
|
27
|
-
|
28
|
-
class Texture(Component):
|
27
|
+
class Texture(ImageComponent):
|
29
28
|
"""Class which generates textures for the map using OSM data.
|
30
29
|
|
31
30
|
Attributes:
|
@@ -36,167 +35,43 @@ class Texture(Component):
|
|
36
35
|
color (tuple[int, int, int]): Color of the layer in BGR format.
|
37
36
|
"""
|
38
37
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
38
|
+
def preprocess(self) -> None:
|
39
|
+
"""Preprocesses the data before the generation."""
|
40
|
+
self.read_layers(self.get_schema())
|
41
|
+
|
42
|
+
self._weights_dir = self.game.weights_dir_path(self.map_directory)
|
43
|
+
self.procedural_dir = os.path.join(self._weights_dir, "masks")
|
44
|
+
os.makedirs(self.procedural_dir, exist_ok=True)
|
45
|
+
|
46
|
+
self.info_save_path = os.path.join(self.map_directory, "generation_info.json")
|
47
|
+
self.info_layer_path = os.path.join(self.info_layers_directory, "textures.json")
|
48
|
+
|
49
|
+
def read_layers(self, layers_schema: list[dict[str, Any]]) -> None:
|
50
|
+
"""Reads layers from the schema.
|
43
51
|
|
44
52
|
Arguments:
|
45
|
-
|
46
|
-
tags (dict[str, str | list[str]]): Dictionary of tags to search for.
|
47
|
-
width (int | None): Width of the polygon in meters (only for LineString).
|
48
|
-
color (tuple[int, int, int]): Color of the layer in BGR format.
|
49
|
-
exclude_weight (bool): Flag to exclude weight from the texture.
|
50
|
-
priority (int | None): Priority of the layer.
|
51
|
-
info_layer (str | None): Name of the corresnponding info layer.
|
52
|
-
usage (str | None): Usage of the layer.
|
53
|
-
background (bool): Flag to determine if the layer is a background.
|
54
|
-
invisible (bool): Flag to determine if the layer is invisible.
|
55
|
-
|
56
|
-
Attributes:
|
57
|
-
name (str): Name of the layer.
|
58
|
-
tags (dict[str, str | list[str]]): Dictionary of tags to search for.
|
59
|
-
width (int | None): Width of the polygon in meters (only for LineString).
|
53
|
+
layers_schema (list[dict[str, Any]]): Schema with layers for textures.
|
60
54
|
"""
|
55
|
+
try:
|
56
|
+
self.layers = [Layer.from_json(layer) for layer in layers_schema]
|
57
|
+
self.logger.debug("Loaded %s layers.", len(self.layers))
|
58
|
+
except Exception as e:
|
59
|
+
raise ValueError(f"Error loading texture layers: {e}") from e
|
61
60
|
|
62
|
-
|
63
|
-
|
64
|
-
self,
|
65
|
-
name: str,
|
66
|
-
count: int,
|
67
|
-
tags: dict[str, str | list[str] | bool] | None = None,
|
68
|
-
width: int | None = None,
|
69
|
-
color: tuple[int, int, int] | list[int] | None = None,
|
70
|
-
exclude_weight: bool = False,
|
71
|
-
priority: int | None = None,
|
72
|
-
info_layer: str | None = None,
|
73
|
-
usage: str | None = None,
|
74
|
-
background: bool = False,
|
75
|
-
invisible: bool = False,
|
76
|
-
procedural: list[str] | None = None,
|
77
|
-
border: int | None = None,
|
78
|
-
):
|
79
|
-
self.name = name
|
80
|
-
self.count = count
|
81
|
-
self.tags = tags
|
82
|
-
self.width = width
|
83
|
-
self.color = color if color else (255, 255, 255)
|
84
|
-
self.exclude_weight = exclude_weight
|
85
|
-
self.priority = priority
|
86
|
-
self.info_layer = info_layer
|
87
|
-
self.usage = usage
|
88
|
-
self.background = background
|
89
|
-
self.invisible = invisible
|
90
|
-
self.procedural = procedural
|
91
|
-
self.border = border
|
92
|
-
|
93
|
-
def to_json(self) -> dict[str, str | list[str] | bool]: # type: ignore
|
94
|
-
"""Returns dictionary with layer data.
|
95
|
-
|
96
|
-
Returns:
|
97
|
-
dict: Dictionary with layer data."""
|
98
|
-
data = {
|
99
|
-
"name": self.name,
|
100
|
-
"count": self.count,
|
101
|
-
"tags": self.tags,
|
102
|
-
"width": self.width,
|
103
|
-
"color": list(self.color),
|
104
|
-
"exclude_weight": self.exclude_weight,
|
105
|
-
"priority": self.priority,
|
106
|
-
"info_layer": self.info_layer,
|
107
|
-
"usage": self.usage,
|
108
|
-
"background": self.background,
|
109
|
-
"invisible": self.invisible,
|
110
|
-
"procedural": self.procedural,
|
111
|
-
"border": self.border,
|
112
|
-
}
|
113
|
-
|
114
|
-
data = {k: v for k, v in data.items() if v is not None}
|
115
|
-
return data # type: ignore
|
116
|
-
|
117
|
-
@classmethod
|
118
|
-
def from_json(cls, data: dict[str, str | list[str] | bool]) -> Texture.Layer:
|
119
|
-
"""Creates a new instance of the class from dictionary.
|
120
|
-
|
121
|
-
Arguments:
|
122
|
-
data (dict[str, str | list[str] | bool]): Dictionary with layer data.
|
123
|
-
|
124
|
-
Returns:
|
125
|
-
Layer: New instance of the class.
|
126
|
-
"""
|
127
|
-
return cls(**data) # type: ignore
|
128
|
-
|
129
|
-
def path(self, weights_directory: str) -> str:
|
130
|
-
"""Returns path to the first texture of the layer.
|
131
|
-
|
132
|
-
Arguments:
|
133
|
-
weights_directory (str): Path to the directory with weights.
|
134
|
-
|
135
|
-
Returns:
|
136
|
-
str: Path to the texture.
|
137
|
-
"""
|
138
|
-
idx = "01" if self.count > 0 else ""
|
139
|
-
weight_postfix = "_weight" if not self.exclude_weight else ""
|
140
|
-
return os.path.join(weights_directory, f"{self.name}{idx}{weight_postfix}.png")
|
141
|
-
|
142
|
-
def path_preview(self, weights_directory: str) -> str:
|
143
|
-
"""Returns path to the preview of the first texture of the layer.
|
144
|
-
|
145
|
-
Arguments:
|
146
|
-
weights_directory (str): Path to the directory with weights.
|
147
|
-
|
148
|
-
Returns:
|
149
|
-
str: Path to the preview.
|
150
|
-
"""
|
151
|
-
return self.path(weights_directory).replace(".png", "_preview.png")
|
152
|
-
|
153
|
-
def get_preview_or_path(self, weights_directory: str) -> str:
|
154
|
-
"""Returns path to the preview of the first texture of the layer if it exists,
|
155
|
-
otherwise returns path to the texture.
|
156
|
-
|
157
|
-
Arguments:
|
158
|
-
weights_directory (str): Path to the directory with weights.
|
159
|
-
|
160
|
-
Returns:
|
161
|
-
str: Path to the preview or texture.
|
162
|
-
"""
|
163
|
-
preview_path = self.path_preview(weights_directory)
|
164
|
-
return preview_path if os.path.isfile(preview_path) else self.path(weights_directory)
|
165
|
-
|
166
|
-
def paths(self, weights_directory: str) -> list[str]:
|
167
|
-
"""Returns a list of paths to the textures of the layer.
|
168
|
-
NOTE: Works only after the textures are generated, since it just lists the directory.
|
169
|
-
|
170
|
-
Arguments:
|
171
|
-
weights_directory (str): Path to the directory with weights.
|
172
|
-
|
173
|
-
Returns:
|
174
|
-
list[str]: List of paths to the textures.
|
175
|
-
"""
|
176
|
-
weight_files = os.listdir(weights_directory)
|
177
|
-
|
178
|
-
# Inconsistent names are the name of textures that are not following the pattern
|
179
|
-
# of texture_name{idx}_weight.png.
|
180
|
-
inconsistent_names = ["forestRockRoot", "waterPuddle"]
|
181
|
-
|
182
|
-
if self.name in inconsistent_names:
|
183
|
-
return [
|
184
|
-
os.path.join(weights_directory, weight_file)
|
185
|
-
for weight_file in weight_files
|
186
|
-
if weight_file.startswith(self.name)
|
187
|
-
]
|
188
|
-
|
189
|
-
return [
|
190
|
-
os.path.join(weights_directory, weight_file)
|
191
|
-
for weight_file in weight_files
|
192
|
-
if re.match(rf"{self.name}\d{{2}}_weight.png", weight_file)
|
193
|
-
]
|
61
|
+
def get_schema(self) -> list[dict[str, Any]]:
|
62
|
+
"""Returns schema with layers for textures.
|
194
63
|
|
195
|
-
|
196
|
-
|
64
|
+
Raises:
|
65
|
+
FileNotFoundError: If the schema file is not found.
|
66
|
+
ValueError: If there is an error loading the schema.
|
67
|
+
ValueError: If the schema is not a list of dictionaries.
|
68
|
+
|
69
|
+
Returns:
|
70
|
+
dict[str, Any]: Schema with layers for textures.
|
71
|
+
"""
|
197
72
|
custom_schema = self.kwargs.get("texture_custom_schema")
|
198
73
|
if custom_schema:
|
199
|
-
layers_schema = custom_schema
|
74
|
+
layers_schema = custom_schema
|
200
75
|
self.logger.debug("Custom schema loaded with %s layers.", len(layers_schema))
|
201
76
|
else:
|
202
77
|
if not os.path.isfile(self.game.texture_schema):
|
@@ -210,27 +85,10 @@ class Texture(Component):
|
|
210
85
|
except json.JSONDecodeError as e:
|
211
86
|
raise ValueError(f"Error loading texture layers schema: {e}") from e
|
212
87
|
|
213
|
-
|
214
|
-
|
215
|
-
self.logger.debug("Loaded %s layers.", len(self.layers))
|
216
|
-
except Exception as e: # pylint: disable=W0703
|
217
|
-
raise ValueError(f"Error loading texture layers: {e}") from e
|
218
|
-
|
219
|
-
base_layer = self.get_base_layer()
|
220
|
-
if base_layer:
|
221
|
-
self.logger.debug("Base layer found: %s.", base_layer.name)
|
88
|
+
if not isinstance(layers_schema, list):
|
89
|
+
raise ValueError("Texture layers schema must be a list of dictionaries.")
|
222
90
|
|
223
|
-
|
224
|
-
self.logger.debug("Weights directory: %s.", self._weights_dir)
|
225
|
-
self.procedural_dir = os.path.join(self._weights_dir, "masks")
|
226
|
-
os.makedirs(self.procedural_dir, exist_ok=True)
|
227
|
-
self.logger.debug("Procedural directory: %s.", self.procedural_dir)
|
228
|
-
|
229
|
-
self.info_save_path = os.path.join(self.map_directory, "generation_info.json")
|
230
|
-
self.logger.debug("Generation info save path: %s.", self.info_save_path)
|
231
|
-
|
232
|
-
self.info_layer_path = os.path.join(self.info_layers_directory, "textures.json")
|
233
|
-
self.logger.debug("Info layer path: %s.", self.info_layer_path)
|
91
|
+
return layers_schema
|
234
92
|
|
235
93
|
def get_base_layer(self) -> Layer | None:
|
236
94
|
"""Returns base layer.
|
@@ -272,9 +130,7 @@ class Texture(Component):
|
|
272
130
|
self.draw()
|
273
131
|
self.rotate_textures()
|
274
132
|
self.add_borders()
|
275
|
-
if self.map.texture_settings.dissolve and self.game.
|
276
|
-
# FS22 has textures splitted into 4 sublayers, which leads to a very
|
277
|
-
# long processing time when dissolving them.
|
133
|
+
if self.map.texture_settings.dissolve and self.game.dissolve:
|
278
134
|
self.dissolve()
|
279
135
|
self.copy_procedural()
|
280
136
|
|
@@ -297,31 +153,16 @@ class Texture(Component):
|
|
297
153
|
# And set it to 0 in the current layer image.
|
298
154
|
layer_image = cv2.imread(layer.path(self._weights_dir), cv2.IMREAD_UNCHANGED)
|
299
155
|
border = layer.border
|
300
|
-
if border
|
156
|
+
if not border:
|
301
157
|
continue
|
302
158
|
|
303
|
-
|
304
|
-
right = layer_image[:, -border:] # type: ignore
|
305
|
-
bottom = layer_image[-border:, :] # type: ignore
|
306
|
-
left = layer_image[:, :border] # type: ignore
|
307
|
-
|
308
|
-
if base_layer_image is not None:
|
309
|
-
base_layer_image[:border, :][top != 0] = 255 # type: ignore
|
310
|
-
base_layer_image[:, -border:][right != 0] = 255 # type: ignore
|
311
|
-
base_layer_image[-border:, :][bottom != 0] = 255 # type: ignore
|
312
|
-
base_layer_image[:, :border][left != 0] = 255 # type: ignore
|
313
|
-
|
314
|
-
layer_image[:border, :] = 0 # type: ignore
|
315
|
-
layer_image[:, -border:] = 0 # type: ignore
|
316
|
-
layer_image[-border:, :] = 0 # type: ignore
|
317
|
-
layer_image[:, :border] = 0 # type: ignore
|
159
|
+
self.transfer_border(layer_image, base_layer_image, border)
|
318
160
|
|
319
161
|
cv2.imwrite(layer.path(self._weights_dir), layer_image)
|
320
162
|
self.logger.debug("Borders added to layer %s.", layer.name)
|
321
163
|
|
322
164
|
if base_layer_image is not None:
|
323
165
|
cv2.imwrite(base_layer.path(self._weights_dir), base_layer_image) # type: ignore
|
324
|
-
self.logger.debug("Borders added to base layer %s.", base_layer.name) # type: ignore
|
325
166
|
|
326
167
|
def copy_procedural(self) -> None:
|
327
168
|
"""Copies some of the textures to use them as mask for procedural generation.
|
@@ -350,7 +191,6 @@ class Texture(Component):
|
|
350
191
|
# If there are more than one texture, merge them.
|
351
192
|
merged_texture = np.zeros((self.map_size, self.map_size), dtype=np.uint8)
|
352
193
|
for texture_path in texture_paths:
|
353
|
-
# pylint: disable=E1101
|
354
194
|
texture = cv2.imread(texture_path, cv2.IMREAD_UNCHANGED)
|
355
195
|
merged_texture[texture == 255] = 255
|
356
196
|
cv2.imwrite(procedural_save_path, merged_texture)
|
@@ -390,12 +230,9 @@ class Texture(Component):
|
|
390
230
|
"Skipping rotation of layer %s because it has no tags.", layer.name
|
391
231
|
)
|
392
232
|
|
393
|
-
# pylint: disable=W0201
|
394
233
|
def _read_parameters(self) -> None:
|
395
234
|
"""Reads map parameters from OSM data, such as:
|
396
235
|
- minimum and maximum coordinates
|
397
|
-
- map dimensions in meters
|
398
|
-
- map coefficients (meters per pixel)
|
399
236
|
"""
|
400
237
|
bbox = ox.utils_geo.bbox_from_point(self.coordinates, dist=self.map_rotated_size / 2)
|
401
238
|
self.minimum_x, self.minimum_y, self.maximum_x, self.maximum_y = bbox
|
@@ -480,20 +317,16 @@ class Texture(Component):
|
|
480
317
|
),
|
481
318
|
)
|
482
319
|
|
483
|
-
# pylint: disable = R0912
|
484
320
|
def draw(self) -> None:
|
485
321
|
"""Iterates over layers and fills them with polygons from OSM data."""
|
486
322
|
layers = self.layers_by_priority()
|
487
|
-
|
488
|
-
self.logger.debug(
|
489
|
-
"Sorted layers by priority: %s.", [(layer.name, layer.priority) for layer in layers]
|
490
|
-
)
|
323
|
+
layers = [layer for layer in layers if layer.tags is not None]
|
491
324
|
|
492
325
|
cumulative_image = None
|
493
326
|
|
494
327
|
# Dictionary to store info layer data.
|
495
328
|
# Key is a layer.info_layer, value is a list of polygon points as tuples (x, y).
|
496
|
-
info_layer_data = defaultdict(list)
|
329
|
+
info_layer_data: dict[str, list[list[int]]] = defaultdict(list)
|
497
330
|
|
498
331
|
for layer in tqdm(
|
499
332
|
layers, desc="Drawing textures", unit="layer", disable=self.map.is_public
|
@@ -501,9 +334,6 @@ class Texture(Component):
|
|
501
334
|
if self.map.texture_settings.skip_drains and layer.usage == "drain":
|
502
335
|
self.logger.debug("Skipping layer %s because of the usage.", layer.name)
|
503
336
|
continue
|
504
|
-
if not layer.tags:
|
505
|
-
self.logger.debug("Layer %s has no tags, there's nothing to draw.", layer.name)
|
506
|
-
continue
|
507
337
|
if layer.priority == 0:
|
508
338
|
self.logger.debug(
|
509
339
|
"Found base layer %s. Postponing that to be the last layer drawn.", layer.name
|
@@ -518,34 +348,10 @@ class Texture(Component):
|
|
518
348
|
cumulative_image = layer_image
|
519
349
|
|
520
350
|
mask = cv2.bitwise_not(cumulative_image)
|
521
|
-
|
522
|
-
|
523
|
-
layer.tags, layer.width, layer.info_layer
|
524
|
-
):
|
525
|
-
if not len(polygon) > 2:
|
526
|
-
self.logger.debug("Skipping polygon with less than 3 points.")
|
527
|
-
continue
|
528
|
-
if layer.info_layer:
|
529
|
-
info_layer_data[layer.info_layer].append(
|
530
|
-
self.np_to_polygon_points(polygon) # type: ignore
|
531
|
-
)
|
532
|
-
if not layer.invisible:
|
533
|
-
try:
|
534
|
-
cv2.fillPoly(layer_image, [polygon], color=255) # type: ignore
|
535
|
-
except Exception as e: # pylint: disable=W0718
|
536
|
-
self.logger.warning("Error drawing polygon: %s.", repr(e))
|
537
|
-
continue
|
538
|
-
|
539
|
-
if layer.info_layer == "roads":
|
540
|
-
for linestring in self.objects_generator(
|
541
|
-
layer.tags, layer.width, layer.info_layer, yield_linestrings=True
|
542
|
-
):
|
543
|
-
info_layer_data[f"{layer.info_layer}_polylines"].append(
|
544
|
-
linestring # type: ignore
|
545
|
-
)
|
351
|
+
self._draw_layer(layer, info_layer_data, layer_image)
|
352
|
+
self._add_roads(layer, info_layer_data)
|
546
353
|
|
547
354
|
output_image = cv2.bitwise_and(layer_image, mask)
|
548
|
-
|
549
355
|
cumulative_image = cv2.bitwise_or(cumulative_image, output_image)
|
550
356
|
|
551
357
|
cv2.imwrite(layer_path, output_image)
|
@@ -566,6 +372,44 @@ class Texture(Component):
|
|
566
372
|
if cumulative_image is not None:
|
567
373
|
self.draw_base_layer(cumulative_image)
|
568
374
|
|
375
|
+
def _draw_layer(
|
376
|
+
self, layer: Layer, info_layer_data: dict[str, list[list[int]]], layer_image: np.ndarray
|
377
|
+
) -> None:
|
378
|
+
"""Draws polygons from OSM data on the layer image and updates the info layer data.
|
379
|
+
|
380
|
+
Arguments:
|
381
|
+
layer (Layer): Layer with textures and tags.
|
382
|
+
info_layer_data (dict[list[list[int]]]): Dictionary to store info layer data.
|
383
|
+
layer_image (np.ndarray): Layer image.
|
384
|
+
"""
|
385
|
+
for polygon in self.objects_generator(layer.tags, layer.width, layer.info_layer):
|
386
|
+
if not len(polygon) > 2:
|
387
|
+
self.logger.debug("Skipping polygon with less than 3 points.")
|
388
|
+
continue
|
389
|
+
if layer.info_layer:
|
390
|
+
info_layer_data[layer.info_layer].append(
|
391
|
+
self.np_to_polygon_points(polygon) # type: ignore
|
392
|
+
)
|
393
|
+
if not layer.invisible:
|
394
|
+
try:
|
395
|
+
cv2.fillPoly(layer_image, [polygon], color=255) # type: ignore
|
396
|
+
except Exception as e:
|
397
|
+
self.logger.warning("Error drawing polygon: %s.", repr(e))
|
398
|
+
continue
|
399
|
+
|
400
|
+
def _add_roads(self, layer: Layer, info_layer_data: dict[str, list[list[int]]]) -> None:
|
401
|
+
"""Adds roads to the info layer data.
|
402
|
+
|
403
|
+
Arguments:
|
404
|
+
layer (Layer): Layer with textures and tags.
|
405
|
+
info_layer_data (dict[list[list[int]]]): Dictionary to store info layer data.
|
406
|
+
"""
|
407
|
+
if layer.info_layer == "roads":
|
408
|
+
for linestring in self.objects_generator(
|
409
|
+
layer.tags, layer.width, layer.info_layer, yield_linestrings=True
|
410
|
+
):
|
411
|
+
info_layer_data[f"{layer.info_layer}_polylines"].append(linestring) # type: ignore
|
412
|
+
|
569
413
|
def dissolve(self) -> None:
|
570
414
|
"""Dissolves textures of the layers with tags into sublayers for them to look more
|
571
415
|
natural in the game.
|
@@ -657,12 +501,11 @@ class Texture(Component):
|
|
657
501
|
"""
|
658
502
|
return [(int(x), int(y)) for x, y in np_array.reshape(-1, 2)]
|
659
503
|
|
660
|
-
|
661
|
-
def _to_np(self, geometry: shapely.geometry.polygon.Polygon, *args) -> np.ndarray:
|
504
|
+
def _to_np(self, geometry: Polygon, *args) -> np.ndarray:
|
662
505
|
"""Converts Polygon geometry to numpy array of polygon points.
|
663
506
|
|
664
507
|
Arguments:
|
665
|
-
geometry (
|
508
|
+
geometry (Polygon): Polygon geometry.
|
666
509
|
*Arguments: Additional arguments:
|
667
510
|
- width (int | None): Width of the polygon in meters.
|
668
511
|
|
@@ -670,24 +513,19 @@ class Texture(Component):
|
|
670
513
|
np.ndarray: Numpy array of polygon points.
|
671
514
|
"""
|
672
515
|
coords = list(geometry.exterior.coords)
|
673
|
-
pts = np.array(
|
674
|
-
[self.latlon_to_pixel(coord[1], coord[0]) for coord in coords],
|
675
|
-
np.int32,
|
676
|
-
)
|
516
|
+
pts = np.array(coords, np.int32)
|
677
517
|
pts = pts.reshape((-1, 1, 2))
|
678
518
|
return pts
|
679
519
|
|
680
|
-
def _to_polygon(
|
681
|
-
|
682
|
-
) -> shapely.geometry.polygon.Polygon:
|
683
|
-
"""Converts OSM object to numpy array of polygon points.
|
520
|
+
def _to_polygon(self, obj: pd.core.series.Series, width: int | None) -> Polygon:
|
521
|
+
"""Converts OSM object to numpy array of polygon points and converts coordinates to pixels.
|
684
522
|
|
685
523
|
Arguments:
|
686
524
|
obj (pd.core.series.Series): OSM object.
|
687
525
|
width (int | None): Width of the polygon in meters.
|
688
526
|
|
689
527
|
Returns:
|
690
|
-
|
528
|
+
Polygon: Polygon geometry with pixel coordinates.
|
691
529
|
"""
|
692
530
|
geometry = obj["geometry"]
|
693
531
|
geometry_type = geometry.geom_type
|
@@ -697,47 +535,80 @@ class Texture(Component):
|
|
697
535
|
return None
|
698
536
|
return converter(geometry, width)
|
699
537
|
|
700
|
-
def
|
701
|
-
|
702
|
-
geometry: shapely.geometry.linestring.LineString | shapely.geometry.point.Point,
|
703
|
-
width: int | None,
|
704
|
-
) -> shapely.geometry.polygon.Polygon:
|
705
|
-
"""Converts LineString or Point geometry to numpy array of polygon points.
|
538
|
+
def polygon_to_pixel_coordinates(self, polygon: Polygon) -> Polygon:
|
539
|
+
"""Converts polygon coordinates from lat lon to pixel coordinates.
|
706
540
|
|
707
541
|
Arguments:
|
708
|
-
|
709
|
-
LineString or Point geometry.
|
710
|
-
width (int | None): Width of the polygon in meters.
|
542
|
+
polygon (Polygon): Polygon geometry.
|
711
543
|
|
712
544
|
Returns:
|
713
|
-
|
545
|
+
Polygon: Polygon geometry.
|
714
546
|
"""
|
715
|
-
|
716
|
-
|
547
|
+
coords_pixel = [
|
548
|
+
self.latlon_to_pixel(lat, lon) for lon, lat in list(polygon.exterior.coords)
|
549
|
+
]
|
550
|
+
return Polygon(coords_pixel)
|
717
551
|
|
718
|
-
def
|
719
|
-
"""Converts
|
552
|
+
def linestring_to_pixel_coordinates(self, linestring: LineString) -> LineString:
|
553
|
+
"""Converts LineString coordinates from lat lon to pixel coordinates.
|
720
554
|
|
721
555
|
Arguments:
|
722
|
-
|
556
|
+
linestring (LineString): LineString geometry.
|
723
557
|
|
724
558
|
Returns:
|
725
|
-
|
559
|
+
LineString: LineString geometry.
|
726
560
|
"""
|
727
|
-
|
561
|
+
coords_pixel = [self.latlon_to_pixel(lat, lon) for lon, lat in list(linestring.coords)]
|
562
|
+
return LineString(coords_pixel)
|
728
563
|
|
729
|
-
def
|
730
|
-
|
731
|
-
) -> shapely.geometry.polygon.Polygon:
|
732
|
-
"""Returns the same geometry.
|
564
|
+
def point_to_pixel_coordinates(self, point: Point) -> Point:
|
565
|
+
"""Converts Point coordinates from lat lon to pixel coordinates.
|
733
566
|
|
734
567
|
Arguments:
|
735
|
-
|
568
|
+
point (Point): Point geometry.
|
736
569
|
|
737
570
|
Returns:
|
738
|
-
|
571
|
+
Point: Point geometry.
|
739
572
|
"""
|
740
|
-
|
573
|
+
x, y = self.latlon_to_pixel(point.y, point.x)
|
574
|
+
return Point(x, y)
|
575
|
+
|
576
|
+
def _to_pixel(self, geometry: Polygon, *args, **kwargs) -> Polygon:
|
577
|
+
"""Returns the same geometry with pixel coordinates.
|
578
|
+
|
579
|
+
Arguments:
|
580
|
+
geometry (Polygon): Polygon geometry.
|
581
|
+
|
582
|
+
Returns:
|
583
|
+
Polygon: Polygon geometry with pixel coordinates.
|
584
|
+
"""
|
585
|
+
return self.polygon_to_pixel_coordinates(geometry)
|
586
|
+
|
587
|
+
def _sequence_to_pixel(
|
588
|
+
self,
|
589
|
+
geometry: LineString | Point,
|
590
|
+
width: int | None,
|
591
|
+
) -> Polygon:
|
592
|
+
"""Converts LineString or Point geometry to numpy array of polygon points.
|
593
|
+
|
594
|
+
Arguments:
|
595
|
+
geometry (LineString | Point): LineString or Point geometry.
|
596
|
+
width (int | None): Width of the polygon in meters.
|
597
|
+
|
598
|
+
Raises:
|
599
|
+
ValueError: If the geometry type is not supported
|
600
|
+
|
601
|
+
Returns:
|
602
|
+
Polygon: Polygon geometry.
|
603
|
+
"""
|
604
|
+
if isinstance(geometry, LineString):
|
605
|
+
geometry = self.linestring_to_pixel_coordinates(geometry)
|
606
|
+
elif isinstance(geometry, Point):
|
607
|
+
geometry = self.point_to_pixel_coordinates(geometry)
|
608
|
+
else:
|
609
|
+
raise ValueError(f"Geometry type {type(geometry)} not supported.")
|
610
|
+
|
611
|
+
return geometry.buffer(width if width else 0)
|
741
612
|
|
742
613
|
def _converters(
|
743
614
|
self, geom_type: str
|
@@ -750,12 +621,16 @@ class Texture(Component):
|
|
750
621
|
Returns:
|
751
622
|
Callable[[shapely.geometry, int | None], np.ndarray]: Converter function.
|
752
623
|
"""
|
753
|
-
converters = {
|
624
|
+
converters = {
|
625
|
+
"Polygon": self._to_pixel,
|
626
|
+
"LineString": self._sequence_to_pixel,
|
627
|
+
"Point": self._sequence_to_pixel,
|
628
|
+
}
|
754
629
|
return converters.get(geom_type) # type: ignore
|
755
630
|
|
756
631
|
def objects_generator(
|
757
632
|
self,
|
758
|
-
tags: dict[str, str | list[str] | bool],
|
633
|
+
tags: dict[str, str | list[str] | bool] | None,
|
759
634
|
width: int | None,
|
760
635
|
info_layer: str | None = None,
|
761
636
|
yield_linestrings: bool = False,
|
@@ -772,6 +647,8 @@ class Texture(Component):
|
|
772
647
|
Generator[np.ndarray, None, None] | Generator[list[tuple[int, int]], None, None]:
|
773
648
|
Numpy array of polygon points or list of point coordinates.
|
774
649
|
"""
|
650
|
+
if tags is None:
|
651
|
+
return
|
775
652
|
is_fieds = info_layer == "fields"
|
776
653
|
|
777
654
|
ox_settings.use_cache = self.map.texture_settings.use_cache
|
@@ -784,7 +661,7 @@ class Texture(Component):
|
|
784
661
|
objects = ox.features_from_xml(self.map.custom_osm, tags=tags)
|
785
662
|
else:
|
786
663
|
objects = ox.features_from_bbox(bbox=self.new_bbox, tags=tags)
|
787
|
-
except Exception as e:
|
664
|
+
except Exception as e:
|
788
665
|
self.logger.debug("Error fetching objects for tags: %s. Error: %s.", tags, e)
|
789
666
|
return
|
790
667
|
self.logger.debug("Fetched %s elements for tags: %s.", len(objects), tags)
|
@@ -806,7 +683,7 @@ class Texture(Component):
|
|
806
683
|
"""
|
807
684
|
for _, obj in objects.iterrows():
|
808
685
|
geometry = obj["geometry"]
|
809
|
-
if isinstance(geometry,
|
686
|
+
if isinstance(geometry, LineString):
|
810
687
|
points = [self.latlon_to_pixel(x, y) for y, x in geometry.coords]
|
811
688
|
yield points
|
812
689
|
|
@@ -826,20 +703,18 @@ class Texture(Component):
|
|
826
703
|
for _, obj in objects.iterrows():
|
827
704
|
try:
|
828
705
|
polygon = self._to_polygon(obj, width)
|
829
|
-
except Exception as e:
|
706
|
+
except Exception as e:
|
830
707
|
self.logger.warning("Error converting object to polygon: %s.", e)
|
831
708
|
continue
|
832
709
|
if polygon is None:
|
833
710
|
continue
|
834
711
|
|
835
712
|
if is_fieds and self.map.texture_settings.fields_padding > 0:
|
836
|
-
padded_polygon = polygon.buffer(
|
837
|
-
-self.meters_to_degrees(self.map.texture_settings.fields_padding)
|
838
|
-
)
|
713
|
+
padded_polygon = polygon.buffer(-self.map.texture_settings.fields_padding)
|
839
714
|
|
840
|
-
if not isinstance(padded_polygon,
|
841
|
-
|
842
|
-
|
715
|
+
if not isinstance(padded_polygon, Polygon) or not list(
|
716
|
+
padded_polygon.exterior.coords
|
717
|
+
):
|
843
718
|
self.logger.debug("The padding value is too high, field will not padded.")
|
844
719
|
else:
|
845
720
|
polygon = padded_polygon
|
maps4fs/generator/game.py
CHANGED
@@ -11,7 +11,7 @@ from maps4fs.generator.component.config import Config
|
|
11
11
|
from maps4fs.generator.component.grle import GRLE
|
12
12
|
from maps4fs.generator.component.i3d import I3d
|
13
13
|
from maps4fs.generator.component.satellite import Satellite
|
14
|
-
from maps4fs.generator.texture import Texture
|
14
|
+
from maps4fs.generator.component.texture import Texture
|
15
15
|
|
16
16
|
working_directory = os.getcwd()
|
17
17
|
|
@@ -40,6 +40,7 @@ class Game:
|
|
40
40
|
_tree_schema: str | None = None
|
41
41
|
_i3d_processing: bool = True
|
42
42
|
_plants_processing: bool = True
|
43
|
+
_dissolve: bool = True
|
43
44
|
|
44
45
|
# Order matters! Some components depend on others.
|
45
46
|
components = [Texture, Background, GRLE, I3d, Config, Satellite]
|
@@ -225,8 +226,15 @@ class Game:
|
|
225
226
|
i3d_base_directory = os.path.dirname(self.i3d_file_path(map_directory))
|
226
227
|
return os.path.join(i3d_base_directory, "splines.i3d")
|
227
228
|
|
229
|
+
@property
|
230
|
+
def dissolve(self) -> bool:
|
231
|
+
"""Returns whether the dissolve should be applied.
|
232
|
+
|
233
|
+
Returns:
|
234
|
+
bool: True if the dissolve should be applied, False otherwise."""
|
235
|
+
return self._dissolve
|
236
|
+
|
228
237
|
|
229
|
-
# pylint: disable=W0223
|
230
238
|
class FS22(Game):
|
231
239
|
"""Class used to define the game version FS22."""
|
232
240
|
|
@@ -235,6 +243,7 @@ class FS22(Game):
|
|
235
243
|
_texture_schema = os.path.join(working_directory, "data", "fs22-texture-schema.json")
|
236
244
|
_i3d_processing = False
|
237
245
|
_plants_processing = False
|
246
|
+
_dissolve = False
|
238
247
|
|
239
248
|
def dem_file_path(self, map_directory: str) -> str:
|
240
249
|
"""Returns the path to the DEM file.
|
maps4fs/generator/map.py
CHANGED
@@ -9,6 +9,8 @@ from typing import Any, Generator
|
|
9
9
|
|
10
10
|
from maps4fs.generator.component.background import Background
|
11
11
|
from maps4fs.generator.component.base.component import Component
|
12
|
+
from maps4fs.generator.component.layer import Layer
|
13
|
+
from maps4fs.generator.component.texture import Texture
|
12
14
|
from maps4fs.generator.dtm.dtm import DTMProvider, DTMProviderSettings
|
13
15
|
from maps4fs.generator.game import Game
|
14
16
|
from maps4fs.generator.settings import (
|
@@ -21,7 +23,6 @@ from maps4fs.generator.settings import (
|
|
21
23
|
SplineSettings,
|
22
24
|
TextureSettings,
|
23
25
|
)
|
24
|
-
from maps4fs.generator.texture import Texture
|
25
26
|
from maps4fs.logger import Logger
|
26
27
|
|
27
28
|
|
@@ -260,14 +261,14 @@ class Map:
|
|
260
261
|
return None
|
261
262
|
return component
|
262
263
|
|
263
|
-
def get_texture_layer(self, by_usage: str | None = None) ->
|
264
|
+
def get_texture_layer(self, by_usage: str | None = None) -> Layer | None:
|
264
265
|
"""Get texture layer by usage.
|
265
266
|
|
266
267
|
Arguments:
|
267
268
|
by_usage (str, optional): Texture usage.
|
268
269
|
|
269
270
|
Returns:
|
270
|
-
|
271
|
+
Layer | None: Texture layer instance or None if not found.
|
271
272
|
"""
|
272
273
|
texture_component = self.get_texture_component()
|
273
274
|
if not texture_component:
|
@@ -2,20 +2,21 @@ maps4fs/__init__.py,sha256=nKKMY2PGVAluIcIdLp5sgspSDCBDriF3iE8Pd6xyKWI,1563
|
|
2
2
|
maps4fs/logger.py,sha256=HQrDyj72mUjVYo25aR_-_SxVn2rfFjDCNbj-JKJdSnE,1488
|
3
3
|
maps4fs/generator/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
|
4
4
|
maps4fs/generator/dem.py,sha256=dyzCFfDL6W_nCjwi9uF3-PCiL36rfOh3jGXlDuwiJYg,11795
|
5
|
-
maps4fs/generator/game.py,sha256=
|
6
|
-
maps4fs/generator/map.py,sha256=
|
5
|
+
maps4fs/generator/game.py,sha256=NZaxj5z7WzMiHzAvQyr-TvVjGoHgqGldM6ZsItuYyzA,11292
|
6
|
+
maps4fs/generator/map.py,sha256=BV7OTrv3zHud6DbTGpm3TU5JNWJlKgRhZUGfs_wxntw,11513
|
7
7
|
maps4fs/generator/qgis.py,sha256=Es8hLuqN_KH8lDfnJE6He2rWYbAKJ3RGPn-o87S6CPI,6116
|
8
8
|
maps4fs/generator/settings.py,sha256=cFlN-gK8QcySqyPtcGm-2fLnxQnlmC3Y9kQufJxwI3Y,6270
|
9
|
-
maps4fs/generator/texture.py,sha256=kCbTHoHhJPRDLWNhqBF0ElC2bQBPztvaj2qaFrS9vnY,36774
|
10
9
|
maps4fs/generator/component/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
|
11
|
-
maps4fs/generator/component/background.py,sha256=
|
10
|
+
maps4fs/generator/component/background.py,sha256=IF56eQ2eHM37kdK1kz2vEFt-s88Esn9XKex_TbWtUZY,18800
|
12
11
|
maps4fs/generator/component/config.py,sha256=RitKgFDZPzjA1fi8GcEi1na75qqaueUvpcITHjBvCXc,3674
|
13
12
|
maps4fs/generator/component/grle.py,sha256=nDA6vjjfWFL0Hkz6aso1aLIwuksbxzZw9syNax1bD04,19134
|
14
13
|
maps4fs/generator/component/i3d.py,sha256=kJ3Th1mSdBC9j8cyWBwIyYm0fKzYJtocI0jYWkVX3AU,19713
|
14
|
+
maps4fs/generator/component/layer.py,sha256=QPcEzTv_8N9wYvHAZy8OezfATaVLG-YetSfCXf2lnFI,5892
|
15
15
|
maps4fs/generator/component/satellite.py,sha256=xzxqHp-G-jRgyI38-XdaMPdGWiC3PdhVJAjBnZL9wL8,5004
|
16
|
+
maps4fs/generator/component/texture.py,sha256=VqxUmK7HHw-G_C_qS1rb5mSpkzGwI0dAnW3-o5HCGgU,31026
|
16
17
|
maps4fs/generator/component/base/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
|
17
18
|
maps4fs/generator/component/base/component.py,sha256=apGuQ7TcwqL0neJZiciNLGO22wZwYyqoDZM7aI1RHw8,21273
|
18
|
-
maps4fs/generator/component/base/component_image.py,sha256=
|
19
|
+
maps4fs/generator/component/base/component_image.py,sha256=2QnJ9xm0D54v4whg7bc1s-kwRVjZHhOo1OR5jHr1Qp0,4786
|
19
20
|
maps4fs/generator/component/base/component_mesh.py,sha256=43JY8X0ugIWAJq5y11vTJM9UfbL7SSugj8LkdPmni10,8871
|
20
21
|
maps4fs/generator/component/base/component_xml.py,sha256=6OO1dKoceO1ACk7-k1oGtnkfNud8ZN3u3ZNjdNMpTqw,3967
|
21
22
|
maps4fs/generator/dtm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -46,8 +47,8 @@ maps4fs/toolbox/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,4
|
|
46
47
|
maps4fs/toolbox/background.py,sha256=RclEqxEWLbMxuEkkegQP8jybzugwQ1_R3rdfDe0s21U,2104
|
47
48
|
maps4fs/toolbox/custom_osm.py,sha256=X6ZlPqiOhNjkmdD_qVroIfdOl9Rb90cDwVSLDVYgx80,1892
|
48
49
|
maps4fs/toolbox/dem.py,sha256=z9IPFNmYbjiigb3t02ZenI3Mo8odd19c5MZbjDEovTo,3525
|
49
|
-
maps4fs-1.8.
|
50
|
-
maps4fs-1.8.
|
51
|
-
maps4fs-1.8.
|
52
|
-
maps4fs-1.8.
|
53
|
-
maps4fs-1.8.
|
50
|
+
maps4fs-1.8.187.dist-info/LICENSE.md,sha256=pTKD_oUexcn-yccFCTrMeLkZy0ifLRa-VNcDLqLZaIw,10749
|
51
|
+
maps4fs-1.8.187.dist-info/METADATA,sha256=qAJxxbv2n4e87XZeC61IXX6RgOnsYuys4dxFptebSPo,44584
|
52
|
+
maps4fs-1.8.187.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
53
|
+
maps4fs-1.8.187.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
|
54
|
+
maps4fs-1.8.187.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|