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
+
+
+
+