diff --git a/.gitignore b/.gitignore index 0ce64a31..32f89517 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ package-lock.json /frontend/setup frontend/server/public/plugins/controltipsplugin/index.js frontend/website/plugins/controltips/index.js +/frontend/server/public/maps diff --git a/frontend/website/src/constants/constants.ts b/frontend/website/src/constants/constants.ts index 7ada419b..e544d31f 100644 --- a/frontend/website/src/constants/constants.ts +++ b/frontend/website/src/constants/constants.ts @@ -190,6 +190,12 @@ export const mapLayers = { minZoom: 1, maxZoom: 20, attribution: 'CyclOSM | Map data: © OpenStreetMap contributors' + }, + "DCS": { + urlTemplate: 'http://localhost:3000/maps/dcs/{z}/{x}/{y}.png', + minZoom: 16, + maxZoom: 16, + attribution: 'Eagle Dynamics' } } diff --git a/frontend/website/src/map/map.ts b/frontend/website/src/map/map.ts index f2af5ce7..a10afd7d 100644 --- a/frontend/website/src/map/map.ts +++ b/frontend/website/src/map/map.ts @@ -7,7 +7,7 @@ import { AirbaseContextMenu } from "../contextmenus/airbasecontextmenu"; import { Dropdown } from "../controls/dropdown"; import { Airbase } from "../mission/airbase"; import { Unit } from "../unit/unit"; -import { bearing, createCheckboxOption, polyContains } from "../other/utils"; +import { bearing, createCheckboxOption, deg2rad, getGroundElevation, polyContains } from "../other/utils"; import { DestinationPreviewMarker } from "./markers/destinationpreviewmarker"; import { TemporaryUnitMarker } from "./markers/temporaryunitmarker"; import { ClickableMiniMap } from "./clickableminimap"; @@ -157,6 +157,7 @@ export class Map extends L.Map { this.on('drag', (e: any) => this.#onMouseMove(e)); this.on('keydown', (e: any) => this.#onKeyDown(e)); this.on('keyup', (e: any) => this.#onKeyUp(e)); + this.on('move', (e: any) => this.#broadcastPosition(e)); /* Event listeners */ document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => { @@ -704,6 +705,29 @@ export class Map extends L.Map { this.#isZooming = false; } + #broadcastPosition(e: any) { + getGroundElevation(this.getCenter(), (response: string) => { + var groundElevation: number | null = null; + try { + groundElevation = parseFloat(response); + var xmlHttp = new XMLHttpRequest(); + xmlHttp.open("PUT", "http://localhost:8080"); + xmlHttp.setRequestHeader("Content-Type", "application/json"); + + const C = 40075016.686; + let mpp = C * Math.cos(deg2rad(this.getCenter().lat)) / Math.pow(2, this.getZoom() + 8); + let d = mpp * 1920; + let alt = d / 2 * 1 / Math.tan(deg2rad(40)); + if (alt > 100000) + alt = 100000; + xmlHttp.send(JSON.stringify({lat: this.getCenter().lat, lng: this.getCenter().lng, alt: alt + groundElevation})); + } catch { + console.warn("broadcastPosition: could not retrieve ground elevation") + } + }); + + } + /* */ #panToUnit(unit: Unit) { var unitPosition = new L.LatLng(unit.getPosition().lat, unit.getPosition().lng); diff --git a/scripts/python/generateMaps/.vscode/launch.json b/scripts/python/generateMaps/.vscode/launch.json new file mode 100644 index 00000000..306f58eb --- /dev/null +++ b/scripts/python/generateMaps/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Current File", + "type": "python", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "justMyCode": true + } + ] +} \ No newline at end of file diff --git a/scripts/python/generateMaps/generate_tiles.py b/scripts/python/generateMaps/generate_tiles.py new file mode 100644 index 00000000..a6663147 --- /dev/null +++ b/scripts/python/generateMaps/generate_tiles.py @@ -0,0 +1,41 @@ +import os +from PIL import Image +import concurrent.futures + +zoom = 16 +path = "output" + +def crop_image(filename): + img = Image.open(os.path.join(path, filename)) + center_X, center_Y = filename.removesuffix(".png").split("_") + center_X = int(center_X) + center_Y = int(center_Y) + w, h = img.size + + box_top_left = (w / 2 - 256, h / 2 - 256, w / 2, h / 2) + box_top_right = (w / 2, h / 2 - 256, w / 2 + 256, h / 2) + box_bottom_left = (w / 2 - 256, h / 2, w / 2, h / 2 + 256) + box_bottom_right = (w / 2, h / 2, w / 2 + 256, h / 2 + 256) + + if not os.path.exists(f"output/{zoom}/{center_X - 1}"): + os.mkdir(f"output/{zoom}/{center_X - 1}") + + if not os.path.exists(f"output/{zoom}/{center_X}"): + os.mkdir(f"output/{zoom}/{center_X}") + + img.crop(box_top_left).save(f"output/{zoom}/{center_X - 1}/{center_Y - 1}.png") + img.crop(box_top_right).save(f"output/{zoom}/{center_X}/{center_Y - 1}.png") + img.crop(box_bottom_left).save(f"output/{zoom}/{center_X - 1}/{center_Y}.png") + img.crop(box_bottom_right).save(f"output/{zoom}/{center_X}/{center_Y}.png") + + return True + +filenames = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))] + +# Create output folder +if not os.path.exists(f"output/{zoom}"): + os.mkdir(f"output/{zoom}") + +with concurrent.futures.ThreadPoolExecutor() as executor: + futures = [executor.submit(crop_image, filename) for filename in filenames] + results = [future.result() for future in concurrent.futures.as_completed(futures)] \ No newline at end of file diff --git a/scripts/python/generateMaps/take_screenshots.py b/scripts/python/generateMaps/take_screenshots.py new file mode 100644 index 00000000..160f7e4b --- /dev/null +++ b/scripts/python/generateMaps/take_screenshots.py @@ -0,0 +1,75 @@ +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 + + + +