aceti 0.0.1__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.
- aceti-0.0.1/.gitignore +2 -0
- aceti-0.0.1/LICENSE +19 -0
- aceti-0.0.1/MANIFEST.in +1 -0
- aceti-0.0.1/PKG-INFO +40 -0
- aceti-0.0.1/README.md +29 -0
- aceti-0.0.1/README_LEG.md +22 -0
- aceti-0.0.1/pyproject.toml +28 -0
- aceti-0.0.1/src/aceti/__init__.py +2 -0
- aceti-0.0.1/src/aceti/map_importer.py +29 -0
- aceti-0.0.1/src/aceti/maps/alamillo30x49latlon.npy +0 -0
- aceti-0.0.1/src/aceti/maps/alamillo30x49mask.npy +0 -0
- aceti-0.0.1/src/aceti/maps/alamilloaccess11x15latlon.npy +0 -0
- aceti-0.0.1/src/aceti/maps/alamilloaccess11x15mask.npy +0 -0
- aceti-0.0.1/src/aceti/old/create grid.py +573 -0
- aceti-0.0.1/src/aceti/old/gen_lat_lon_map.py +38 -0
- aceti-0.0.1/src/aceti/old/generate_map.py +40 -0
- aceti-0.0.1/src/aceti/old/legendarium.py +180 -0
- aceti-0.0.1/src/aceti/old/maps_utils.py +128 -0
- aceti-0.0.1/src/aceti/old/path_planner_checker.py +65 -0
- aceti-0.0.1/tests/test.py +3 -0
aceti-0.0.1/.gitignore
ADDED
aceti-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2025 Alejandro Casado Pérez
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
aceti-0.0.1/MANIFEST.in
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
graft maps
|
aceti-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aceti
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A simple package to store and create maps for an experiment
|
|
5
|
+
Project-URL: Homepage, https://github.com/aceti-pub/common_maps
|
|
6
|
+
Project-URL: Issues, https://github.com/aceti-pub/common_maps/issues
|
|
7
|
+
Author-email: Alejandro Casado <acasado4@us.es>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Education
|
|
12
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
13
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
14
|
+
Classifier: Programming Language :: Python :: 2
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Requires-Dist: numpy
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
aceti_maps
|
|
20
|
+
===========
|
|
21
|
+
|
|
22
|
+
* Python library to make an efficient use of data
|
|
23
|
+
* License: MIT
|
|
24
|
+
* Compatible With: python 3.0+
|
|
25
|
+
|
|
26
|
+
Develop API Usage
|
|
27
|
+
-----------------
|
|
28
|
+
|
|
29
|
+
available maps
|
|
30
|
+
- Alamillo
|
|
31
|
+
- Alamillo11x15
|
|
32
|
+
- Alamillo30x49
|
|
33
|
+
|
|
34
|
+
<pre>
|
|
35
|
+
<code>
|
|
36
|
+
>>>from aceti_maps import import_map
|
|
37
|
+
>>>base_matrix, lat_lon_map = import_map("alamillo11x15")
|
|
38
|
+
|
|
39
|
+
</code>
|
|
40
|
+
</pre>
|
aceti-0.0.1/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
La forma de hacer upload de este paquete se realiza mediante Hatchling siguiendo el [tutorial de python](https://packaging.python.org/en/latest/tutorials/packaging-projects/)
|
|
2
|
+
|
|
3
|
+
esta librería recibe el nombre de aceti_maps
|
|
4
|
+
|
|
5
|
+
asegurarse de tener pip actualizado a la última versión
|
|
6
|
+
```bash
|
|
7
|
+
$ python3 -m pip install --upgrade pip
|
|
8
|
+
```
|
|
9
|
+
construir el repositorio
|
|
10
|
+
```bash
|
|
11
|
+
$ python3 -m build
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
subirlo al repositorio deseado
|
|
15
|
+
```bash
|
|
16
|
+
$ python3 -m twine upload --repository testpypi dist/*
|
|
17
|
+
$ python3 -m twine upload dist/*
|
|
18
|
+
```
|
|
19
|
+
el token lo tiene acasado4@us.es
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
instalar mediante
|
|
24
|
+
```bash
|
|
25
|
+
$ python3 -m pip install --index-url https://test.pypi.org/simple/ --no-deps aceti_maps
|
|
26
|
+
$ pip install aceti_maps
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
la guía de uso está disponible en [README_LEG](./README_LEG.md)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
aceti_maps
|
|
2
|
+
===========
|
|
3
|
+
|
|
4
|
+
* Python library to make an efficient use of data
|
|
5
|
+
* License: MIT
|
|
6
|
+
* Compatible With: python 3.0+
|
|
7
|
+
|
|
8
|
+
Develop API Usage
|
|
9
|
+
-----------------
|
|
10
|
+
|
|
11
|
+
available maps
|
|
12
|
+
- Alamillo
|
|
13
|
+
- Alamillo11x15
|
|
14
|
+
- Alamillo30x49
|
|
15
|
+
|
|
16
|
+
<pre>
|
|
17
|
+
<code>
|
|
18
|
+
>>>from aceti_maps import import_map
|
|
19
|
+
>>>base_matrix, lat_lon_map = import_map("alamillo11x15")
|
|
20
|
+
|
|
21
|
+
</code>
|
|
22
|
+
</pre>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling >= 1.26"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
[project]
|
|
5
|
+
name = "aceti"
|
|
6
|
+
version = "0.0.1"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name="Alejandro Casado", email="acasado4@us.es" },
|
|
9
|
+
]
|
|
10
|
+
description = "A simple package to store and create maps for an experiment"
|
|
11
|
+
readme = "README_LEG.md"
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 3 - Alpha",
|
|
14
|
+
"Intended Audience :: Education",
|
|
15
|
+
"Programming Language :: Python :: 2",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Operating System :: MacOS :: MacOS X",
|
|
18
|
+
"Operating System :: Microsoft :: Windows",
|
|
19
|
+
]
|
|
20
|
+
license = "MIT"
|
|
21
|
+
license-files = ["LICEN[CS]E*"]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"numpy",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/aceti-pub/common_maps"
|
|
28
|
+
Issues = "https://github.com/aceti-pub/common_maps/issues"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
if sys.version_info < (3, 9):
|
|
5
|
+
# importlib.resources either doesn't exist or lacks the files() function, so use the PyPI version:
|
|
6
|
+
import importlib_resources
|
|
7
|
+
else:
|
|
8
|
+
# importlib.resources has files(), so use that:
|
|
9
|
+
import importlib.resources as importlib_resources
|
|
10
|
+
|
|
11
|
+
def import_map(map_name: str):
|
|
12
|
+
"""
|
|
13
|
+
Import a map from a file and return the base matrix and lat/lon map.
|
|
14
|
+
Args:
|
|
15
|
+
map_name (str): The name of the map to import.
|
|
16
|
+
Returns:
|
|
17
|
+
tuple: A tuple containing the base matrix and lat/lon map.
|
|
18
|
+
"""
|
|
19
|
+
map_name = map_name.lower()
|
|
20
|
+
pkg = importlib_resources.files("aceti_maps")
|
|
21
|
+
if os.path.exists(pkg.joinpath("maps", f"{map_name}mask.npy")):
|
|
22
|
+
print(f"Loading map {map_name} from repo directory")
|
|
23
|
+
|
|
24
|
+
base_matrix = np.load(pkg.joinpath("maps", f"{map_name}mask.npy"))
|
|
25
|
+
|
|
26
|
+
# Load the lat/lon map
|
|
27
|
+
lat_lon_map = np.load(pkg.joinpath("maps", f"{map_name}latlon.npy"))
|
|
28
|
+
|
|
29
|
+
return base_matrix, lat_lon_map
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
|
|
2
|
+
# modified from the scripts created by Hayden Eskriett [http://eskriett.com] and jbndlr
|
|
3
|
+
# Find the associated blog post at: http://blog.eskriett.com/2013/07/19/downloading-google-maps/
|
|
4
|
+
# but it has been done in a better way using the data in https://developers.google.com/maps/documentation/javascript/coordinates
|
|
5
|
+
# and https://stackoverflow.com/questions/40342355/how-can-i-generate-a-regular-geographic-grid-using-python
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
###################################IMPORTANT##################################
|
|
12
|
+
#Google maps works with [Latitude Longitude] variables however
|
|
13
|
+
#Dronekit and other programs (such as gridder) work with [Longitude Latitude] variables
|
|
14
|
+
##############################################################################
|
|
15
|
+
|
|
16
|
+
import urllib.request
|
|
17
|
+
from PIL import Image
|
|
18
|
+
import os
|
|
19
|
+
import math
|
|
20
|
+
import pyproj
|
|
21
|
+
import matplotlib.pyplot as plt
|
|
22
|
+
import numpy as np
|
|
23
|
+
# from KMLMissionGeneration import KMLMissionGenerator
|
|
24
|
+
class CustomError(Exception):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
class GoogleMapDownloader:
|
|
28
|
+
"""
|
|
29
|
+
A class which generates high resolution google maps image given
|
|
30
|
+
Northwest and Southeast points in [Lat Lon] and zoom level
|
|
31
|
+
You can change the quality of the image by changing the zoom and layer values
|
|
32
|
+
|
|
33
|
+
19 is a valid zoom value knowing the size of our drone
|
|
34
|
+
|
|
35
|
+
for the maps you have available
|
|
36
|
+
ROADMAP = "v"
|
|
37
|
+
TERRAIN = "p"
|
|
38
|
+
ALTERED_ROADMAP = "r"
|
|
39
|
+
SATELLITE = "s"
|
|
40
|
+
TERRAIN_ONLY = "t"
|
|
41
|
+
HYBRID = "y"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(self, Northwest, Southeast, zoom=19, layer="s"):
|
|
47
|
+
"""
|
|
48
|
+
GoogleMapDownloader Constructor
|
|
49
|
+
Args:
|
|
50
|
+
lat: The latitude of the location required
|
|
51
|
+
lng: The longitude of the location required
|
|
52
|
+
zoom: The zoom level of the location required, ranges from 0 - 23
|
|
53
|
+
"""
|
|
54
|
+
self.Northwest = Northwest
|
|
55
|
+
self.Southeast = Southeast
|
|
56
|
+
self._zoom = zoom
|
|
57
|
+
self._layer = layer
|
|
58
|
+
|
|
59
|
+
if Northwest[0]<=Southeast[0] or Northwest[1]>=Southeast[1]:
|
|
60
|
+
raise CustomError("You must use Northwest(top-left) and Southeast(bottom-right) coordinates")
|
|
61
|
+
self.get_nw_coordinates()
|
|
62
|
+
self.get_se_coordinates()
|
|
63
|
+
|
|
64
|
+
if (abs(self.nw_tile[0]-self.se_tile[0]) == 0) or (abs(self.nw_tile[1]-self.se_tile[1]) == 0):
|
|
65
|
+
raise CustomError("Insuficient zoom, points are too close, i am lazy to program this, increase zoom")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_nw_coordinates(self):
|
|
70
|
+
"""
|
|
71
|
+
Generates an X,Y tile and pixel coordinate based on the latitude, longitude
|
|
72
|
+
and zoom level
|
|
73
|
+
Returns: An X,Y pixel coordinate
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
tile_size = 256 #each tile has 256 pixels
|
|
77
|
+
|
|
78
|
+
# Use a left shift to get the power of 2, zoom 0 is a world map, zoom 1 is wold map divided in 4 tiles, zoom 2 is world map divided in 16 tiles
|
|
79
|
+
# i.e. a zoom level of 2 will have 2^2 = 4 tiles as coordinate sistem divides axis in 4 (x+y+,x-y+,x-y-,x+y-)
|
|
80
|
+
numTiles = 1 << self._zoom
|
|
81
|
+
|
|
82
|
+
# Find the x_pixel given the longitude
|
|
83
|
+
x_pixel = (tile_size / 2 + self.Northwest[1] * tile_size / 360.0) * numTiles
|
|
84
|
+
|
|
85
|
+
# Convert the latitude to radians and take the sine
|
|
86
|
+
sin_y = math.sin(self.Northwest[0] * (math.pi / 180.0))
|
|
87
|
+
|
|
88
|
+
# Calulate the y_pixel
|
|
89
|
+
y_pixel = ((tile_size / 2) + 0.5 * math.log((1 + sin_y) / (1 - sin_y)) * -(
|
|
90
|
+
tile_size / (2 * math.pi))) * numTiles
|
|
91
|
+
|
|
92
|
+
self.nw_pixel=[x_pixel, y_pixel]
|
|
93
|
+
self.nw_tile=[int(x_pixel // tile_size), int(y_pixel // tile_size)]
|
|
94
|
+
|
|
95
|
+
def get_se_coordinates(self):
|
|
96
|
+
"""
|
|
97
|
+
Generates an X,Y tile and pixel coordinate based on the latitude, longitude
|
|
98
|
+
and zoom level
|
|
99
|
+
Returns: An X,Y pixel coordinate
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
tile_size = 256 #each tile has 256 pixels
|
|
103
|
+
|
|
104
|
+
# Use a left shift to get the power of 2, zoom 0 is a world map, zoom 1 is wold map divided in 4 tiles, zoom 2 is world map divided in 16 tiles
|
|
105
|
+
# i.e. a zoom level of 2 will have 2^2 = 4 tiles as coordinate sistem divides axis in 4 (x+y+,x-y+,x-y-,x+y-)
|
|
106
|
+
numTiles = 1 << self._zoom
|
|
107
|
+
|
|
108
|
+
# Find the x_pixel given the longitude
|
|
109
|
+
x_pixel = (tile_size / 2 + self.Southeast[1] * tile_size / 360.0) * numTiles
|
|
110
|
+
|
|
111
|
+
# Convert the latitude to radians and take the sine
|
|
112
|
+
sin_y = math.sin(self.Southeast[0] * (math.pi / 180.0))
|
|
113
|
+
|
|
114
|
+
# Calulate the y_pixel
|
|
115
|
+
y_pixel = ((tile_size / 2) + 0.5 * math.log((1 + sin_y) / (1 - sin_y)) * -(
|
|
116
|
+
tile_size / (2 * math.pi))) * numTiles
|
|
117
|
+
|
|
118
|
+
self.se_pixel=[x_pixel, y_pixel]
|
|
119
|
+
self.se_tile=[math.ceil(x_pixel // tile_size)+1, math.ceil(y_pixel // tile_size)+1]
|
|
120
|
+
|
|
121
|
+
def generateImage(self, ):
|
|
122
|
+
"""
|
|
123
|
+
Generates an image by stitching a number of google map tiles together.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
start_x: The top-left x-tile coordinate
|
|
127
|
+
start_y: The top-left y-tile coordinate
|
|
128
|
+
tile_width: The number of tiles wide the image should be -
|
|
129
|
+
defaults to 5
|
|
130
|
+
tile_height: The number of tiles high the image should be -
|
|
131
|
+
defaults to 5
|
|
132
|
+
Returns:
|
|
133
|
+
A high-resolution Goole Map image.
|
|
134
|
+
"""
|
|
135
|
+
tile_width = abs(self.nw_tile[0]-self.se_tile[0])
|
|
136
|
+
tile_height = abs(self.nw_tile[1]-self.se_tile[1])
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# Determine the size of the image, for simplicity, we will print the whole image and later crop it
|
|
141
|
+
width, height = 256 * tile_width, 256 * tile_height
|
|
142
|
+
|
|
143
|
+
# Create a new image of the size required
|
|
144
|
+
map_img = Image.new('RGB', (width, height))
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# calculate northwest pixel of the tile
|
|
148
|
+
start_x=self.nw_tile[0]
|
|
149
|
+
start_y=self.nw_tile[1]
|
|
150
|
+
|
|
151
|
+
# print the map
|
|
152
|
+
for x in range(0, tile_width):
|
|
153
|
+
for y in range(0, tile_height):
|
|
154
|
+
|
|
155
|
+
url = f'https://mt0.google.com/vt?lyrs={self._layer}&x=' + str(start_x + x) + '&y=' + str(start_y + y) + '&z=' + str(
|
|
156
|
+
self._zoom)
|
|
157
|
+
|
|
158
|
+
current_tile = str(x) + '-' + str(y)
|
|
159
|
+
urllib.request.urlretrieve(url, current_tile)
|
|
160
|
+
|
|
161
|
+
im = Image.open(current_tile)
|
|
162
|
+
map_img.paste(im, (x * 256, y * 256))
|
|
163
|
+
|
|
164
|
+
os.remove(current_tile)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# cut corners
|
|
168
|
+
left=abs(self.nw_tile[0] * 256 - self.nw_pixel[0])
|
|
169
|
+
top=abs(self.nw_tile[1] * 256 - self.nw_pixel[1])
|
|
170
|
+
right=left + abs(self.se_pixel[0] - self.nw_pixel[0])
|
|
171
|
+
bottom=top + abs(self.nw_pixel[1] - self.se_pixel[1])
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
map_img = map_img.crop((left, top, right, bottom))
|
|
175
|
+
return map_img
|
|
176
|
+
|
|
177
|
+
class gridder:
|
|
178
|
+
|
|
179
|
+
def __init__(self, NW_corner, SE_corner, grid_size, zoom =19):
|
|
180
|
+
"""
|
|
181
|
+
Args:
|
|
182
|
+
NW_corner:
|
|
183
|
+
SE_corner:
|
|
184
|
+
grid_size:
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
# Set up transformers, EPSG:3857 is metric, same as EPSG:900913
|
|
190
|
+
self.to_proxy_transformer = pyproj.Transformer.from_crs('epsg:4326', 'epsg:3857')
|
|
191
|
+
self.to_original_transformer = pyproj.Transformer.from_crs('epsg:3857', 'epsg:4326')
|
|
192
|
+
|
|
193
|
+
# Project corners to target projection
|
|
194
|
+
self.NW_corner=[NW_corner[1],NW_corner[0]]
|
|
195
|
+
self.SE_corner=[SE_corner[1],SE_corner[0]]
|
|
196
|
+
|
|
197
|
+
#this transformation works in [Longitude Latitude]
|
|
198
|
+
self.transformed_nw = self.to_proxy_transformer.transform(self.NW_corner[1], self.NW_corner[0]) # Transform NW point to 3857
|
|
199
|
+
self.transformed_se = self.to_proxy_transformer.transform(self.SE_corner[1], self.SE_corner[0]) # .. same for SE
|
|
200
|
+
|
|
201
|
+
self.pole=["E","N"]
|
|
202
|
+
self.grid_size=grid_size
|
|
203
|
+
self.zoom = zoom
|
|
204
|
+
print(f"map size is {int(abs(self.transformed_nw[1]-self.transformed_se[1]))}x{int(abs(self.transformed_nw[0]-self.transformed_se[0]))}m")
|
|
205
|
+
self.create_grid()
|
|
206
|
+
print(f"grid size is {self.width}x{self.heigth} cells")
|
|
207
|
+
self.calculate_pixels_per_grid()
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def save_to_file(self, name):
|
|
214
|
+
with open(name, 'w') as of:
|
|
215
|
+
for j in range(self.heigth):
|
|
216
|
+
for i in range(self.width):
|
|
217
|
+
of.write('{:f},{:f};'.format(self.gridpoints[j][i][0], self.gridpoints[j][i][1]))
|
|
218
|
+
of.write('\n')
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def create_grid(self):
|
|
222
|
+
# Iterate over 2D area
|
|
223
|
+
self.gridpoints = []
|
|
224
|
+
countx=0
|
|
225
|
+
county=0
|
|
226
|
+
x = self.transformed_nw[0]
|
|
227
|
+
if self.transformed_nw[0] >self.transformed_se[0]:
|
|
228
|
+
self.pole[0]="N"
|
|
229
|
+
else:
|
|
230
|
+
self.pole[0]="S"
|
|
231
|
+
while abs(x) > abs(self.transformed_se[0]):
|
|
232
|
+
county+=1
|
|
233
|
+
countx=0
|
|
234
|
+
column=[]
|
|
235
|
+
y = self.transformed_nw[1]
|
|
236
|
+
if self.transformed_nw[1] <self.transformed_se[1]:
|
|
237
|
+
self.pole[1]="E"
|
|
238
|
+
else:
|
|
239
|
+
self.pole[1]="W"
|
|
240
|
+
while abs(y) > abs(self.transformed_se[1]):
|
|
241
|
+
p = self.to_original_transformer.transform(x, y)
|
|
242
|
+
column.append([p[0],p[1]])
|
|
243
|
+
if self.pole[1]=="E":
|
|
244
|
+
y += self.grid_size
|
|
245
|
+
else:
|
|
246
|
+
y -= self.grid_size
|
|
247
|
+
countx+=1
|
|
248
|
+
column.pop() #cut corners
|
|
249
|
+
self.gridpoints.append(column)
|
|
250
|
+
if self.pole[0]=="N":
|
|
251
|
+
x -=self.grid_size
|
|
252
|
+
else:
|
|
253
|
+
x +=self.grid_size
|
|
254
|
+
self.gridpoints.pop() #cut corners
|
|
255
|
+
self.width=len(self.gridpoints[0])
|
|
256
|
+
self.heigth=len(self.gridpoints)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def to_pixel(self, GPS_point, zoom=19):
|
|
260
|
+
"""
|
|
261
|
+
Generates an X,Y pixel coordinate based on the latitude, longitude
|
|
262
|
+
and zoom level
|
|
263
|
+
Returns: An X,Y pixel coordinate
|
|
264
|
+
"""
|
|
265
|
+
#check if we are inside the map
|
|
266
|
+
if self.pole[1]=="W":
|
|
267
|
+
if ((self.SE_corner[1])>(GPS_point[1])>(self.NW_corner[1])):
|
|
268
|
+
raise CustomError(f"point {GPS_point} 1W is outside the map {[self.NW_corner,self.SE_corner]}")
|
|
269
|
+
else:
|
|
270
|
+
if ((self.SE_corner[1])<(GPS_point[1])<(self.NW_corner[1])):
|
|
271
|
+
raise CustomError(f"point {GPS_point} 1E is outside the map {[self.NW_corner,self.SE_corner]}")
|
|
272
|
+
if self.pole[0]=="N":
|
|
273
|
+
if ((self.SE_corner[0])>(GPS_point[0])>(self.NW_corner[0])):
|
|
274
|
+
raise CustomError(f"point {GPS_point} 0N is outside the map {[self.NW_corner,self.SE_corner]}")
|
|
275
|
+
else:
|
|
276
|
+
if ((self.SE_corner[0])<(GPS_point[0])<(self.NW_corner[0])):
|
|
277
|
+
raise CustomError(f"point {GPS_point} 0S is outside the map {[self.NW_corner,self.SE_corner]}")
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
tile_size = 256 #each tile has 256 pixels
|
|
282
|
+
|
|
283
|
+
# Use a left shift to get the power of 2, zoom 0 is a world map, zoom 1 is wold map divided in 4 tiles, zoom 2 is world map divided in 16 tiles
|
|
284
|
+
# i.e. a zoom level of 2 will have 2^2 = 4 tiles as coordinate sistem divides axis in 4 (x+y+,x-y+,x-y-,x+y-)
|
|
285
|
+
numTiles = 1 << self.zoom
|
|
286
|
+
|
|
287
|
+
# Find the x_pixel given the longitude
|
|
288
|
+
x_pixel = (tile_size / 2 + GPS_point[1] * tile_size / 360.0) * numTiles
|
|
289
|
+
|
|
290
|
+
# Convert the latitude to radians and take the sine
|
|
291
|
+
sin_y = math.sin(GPS_point[0] * (math.pi / 180.0))
|
|
292
|
+
|
|
293
|
+
# Calulate the y_pixel
|
|
294
|
+
y_pixel = ((tile_size / 2) + 0.5 * math.log((1 + sin_y) / (1 - sin_y)) * -(
|
|
295
|
+
tile_size / (2 * math.pi))) * numTiles
|
|
296
|
+
|
|
297
|
+
return [x_pixel-self.corner_pixel[0], y_pixel-self.corner_pixel[1]]
|
|
298
|
+
|
|
299
|
+
def calculate_pixels_per_grid(self):
|
|
300
|
+
|
|
301
|
+
self.calculate_corner_pixel()
|
|
302
|
+
p=self.to_pixel(self.gridpoints[self.heigth-1][self.width-1])
|
|
303
|
+
x=p[0]
|
|
304
|
+
y=p[1]
|
|
305
|
+
self.pixel_width=x/(self.width-1)
|
|
306
|
+
self.pixel_heigth=y/(self.heigth-1)
|
|
307
|
+
print(f"pixel size is {x}x{y} with corner {self.corner_pixel}")
|
|
308
|
+
|
|
309
|
+
def calculate_corner_pixel(self):
|
|
310
|
+
"""
|
|
311
|
+
Generates an X,Y pixel coordinate based on the latitude, longitude
|
|
312
|
+
and zoom level
|
|
313
|
+
Returns: An X,Y pixel coordinate
|
|
314
|
+
"""
|
|
315
|
+
|
|
316
|
+
tile_size = 256 #each tile has 256 pixels
|
|
317
|
+
|
|
318
|
+
# Use a left shift to get the power of 2, zoom 0 is a world map, zoom 1 is wold map divided in 4 tiles, zoom 2 is world map divided in 16 tiles
|
|
319
|
+
# i.e. a zoom level of 2 will have 2^2 = 4 tiles as coordinate sistem divides axis in 4 (x+y+,x-y+,x-y-,x+y-)
|
|
320
|
+
numTiles = 1 << self.zoom
|
|
321
|
+
|
|
322
|
+
# Find the x_pixel given the longitude
|
|
323
|
+
x_pixel = (tile_size / 2 + self.NW_corner[0] * tile_size / 360.0) * numTiles
|
|
324
|
+
|
|
325
|
+
# Convert the latitude to radians and take the sine
|
|
326
|
+
sin_y = math.sin(self.NW_corner[1] * (math.pi / 180.0))
|
|
327
|
+
|
|
328
|
+
# Calulate the y_pixel
|
|
329
|
+
y_pixel = ((tile_size / 2) + 0.5 * math.log((1 + sin_y) / (1 - sin_y)) * -(
|
|
330
|
+
tile_size / (2 * math.pi))) * numTiles
|
|
331
|
+
|
|
332
|
+
self.corner_pixel=[x_pixel, y_pixel]
|
|
333
|
+
|
|
334
|
+
def show_grid(self, img):
|
|
335
|
+
|
|
336
|
+
#show grid
|
|
337
|
+
x=[]
|
|
338
|
+
y=[]
|
|
339
|
+
plt.figure()
|
|
340
|
+
plt.imshow(img, cmap=plt.get_cmap('binary'))
|
|
341
|
+
|
|
342
|
+
#horizontal lines
|
|
343
|
+
for j in range(self.heigth):
|
|
344
|
+
for i in range(self.width):
|
|
345
|
+
p=self.to_pixel(self.gridpoints[j][i])
|
|
346
|
+
x.append(p[0])
|
|
347
|
+
y.append(p[1])
|
|
348
|
+
plt.plot(x,y,'b-', linewidth=0.2)
|
|
349
|
+
x=[]
|
|
350
|
+
y=[]
|
|
351
|
+
#vertical lines
|
|
352
|
+
|
|
353
|
+
for i in range(self.width):
|
|
354
|
+
for j in range(self.heigth):
|
|
355
|
+
p=self.to_pixel(self.gridpoints[j][i])
|
|
356
|
+
x.append(p[0])
|
|
357
|
+
y.append(p[1])
|
|
358
|
+
plt.plot(x,y,'b-', linewidth=0.2)
|
|
359
|
+
x=[]
|
|
360
|
+
y=[]
|
|
361
|
+
plt.savefig('grid.png', dpi=400)
|
|
362
|
+
|
|
363
|
+
img=Image.open('grid.png')
|
|
364
|
+
img.show()
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def show_point(self, img, point):
|
|
368
|
+
|
|
369
|
+
#print a point in the map
|
|
370
|
+
pixel=self.to_pixel(point)
|
|
371
|
+
|
|
372
|
+
plt.figure()
|
|
373
|
+
plt.imshow(img, cmap=plt.get_cmap('binary'))
|
|
374
|
+
plt.plot(pixel[0],pixel[1],'ro', markersize=5)
|
|
375
|
+
plt.show()
|
|
376
|
+
|
|
377
|
+
def save_empty_mask(self, file):
|
|
378
|
+
with open(file, 'w') as of:
|
|
379
|
+
for j in range(self.heigth-1):
|
|
380
|
+
for i in range(self.width-1):
|
|
381
|
+
of.write('0;')
|
|
382
|
+
of.write('\n')
|
|
383
|
+
|
|
384
|
+
def save_mask(self, file):
|
|
385
|
+
with open(file, 'w') as of:
|
|
386
|
+
for j in range(self.heigth-1):
|
|
387
|
+
for i in range(self.width-2):
|
|
388
|
+
of.write('{:d};'.format(self.mask[j][i]))
|
|
389
|
+
of.write('{:d}\n'.format(self.mask[j][i]))
|
|
390
|
+
|
|
391
|
+
def create_mask(self, img):
|
|
392
|
+
grayImage=img.convert(mode="L")
|
|
393
|
+
array = np.array(grayImage)
|
|
394
|
+
self.mask=[]
|
|
395
|
+
for j in range(self.heigth-1):
|
|
396
|
+
column=[]
|
|
397
|
+
for i in range(self.width-1):
|
|
398
|
+
p1=self.to_pixel(self.gridpoints[j][i])
|
|
399
|
+
p2=self.to_pixel(self.gridpoints[j+1][i])
|
|
400
|
+
p3=self.to_pixel(self.gridpoints[j][i+1])
|
|
401
|
+
p4=self.to_pixel(self.gridpoints[j+1][i+1])
|
|
402
|
+
p1=array[int(p1[1])][int(p1[0])]
|
|
403
|
+
p2=array[int(p2[1])][int(p2[0])]
|
|
404
|
+
p3=array[int(p3[1])][int(p3[0])]
|
|
405
|
+
p4=array[int(p4[1])][int(p4[0])]
|
|
406
|
+
white=0
|
|
407
|
+
if p1:
|
|
408
|
+
white+=1
|
|
409
|
+
if p2:
|
|
410
|
+
white+=1
|
|
411
|
+
if p3:
|
|
412
|
+
white+=1
|
|
413
|
+
if p4:
|
|
414
|
+
white+=1
|
|
415
|
+
if white>2:
|
|
416
|
+
column.append(0)
|
|
417
|
+
else:
|
|
418
|
+
column.append(1)
|
|
419
|
+
self.mask.append(column)
|
|
420
|
+
def show_mask(self, img):
|
|
421
|
+
#show grid
|
|
422
|
+
|
|
423
|
+
plt.figure()
|
|
424
|
+
plt.imshow(img, cmap=plt.get_cmap('binary'))
|
|
425
|
+
|
|
426
|
+
for j in range(self.heigth-1):
|
|
427
|
+
for i in range(self.width-1):
|
|
428
|
+
if not self.mask[j][i]:
|
|
429
|
+
x=[]
|
|
430
|
+
y=[]
|
|
431
|
+
p1=self.to_pixel(self.gridpoints[j][i])
|
|
432
|
+
p2=self.to_pixel(self.gridpoints[j+1][i])
|
|
433
|
+
p3=self.to_pixel(self.gridpoints[j][i+1])
|
|
434
|
+
p4=self.to_pixel(self.gridpoints[j+1][i+1])
|
|
435
|
+
x.append(p1[0])
|
|
436
|
+
y.append(p1[1])
|
|
437
|
+
x.append(p2[0])
|
|
438
|
+
y.append(p2[1])
|
|
439
|
+
x.append(p4[0])
|
|
440
|
+
y.append(p4[1])
|
|
441
|
+
x.append(p3[0])
|
|
442
|
+
y.append(p3[1])
|
|
443
|
+
x.append(p1[0])
|
|
444
|
+
y.append(p1[1])
|
|
445
|
+
plt.plot(x,y,'b-', linewidth=0.2)
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
plt.savefig('mask.png', dpi=400)
|
|
449
|
+
|
|
450
|
+
img=Image.open('mask.png')
|
|
451
|
+
img.show()
|
|
452
|
+
|
|
453
|
+
# def show_mission(self, img):
|
|
454
|
+
# plt.figure()
|
|
455
|
+
# plt.imshow(img, cmap=plt.get_cmap('binary'))
|
|
456
|
+
# point=0
|
|
457
|
+
# KML=KMLMissionGenerator("MisionesLoyola_dron_3.kml")
|
|
458
|
+
# mission=KML.get_mission_list()[0]
|
|
459
|
+
|
|
460
|
+
# for j in range(self.heigth-1):
|
|
461
|
+
# for i in range(self.width-1):
|
|
462
|
+
# if not self.mask[j][i]:
|
|
463
|
+
# x=[]
|
|
464
|
+
# y=[]
|
|
465
|
+
# p1=self.to_pixel(self.gridpoints[j][i])
|
|
466
|
+
# p2=self.to_pixel(self.gridpoints[j+1][i])
|
|
467
|
+
# p3=self.to_pixel(self.gridpoints[j][i+1])
|
|
468
|
+
# p4=self.to_pixel(self.gridpoints[j+1][i+1])
|
|
469
|
+
# x.append(p1[0])
|
|
470
|
+
# y.append(p1[1])
|
|
471
|
+
# x.append(p2[0])
|
|
472
|
+
# y.append(p2[1])
|
|
473
|
+
# x.append(p4[0])
|
|
474
|
+
# y.append(p4[1])
|
|
475
|
+
# x.append(p3[0])
|
|
476
|
+
# y.append(p3[1])
|
|
477
|
+
# x.append(p1[0])
|
|
478
|
+
# y.append(p1[1])
|
|
479
|
+
# plt.plot(x,y,'b-', linewidth=0.2)
|
|
480
|
+
# for i in mission:
|
|
481
|
+
# point=[i[1], i[0]]
|
|
482
|
+
# pixel=self.to_pixel(point)
|
|
483
|
+
# plt.plot(pixel[0],pixel[1],'ro', markersize=0.5)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
# plt.savefig('mission.png', dpi=400)
|
|
487
|
+
|
|
488
|
+
# img=Image.open('mission.png')
|
|
489
|
+
# img.show()
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def main():
|
|
493
|
+
# Create a new instance of GoogleMap Downloader
|
|
494
|
+
"""
|
|
495
|
+
Loyola
|
|
496
|
+
NW_corner=[37.30877610927852, -5.940516378146266] #northwest point of the map [latitude longitude]
|
|
497
|
+
SE_corner=[37.30661986681041, -5.939422309058239] #southeast point of the map [latitude longitude]
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
NW_corner=[36.123388953996404, -5.443591065870082] #northwest point of the map [latitude longitude]
|
|
501
|
+
SE_corner=[36.11595558243919, -5.430975637057081] #southeast point of the map [latitude longitude]
|
|
502
|
+
#alamillo
|
|
503
|
+
|
|
504
|
+
NW_corner=[36.227154628215054, -6.188738765767977] #northwest point of the map [latitude longitude]
|
|
505
|
+
SE_corner=[35.67592758704647, -5.13798693812742] #southeast point of the map [latitude longitude]
|
|
506
|
+
#Estrecho
|
|
507
|
+
"""
|
|
508
|
+
# NW_corner=[30.719200,-86.115300] #northwest point of the map [latitude longitude]
|
|
509
|
+
# SE_corner=[30.7148,-86.110014] #southeast point of the map [latitude longitude]
|
|
510
|
+
# #Lake DeFuniak
|
|
511
|
+
|
|
512
|
+
NW_corner=[37.420088017565696, -6.001345595400041] #northwest point of the map [latitude longitude]
|
|
513
|
+
SE_corner=[37.41837443521307, -5.997484063063451] #southeast point of the map [latitude longitude]
|
|
514
|
+
#alamillo
|
|
515
|
+
|
|
516
|
+
#SE_corner=[37.29989089621092, -5.9274044593699236]
|
|
517
|
+
grid_size= 2.5 #size of the grid in meters
|
|
518
|
+
zoom=19
|
|
519
|
+
|
|
520
|
+
#create class of the map downloader
|
|
521
|
+
gmd = GoogleMapDownloader(NW_corner ,SE_corner, zoom =zoom) #zoom 19, aproximadamente tiles de 50m^2, más zoom no se aprecian obstaculos nuevos
|
|
522
|
+
|
|
523
|
+
try:
|
|
524
|
+
# Get the high resolution image
|
|
525
|
+
img = gmd.generateImage()
|
|
526
|
+
except IOError:
|
|
527
|
+
print("Could not generate the image - try adjusting the zoom level and checking your coordinates")
|
|
528
|
+
else:
|
|
529
|
+
# Save the image to disk
|
|
530
|
+
img.rotate(17)
|
|
531
|
+
img.save("high_resolution_image2.png")
|
|
532
|
+
print("The map has successfully been created")
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
#Show the image
|
|
536
|
+
plt.figure()
|
|
537
|
+
plt.imshow(img, cmap=plt.get_cmap('binary'))
|
|
538
|
+
plt.show()
|
|
539
|
+
|
|
540
|
+
#create grid
|
|
541
|
+
print(f"generating map for {NW_corner}, {SE_corner}")
|
|
542
|
+
grid= gridder(NW_corner,SE_corner,grid_size, zoom =zoom)
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
#show the grid on the image
|
|
547
|
+
aux=0
|
|
548
|
+
while aux!="y" and aux!="n":
|
|
549
|
+
aux=input("is there already an existing mask (y/n)").lower()
|
|
550
|
+
if aux=='n':
|
|
551
|
+
grid.show_grid(img)
|
|
552
|
+
grid.save_to_file("grid.csv")
|
|
553
|
+
grid.save_empty_mask("empty_mask.csv")
|
|
554
|
+
return
|
|
555
|
+
|
|
556
|
+
while not os.path.exists(str("./"+aux)) or aux[-1]!="g":
|
|
557
|
+
aux=input("insert mask name: ").lower()
|
|
558
|
+
imgn=Image.open(aux)
|
|
559
|
+
grid.show_grid(imgn)
|
|
560
|
+
name=aux[0:-4]
|
|
561
|
+
grid.create_mask(imgn)
|
|
562
|
+
grid.save_mask(str(name+".csv"))
|
|
563
|
+
grid.show_mask(img)
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
#show a point
|
|
567
|
+
#grid.show_point(img, [37.307659405593995, -5.940044140455814])
|
|
568
|
+
|
|
569
|
+
#grid.show_mission(img)
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
if __name__ == '__main__': main()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
sys.path.append('.')
|
|
3
|
+
|
|
4
|
+
from Environment.generate_map import img2pixels
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
# Esquina inferior izquierda
|
|
9
|
+
lat = np.float64(37.41856004857407)
|
|
10
|
+
lon = np.float64(-6.001322282719345)
|
|
11
|
+
|
|
12
|
+
# Esquina superior derecha
|
|
13
|
+
lat2 = np.float64(37.41953493274117)
|
|
14
|
+
lon2 = np.float64(-5.9987915127912)
|
|
15
|
+
|
|
16
|
+
path = 'Environment/maps/alamillo.png'
|
|
17
|
+
|
|
18
|
+
pixels = img2pixels(path, threshold=128, height=40)
|
|
19
|
+
|
|
20
|
+
pixels = 1 - pixels
|
|
21
|
+
|
|
22
|
+
# Sabiendo que lat,lon hace referencia a la esquina inferior izquierda y lat2,lon2 a la esquina superior derecha,
|
|
23
|
+
# calculamos, para cada pixel, su latitud y longitud
|
|
24
|
+
latitudes = np.linspace(lat2, lat, pixels.shape[0], dtype=np.float64)
|
|
25
|
+
longitudes = np.linspace(lon, lon2, pixels.shape[1], dtype=np.float64)
|
|
26
|
+
|
|
27
|
+
# Creamos un array de MxNx2 dimensiones con las latitudes y longitudes
|
|
28
|
+
lat_lon_map = np.zeros((pixels.shape[0], pixels.shape[1], 2), dtype=np.float64)
|
|
29
|
+
|
|
30
|
+
for i in range(pixels.shape[0]):
|
|
31
|
+
for j in range(pixels.shape[1]):
|
|
32
|
+
lat_lon_map[i,j] = [latitudes[i], longitudes[j]]
|
|
33
|
+
|
|
34
|
+
np.save('Environment/maps/lat_lon_alamillo_big.npy', lat_lon_map)
|
|
35
|
+
np.save('Environment/maps/alamillo_big.npy', pixels)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Programa para pasar de una imagen a una matriz de pixeles 0,1 con un umbral y un alto en pixeles
|
|
2
|
+
import sys
|
|
3
|
+
from PIL import Image
|
|
4
|
+
import numpy as np
|
|
5
|
+
import matplotlib.pyplot as plt
|
|
6
|
+
import argparse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def img2pixels(img_path, threshold=128, height=100):
|
|
11
|
+
|
|
12
|
+
img = Image.open(img_path)
|
|
13
|
+
img = img.convert('L') # Convert to grayscale
|
|
14
|
+
img = img.resize((int(img.width * height / img.height), height), Image.LANCZOS)
|
|
15
|
+
img = np.array(img)
|
|
16
|
+
|
|
17
|
+
# Threshold
|
|
18
|
+
img = (img > threshold).astype(int)
|
|
19
|
+
|
|
20
|
+
return img
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
if __name__ == "__main__":
|
|
25
|
+
|
|
26
|
+
argparser = argparse.ArgumentParser(description='Convert image to pixel matrix')
|
|
27
|
+
argparser.add_argument('--path', type=str, help='Path to image')
|
|
28
|
+
argparser.add_argument('--threshold', type=int, default=128, help='Threshold for binarization')
|
|
29
|
+
argparser.add_argument('--height', type=int, default=100, help='Height of the output image')
|
|
30
|
+
argparser.add_argument('--output', type=str, help='Output file')
|
|
31
|
+
|
|
32
|
+
args = argparser.parse_args()
|
|
33
|
+
|
|
34
|
+
# Example
|
|
35
|
+
pixels = img2pixels(args.path, threshold=args.threshold, height=args.height)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# To numpy and save to file
|
|
39
|
+
|
|
40
|
+
np.save(args.output + '.npy', pixels)
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
sys.path.append('.')
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import numpy as np
|
|
6
|
+
import os
|
|
7
|
+
from typing import Union
|
|
8
|
+
import lzma
|
|
9
|
+
import pickle
|
|
10
|
+
import yaml
|
|
11
|
+
from collections import defaultdict
|
|
12
|
+
|
|
13
|
+
class Legendarium:
|
|
14
|
+
|
|
15
|
+
def __init__(self, experiment_name : str, experiment_description : str, path : str):
|
|
16
|
+
|
|
17
|
+
self._exp_name = experiment_name
|
|
18
|
+
self._exp_desc = experiment_description
|
|
19
|
+
self._path = path
|
|
20
|
+
|
|
21
|
+
# Check if directory exists
|
|
22
|
+
if path is not None:
|
|
23
|
+
if not os.path.exists(self._path):
|
|
24
|
+
os.makedirs(self._path)
|
|
25
|
+
else:
|
|
26
|
+
# Check if the there is a file with the same experiment name
|
|
27
|
+
if os.path.exists(os.path.join(self._path, f"{self._exp_name}.metrics.xz")):
|
|
28
|
+
raise Exception("Experiment already exists!")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
self.metrics_meta = {}
|
|
32
|
+
self.metrics = []
|
|
33
|
+
self.parameters = {}
|
|
34
|
+
self.n_metrics = 0
|
|
35
|
+
self.data_names = []
|
|
36
|
+
|
|
37
|
+
def create_parameter(self, parameter_name : str, parameter_value):
|
|
38
|
+
"""
|
|
39
|
+
Create a parameter for the experiment
|
|
40
|
+
"""
|
|
41
|
+
self.parameters.update({parameter_name : parameter_value})
|
|
42
|
+
|
|
43
|
+
def create_metric(self, metric_name : str, data_type : type, description : str, unit : str):
|
|
44
|
+
|
|
45
|
+
self.metrics_meta.update({metric_name : {"type" : data_type, "description" : description, "unit" : unit, "order": self.n_metrics}})
|
|
46
|
+
self.n_metrics += 1
|
|
47
|
+
|
|
48
|
+
def write(self, run : int, step : int, **kwargs):
|
|
49
|
+
"""
|
|
50
|
+
Write data to the metric
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
# Check if metric exists
|
|
54
|
+
for metric_name, new_data in kwargs.items():
|
|
55
|
+
|
|
56
|
+
if metric_name not in self.metrics_meta:
|
|
57
|
+
raise Exception(f"Metric {metric_name} not found!")
|
|
58
|
+
|
|
59
|
+
# Check if the data type is correct
|
|
60
|
+
if not isinstance(run, int):
|
|
61
|
+
raise Exception("Run should be an integer!")
|
|
62
|
+
|
|
63
|
+
if not isinstance(step, int):
|
|
64
|
+
raise Exception("Step should be an integer!")
|
|
65
|
+
|
|
66
|
+
# Check if the data type is correct
|
|
67
|
+
if not issubclass(type(kwargs[metric_name]), self.metrics_meta[metric_name]["type"]):
|
|
68
|
+
raise Exception(f"Data type mismatch for metric {metric_name}!")
|
|
69
|
+
|
|
70
|
+
# Check if all the metrics are present
|
|
71
|
+
if len(kwargs) != self.n_metrics:
|
|
72
|
+
raise Exception("Not all metrics are present in the data!")
|
|
73
|
+
|
|
74
|
+
new_data = [run, step] + [None] * self.n_metrics
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
for key, value in kwargs.items():
|
|
78
|
+
|
|
79
|
+
# Comprobamos si la clave es una metrica registrada
|
|
80
|
+
if key not in self.metrics_meta:
|
|
81
|
+
raise Exception(f"Metric {key} not found!")
|
|
82
|
+
|
|
83
|
+
# Comprobamos si el tipo de dato es correcto
|
|
84
|
+
if not isinstance(value, self.metrics_meta[key]["type"]):
|
|
85
|
+
raise Exception(f"Data type mismatch for metric {key}!")
|
|
86
|
+
|
|
87
|
+
# Obtenemos la posición en la lista
|
|
88
|
+
pos = self.metrics_meta[key]["order"]
|
|
89
|
+
new_data[pos + 2] = value
|
|
90
|
+
|
|
91
|
+
# Añadimos la información de los parámetros
|
|
92
|
+
for key, value in self.parameters.items():
|
|
93
|
+
new_data.append(value)
|
|
94
|
+
|
|
95
|
+
self.metrics.append(new_data)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def save(self):
|
|
99
|
+
"""
|
|
100
|
+
Save the experiment
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
data_names = ["run", "step"] + list(self.metrics_meta.keys()) + list(self.parameters.keys())
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# Save the metrics
|
|
107
|
+
with lzma.open(os.path.join(self._path, f"{self._exp_name}.metrics.xz"), "wb") as f:
|
|
108
|
+
pickle.dump(self.metrics, f)
|
|
109
|
+
|
|
110
|
+
# Save the metrics meta
|
|
111
|
+
with open(os.path.join(self._path, f"{self._exp_name}.meta.yaml"), "w") as f:
|
|
112
|
+
yaml.dump(data_names, f)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def load_experiment_pd(experiment_name : str, path : str):
|
|
116
|
+
# Load the metrics as a pandas dataframe
|
|
117
|
+
|
|
118
|
+
# Load the metrics
|
|
119
|
+
with lzma.open(os.path.join(path, f"{experiment_name}.metrics.xz"), "rb") as f:
|
|
120
|
+
metrics = pickle.load(f)
|
|
121
|
+
|
|
122
|
+
# Load the metrics meta
|
|
123
|
+
with open(os.path.join(path, f"{experiment_name}.meta.yaml"), "r") as f:
|
|
124
|
+
data_names = yaml.load(f, Loader=yaml.FullLoader)
|
|
125
|
+
|
|
126
|
+
# Create the dataframe
|
|
127
|
+
df = pd.DataFrame(metrics, columns = data_names)
|
|
128
|
+
|
|
129
|
+
return df
|
|
130
|
+
|
|
131
|
+
def load_experiments(path : str):
|
|
132
|
+
# Load all the experiments in a directory and return a pandas dataframe
|
|
133
|
+
|
|
134
|
+
pds = []
|
|
135
|
+
|
|
136
|
+
for file in os.listdir(path):
|
|
137
|
+
if file.endswith(".metrics.xz"):
|
|
138
|
+
experiment_name = file.split(".")[0]
|
|
139
|
+
try:
|
|
140
|
+
df = load_experiment_pd(experiment_name, path)
|
|
141
|
+
pds.append(df)
|
|
142
|
+
except Exception as e:
|
|
143
|
+
print(f"Error loading experiment {experiment_name}: {e}")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
return pd.concat(pds)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if __name__ == "__main__":
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# Create an instance of the class
|
|
153
|
+
exp = Legendarium(f"test", "Test experiment", "experiments")
|
|
154
|
+
|
|
155
|
+
# Create a parameter
|
|
156
|
+
exp.create_parameter("algorithm", "Greedy")
|
|
157
|
+
exp.create_parameter("max_distance", 100.0)
|
|
158
|
+
|
|
159
|
+
# Create a metric
|
|
160
|
+
exp.create_metric("reward", float, "Reward function", "points")
|
|
161
|
+
exp.create_metric("map", np.ndarray, "Mean map", "%")
|
|
162
|
+
|
|
163
|
+
for run in range(3):
|
|
164
|
+
|
|
165
|
+
# Write data
|
|
166
|
+
for i in range(100):
|
|
167
|
+
exp.write(run = run, step = i, reward = np.random.rand(), map = np.random.rand(100,100))
|
|
168
|
+
|
|
169
|
+
# Save the data
|
|
170
|
+
exp.save()
|
|
171
|
+
|
|
172
|
+
# Load the data
|
|
173
|
+
df = load_experiments("experiments")
|
|
174
|
+
print(df.head())
|
|
175
|
+
|
|
176
|
+
import seaborn as sns
|
|
177
|
+
import matplotlib.pyplot as plt
|
|
178
|
+
|
|
179
|
+
sns.lineplot(data = df, x = "step", y = "reward", hue = "run")
|
|
180
|
+
plt.show()
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import cv2
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
def png_to_csv(name, delim=' '):
|
|
5
|
+
''' Convert a binary image to a CSV file. '''
|
|
6
|
+
# Load the image
|
|
7
|
+
image = cv2.imread(f'Environment/maps/{name}.png', 0)
|
|
8
|
+
|
|
9
|
+
# Binarize the image
|
|
10
|
+
_, binary_image = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY)
|
|
11
|
+
|
|
12
|
+
# Convert the binary image to 0s and 1s
|
|
13
|
+
binary_image = np.where(binary_image > 0, 1, 0)
|
|
14
|
+
|
|
15
|
+
# Save the binary image as a CSV file
|
|
16
|
+
np.savetxt(f'Environment/maps/{name}.csv', binary_image, delimiter=delim, fmt='%d')
|
|
17
|
+
|
|
18
|
+
def csv_to_png(name, delim=' '):
|
|
19
|
+
''' Convert a CSV file to a binary image. '''
|
|
20
|
+
# Load the CSV file
|
|
21
|
+
binary_image = np.loadtxt(f'Environment/maps/{name}.csv', delimiter=delim)
|
|
22
|
+
|
|
23
|
+
# Convert the binary image to 0s and 255s
|
|
24
|
+
binary_image = np.where(binary_image > 0, 255, 0)
|
|
25
|
+
|
|
26
|
+
# Save the binary image as a PNG file
|
|
27
|
+
cv2.imwrite(f'Environment/maps/{name}.png', binary_image)
|
|
28
|
+
|
|
29
|
+
def csv_to_npy(name, delim=' '):
|
|
30
|
+
''' Convert a CSV file to a NumPy file. '''
|
|
31
|
+
if 'mask' in name or 'plantilla' in name or 'nodes' in name:
|
|
32
|
+
# Load the CSV file
|
|
33
|
+
binary_image = np.loadtxt(f'Environment/maps/{name}.csv', delimiter=delim)
|
|
34
|
+
|
|
35
|
+
# Save the binary image as a NumPy file
|
|
36
|
+
np.save(f'Environment/maps/{name}.npy', binary_image)
|
|
37
|
+
elif 'latlon' in name or 'grid' in name:
|
|
38
|
+
# There is a two component array in each cell #
|
|
39
|
+
|
|
40
|
+
# Load the CSV file
|
|
41
|
+
binary_image = np.loadtxt(f'Environment/maps/{name}.csv', delimiter=delim, dtype=str)
|
|
42
|
+
lat_lon_map = np.zeros((binary_image.shape[0], binary_image.shape[1], 2), dtype=np.float64)
|
|
43
|
+
|
|
44
|
+
# Convert the strings to tuples
|
|
45
|
+
for i in range(binary_image.shape[0]):
|
|
46
|
+
for j in range(binary_image.shape[1]):
|
|
47
|
+
lat = binary_image[i, j].split(',')[0]
|
|
48
|
+
lon = binary_image[i, j].split(',')[1]
|
|
49
|
+
lat_lon_map[i, j] = [float(lat), float(lon)]
|
|
50
|
+
|
|
51
|
+
# Save the binary image as a NumPy file
|
|
52
|
+
np.save(f'Environment/maps/{name}.npy', lat_lon_map)
|
|
53
|
+
|
|
54
|
+
def csv_downsize(name, delim=' ', factor=2, output_name=None):
|
|
55
|
+
''' Downsize a CSV file. '''
|
|
56
|
+
# Load the CSV file
|
|
57
|
+
binary_image = np.loadtxt(f'Environment/maps/{name}.csv', delimiter=delim, dtype=str)
|
|
58
|
+
|
|
59
|
+
# Downsize the binary image
|
|
60
|
+
binary_image = binary_image[::factor, ::factor]
|
|
61
|
+
|
|
62
|
+
output_name1 = output_name.split('_')[0]
|
|
63
|
+
output_name2 = output_name.split('_')[1]
|
|
64
|
+
|
|
65
|
+
# Save the binary image as a CSV file
|
|
66
|
+
np.savetxt(f'Environment/maps/{output_name1}{binary_image.shape[0]}x{binary_image.shape[1]}{output_name2}.csv', binary_image, delimiter=delim, fmt='%s')
|
|
67
|
+
|
|
68
|
+
def csv_shrink(name, delim=' ', init_column=0, final_column=None, init_row=0, final_row=None, output_name=None):
|
|
69
|
+
''' Shrink a CSV file. '''
|
|
70
|
+
# Load the CSV file
|
|
71
|
+
binary_image = np.loadtxt(f'Environment/maps/{name}.csv', delimiter=delim, dtype=str)
|
|
72
|
+
|
|
73
|
+
# Set the final column and row if they are None
|
|
74
|
+
if final_column is None:
|
|
75
|
+
final_column = binary_image.shape[1]
|
|
76
|
+
if final_row is None:
|
|
77
|
+
final_row = binary_image.shape[0]
|
|
78
|
+
|
|
79
|
+
# Shrink the binary image
|
|
80
|
+
binary_image = binary_image[init_row:final_row, init_column:final_column]
|
|
81
|
+
|
|
82
|
+
output_name1 = output_name.split('_')[0]
|
|
83
|
+
output_name2 = output_name.split('_')[1]
|
|
84
|
+
|
|
85
|
+
# Save the binary image as a CSV file
|
|
86
|
+
np.savetxt(f'Environment/maps/{output_name1}{binary_image.shape[0]}x{binary_image.shape[1]}{output_name2}.csv', binary_image, delimiter=delim, fmt='%s')
|
|
87
|
+
|
|
88
|
+
def get_output_name(name):
|
|
89
|
+
''' Get the output name if the format is correct. '''
|
|
90
|
+
output_name = name
|
|
91
|
+
first_number_pos = None
|
|
92
|
+
last_number_pos = None
|
|
93
|
+
for i, c in enumerate(name):
|
|
94
|
+
if c.isdigit():
|
|
95
|
+
if first_number_pos is None:
|
|
96
|
+
first_number_pos = i
|
|
97
|
+
last_number_pos = i
|
|
98
|
+
if first_number_pos is not None and last_number_pos is not None:
|
|
99
|
+
output_name = name[:first_number_pos] + '_' + name[last_number_pos+1:]
|
|
100
|
+
else:
|
|
101
|
+
output_name = 'output_map'
|
|
102
|
+
return output_name
|
|
103
|
+
|
|
104
|
+
def invert_map(name, delim=' '):
|
|
105
|
+
''' Invert a map. '''
|
|
106
|
+
# Load the CSV file
|
|
107
|
+
binary_image = np.loadtxt(f'Environment/maps/{name}.csv', delimiter=delim)
|
|
108
|
+
|
|
109
|
+
# Invert the binary image
|
|
110
|
+
binary_image = np.where(binary_image == 1, 0, 1)
|
|
111
|
+
|
|
112
|
+
# Save the binary image as a CSV file
|
|
113
|
+
np.savetxt(f'Environment/maps/{name}.csv', binary_image, delimiter=delim, fmt='%d')
|
|
114
|
+
|
|
115
|
+
if __name__ == '__main__':
|
|
116
|
+
|
|
117
|
+
# name = 'rawfiles/Alamillo95x216grid'
|
|
118
|
+
# delim = ';'
|
|
119
|
+
|
|
120
|
+
name = 'rawfiles/Alamillo95x216plantilla'
|
|
121
|
+
delim = ' '
|
|
122
|
+
|
|
123
|
+
# invert_map(name, delim)
|
|
124
|
+
# png_to_csv(name, delim)
|
|
125
|
+
# csv_to_png(name, delim)
|
|
126
|
+
# csv_to_npy(name, delim)
|
|
127
|
+
# csv_downsize(name, delim, factor=3, output_name=get_output_name(name))
|
|
128
|
+
# csv_shrink(name, delim, init_column=0, final_column=145, init_row=0, final_row=90, output_name=get_output_name(name))
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.ndimage import binary_dilation
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
|
|
5
|
+
# NODOS
|
|
6
|
+
nodos_mask = np.load('Environment/maps/AlamilloAccess11x15mask.npy')
|
|
7
|
+
nodos_lat_lon = np.load('Environment/maps/AlamilloAccess11x15latlon.npy')
|
|
8
|
+
|
|
9
|
+
# "REALES"
|
|
10
|
+
real_mask = np.genfromtxt('Environment/maps/rawfiles/Alamillo95x216plantilla.csv', dtype=int, delimiter=' ')
|
|
11
|
+
raw_grid_csv = np.genfromtxt('Environment/maps/rawfiles/Alamillo95x216grid.csv', delimiter=';', dtype=str)
|
|
12
|
+
real_lat_long = np.zeros((*real_mask.shape, 2))
|
|
13
|
+
# Transform the map to a float for long and lat
|
|
14
|
+
for i in range(real_mask.shape[0]):
|
|
15
|
+
for j in range(real_mask.shape[1]):
|
|
16
|
+
real_lat_long[i, j, 0] = float(raw_grid_csv[i, j].split(',')[0])
|
|
17
|
+
real_lat_long[i, j, 1] = float(raw_grid_csv[i, j].split(',')[1])
|
|
18
|
+
|
|
19
|
+
latitudes = real_lat_long[:,:, 0]
|
|
20
|
+
longitudes = real_lat_long[:,:, 1]
|
|
21
|
+
|
|
22
|
+
navigables_latitudes = latitudes[real_mask == 1]
|
|
23
|
+
navigables_longitudes = longitudes[real_mask == 1]
|
|
24
|
+
|
|
25
|
+
# Comprobar si los puntos de navegación en el mapa real están en el mapa de nodos
|
|
26
|
+
# Para cada punto navegable (1) en base_matrix, obtener su latitud y longitud. Y comprobar si todos son alcanzables.
|
|
27
|
+
cont_unreach = 0
|
|
28
|
+
cont_reach = 0
|
|
29
|
+
non_reachable_points = []
|
|
30
|
+
reachable_points = []
|
|
31
|
+
for i in range(nodos_mask.shape[0]):
|
|
32
|
+
for j in range(nodos_mask.shape[1]):
|
|
33
|
+
if nodos_mask[i, j] == 1:
|
|
34
|
+
# Comprobar si está como navegable en el mapa de navegación
|
|
35
|
+
if nodos_lat_lon[i, j, 0] in navigables_latitudes and nodos_lat_lon[i, j, 1] in navigables_longitudes:
|
|
36
|
+
# Obtener los índices de la latitud y longitud en el mapa de navegación
|
|
37
|
+
lat_indexs = np.where(navigables_latitudes == nodos_lat_lon[i, j, 0])[0]
|
|
38
|
+
lon_indexs = np.where(navigables_longitudes == nodos_lat_lon[i, j, 1])[0]
|
|
39
|
+
|
|
40
|
+
# Comprobar si hay algún índice en común
|
|
41
|
+
if len(set(lat_indexs).intersection(set(lon_indexs))) == 0:
|
|
42
|
+
print(f'No alcanzable (no común): {nodos_lat_lon[i, j, 0]}, {nodos_lat_lon[i, j, 1]}')
|
|
43
|
+
cont_unreach += 1
|
|
44
|
+
non_reachable_points.append((i, j))
|
|
45
|
+
else:
|
|
46
|
+
cont_reach += 1
|
|
47
|
+
reachable_points.append((i, j))
|
|
48
|
+
else:
|
|
49
|
+
print(f'No alcanzable: {nodos_lat_lon[i, j, 0]}, {nodos_lat_lon[i, j, 1]}')
|
|
50
|
+
cont_unreach += 1
|
|
51
|
+
non_reachable_points.append((i, j))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
if cont_unreach == 0:
|
|
55
|
+
print(f'Todos los puntos son alcanzables: {cont_reach}')
|
|
56
|
+
else:
|
|
57
|
+
print(f'{cont_unreach} puntos no son alcanzables de un total de {np.sum(nodos_mask)}')
|
|
58
|
+
|
|
59
|
+
# Pintar los puntos no alcanzables
|
|
60
|
+
plt.imshow(nodos_mask)
|
|
61
|
+
for point in non_reachable_points:
|
|
62
|
+
plt.scatter(point[1], point[0], c='r', s=10)
|
|
63
|
+
for point in reachable_points:
|
|
64
|
+
plt.scatter(point[1], point[0], c='g', s=10)
|
|
65
|
+
plt.show()
|