maps4fs 0.6.0__tar.gz
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-0.6.0/LICENSE.md +21 -0
- maps4fs-0.6.0/PKG-INFO +147 -0
- maps4fs-0.6.0/README.md +128 -0
- maps4fs-0.6.0/maps4fs/__init__.py +4 -0
- maps4fs-0.6.0/maps4fs/generator/__init__.py +1 -0
- maps4fs-0.6.0/maps4fs/generator/component.py +55 -0
- maps4fs-0.6.0/maps4fs/generator/config.py +44 -0
- maps4fs-0.6.0/maps4fs/generator/dem.py +207 -0
- maps4fs-0.6.0/maps4fs/generator/game.py +140 -0
- maps4fs-0.6.0/maps4fs/generator/map.py +110 -0
- maps4fs-0.6.0/maps4fs/generator/texture.py +535 -0
- maps4fs-0.6.0/maps4fs/logger.py +46 -0
- maps4fs-0.6.0/maps4fs.egg-info/PKG-INFO +147 -0
- maps4fs-0.6.0/maps4fs.egg-info/SOURCES.txt +18 -0
- maps4fs-0.6.0/maps4fs.egg-info/dependency_links.txt +1 -0
- maps4fs-0.6.0/maps4fs.egg-info/requires.txt +3 -0
- maps4fs-0.6.0/maps4fs.egg-info/top_level.txt +1 -0
- maps4fs-0.6.0/pyproject.toml +32 -0
- maps4fs-0.6.0/setup.cfg +4 -0
- maps4fs-0.6.0/tests/test_generator.py +203 -0
maps4fs-0.6.0/LICENSE.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# MIT License
|
2
|
+
|
3
|
+
Copyright © [2024] [iwatkot]
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
maps4fs-0.6.0/PKG-INFO
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: maps4fs
|
3
|
+
Version: 0.6.0
|
4
|
+
Summary: Generate map templates for Farming Simulator from real places.
|
5
|
+
Author-email: iwatkot <iwatkot@gmail.com>
|
6
|
+
License: MIT License
|
7
|
+
Project-URL: Homepage, https://github.com/iwatkot/maps4fs
|
8
|
+
Project-URL: Repository, https://github.com/iwatkot/maps4fs
|
9
|
+
Keywords: farmingsimulator,fs,farmingsimulator22,farmingsimulator25,fs22,fs25
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
13
|
+
Classifier: Operating System :: OS Independent
|
14
|
+
Description-Content-Type: text/markdown
|
15
|
+
License-File: LICENSE.md
|
16
|
+
Requires-Dist: pydantic>=2.0.0
|
17
|
+
Requires-Dist: requests>=2.0.0
|
18
|
+
Requires-Dist: httpx>=0.20.0
|
19
|
+
|
20
|
+
<div align="center" markdown>
|
21
|
+
<img src="https://github.com/iwatkot/maps4fs/assets/118521851/ffd7f0a3-e317-4c3f-911f-2c2fb736fbfa">
|
22
|
+
|
23
|
+
<p align="center">
|
24
|
+
<a href="#Quick-Start">Quick Start</a> •
|
25
|
+
<a href="#Overview">Overview</a> •
|
26
|
+
<a href="#How-To-Run">How-To-Run</a> •
|
27
|
+
<a href="#Features">Features</a> •
|
28
|
+
<a href="#Supported-objects">Supported objects</a> •
|
29
|
+
<a href="Settings">Settings</a> •
|
30
|
+
<a href="#Bugs-and-feature-requests">Bugs and feature requests</a>
|
31
|
+
</p>
|
32
|
+
|
33
|
+
|
34
|
+
[](https://github.com/iwatkot/maps4fs/releases)
|
35
|
+
[](https://hub.docker.com/repository/docker/iwatkot/maps4fs/general)
|
36
|
+
[](https://github.com/iwatkot/maps4fs/issues)
|
37
|
+
[](https://codeclimate.com/github/iwatkot/maps4fs/maintainability)<br>
|
38
|
+
[](https://mypy-lang.org/)
|
39
|
+
[](https://github.com/iwatkot/maps4fs/actions)
|
40
|
+
[](https://codeclimate.com/github/iwatkot/maps4fs/test_coverage)
|
41
|
+
[](https://github.com/iwatkot/maps4fs/stargazers)
|
42
|
+
|
43
|
+
</div>
|
44
|
+
|
45
|
+
### Supported Games
|
46
|
+
|
47
|
+
✅ Farming Simulator 22<br>
|
48
|
+
🔃 Farming Simulator 25 (changes in the library are ready, waiting for the Giants to release the Giants Editor v10)<br>
|
49
|
+
|
50
|
+
## Quick Start
|
51
|
+
For those, who don't want to read anything, here you go:<br>
|
52
|
+
**Option 1:** launch the [@maps4fs](https://t.me/maps4fsbot) Telegram bot and generate a map template in a few clicks.<br>
|
53
|
+
**Option 2:** run the Docker version in your browser. Launch the following command in your terminal:
|
54
|
+
```bash
|
55
|
+
docker run -d -p 8501:8501 iwatkot/maps4fs
|
56
|
+
```
|
57
|
+
And open [http://localhost:8501](http://localhost:8501) in your browser.<br>
|
58
|
+
If you don't know how to use Docker, navigate to the [Docker version](#option-2-docker-version), it's really simple.<br>
|
59
|
+
|
60
|
+
## Overview
|
61
|
+
The core idea is coming from the awesome [maps4cim](https://github.com/klamann/maps4cim) project.<br>
|
62
|
+
|
63
|
+
The main goal of this project is to generate map templates, based on real-world data, for the Farming Simulator. It's important to mention that **templates are not maps**. They are just a starting point for creating a map. This tool just uses built-in textures to highlight different types of terrain and buildings with correct shapes and scales and to generate a height map. The rest of the work is up to you. So if you thought that you could just run this tool and get a playable map, then I'm sorry to disappoint you. But if you are a map maker, then this tool will save you a lot of time.<br>
|
64
|
+
So, if you're new to map making, here's a quick overview of the process:
|
65
|
+
1. Generate a map template using this tool.
|
66
|
+
2. Download the Giants Editor.
|
67
|
+
3. Open the map template in the Giants Editor.
|
68
|
+
4. Now you can start creating your map (adding roads, fields, buildings, etc.).
|
69
|
+
|
70
|
+
## How-To-Run
|
71
|
+
|
72
|
+
You'll find detailed instructions on how to run the project below. But if you prefer video tutorials, here's one for you:
|
73
|
+
<a href="https://www.youtube.com/watch?v=ujwWKHVKsw8" target="_blank"><img src="https://github.com/user-attachments/assets/6dbbbc71-d04f-40b2-9fba-81e5e4857407"/></a>
|
74
|
+
<i>Video tutorial: How to generate a Farming Simulator 22 map from real-world data.</i>
|
75
|
+
|
76
|
+
### Option 1: Telegram bot
|
77
|
+
**🗺️ Supported map sizes:** 2x2, 4x4 km.<br>
|
78
|
+
🟢 Recommended for all users, you don't need to install anything.<br>
|
79
|
+
Using Telegram bot [@maps4fs](https://t.me/maps4fsbot).<br>
|
80
|
+
Note: due to CPU and RAM limitations of the hosting, only 2x2 and 4x4 km maps are available. If you need bigger maps, use the [Docker version](#option-2-docker-version).<br>
|
81
|
+
|
82
|
+

|
83
|
+
<br>
|
84
|
+
|
85
|
+
Using it is easy and doesn't require any guides. Enjoy!
|
86
|
+
|
87
|
+
### Option 2: Docker version
|
88
|
+
**🗺️ Supported map sizes:** 2x2, 4x4, 8x8, 16x16 km.<br>
|
89
|
+
🟢 Recommended for users who need bigger maps, very simple installation.<br>
|
90
|
+
You can launch the project with minimalistic UI in your browser using Docker. Follow these steps:
|
91
|
+
|
92
|
+
1. Install [Docker](https://docs.docker.com/get-docker/) for your OS.
|
93
|
+
2. Run the following command in your terminal:
|
94
|
+
```bash
|
95
|
+
docker run -d -p 8501:8501 iwatkot/maps4fs
|
96
|
+
```
|
97
|
+
3. Open your browser and go to [http://localhost:8501](http://localhost:8501).
|
98
|
+
4. Fill in the required fields and click on the `Generate` button.
|
99
|
+
5. When the map is generated click on the `Download` button to get the map.
|
100
|
+
|
101
|
+

|
102
|
+
|
103
|
+
## Settings
|
104
|
+
Advanced settings are available in the tool's UI under the **Advanced Settings** tab. Here's the list of them:
|
105
|
+
- `max_height` - the maximum height of the map. The default value is 400. Select smaller values for plain-like maps and bigger values for mountainous maps. You may need to experiment with this value to get the desired result.
|
106
|
+
- `blur_seed` - the seed for the blur algorithm. The default value is 5, which means 5 meters. The bigger the value, the smoother the map will be. The smaller the value, the more detailed the map will be. Keep in mind that for some regions, where terrain is bumpy, disabling the blur algorithm may lead to a very rough map. So, I recommend leaving this value as it is.
|
107
|
+
|
108
|
+
## Features
|
109
|
+
- Allows to enter a location by lat and lon (e.g. from Google Maps).
|
110
|
+
- Allows to select a size of the map (2x2, 4x4, 8x8 km, 16x16 km).
|
111
|
+
- Generates a map template (check the list of supported objects in [this section](#supported-objects)).
|
112
|
+
- Generates a height map.
|
113
|
+
|
114
|
+
## Supported objects
|
115
|
+
The project is based on the [OpenStreetMap](https://www.openstreetmap.org/) data. So, refer to [this page](https://wiki.openstreetmap.org/wiki/Map_Features) to understand the list below.
|
116
|
+
- "building": True
|
117
|
+
- "highway": ["motorway", "trunk", "primary"]
|
118
|
+
- "highway": ["secondary", "tertiary", "road"]
|
119
|
+
- "highway": ["unclassified", "residential", "track"]
|
120
|
+
- "natural": "grassland"
|
121
|
+
- "landuse": "farmland"
|
122
|
+
- "natural": ["water"]
|
123
|
+
- "waterway": True
|
124
|
+
- "natural": ["wood", "tree_row"]
|
125
|
+
|
126
|
+
The list will be updated as the project develops.
|
127
|
+
|
128
|
+
## Info sequence
|
129
|
+
The script will also generate the `generation_info.json` file in the `output` folder. It contains the following keys: <br>
|
130
|
+
`"coordinates"` - the coordinates of the map center which you entered,<br>
|
131
|
+
`"bbox"` - the bounding box of the map in lat and lon,<br>
|
132
|
+
`"distance"` - the size of the map in meters,<br>
|
133
|
+
`"minimum_x"` - the minimum x coordinate of the map (UTM projection),<br>
|
134
|
+
`"minimum_y"` - the minimum y coordinate of the map (UTM projection),<br>
|
135
|
+
`"maximum_x"` - the maximum x coordinate of the map (UTM projection),<br>
|
136
|
+
`"maximum_y"` - the maximum y coordinate of the map (UTM projection),<br>
|
137
|
+
`"height"` - the height of the map in meters (it won't be equal to the distance since the Earth is not flat, sorry flat-earthers),<br>
|
138
|
+
`"width"` - the width of the map in meters,<br>
|
139
|
+
`"height_coef"` - since we need a texture of exact size, the height of the map is multiplied by this coefficient,<br>
|
140
|
+
`"width_coef"` - same as above but for the width,<br>
|
141
|
+
`"tile_name"` - the name of the SRTM tile which was used to generate the height map, e.g. "N52E013"<br>
|
142
|
+
|
143
|
+
You can use this information to adjust some other sources of data to the map, e.g. textures, height maps, etc.
|
144
|
+
|
145
|
+
## Bugs and feature requests
|
146
|
+
If you find a bug or have an idea for a new feature, please create an issue [here](https://github.com/iwatkot/maps4fs/issues) or contact me directly on [Telegram](https://t.me/iwatkot).<br>
|
147
|
+
ℹ️ Please, don't bother me if the Telegram bot is down. As I said before this is related to the hosting limitations, if you want you can always run the tool locally or support the project by donating, so maybe I'll be able to afford better hosting.
|
maps4fs-0.6.0/README.md
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
<div align="center" markdown>
|
2
|
+
<img src="https://github.com/iwatkot/maps4fs/assets/118521851/ffd7f0a3-e317-4c3f-911f-2c2fb736fbfa">
|
3
|
+
|
4
|
+
<p align="center">
|
5
|
+
<a href="#Quick-Start">Quick Start</a> •
|
6
|
+
<a href="#Overview">Overview</a> •
|
7
|
+
<a href="#How-To-Run">How-To-Run</a> •
|
8
|
+
<a href="#Features">Features</a> •
|
9
|
+
<a href="#Supported-objects">Supported objects</a> •
|
10
|
+
<a href="Settings">Settings</a> •
|
11
|
+
<a href="#Bugs-and-feature-requests">Bugs and feature requests</a>
|
12
|
+
</p>
|
13
|
+
|
14
|
+
|
15
|
+
[](https://github.com/iwatkot/maps4fs/releases)
|
16
|
+
[](https://hub.docker.com/repository/docker/iwatkot/maps4fs/general)
|
17
|
+
[](https://github.com/iwatkot/maps4fs/issues)
|
18
|
+
[](https://codeclimate.com/github/iwatkot/maps4fs/maintainability)<br>
|
19
|
+
[](https://mypy-lang.org/)
|
20
|
+
[](https://github.com/iwatkot/maps4fs/actions)
|
21
|
+
[](https://codeclimate.com/github/iwatkot/maps4fs/test_coverage)
|
22
|
+
[](https://github.com/iwatkot/maps4fs/stargazers)
|
23
|
+
|
24
|
+
</div>
|
25
|
+
|
26
|
+
### Supported Games
|
27
|
+
|
28
|
+
✅ Farming Simulator 22<br>
|
29
|
+
🔃 Farming Simulator 25 (changes in the library are ready, waiting for the Giants to release the Giants Editor v10)<br>
|
30
|
+
|
31
|
+
## Quick Start
|
32
|
+
For those, who don't want to read anything, here you go:<br>
|
33
|
+
**Option 1:** launch the [@maps4fs](https://t.me/maps4fsbot) Telegram bot and generate a map template in a few clicks.<br>
|
34
|
+
**Option 2:** run the Docker version in your browser. Launch the following command in your terminal:
|
35
|
+
```bash
|
36
|
+
docker run -d -p 8501:8501 iwatkot/maps4fs
|
37
|
+
```
|
38
|
+
And open [http://localhost:8501](http://localhost:8501) in your browser.<br>
|
39
|
+
If you don't know how to use Docker, navigate to the [Docker version](#option-2-docker-version), it's really simple.<br>
|
40
|
+
|
41
|
+
## Overview
|
42
|
+
The core idea is coming from the awesome [maps4cim](https://github.com/klamann/maps4cim) project.<br>
|
43
|
+
|
44
|
+
The main goal of this project is to generate map templates, based on real-world data, for the Farming Simulator. It's important to mention that **templates are not maps**. They are just a starting point for creating a map. This tool just uses built-in textures to highlight different types of terrain and buildings with correct shapes and scales and to generate a height map. The rest of the work is up to you. So if you thought that you could just run this tool and get a playable map, then I'm sorry to disappoint you. But if you are a map maker, then this tool will save you a lot of time.<br>
|
45
|
+
So, if you're new to map making, here's a quick overview of the process:
|
46
|
+
1. Generate a map template using this tool.
|
47
|
+
2. Download the Giants Editor.
|
48
|
+
3. Open the map template in the Giants Editor.
|
49
|
+
4. Now you can start creating your map (adding roads, fields, buildings, etc.).
|
50
|
+
|
51
|
+
## How-To-Run
|
52
|
+
|
53
|
+
You'll find detailed instructions on how to run the project below. But if you prefer video tutorials, here's one for you:
|
54
|
+
<a href="https://www.youtube.com/watch?v=ujwWKHVKsw8" target="_blank"><img src="https://github.com/user-attachments/assets/6dbbbc71-d04f-40b2-9fba-81e5e4857407"/></a>
|
55
|
+
<i>Video tutorial: How to generate a Farming Simulator 22 map from real-world data.</i>
|
56
|
+
|
57
|
+
### Option 1: Telegram bot
|
58
|
+
**🗺️ Supported map sizes:** 2x2, 4x4 km.<br>
|
59
|
+
🟢 Recommended for all users, you don't need to install anything.<br>
|
60
|
+
Using Telegram bot [@maps4fs](https://t.me/maps4fsbot).<br>
|
61
|
+
Note: due to CPU and RAM limitations of the hosting, only 2x2 and 4x4 km maps are available. If you need bigger maps, use the [Docker version](#option-2-docker-version).<br>
|
62
|
+
|
63
|
+

|
64
|
+
<br>
|
65
|
+
|
66
|
+
Using it is easy and doesn't require any guides. Enjoy!
|
67
|
+
|
68
|
+
### Option 2: Docker version
|
69
|
+
**🗺️ Supported map sizes:** 2x2, 4x4, 8x8, 16x16 km.<br>
|
70
|
+
🟢 Recommended for users who need bigger maps, very simple installation.<br>
|
71
|
+
You can launch the project with minimalistic UI in your browser using Docker. Follow these steps:
|
72
|
+
|
73
|
+
1. Install [Docker](https://docs.docker.com/get-docker/) for your OS.
|
74
|
+
2. Run the following command in your terminal:
|
75
|
+
```bash
|
76
|
+
docker run -d -p 8501:8501 iwatkot/maps4fs
|
77
|
+
```
|
78
|
+
3. Open your browser and go to [http://localhost:8501](http://localhost:8501).
|
79
|
+
4. Fill in the required fields and click on the `Generate` button.
|
80
|
+
5. When the map is generated click on the `Download` button to get the map.
|
81
|
+
|
82
|
+

|
83
|
+
|
84
|
+
## Settings
|
85
|
+
Advanced settings are available in the tool's UI under the **Advanced Settings** tab. Here's the list of them:
|
86
|
+
- `max_height` - the maximum height of the map. The default value is 400. Select smaller values for plain-like maps and bigger values for mountainous maps. You may need to experiment with this value to get the desired result.
|
87
|
+
- `blur_seed` - the seed for the blur algorithm. The default value is 5, which means 5 meters. The bigger the value, the smoother the map will be. The smaller the value, the more detailed the map will be. Keep in mind that for some regions, where terrain is bumpy, disabling the blur algorithm may lead to a very rough map. So, I recommend leaving this value as it is.
|
88
|
+
|
89
|
+
## Features
|
90
|
+
- Allows to enter a location by lat and lon (e.g. from Google Maps).
|
91
|
+
- Allows to select a size of the map (2x2, 4x4, 8x8 km, 16x16 km).
|
92
|
+
- Generates a map template (check the list of supported objects in [this section](#supported-objects)).
|
93
|
+
- Generates a height map.
|
94
|
+
|
95
|
+
## Supported objects
|
96
|
+
The project is based on the [OpenStreetMap](https://www.openstreetmap.org/) data. So, refer to [this page](https://wiki.openstreetmap.org/wiki/Map_Features) to understand the list below.
|
97
|
+
- "building": True
|
98
|
+
- "highway": ["motorway", "trunk", "primary"]
|
99
|
+
- "highway": ["secondary", "tertiary", "road"]
|
100
|
+
- "highway": ["unclassified", "residential", "track"]
|
101
|
+
- "natural": "grassland"
|
102
|
+
- "landuse": "farmland"
|
103
|
+
- "natural": ["water"]
|
104
|
+
- "waterway": True
|
105
|
+
- "natural": ["wood", "tree_row"]
|
106
|
+
|
107
|
+
The list will be updated as the project develops.
|
108
|
+
|
109
|
+
## Info sequence
|
110
|
+
The script will also generate the `generation_info.json` file in the `output` folder. It contains the following keys: <br>
|
111
|
+
`"coordinates"` - the coordinates of the map center which you entered,<br>
|
112
|
+
`"bbox"` - the bounding box of the map in lat and lon,<br>
|
113
|
+
`"distance"` - the size of the map in meters,<br>
|
114
|
+
`"minimum_x"` - the minimum x coordinate of the map (UTM projection),<br>
|
115
|
+
`"minimum_y"` - the minimum y coordinate of the map (UTM projection),<br>
|
116
|
+
`"maximum_x"` - the maximum x coordinate of the map (UTM projection),<br>
|
117
|
+
`"maximum_y"` - the maximum y coordinate of the map (UTM projection),<br>
|
118
|
+
`"height"` - the height of the map in meters (it won't be equal to the distance since the Earth is not flat, sorry flat-earthers),<br>
|
119
|
+
`"width"` - the width of the map in meters,<br>
|
120
|
+
`"height_coef"` - since we need a texture of exact size, the height of the map is multiplied by this coefficient,<br>
|
121
|
+
`"width_coef"` - same as above but for the width,<br>
|
122
|
+
`"tile_name"` - the name of the SRTM tile which was used to generate the height map, e.g. "N52E013"<br>
|
123
|
+
|
124
|
+
You can use this information to adjust some other sources of data to the map, e.g. textures, height maps, etc.
|
125
|
+
|
126
|
+
## Bugs and feature requests
|
127
|
+
If you find a bug or have an idea for a new feature, please create an issue [here](https://github.com/iwatkot/maps4fs/issues) or contact me directly on [Telegram](https://t.me/iwatkot).<br>
|
128
|
+
ℹ️ Please, don't bother me if the Telegram bot is down. As I said before this is related to the hosting limitations, if you want you can always run the tool locally or support the project by donating, so maybe I'll be able to afford better hosting.
|
@@ -0,0 +1 @@
|
|
1
|
+
# pylint: disable=missing-module-docstring
|
@@ -0,0 +1,55 @@
|
|
1
|
+
"""This module contains the base class for all map generation components."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from typing import TYPE_CHECKING, Any
|
6
|
+
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
from maps4fs.generator.game import Game
|
9
|
+
|
10
|
+
|
11
|
+
# pylint: disable=R0801, R0903
|
12
|
+
class Component:
|
13
|
+
"""Base class for all map generation components.
|
14
|
+
|
15
|
+
Args:
|
16
|
+
coordinates (tuple[float, float]): The latitude and longitude of the center of the map.
|
17
|
+
distance (int): The distance from the center to the edge of the map.
|
18
|
+
map_directory (str): The directory where the map files are stored.
|
19
|
+
logger (Any, optional): The logger to use. Must have at least three basic methods: debug,
|
20
|
+
info, warning. If not provided, default logging will be used.
|
21
|
+
"""
|
22
|
+
|
23
|
+
def __init__(
|
24
|
+
self,
|
25
|
+
game: Game,
|
26
|
+
coordinates: tuple[float, float],
|
27
|
+
distance: int,
|
28
|
+
map_directory: str,
|
29
|
+
logger: Any = None,
|
30
|
+
**kwargs, # pylint: disable=W0613, R0913, R0917
|
31
|
+
):
|
32
|
+
self.game = game
|
33
|
+
self.coordinates = coordinates
|
34
|
+
self.distance = distance
|
35
|
+
self.map_directory = map_directory
|
36
|
+
self.logger = logger
|
37
|
+
self.kwargs = kwargs
|
38
|
+
|
39
|
+
self.preprocess()
|
40
|
+
|
41
|
+
def preprocess(self) -> None:
|
42
|
+
"""Prepares the component for processing. Must be implemented in the child class.
|
43
|
+
|
44
|
+
Raises:
|
45
|
+
NotImplementedError: If the method is not implemented in the child class.
|
46
|
+
"""
|
47
|
+
raise NotImplementedError
|
48
|
+
|
49
|
+
def process(self) -> None:
|
50
|
+
"""Launches the component processing. Must be implemented in the child class.
|
51
|
+
|
52
|
+
Raises:
|
53
|
+
NotImplementedError: If the method is not implemented in the child class.
|
54
|
+
"""
|
55
|
+
raise NotImplementedError
|
@@ -0,0 +1,44 @@
|
|
1
|
+
"""This module contains the Config class for map settings and configuration."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import os
|
6
|
+
from xml.etree import ElementTree as ET
|
7
|
+
|
8
|
+
from maps4fs.generator.component import Component
|
9
|
+
|
10
|
+
|
11
|
+
# pylint: disable=R0903
|
12
|
+
class Config(Component):
|
13
|
+
"""Component for map settings and configuration.
|
14
|
+
|
15
|
+
Args:
|
16
|
+
coordinates (tuple[float, float]): The latitude and longitude of the center of the map.
|
17
|
+
distance (int): The distance from the center to the edge of the map.
|
18
|
+
map_directory (str): The directory where the map files are stored.
|
19
|
+
logger (Any, optional): The logger to use. Must have at least three basic methods: debug,
|
20
|
+
info, warning. If not provided, default logging will be used.
|
21
|
+
"""
|
22
|
+
|
23
|
+
def preprocess(self) -> None:
|
24
|
+
self._map_xml_path = self.game.map_xml_path(self.map_directory)
|
25
|
+
self.logger.debug(f"Map XML path: {self._map_xml_path}")
|
26
|
+
|
27
|
+
def process(self):
|
28
|
+
self._set_map_size()
|
29
|
+
|
30
|
+
def _set_map_size(self):
|
31
|
+
"""Edits map.xml file to set correct map size."""
|
32
|
+
if not os.path.isfile(self._map_xml_path):
|
33
|
+
self.logger.warning("Map XML file not found: %s.", self._map_xml_path)
|
34
|
+
return
|
35
|
+
tree = ET.parse(self._map_xml_path)
|
36
|
+
self.logger.debug("Map XML file loaded from: %s.", self._map_xml_path)
|
37
|
+
root = tree.getroot()
|
38
|
+
for map_elem in root.iter("map"):
|
39
|
+
width = height = str(self.distance * 2)
|
40
|
+
map_elem.set("width", width)
|
41
|
+
map_elem.set("height", height)
|
42
|
+
self.logger.debug("Map size set to %sx%s in Map XML file.", width, height)
|
43
|
+
tree.write(self._map_xml_path)
|
44
|
+
self.logger.debug("Map XML file saved to: %s.", self._map_xml_path)
|
@@ -0,0 +1,207 @@
|
|
1
|
+
"""This module contains DEM class for processing Digital Elevation Model data."""
|
2
|
+
|
3
|
+
import gzip
|
4
|
+
import math
|
5
|
+
import os
|
6
|
+
import shutil
|
7
|
+
|
8
|
+
import cv2
|
9
|
+
import numpy as np
|
10
|
+
import osmnx as ox # type: ignore
|
11
|
+
import rasterio # type: ignore
|
12
|
+
import requests
|
13
|
+
|
14
|
+
from maps4fs.generator.component import Component
|
15
|
+
|
16
|
+
SRTM = "https://elevation-tiles-prod.s3.amazonaws.com/skadi/{latitude_band}/{tile_name}.hgt.gz"
|
17
|
+
|
18
|
+
|
19
|
+
# pylint: disable=R0903
|
20
|
+
class DEM(Component):
|
21
|
+
"""Component for map settings and configuration.
|
22
|
+
|
23
|
+
Args:
|
24
|
+
coordinates (tuple[float, float]): The latitude and longitude of the center of the map.
|
25
|
+
distance (int): The distance from the center to the edge of the map.
|
26
|
+
map_directory (str): The directory where the map files are stored.
|
27
|
+
logger (Any, optional): The logger to use. Must have at least three basic methods: debug,
|
28
|
+
info, warning. If not provided, default logging will be used.
|
29
|
+
"""
|
30
|
+
|
31
|
+
def preprocess(self) -> None:
|
32
|
+
self._blur_seed: int = self.kwargs.get("blur_seed") or 5
|
33
|
+
self._max_height: int = self.kwargs.get("max_height") or 200
|
34
|
+
|
35
|
+
self._dem_path = self.game.dem_file_path(self.map_directory)
|
36
|
+
self.temp_dir = "temp"
|
37
|
+
self.hgt_dir = os.path.join(self.temp_dir, "hgt")
|
38
|
+
self.gz_dir = os.path.join(self.temp_dir, "gz")
|
39
|
+
os.makedirs(self.hgt_dir, exist_ok=True)
|
40
|
+
os.makedirs(self.gz_dir, exist_ok=True)
|
41
|
+
|
42
|
+
# pylint: disable=no-member
|
43
|
+
def process(self) -> None:
|
44
|
+
"""Reads SRTM file, crops it to map size, normalizes and blurs it,
|
45
|
+
saves to map directory."""
|
46
|
+
north, south, east, west = ox.utils_geo.bbox_from_point( # pylint: disable=W0632
|
47
|
+
self.coordinates, dist=self.distance
|
48
|
+
)
|
49
|
+
self.logger.debug(
|
50
|
+
f"Processing DEM. North: {north}, south: {south}, east: {east}, west: {west}."
|
51
|
+
)
|
52
|
+
|
53
|
+
dem_output_size = self.distance * self.game.dem_multipliyer + 1
|
54
|
+
self.logger.debug(
|
55
|
+
"DEM multiplier is %s, DEM output size is %s.",
|
56
|
+
self.game.dem_multipliyer,
|
57
|
+
dem_output_size,
|
58
|
+
)
|
59
|
+
dem_output_resolution = (dem_output_size, dem_output_size)
|
60
|
+
self.logger.debug("DEM output resolution: %s.", dem_output_resolution)
|
61
|
+
|
62
|
+
tile_path = self._srtm_tile()
|
63
|
+
if not tile_path:
|
64
|
+
self.logger.warning("Tile was not downloaded, DEM file will be filled with zeros.")
|
65
|
+
self._save_empty_dem(dem_output_resolution)
|
66
|
+
return
|
67
|
+
|
68
|
+
with rasterio.open(tile_path) as src:
|
69
|
+
self.logger.debug("Opened tile, shape: %s, dtype: %s.", src.shape, src.dtypes[0])
|
70
|
+
window = rasterio.windows.from_bounds(west, south, east, north, src.transform)
|
71
|
+
self.logger.debug(
|
72
|
+
"Window parameters. Column offset: %s, row offset: %s, width: %s, height: %s.",
|
73
|
+
window.col_off,
|
74
|
+
window.row_off,
|
75
|
+
window.width,
|
76
|
+
window.height,
|
77
|
+
)
|
78
|
+
data = src.read(1, window=window)
|
79
|
+
|
80
|
+
if not data.size > 0:
|
81
|
+
self.logger.warning("DEM data is empty, DEM file will be filled with zeros.")
|
82
|
+
self._save_empty_dem(dem_output_resolution)
|
83
|
+
return
|
84
|
+
|
85
|
+
self.logger.debug(
|
86
|
+
f"DEM data was read from SRTM file. Shape: {data.shape}, dtype: {data.dtype}. "
|
87
|
+
f"Min: {data.min()}, max: {data.max()}."
|
88
|
+
)
|
89
|
+
|
90
|
+
normalized_data = self._normalize_dem(data)
|
91
|
+
|
92
|
+
resampled_data = cv2.resize(
|
93
|
+
normalized_data, dem_output_resolution, interpolation=cv2.INTER_LINEAR
|
94
|
+
)
|
95
|
+
self.logger.debug(
|
96
|
+
f"DEM data was resampled. Shape: {resampled_data.shape}, "
|
97
|
+
f"dtype: {resampled_data.dtype}. "
|
98
|
+
f"Min: {resampled_data.min()}, max: {resampled_data.max()}."
|
99
|
+
)
|
100
|
+
|
101
|
+
blurred_data = cv2.GaussianBlur(resampled_data, (self._blur_seed, self._blur_seed), 0)
|
102
|
+
cv2.imwrite(self._dem_path, blurred_data)
|
103
|
+
self.logger.debug("DEM data was saved to %s.", self._dem_path)
|
104
|
+
|
105
|
+
def _tile_info(self, lat: float, lon: float) -> tuple[str, str]:
|
106
|
+
"""Returns latitude band and tile name for SRTM tile from coordinates.
|
107
|
+
|
108
|
+
Args:
|
109
|
+
lat (float): Latitude.
|
110
|
+
lon (float): Longitude.
|
111
|
+
|
112
|
+
Returns:
|
113
|
+
tuple[str, str]: Latitude band and tile name.
|
114
|
+
"""
|
115
|
+
tile_latitude = math.floor(lat)
|
116
|
+
tile_longitude = math.floor(lon)
|
117
|
+
|
118
|
+
latitude_band = f"N{abs(tile_latitude):02d}" if lat >= 0 else f"S{abs(tile_latitude):02d}"
|
119
|
+
if lon < 0:
|
120
|
+
tile_name = f"{latitude_band}W{abs(tile_longitude):03d}"
|
121
|
+
else:
|
122
|
+
tile_name = f"{latitude_band}E{abs(tile_longitude):03d}"
|
123
|
+
|
124
|
+
self.logger.debug(
|
125
|
+
"Detected tile name: %s for coordinates: lat %s, lon %s.", tile_name, lat, lon
|
126
|
+
)
|
127
|
+
return latitude_band, tile_name
|
128
|
+
|
129
|
+
def _download_tile(self) -> str | None:
|
130
|
+
"""Downloads SRTM tile from Amazon S3 using coordinates.
|
131
|
+
|
132
|
+
Returns:
|
133
|
+
str: Path to compressed tile or None if download failed.
|
134
|
+
"""
|
135
|
+
latitude_band, tile_name = self._tile_info(*self.coordinates)
|
136
|
+
compressed_file_path = os.path.join(self.gz_dir, f"{tile_name}.hgt.gz")
|
137
|
+
url = SRTM.format(latitude_band=latitude_band, tile_name=tile_name)
|
138
|
+
self.logger.debug("Trying to get response from %s...", url)
|
139
|
+
response = requests.get(url, stream=True, timeout=10)
|
140
|
+
|
141
|
+
if response.status_code == 200:
|
142
|
+
self.logger.debug("Response received. Saving to %s...", compressed_file_path)
|
143
|
+
with open(compressed_file_path, "wb") as f:
|
144
|
+
for chunk in response.iter_content(chunk_size=8192):
|
145
|
+
f.write(chunk)
|
146
|
+
self.logger.debug("Compressed tile successfully downloaded.")
|
147
|
+
else:
|
148
|
+
self.logger.error("Response was failed with status code %s.", response.status_code)
|
149
|
+
return None
|
150
|
+
|
151
|
+
return compressed_file_path
|
152
|
+
|
153
|
+
def _srtm_tile(self) -> str | None:
|
154
|
+
"""Determines SRTM tile name from coordinates downloads it if necessary, and decompresses.
|
155
|
+
|
156
|
+
Returns:
|
157
|
+
str: Path to decompressed tile or None if download failed.
|
158
|
+
"""
|
159
|
+
latitude_band, tile_name = self._tile_info(*self.coordinates)
|
160
|
+
self.logger.debug("SRTM tile name %s from latitude band %s.", tile_name, latitude_band)
|
161
|
+
|
162
|
+
decompressed_file_path = os.path.join(self.hgt_dir, f"{tile_name}.hgt")
|
163
|
+
if os.path.isfile(decompressed_file_path):
|
164
|
+
self.logger.info(
|
165
|
+
f"Decompressed tile already exists: {decompressed_file_path}, skipping download."
|
166
|
+
)
|
167
|
+
return decompressed_file_path
|
168
|
+
|
169
|
+
compressed_file_path = self._download_tile()
|
170
|
+
if not compressed_file_path:
|
171
|
+
self.logger.error("Download from SRTM failed, DEM file will be filled with zeros.")
|
172
|
+
return None
|
173
|
+
with gzip.open(compressed_file_path, "rb") as f_in:
|
174
|
+
with open(decompressed_file_path, "wb") as f_out:
|
175
|
+
shutil.copyfileobj(f_in, f_out)
|
176
|
+
self.logger.debug("Tile decompressed to %s.", decompressed_file_path)
|
177
|
+
return decompressed_file_path
|
178
|
+
|
179
|
+
def _save_empty_dem(self, dem_output_resolution: tuple[int, int]) -> None:
|
180
|
+
"""Saves empty DEM file filled with zeros."""
|
181
|
+
dem_data = np.zeros(dem_output_resolution, dtype="uint16")
|
182
|
+
cv2.imwrite(self._dem_path, dem_data) # pylint: disable=no-member
|
183
|
+
self.logger.warning("DEM data filled with zeros and saved to %s.", self._dem_path)
|
184
|
+
|
185
|
+
def _normalize_dem(self, data: np.ndarray) -> np.ndarray:
|
186
|
+
"""Normalize DEM data to 16-bit unsigned integer using max height from settings.
|
187
|
+
|
188
|
+
Args:
|
189
|
+
data (np.ndarray): DEM data from SRTM file after cropping.
|
190
|
+
|
191
|
+
Returns:
|
192
|
+
np.ndarray: Normalized DEM data.
|
193
|
+
"""
|
194
|
+
max_dev = data.max() - data.min()
|
195
|
+
scaling_factor = max_dev / self._max_height if max_dev < self._max_height else 1
|
196
|
+
adjusted_max_height = int(65535 * scaling_factor)
|
197
|
+
self.logger.debug(
|
198
|
+
f"Maximum deviation: {max_dev}. Scaling factor: {scaling_factor}. "
|
199
|
+
f"Adjusted max height: {adjusted_max_height}."
|
200
|
+
)
|
201
|
+
normalized_data = (
|
202
|
+
(data - data.min()) / (data.max() - data.min()) * adjusted_max_height
|
203
|
+
).astype("uint16")
|
204
|
+
self.logger.debug(
|
205
|
+
f"DEM data was normalized to {normalized_data.min()} - {normalized_data.max()}."
|
206
|
+
)
|
207
|
+
return normalized_data
|