More work on automatic map creation

This commit is contained in:
Davide Passoni
2024-02-22 17:46:34 +01:00
parent 14679bd7d8
commit acb55044d1
13 changed files with 408 additions and 80 deletions

View File

@@ -10,7 +10,7 @@
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": true
"args": ["./configs/LasVegas/LasVegas.yml"]
}
]
}

View File

@@ -0,0 +1,81 @@
import math
import requests
import json
import pyautogui
import time
import os
from pyproj import Geod
from fastkml import kml
from shapely import wkt
def deg_to_num(lat_deg, lon_deg, zoom):
lat_rad = math.radians(lat_deg)
n = 1 << zoom
xtile = int((lon_deg + 180.0) / 360.0 * n)
ytile = int((1.0 - math.asinh(math.tan(lat_rad)) / math.pi) / 2.0 * n)
return xtile, ytile
def num_to_deg(xtile, ytile, zoom):
n = 1 << zoom
lon_deg = xtile / n * 360.0 - 180.0
lat_rad = math.atan(math.sinh(math.pi * (1 - 2 * ytile / n)))
lat_deg = math.degrees(lat_rad)
return lat_deg, lon_deg
def run(map_config):
with open(map_config["boundary_file"], 'rt', encoding="utf-8") as bp:
# Read the config file
doc = bp.read()
k = kml.KML()
k.from_string(doc)
geod = Geod(ellps="WGS84")
features = [f for f in list(k.features()) if not f.isopen]
print(f"Found {len(features)} closed features in the provided kml file")
area = 0
for feature in features:
for sub_feature in list(feature.features()):
geo = sub_feature.geometry
start_lat = geo.bounds[1]
start_lng = geo.bounds[0]
end_lat = geo.bounds[3]
end_lng = geo.bound[2]
# Find the starting and ending points
start_X, start_Y = deg_to_num(start_lat, start_lng, zoom)
end_X, end_Y = deg_to_num(end_lat, end_lng, zoom)
time.sleep(2)
# Create output folder
if not os.path.exists("output"):
os.mkdir("output")
# Start looping
n = 1
total = math.floor((end_X - start_X) / 2) * math.floor((end_Y - start_Y) / 2)
for X in range(start_X, end_X, 2):
for Y in range(start_Y, end_Y, 2):
# Find the center of the screen
center_lat, center_lng = num_to_deg(X + 1, Y + 1, zoom)
center_alt = camera_altitude(center_lat)
# Making PUT request
data = json.dumps({'lat': center_lat, 'lng': center_lng, 'alt': center_alt})
r = requests.put('http://localhost:8080', data = data)
# Take and save screenshot
screenshot = pyautogui.screenshot()
screenshot.save(f"output/{X + 1}_{Y + 1}_{zoom}.png")
time.sleep(0.5)
print(f"Shot {n} of {total}")
n = n + 1

View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom">
<Document>
<name>Senza titolo</name>
<gx:CascadingStyle kml:id="__managed_style_29D7120C702F06CED14C">
<Style>
<IconStyle>
<scale>1.2</scale>
<Icon>
<href>https://earth.google.com/earth/rpc/cc/icon?color=1976d2&amp;id=2000&amp;scale=4</href>
</Icon>
<hotSpot x="64" y="128" xunits="pixels" yunits="insetPixels"/>
</IconStyle>
<LabelStyle>
</LabelStyle>
<LineStyle>
<color>ff2dc0fb</color>
<width>6</width>
</LineStyle>
<PolyStyle>
<color>40ffffff</color>
</PolyStyle>
<BalloonStyle>
<displayMode>hide</displayMode>
</BalloonStyle>
</Style>
</gx:CascadingStyle>
<gx:CascadingStyle kml:id="__managed_style_1E6AF60F852F06CED14C">
<Style>
<IconStyle>
<Icon>
<href>https://earth.google.com/earth/rpc/cc/icon?color=1976d2&amp;id=2000&amp;scale=4</href>
</Icon>
<hotSpot x="64" y="128" xunits="pixels" yunits="insetPixels"/>
</IconStyle>
<LabelStyle>
</LabelStyle>
<LineStyle>
<color>ff2dc0fb</color>
<width>4</width>
</LineStyle>
<PolyStyle>
<color>40ffffff</color>
</PolyStyle>
<BalloonStyle>
<displayMode>hide</displayMode>
</BalloonStyle>
</Style>
</gx:CascadingStyle>
<StyleMap id="__managed_style_05AC9C65832F06CED14C">
<Pair>
<key>normal</key>
<styleUrl>#__managed_style_1E6AF60F852F06CED14C</styleUrl>
</Pair>
<Pair>
<key>highlight</key>
<styleUrl>#__managed_style_29D7120C702F06CED14C</styleUrl>
</Pair>
</StyleMap>
<Placemark id="04A23CB2A82F06CED14C">
<name>Poligono senza titolo</name>
<LookAt>
<longitude>-115.7575513617584</longitude>
<latitude>36.45909683572987</latitude>
<altitude>1668.83938821393</altitude>
<heading>0</heading>
<tilt>0</tilt>
<gx:fovy>35</gx:fovy>
<range>382627.9679017514</range>
<altitudeMode>absolute</altitudeMode>
</LookAt>
<styleUrl>#__managed_style_05AC9C65832F06CED14C</styleUrl>
<Polygon>
<outerBoundaryIs>
<LinearRing>
<coordinates>
-115.5765603362792,35.92113103850846,0 -114.7667743850415,35.914903046417,0 -114.7800419428627,36.39467581209903,0 -115.6198023719551,36.39886214564519,0 -115.5765603362792,35.92113103850846,0
</coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>
</Document>
</kml>

View File

@@ -0,0 +1,4 @@
{
'output_directory': './LasVegas', # Where to save the output files
'boundary_file': './configs/LasVegas/LasVegas.kml'
}

View File

@@ -0,0 +1,10 @@
{
'width': 1920, # The width of your screen, in pixels
'height': 1080, # The height of your screen, in pixels
'geo_resolution': 1.0 # The resolution of the map on the screen, in meters per pixel.
# To measure this value, first set the F10 map at the desired zoom level.
# Then, use F10's map measure tool, and measure the width of the screen in meters.
# Finally, divide that value by the width in pixels.
# A good value would be around 1 meter per pixel, meaning a 1920px wide map would measure about 1 nautical mile across on the F10 map
# Lower values will produce higher resolution maps, but beware of space usage!
}

View File

@@ -1,12 +1,20 @@
import os
from PIL import Image
import concurrent.futures
import math
# correction parameters
# NTTR
rotation = math.degrees(0.01895)
scale = 0.973384
zoom = 16
path = "output"
def crop_image(filename):
img = Image.open(os.path.join(path, filename))
img = Image.open(os.path.join(path, filename)).rotate(-rotation)
img = img.resize((math.floor(img.width * scale), math.floor(img.height * scale)))
center_X, center_Y = filename.removesuffix(".png").split("_")
center_X = int(center_X)
center_Y = int(center_Y)

View File

@@ -0,0 +1,66 @@
import sys
import yaml
from pyproj import Geod
from fastkml import kml
from shapely import wkt
from datetime import timedelta
import capture_screen
if len(sys.argv) == 1:
print("Please provide a configuration file as first argument. You can also drop the configuration file on this script to run it.")
else:
config_file = sys.argv[1]
print(f"Using config file: {config_file}")
with open('configs/screen_properties.yml', 'r') as sp:
with open(config_file, 'r') as cp:
screen_config = yaml.safe_load(sp)
map_config = yaml.safe_load(cp)
print("#################################################################################################################################################")
print("# IMPORTANT NOTE: the screen properties must be configured according to your screen and desired zoom level. Make sure you set them accordingly. #")
print("#################################################################################################################################################")
print("Screen parameters:")
print(f"-> Screen width: {screen_config["width"]}px")
print(f"-> Screen height: {screen_config["height"]}px")
print(f"-> Geographic resolution: {screen_config["geo_resolution"]} meters/pixel")
print("Map parameters:")
print(f"-> Output directory: {map_config["output_directory"]}")
print(f"-> Boundary file: {map_config["boundary_file"]}")
with open(map_config["boundary_file"], 'rt', encoding="utf-8") as bp:
# Read the config file and compute the total area of the covered map
doc = bp.read()
k = kml.KML()
k.from_string(doc)
geod = Geod(ellps="WGS84")
features = [f for f in list(k.features()) if not f.isopen]
print(f"Found {len(features)} closed features in the provided kml file")
area = 0
for feature in features:
for sub_feature in list(feature.features()):
geo = sub_feature.geometry
area += abs(geod.geometry_area_perimeter(wkt.loads(geo.wkt))[0])
tile_size = 256 * screen_config["geo_resolution"] # meters
tiles_per_screenshot = int(screen_config["width"] / 256) * int(screen_config["height"] / 256)
tiles_num = int(area / (tile_size * tile_size))
screenshots_num = int(tiles_num / tiles_per_screenshot)
total_time = int(screenshots_num / 1.0)
print(f"Total area: {int(area / 1e6)} square kilometers")
print(f"Estimated number of tiles: {tiles_num}")
print(f"Estimated number of screenshots: {screenshots_num}")
print(f"Estimated time to complete: {timedelta(seconds=total_time)} (hh:mm:ss)")
print("The script is ready to go. After you press any key, it will wait for 5 seconds, and then it will start.")
input("Press any key to continue...")
capture_screen.run(map_config)

View File

@@ -0,0 +1,23 @@
certifi==2024.2.2
charset-normalizer==3.3.2
fastkml==0.12
idna==3.6
MouseInfo==0.1.3
numpy==1.26.4
pillow==10.2.0
PyAutoGUI==0.9.54
pygeoif==0.7
PyGetWindow==0.0.9
PyMsgBox==1.0.9
pyperclip==1.8.2
pyproj==3.6.1
PyRect==0.2.0
PyScreeze==0.1.30
python-dateutil==2.8.2
pytweening==1.2.0
PyYAML==6.0.1
requests==2.31.0
setuptools==69.1.0
shapely==2.0.3
six==1.16.0
urllib3==2.2.1

View File

@@ -1,75 +0,0 @@
import math
import requests
import json
import pyautogui
import time
import os
# parameters
start_lat = 36.31669444 # degs
start_lng = -115.38336111 # degs
end_lat = 35.93336111 # degs
end_lng = -114.95002778 # degs
fov = 10 # deg
zoom = 16
# constants
C = 40075016.686 # meters
def deg_to_num(lat_deg, lon_deg, zoom):
lat_rad = math.radians(lat_deg)
n = 1 << zoom
xtile = int((lon_deg + 180.0) / 360.0 * n)
ytile = int((1.0 - math.asinh(math.tan(lat_rad)) / math.pi) / 2.0 * n)
return xtile, ytile
def num_to_deg(xtile, ytile, zoom):
n = 1 << zoom
lon_deg = xtile / n * 360.0 - 180.0
lat_rad = math.atan(math.sinh(math.pi * (1 - 2 * ytile / n)))
lat_deg = math.degrees(lat_rad)
return lat_deg, lon_deg
def camera_altitude(lat_deg):
mpp = C * math.cos(math.radians(lat_deg)) / math.pow(2, zoom + 8)
d = mpp * 1920
alt = d / 2 * 1 / math.tan(math.radians(fov) / 2)
return alt
# Find the starting and ending points
start_X, start_Y = deg_to_num(start_lat, start_lng, zoom)
end_X, end_Y = deg_to_num(end_lat, end_lng, zoom)
time.sleep(2)
# Create output folder
if not os.path.exists("output"):
os.mkdir("output")
# Start looping
n = 1
total = math.floor((end_X - start_X) / 2) * math.floor((end_Y - start_Y) / 2)
for X in range(start_X, end_X, 2):
for Y in range(start_Y, end_Y, 2):
# Find the center of the screen
center_lat, center_lng = num_to_deg(X + 1, Y + 1, zoom)
center_alt = camera_altitude(center_lat)
# Making PUT request
data = json.dumps({'lat': center_lat, 'lng': center_lng, 'alt': center_alt})
r = requests.put('http://localhost:8080', data = data)
# Take and save screenshot
screenshot = pyautogui.screenshot()
screenshot.save(f"output/{X + 1}_{Y + 1}_{zoom}.png")
time.sleep(0.5)
print(f"Shot {n} of {total}")
n = n + 1