From 79f99054131fa0367fdba5fe4a1f2fcec0539db9 Mon Sep 17 00:00:00 2001 From: Davide Passoni Date: Tue, 28 Jan 2025 09:47:44 +0100 Subject: [PATCH] feat: completed orbit management --- backend/core/include/airunit.h | 4 + backend/core/src/airunit.cpp | 43 ++++- backend/core/src/scheduler.cpp | 14 +- .../public/images/favicons/site.webmanifest | 4 +- frontend/react/src/map/markers/arrowmarker.ts | 34 ++++ .../react/src/map/markers/explosionmarker.ts | 2 +- frontend/react/src/map/markers/smokemarker.ts | 2 +- .../src/map/markers/temporaryunitmarker.ts | 4 +- frontend/react/src/map/stylesheets/map.css | 12 +- frontend/react/src/mission/airbase.ts | 2 +- frontend/react/src/mission/bullseye.ts | 2 +- frontend/react/src/server/servermanager.ts | 6 + frontend/react/src/ui/modals/loginmodal.tsx | 8 +- frontend/react/src/ui/panels/header.tsx | 2 +- .../react/src/ui/panels/unitspawnmenu.tsx | 4 +- frontend/react/src/ui/serveroverlay.tsx | 2 +- frontend/react/src/unit/unit.ts | 156 +++++++++++++----- 17 files changed, 241 insertions(+), 60 deletions(-) create mode 100644 frontend/react/src/map/markers/arrowmarker.ts diff --git a/backend/core/include/airunit.h b/backend/core/include/airunit.h index 797a4e0d..66610466 100644 --- a/backend/core/include/airunit.h +++ b/backend/core/include/airunit.h @@ -18,6 +18,10 @@ public: virtual void changeSpeed(string change) = 0; virtual void changeAltitude(string change) = 0; virtual double getDestinationReachedThreshold() { return AIR_DEST_DIST_THR; } + + virtual void setRacetrackLength(double newValue); + virtual void setRacetrackAnchor(Coords newValue); + virtual void setRacetrackBearing(double newValue); protected: virtual void AIloop(); diff --git a/backend/core/src/airunit.cpp b/backend/core/src/airunit.cpp index 99f85fb5..c410e803 100644 --- a/backend/core/src/airunit.cpp +++ b/backend/core/src/airunit.cpp @@ -154,9 +154,11 @@ void AirUnit::AIloop() { srand(static_cast(time(NULL)) + ID); - if (state != State::IDLE) { + /* Reset the anchor, but only if the unit is not a tanker or a AWACS */ + if (state != State::IDLE && !isActiveTanker && !isActiveAWACS) { setRacetrackAnchor(Coords(NULL)); setRacetrackBearing(NULL); + setRacetrackLength(NULL); } /* State machine */ @@ -387,4 +389,43 @@ void AirUnit::AIloop() default: break; } +} + +void AirUnit::setRacetrackLength(double newRacetrackLength) { + if (racetrackLength != newRacetrackLength) { + racetrackLength = newRacetrackLength; + + /* Apply the change */ + setHasTask(false); + resetTaskFailedCounter(); + AIloop(); + + triggerUpdate(DataIndex::racetrackLength); + } +} + +void AirUnit::setRacetrackAnchor(Coords newRacetrackAnchor) { + if (racetrackAnchor != newRacetrackAnchor) { + racetrackAnchor = newRacetrackAnchor; + + /* Apply the change */ + setHasTask(false); + resetTaskFailedCounter(); + AIloop(); + + triggerUpdate(DataIndex::racetrackAnchor); + } +} + +void AirUnit::setRacetrackBearing(double newRacetrackBearing) { + if (racetrackBearing != newRacetrackBearing) { + racetrackBearing = newRacetrackBearing; + + /* Apply the change */ + setHasTask(false); + resetTaskFailedCounter(); + AIloop(); + + triggerUpdate(DataIndex::racetrackBearing); + } } \ No newline at end of file diff --git a/backend/core/src/scheduler.cpp b/backend/core/src/scheduler.cpp index b92953f1..811cdcdc 100644 --- a/backend/core/src/scheduler.cpp +++ b/backend/core/src/scheduler.cpp @@ -354,14 +354,22 @@ void Scheduler::handleRequest(string key, json::value value, string username, js log(username + " set " + unit->getUnitName() + "(" + unit->getName() + ") altitude type: " + to_string(value[L"altitudeType"]), true); } }/************************/ - else if (key.compare("setRacetrackLength") == 0) + else if (key.compare("setRacetrack") == 0) { unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) { - unit->setRacetrackLength(value[L"racetrackLength"].as_double()); - log(username + " set " + unit->getUnitName() + "(" + unit->getName() + ") racetrack length: " + to_string(value[L"racetrackLength"].as_double()), true); + unit->setRacetrackLength(value[L"length"].as_double()); + + double lat = value[L"location"][L"lat"].as_double(); + double lng = value[L"location"][L"lng"].as_double(); + Coords location; location.lat = lat; location.lng = lng; + unit->setRacetrackAnchor(location); + + unit->setRacetrackBearing(value[L"bearing"].as_double()); + + log(username + " set " + unit->getUnitName() + "(" + unit->getName() + ") racetrack length: " + to_string(value[L"length"].as_double()) + " racetrack bearing: " + to_string(value[L"bearing"].as_double()), true); } } /************************/ diff --git a/frontend/react/public/images/favicons/site.webmanifest b/frontend/react/public/images/favicons/site.webmanifest index b4b407a7..ce7461bc 100644 --- a/frontend/react/public/images/favicons/site.webmanifest +++ b/frontend/react/public/images/favicons/site.webmanifest @@ -3,12 +3,12 @@ "short_name": "DCS Olympus", "icons": [ { - "src": "./images/favicons/android-chrome-192x192.png", + "src": "images/favicons/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, { - "src": "./images/favicons/android-chrome-512x512.png", + "src": "images/favicons/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" } diff --git a/frontend/react/src/map/markers/arrowmarker.ts b/frontend/react/src/map/markers/arrowmarker.ts new file mode 100644 index 00000000..5643bc6f --- /dev/null +++ b/frontend/react/src/map/markers/arrowmarker.ts @@ -0,0 +1,34 @@ +import { DivIcon, LatLng } from "leaflet"; +import { CustomMarker } from "../markers/custommarker"; +import { SVGInjector } from "@tanem/svg-injector"; + +export class ArrowMarker extends CustomMarker { + #bearing: number = 0; + + constructor(latlng: LatLng) { + super(latlng, { interactive: true, draggable: false }); + } + + createIcon() { + this.setIcon( + new DivIcon({ + iconSize: [24, 24], + iconAnchor: [12, 12], + className: "leaflet-arrow-marker", + }) + ); + var div = document.createElement("div"); + var img = document.createElement("img"); + img.src = "images/others/arrow.svg"; + img.onload = () => SVGInjector(img); + div.classList.add("ol-arrow-icon"); + div.append(img); + this.getElement()?.appendChild(div); + } + + setBearing(bearing: number) { + this.#bearing = bearing; + let img = this.getElement()?.querySelector("svg"); + if (img) img.style.transform = `rotate(${bearing}rad)`; + } +} diff --git a/frontend/react/src/map/markers/explosionmarker.ts b/frontend/react/src/map/markers/explosionmarker.ts index 633e82d6..071300f6 100644 --- a/frontend/react/src/map/markers/explosionmarker.ts +++ b/frontend/react/src/map/markers/explosionmarker.ts @@ -31,7 +31,7 @@ export class ExplosionMarker extends CustomMarker { var el = document.createElement("div"); el.classList.add("ol-explosion-icon"); var img = document.createElement("img"); - img.src = "./images/markers/explosion.svg"; + img.src = "images/markers/explosion.svg"; img.onload = () => SVGInjector(img); el.appendChild(img); this.getElement()?.appendChild(el); diff --git a/frontend/react/src/map/markers/smokemarker.ts b/frontend/react/src/map/markers/smokemarker.ts index 86c62a59..cc131620 100644 --- a/frontend/react/src/map/markers/smokemarker.ts +++ b/frontend/react/src/map/markers/smokemarker.ts @@ -28,7 +28,7 @@ export class SmokeMarker extends CustomMarker { el.classList.add("ol-smoke-icon"); el.setAttribute("data-color", this.#color); var img = document.createElement("img"); - img.src = "./images/markers/smoke.svg"; + img.src = "images/markers/smoke.svg"; img.onload = () => SVGInjector(img); el.appendChild(img); this.getElement()?.appendChild(el); diff --git a/frontend/react/src/map/markers/temporaryunitmarker.ts b/frontend/react/src/map/markers/temporaryunitmarker.ts index 87ad7703..ff09d38e 100644 --- a/frontend/react/src/map/markers/temporaryunitmarker.ts +++ b/frontend/react/src/map/markers/temporaryunitmarker.ts @@ -60,7 +60,7 @@ export class TemporaryUnitMarker extends CustomMarker { var unitIcon = document.createElement("div"); unitIcon.classList.add("unit-icon"); var img = document.createElement("img"); - img.src = `./images/units/map/${/*TODO getApp().getMap().getOptions().AWACSMode ? "awacs" :*/ "normal"}/${this.#coalition}/${blueprint.markerFile ?? blueprint.category}.svg`; + img.src = `images/units/map/${/*TODO getApp().getMap().getOptions().AWACSMode ? "awacs" :*/ "normal"}/${this.#coalition}/${blueprint.markerFile ?? blueprint.category}.svg`; img.onload = () => SVGInjector(img); unitIcon.appendChild(img); unitIcon.toggleAttribute("data-rotate-to-heading", false); @@ -78,7 +78,7 @@ export class TemporaryUnitMarker extends CustomMarker { if (this.#headingHandle) { var handle = document.createElement("div"); var handleImg = document.createElement("img"); - handleImg.src = "/images/others/arrow.svg"; + handleImg.src = "images/others/arrow.svg"; handleImg.onload = () => SVGInjector(handleImg); handle.classList.add("heading-handle"); el.append(handle); diff --git a/frontend/react/src/map/stylesheets/map.css b/frontend/react/src/map/stylesheets/map.css index 03c5dde3..6581d324 100644 --- a/frontend/react/src/map/stylesheets/map.css +++ b/frontend/react/src/map/stylesheets/map.css @@ -253,4 +253,14 @@ path.leaflet-interactive:focus { .pointer-cursor { cursor: url("/images/cursors/pointer.svg") 13 5, auto !important; -} \ No newline at end of file +} + +.ol-arrow-icon svg { + width: 24px; + height: 24px; +} + +.ol-arrow-icon svg path { + fill: #FFFFFFAA; + +} diff --git a/frontend/react/src/mission/airbase.ts b/frontend/react/src/mission/airbase.ts index 5289500f..95a67d2e 100644 --- a/frontend/react/src/mission/airbase.ts +++ b/frontend/react/src/mission/airbase.ts @@ -69,7 +69,7 @@ export class Airbase extends CustomMarker { el.classList.add("airbase-icon"); el.setAttribute("data-object", "airbase"); - this.#img.src = "./images/markers/airbase.svg"; + this.#img.src = "images/markers/airbase.svg"; this.#img.onload = () => SVGInjector(this.#img); el.appendChild(this.#img); this.getElement()?.appendChild(el); diff --git a/frontend/react/src/mission/bullseye.ts b/frontend/react/src/mission/bullseye.ts index ebc614c7..738dea96 100644 --- a/frontend/react/src/mission/bullseye.ts +++ b/frontend/react/src/mission/bullseye.ts @@ -23,7 +23,7 @@ export class Bullseye extends CustomMarker { el.classList.add("bullseye-icon"); el.setAttribute("data-object", "bullseye"); var img = document.createElement("img"); - img.src = "./images/markers/bullseye.svg"; + img.src = "images/markers/bullseye.svg"; img.onload = () => SVGInjector(img); el.appendChild(img); this.getElement()?.appendChild(el); diff --git a/frontend/react/src/server/servermanager.ts b/frontend/react/src/server/servermanager.ts index 9675381b..c2b4acda 100644 --- a/frontend/react/src/server/servermanager.ts +++ b/frontend/react/src/server/servermanager.ts @@ -488,6 +488,12 @@ export class ServerManager { this.PUT(data, callback); } + setRacetrack(ID: number, length: number, latlng: LatLng, bearing: number, callback: CallableFunction = () => {}) { + var command = { ID: ID, location: latlng, bearing: bearing, length: length }; + var data = { setRacetrack: command }; + this.PUT(data, callback); + } + setAdvancedOptions( ID: number, isActiveTanker: boolean, diff --git a/frontend/react/src/ui/modals/loginmodal.tsx b/frontend/react/src/ui/modals/loginmodal.tsx index 2a27796b..2d9141dc 100644 --- a/frontend/react/src/ui/modals/loginmodal.tsx +++ b/frontend/react/src/ui/modals/loginmodal.tsx @@ -94,7 +94,7 @@ export function LoginModal(props: { open: boolean }) { max-md:border-none `} > -
- @@ -360,7 +360,7 @@ export function LoginModal(props: { open: boolean }) { > - + {!scrolledLeft && (
- +
-
diff --git a/frontend/react/src/unit/unit.ts b/frontend/react/src/unit/unit.ts index a8feb9fa..6b8d8540 100644 --- a/frontend/react/src/unit/unit.ts +++ b/frontend/react/src/unit/unit.ts @@ -18,7 +18,6 @@ import { computeBearingRangeString, adjustBrightness, bearingAndDistanceToLatLng, - mToNm, } from "../other/utils"; import { CustomMarker } from "../map/markers/custommarker"; import { SVGInjector } from "@tanem/svg-injector"; @@ -67,12 +66,14 @@ import { UnitSelectedEvent, UnitUpdatedEvent, } from "../events"; +import { CoalitionAreaHandle } from "../map/coalitionarea/coalitionareahandle"; +import { ArrowMarker } from "../map/markers/arrowmarker"; const bearingStrings = ["north", "north-east", "east", "south-east", "south", "south-west", "west", "north-west", "north"]; var pathIcon = new Icon({ - iconUrl: "./images/markers/marker-icon.png", - shadowUrl: "./images/markers/marker-shadow.png", + iconUrl: "images/markers/marker-icon.png", + shadowUrl: "images/markers/marker-shadow.png", iconAnchor: [13, 41], }); @@ -171,9 +172,11 @@ export abstract class Unit extends CustomMarker { #detectionMethods: number[] = []; #trailPositions: LatLng[] = []; #trailPolylines: Polyline[] = []; - #racetrackPolylines: Polyline[] = [new Polyline([]), new Polyline([])]; - #racetrackArcs: Polyline[] = [new Polyline([]), new Polyline([])]; - #anchorMarkers: Marker[]; + #racetrackPolylines: Polyline[] = []; + #racetrackArcs: Polyline[] = []; + #racetrackAnchorMarkers: CoalitionAreaHandle[] = [new CoalitionAreaHandle(new LatLng(0, 0)), new CoalitionAreaHandle(new LatLng(0, 0))]; + #racetrackArrow: ArrowMarker = new ArrowMarker(new LatLng(0, 0)); + #inhibitRacetrackDraw: boolean = false; /* Inputs timers */ #debounceTimeout: number | null = null; @@ -323,7 +326,7 @@ export abstract class Unit extends CustomMarker { getRaceTrackLength() { return this.#racetrackLength; } - getRaceTrackAnchor() { + getRaceTrackAnchor() { return this.#racetrackAnchor; } getRaceTrackBearing() { @@ -343,7 +346,7 @@ export abstract class Unit extends CustomMarker { this.ID = ID; this.#pathPolyline = new Polyline([], { - color: colors.STEEL_BLUE, + color: colors.OLYMPUS_BLUE, weight: 3, opacity: 0.5, smoothFactor: 1, @@ -375,6 +378,33 @@ export abstract class Unit extends CustomMarker { bubblingMouseEvents: false, }); + this.#racetrackPolylines = [ + new Polyline([], { color: colors.OLYMPUS_BLUE, weight: 3, opacity: 0.5, smoothFactor: 1 }), + new Polyline([], { color: colors.OLYMPUS_BLUE, weight: 3, opacity: 0.5, smoothFactor: 1 }), + ]; + + this.#racetrackArcs = [ + new Polyline([], { color: colors.OLYMPUS_BLUE, weight: 3, opacity: 0.5, smoothFactor: 1 }), + new Polyline([], { color: colors.OLYMPUS_BLUE, weight: 3, opacity: 0.5, smoothFactor: 1 }), + ]; + + this.#racetrackAnchorMarkers[0].on("drag", (e: any) => { + this.#inhibitRacetrackDraw = true; + this.#drawRacetrack(); + }); + + this.#racetrackAnchorMarkers[1].on("drag", (e: any) => { + this.#inhibitRacetrackDraw = true; + this.#drawRacetrack(); + }); + + this.#racetrackAnchorMarkers[0].on("dragend", (e: any) => { + this.#applyRacetrackEdit(); + }); + this.#racetrackAnchorMarkers[1].on("dragend", (e: any) => { + this.#applyRacetrackEdit(); + }); + /* Leaflet events listeners */ this.on("mouseup", (e: any) => this.#onMouseUp(e)); this.on("mousedown", (e: any) => this.#onMouseDown(e)); @@ -767,6 +797,8 @@ export abstract class Unit extends CustomMarker { /* Trigger events after all (de-)selecting has been done */ selected ? UnitSelectedEvent.dispatch(this) : UnitDeselectedEvent.dispatch(this); + + this.#inhibitRacetrackDraw = false; } } @@ -1339,6 +1371,10 @@ export abstract class Unit extends CustomMarker { if (!this.#human) getApp().getServerManager().setShotsIntensity(this.ID, shotsIntensity); } + setRacetrack(length: number, anchor: LatLng, bearing: number, callback: () => void) { + if (!this.#human) getApp().getServerManager().setRacetrack(this.ID, length, anchor, bearing, callback); + } + /***********************************************/ onAdd(map: Map): this { super.onAdd(map); @@ -1687,13 +1723,18 @@ export abstract class Unit extends CustomMarker { for (let WP in this.#activePath) { var destination = this.#activePath[WP]; var frozen = this.#pathMarkers[parseInt(WP)].options["freeze"]; - if (!this.#pathMarkers[parseInt(WP)].options["freeze"]) { + if (!frozen) { this.#pathMarkers[parseInt(WP)].setLatLng([destination.lat, destination.lng]); } points.push(new LatLng(destination.lat, destination.lng)); - this.#pathPolyline.setLatLngs(points); } + /* Add racetrack anchor to the path, but only if we are an active tanker or AWACS */ + if (this.getState() !== UnitState.IDLE && (this.getIsActiveAWACS() || this.getIsActiveTanker())) + points.push(this.#racetrackAnchor); + + this.#pathPolyline.setLatLngs(points); + if (points.length == 1) this.#clearPath(); } else { this.#clearPath(); @@ -1712,63 +1753,100 @@ export abstract class Unit extends CustomMarker { #drawRacetrack() { let groundspeed = this.#speed; - + + // Determine racetrack length let racetrackLength = this.#racetrackLength; if (racetrackLength === 0) { - if (this.getIsActiveTanker()) - racetrackLength = nmToM(50); - else - racetrackLength = this.#desiredSpeed * 30; + if (this.getIsActiveTanker()) + racetrackLength = nmToM(50); // Default length for active tanker + else racetrackLength = this.#desiredSpeed * 30; // Default length based on desired speed } + + // Calculate the radius of the racetrack turns const radius = Math.pow(groundspeed, 2) / 9.81 / Math.tan(deg2rad(22.5)); - const point1 = this.#racetrackAnchor; - const point2 = bearingAndDistanceToLatLng(point1.lat, point1.lng, this.#racetrackBearing, racetrackLength); + + let point1; + let point2; + + // Determine the anchor points of the racetrack + if (!this.#inhibitRacetrackDraw) { + point1 = this.#racetrackAnchor; + point2 = bearingAndDistanceToLatLng(point1.lat, point1.lng, this.#racetrackBearing, racetrackLength); + } else { + point1 = this.#racetrackAnchorMarkers[0].getLatLng(); + point2 = this.#racetrackAnchorMarkers[1].getLatLng(); + this.#racetrackBearing = deg2rad(bearing(point1.lat, point1.lng, point2.lat, point2.lng, false)); + this.#racetrackLength = point1.distanceTo(point2); + } + + // Calculate the other points of the racetrack const point3 = bearingAndDistanceToLatLng(point2.lat, point2.lng, this.#racetrackBearing - deg2rad(90), radius * 2); const point4 = bearingAndDistanceToLatLng(point1.lat, point1.lng, this.#racetrackBearing - deg2rad(90), radius * 2); + const pointArrow = bearingAndDistanceToLatLng(point1.lat, point1.lng, this.#racetrackBearing, racetrackLength / 2); + // Calculate the centers of the racetrack turns const center1 = bearingAndDistanceToLatLng(point2.lat, point2.lng, this.#racetrackBearing - deg2rad(90), radius); const center2 = bearingAndDistanceToLatLng(point1.lat, point1.lng, this.#racetrackBearing - deg2rad(90), radius); - if (!getApp().getMap().hasLayer(this.#racetrackPolylines[0])) { - this.#racetrackPolylines[0] = new Polyline([point1, point2]); - this.#racetrackPolylines[0].addTo(getApp().getMap()); - } else { - this.#racetrackPolylines[0].setLatLngs([point1, point2]); - } + // Draw or update the straight segments of the racetrack + if (!getApp().getMap().hasLayer(this.#racetrackPolylines[0])) this.#racetrackPolylines[0].addTo(getApp().getMap()); + this.#racetrackPolylines[0].setLatLngs([point1, point2]); - if (!getApp().getMap().hasLayer(this.#racetrackPolylines[1])) { - this.#racetrackPolylines[1] = new Polyline([point3, point4]); - this.#racetrackPolylines[1].addTo(getApp().getMap()); - } else { - this.#racetrackPolylines[1].setLatLngs([point3, point4]); - } + if (!getApp().getMap().hasLayer(this.#racetrackPolylines[1])) this.#racetrackPolylines[1].addTo(getApp().getMap()); + this.#racetrackPolylines[1].setLatLngs([point3, point4]); const arc1Points: LatLng[] = []; const arc2Points: LatLng[] = []; + // Calculate the points for the racetrack arcs for (let theta = 0; theta <= 180; theta += 5) { arc1Points.push(bearingAndDistanceToLatLng(center1.lat, center1.lng, this.#racetrackBearing + deg2rad(theta - 90), radius)); arc2Points.push(bearingAndDistanceToLatLng(center2.lat, center2.lng, this.#racetrackBearing + deg2rad(theta + 90), radius)); } - if (!getApp().getMap().hasLayer(this.#racetrackArcs[0])) { - this.#racetrackArcs[0] = new Polyline(arc1Points); - this.#racetrackArcs[0].addTo(getApp().getMap()); - } else { - this.#racetrackArcs[0].setLatLngs(arc1Points); + // Draw or update the racetrack arcs + if (!getApp().getMap().hasLayer(this.#racetrackArcs[0])) this.#racetrackArcs[0].addTo(getApp().getMap()); + this.#racetrackArcs[0].setLatLngs(arc1Points); + + if (!getApp().getMap().hasLayer(this.#racetrackArcs[1])) this.#racetrackArcs[1].addTo(getApp().getMap()); + + this.#racetrackArcs[1].setLatLngs(arc2Points); + + // Update the positions of the racetrack anchor markers + this.#racetrackAnchorMarkers[0].setLatLng(point1); + this.#racetrackAnchorMarkers[1].setLatLng(point2); + + // Add the racetrack anchor markers to the map if they are not already present + if (!getApp().getMap().hasLayer(this.#racetrackAnchorMarkers[0])) { + this.#racetrackAnchorMarkers[0].addTo(getApp().getMap()); } - if (!getApp().getMap().hasLayer(this.#racetrackArcs[1])) { - this.#racetrackArcs[1] = new Polyline(arc2Points); - this.#racetrackArcs[1].addTo(getApp().getMap()); - } else { - this.#racetrackArcs[1].setLatLngs(arc2Points); + if (!getApp().getMap().hasLayer(this.#racetrackAnchorMarkers[1])) { + this.#racetrackAnchorMarkers[1].addTo(getApp().getMap()); } + + if (!getApp().getMap().hasLayer(this.#racetrackArrow)) { + this.#racetrackArrow.addTo(getApp().getMap()); + } + this.#racetrackArrow.setLatLng(pointArrow); + this.#racetrackArrow.setBearing(this.#racetrackBearing); } #clearRacetrack() { this.#racetrackPolylines.forEach((polyline) => getApp().getMap().removeLayer(polyline)); this.#racetrackArcs.forEach((arc) => getApp().getMap().removeLayer(arc)); + this.#racetrackAnchorMarkers.forEach((marker) => getApp().getMap().removeLayer(marker)); + this.#racetrackArrow.removeFrom(getApp().getMap()); + } + + #applyRacetrackEdit() { + const point1 = this.#racetrackAnchorMarkers[0].getLatLng(); + const point2 = this.#racetrackAnchorMarkers[1].getLatLng(); + const racetrackLength = point1.distanceTo(point2); + const racetrackBearing = deg2rad(bearing(point1.lat, point1.lng, point2.lat, point2.lng, false)); + this.setRacetrack(racetrackLength, this.#racetrackAnchorMarkers[0].getLatLng(), racetrackBearing, () => { + this.#inhibitRacetrackDraw = false; + }); } #drawContacts() {