feat: Aligned orbits representation, added visibility option

This commit is contained in:
Davide Passoni 2025-02-26 12:05:59 +01:00
parent c7ff73f8a6
commit 8aac6b7d7e
10 changed files with 193 additions and 131 deletions

View File

@ -22,9 +22,9 @@
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="8.9824657"
inkscape:cx="20.985329"
inkscape:cy="21.430641"
inkscape:zoom="12.703125"
inkscape:cx="27.316114"
inkscape:cy="18.145142"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:window-x="1912"
@ -33,7 +33,8 @@
inkscape:current-layer="svg1" />
<!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
<path
d="M 23.307123,3.5189202 C 22.873778,2.4788845 21.852318,1.8041019 20.725614,1.8041019 c -1.126705,0 -2.14817,0.6747826 -2.581515,1.7148183 L 5.2674896,34.22469 c -0.5200196,1.238134 -0.086679,2.661992 1.0214597,3.411065 1.1081335,0.749072 2.5938946,0.600492 3.5410687,-0.346667 L 20.725614,26.393493 31.621209,37.289088 c 0.947171,0.947172 2.426747,1.089559 3.541067,0.346667 1.114324,-0.742886 1.541478,-2.172931 1.021461,-3.411065 z"
d="M 23.307123,5.9592647 C 22.873778,4.919229 21.852318,4.2444464 20.725614,4.2444464 c -1.126705,0 -2.14817,0.6747826 -2.581515,1.7148183 L 4.9526064,31.075858 c -0.5200196,1.238134 -0.749486,3.365412 1.6615957,3.411065 3.2949087,0.06239 25.7802099,-0.167747 27.4538949,0 1.605575,0.16092 2.511915,-2.077361 1.800757,-3.411065 z"
id="path1"
style="stroke:#262626;stroke-width:2.30583;stroke-dasharray:none;stroke-opacity:1" />
style="stroke:none;stroke-width:2.30583;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="csccsscc" />
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -403,6 +403,7 @@ export const MAP_OPTIONS_DEFAULTS: MapOptions = {
showUnitLabels: true,
showUnitsEngagementRings: true,
showUnitsAcquisitionRings: true,
showRacetracks: true,
fillSelectedRing: false,
showMinimap: false,
protectDCSUnits: true,

View File

@ -1,7 +1,7 @@
import { DivIcon, LatLng } from "leaflet";
import { CustomMarker } from "../markers/custommarker";
export class CoalitionAreaHandle extends CustomMarker {
export class DraggableHandle extends CustomMarker {
constructor(latlng: LatLng) {
super(latlng, { interactive: true, draggable: true });
@ -15,11 +15,11 @@ export class CoalitionAreaHandle extends CustomMarker {
new DivIcon({
iconSize: [24, 24],
iconAnchor: [12, 12],
className: "leaflet-coalitionarea-handle-marker",
className: "leaflet-draggable-handle-marker",
})
);
var el = document.createElement("div");
el.classList.add("ol-coalitionarea-handle-icon");
el.classList.add("ol-draggable-handle-icon");
this.getElement()?.appendChild(el);
}
}

View File

@ -1,6 +1,6 @@
import { LatLngExpression, Map, Circle, DivIcon, Marker, CircleOptions, LatLng } from "leaflet";
import { getApp } from "../../olympusapp";
import { CoalitionAreaHandle } from "./coalitionareahandle";
import { DraggableHandle } from "./coalitionareahandle";
import { BLUE_COMMANDER, colors, RED_COMMANDER } from "../../constants/constants";
import { Coalition } from "../../types/types";
import * as turf from "@turf/turf";
@ -12,7 +12,7 @@ export class CoalitionCircle extends Circle {
#coalition: Coalition = "blue";
#selected: boolean = true;
#creating: boolean = false;
#radiusHandle: CoalitionAreaHandle;
#radiusHandle: DraggableHandle;
#labelText: string;
#label: Marker;
#updateTimeout: number | null = null;
@ -140,7 +140,7 @@ export class CoalitionCircle extends Circle {
if (this.#selected) {
const dest = turf.destination(turf.point([this.getLatLng().lng, this.getLatLng().lat]), this.getRadius() / 1000, 0);
this.#radiusHandle = new CoalitionAreaHandle(new LatLng(dest.geometry.coordinates[1], dest.geometry.coordinates[0]));
this.#radiusHandle = new DraggableHandle(new LatLng(dest.geometry.coordinates[1], dest.geometry.coordinates[0]));
this.#radiusHandle.addTo(getApp().getMap());
this.#radiusHandle.on("drag", (e: any) => {
this.setRadius(this.getLatLng().distanceTo(e.latlng));

View File

@ -1,6 +1,6 @@
import { LatLng, LatLngExpression, Map, Point, Polygon, PolylineOptions, DivIcon, Marker } from "leaflet";
import { getApp } from "../../olympusapp";
import { CoalitionAreaHandle } from "./coalitionareahandle";
import { DraggableHandle } from "./coalitionareahandle";
import { CoalitionAreaMiddleHandle } from "./coalitionareamiddlehandle";
import { BLUE_COMMANDER, colors, RED_COMMANDER } from "../../constants/constants";
import { Coalition } from "../../types/types";
@ -13,7 +13,7 @@ export class CoalitionPolygon extends Polygon {
#coalition: Coalition = "blue";
#selected: boolean = true;
#creating: boolean = false;
#handles: CoalitionAreaHandle[] = [];
#handles: DraggableHandle[] = [];
#middleHandles: CoalitionAreaMiddleHandle[] = [];
#activeIndex: number = 0;
#labelText: string;
@ -132,7 +132,7 @@ export class CoalitionPolygon extends Polygon {
onRemove(map: Map): this {
super.onRemove(map);
this.#label?.removeFrom(map);
this.#handles.concat(this.#middleHandles).forEach((handle: CoalitionAreaHandle | CoalitionAreaMiddleHandle) => handle.removeFrom(map));
this.#handles.concat(this.#middleHandles).forEach((handle: DraggableHandle | CoalitionAreaMiddleHandle) => handle.removeFrom(map));
return this;
}
@ -161,13 +161,13 @@ export class CoalitionPolygon extends Polygon {
}
#setHandles() {
this.#handles.forEach((handle: CoalitionAreaHandle) => handle.removeFrom(getApp().getMap()));
this.#handles.forEach((handle: DraggableHandle) => handle.removeFrom(getApp().getMap()));
this.#handles = [];
if (this.getSelected()) {
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs.forEach((latlng: LatLng, idx: number) => {
/* Add the polygon vertex handle (for moving the vertex) */
const handle = new CoalitionAreaHandle(latlng);
const handle = new DraggableHandle(latlng);
handle.addTo(getApp().getMap());
handle.on("drag", (e: any) => {
var latlngs = this.getLatLngs()[0] as LatLng[];

View File

@ -100,20 +100,26 @@
font-weight: 600;
}
.ol-coalitionarea-handle-icon {
cursor: url("/images/cursors/pointer.svg") 13 5, auto;
background-color: #FFFFFFAA;
.ol-draggable-handle-icon {
cursor:
url("/images/cursors/pointer.svg") 13 5,
auto;
background-color: #ffffff;
width: 100%;
height: 100%;
border-radius: 999px;
filter: drop-shadow(3px 3px 3px rgba(0, 0, 0, 0.2));
}
.ol-coalitionarea-middle-handle-icon {
cursor: url("/images/cursors/pointer.svg") 13 5, auto;
background-color: #FFFFFFAA;
cursor:
url("/images/cursors/pointer.svg") 13 5,
auto;
background-color: #ffffff;
width: 100%;
height: 100%;
border-radius: 999px;
filter: drop-shadow(3px 3px 3px rgba(0, 0, 0, 0.2));
}
.ol-coalitionarea-label {
@ -124,7 +130,11 @@
.ol-coalitionarea-label.selected {
color: white;
/* 1 pixel black shadow to left, top, right and bottom */
text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;
text-shadow:
-1px 0 black,
0 1px black,
1px 0 black,
0 -1px black;
}
.ol-coalitionarea-label.blue {
@ -139,15 +149,15 @@
background-image: url("/images/markers/target.svg");
height: 100%;
width: 100%;
filter: drop-shadow( 3px 3px 3px rgba(0, 0, 0, .2));
filter: drop-shadow(3px 3px 3px rgba(0, 0, 0, 0.2));
}
.ol-spot-icon {
background-image: url("/images/markers/target.svg");
height: 100%;
width: 100%;
filter: drop-shadow( 3px 3px 3px rgba(0, 0, 0, .2));
filter: drop-shadow(3px 3px 3px rgba(0, 0, 0, 0.2));
}
.ol-text-icon {
@ -162,10 +172,10 @@
.ol-smoke-icon {
opacity: 75%;
filter: drop-shadow( 3px 3px 3px rgba(0, 0, 0, .2));
filter: drop-shadow(3px 3px 3px rgba(0, 0, 0, 0.2));
}
.ol-explosion-icon * {
.ol-explosion-icon * {
opacity: 75%;
}
@ -185,7 +195,9 @@ path.leaflet-interactive:focus {
cursor: url("/images/cursors/follow.svg"), auto !important;
}
.fire-at-area-cursor, .bomb-cursor, .carpet-bomb-cursor {
.fire-at-area-cursor,
.bomb-cursor,
.carpet-bomb-cursor {
cursor: url("/images/cursors/fire-at-area.svg"), auto !important;
}
@ -213,9 +225,10 @@ path.leaflet-interactive:focus {
cursor: url("/images/cursors/measure.svg"), auto !important;
}
#map-container.leaflet-grab {
cursor: url("/images/cursors/grab.svg") 16 16, auto;
cursor:
url("/images/cursors/grab.svg") 16 16,
auto;
}
.explosion-cursor {
@ -247,7 +260,13 @@ path.leaflet-interactive:focus {
}
.pointer-cursor {
cursor: url("/images/cursors/pointer.svg") 13 5, auto !important;
cursor:
url("/images/cursors/pointer.svg") 13 5,
auto !important;
}
.ol-arrow-icon {
filter: drop-shadow(3px 3px 3px rgba(0, 0, 0, 0.2));
}
.ol-arrow-icon svg {
@ -256,6 +275,5 @@ path.leaflet-interactive:focus {
}
.ol-arrow-icon svg path {
fill: #FFFFFFAA;
fill: #ffffff;
}

View File

@ -18,6 +18,7 @@ export type MapOptions = {
showUnitLabels: boolean;
showUnitsEngagementRings: boolean;
showUnitsAcquisitionRings: boolean;
showRacetracks: boolean;
fillSelectedRing: boolean;
showMinimap: boolean;
protectDCSUnits: boolean;

View File

@ -1,6 +1,6 @@
import React, { useEffect, useRef, useState } from "react";
import { OlRoundStateButton, OlStateButton, OlLockStateButton } from "../components/olstatebutton";
import { faSkull, faCamera, faFlag, faVolumeHigh, faDownload, faUpload, faDrawPolygon } from "@fortawesome/free-solid-svg-icons";
import { faSkull, faCamera, faFlag, faVolumeHigh, faDownload, faUpload, faDrawPolygon, faCircle, faTriangleExclamation, faWifi, faHourglass, faInfo } from "@fortawesome/free-solid-svg-icons";
import { OlDropdownItem, OlDropdown } from "../components/oldropdown";
import { OlLabelToggle } from "../components/ollabeltoggle";
import { getApp, IP } from "../../olympusapp";
@ -146,17 +146,13 @@ export function Header() {
}}
checked={false}
/>
{savingSessionData ? (
{savingSessionData ? (
<div className="text-white">
<FaSpinner
className={`animate-spin text-2xl`}
/>
<FaSpinner className={`animate-spin text-2xl`} />
</div>
) : (
<div className={`relative text-white`}>
<FaFloppyDisk
className={`absolute -top-3 text-2xl`}
/>
<FaFloppyDisk className={`absolute -top-3 text-2xl`} />
<FaCheck
className={`
absolute left-[9px] top-[-6px] text-2xl text-olympus-900
@ -271,6 +267,23 @@ export function Header() {
);
})}
</div>
<div className={`h-8 w-0 border-l-[2px] border-gray-700`}></div>
<div className={`flex h-fit flex-row items-center justify-start gap-1`}>
<OlRoundStateButton
onClick={() => getApp().getMap().setOption("showUnitsEngagementRings", !mapOptions.showUnitsEngagementRings)}
checked={mapOptions.showUnitsEngagementRings}
icon={faTriangleExclamation}
className={""}
tooltip={"Hide/show units engagement rings"}
/>
<OlRoundStateButton
onClick={() => getApp().getMap().setOption("showUnitsAcquisitionRings", !mapOptions.showUnitsAcquisitionRings)}
checked={mapOptions.showUnitsAcquisitionRings}
icon={faWifi}
className={""}
tooltip={"Hide/show units acquisition rings"}
/>
</div>
<OlLabelToggle
toggled={mapOptions.cameraPluginMode === "map"}

View File

@ -11,6 +11,7 @@ import { Shortcut } from "../../shortcut/shortcut";
import { OlSearchBar } from "../components/olsearchbar";
import { FaTrash, FaXmark } from "react-icons/fa6";
import { OlCoalitionToggle } from "../components/olcoalitiontoggle";
import { FaQuestionCircle } from "react-icons/fa";
const enum Accordion {
NONE,
@ -92,7 +93,7 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
onClick={() => getApp().getMap().setOption("showUnitLabels", !mapOptions.showUnitLabels)}
>
<OlCheckbox checked={mapOptions.showUnitLabels} onChange={() => {}}></OlCheckbox>
<span className="my-auto">Show Unit Labels</span>
<span className="my-auto">Show unit labels</span>
</div>
<div
className={`
@ -103,7 +104,7 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
onClick={() => getApp().getMap().setOption("showUnitsEngagementRings", !mapOptions.showUnitsEngagementRings)}
>
<OlCheckbox checked={mapOptions.showUnitsEngagementRings} onChange={() => {}}></OlCheckbox>
<span className="my-auto">Show Threat Rings</span>
<span className="my-auto">Show threat rings</span>
</div>
<div
className={`
@ -114,7 +115,7 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
onClick={() => getApp().getMap().setOption("showUnitsAcquisitionRings", !mapOptions.showUnitsAcquisitionRings)}
>
<OlCheckbox checked={mapOptions.showUnitsAcquisitionRings} onChange={() => {}}></OlCheckbox>
<span className="my-auto">Show Detection rings</span>
<span className="my-auto">Show detection rings</span>
</div>
<div
className={`
@ -125,7 +126,7 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
onClick={() => getApp().getMap().setOption("showUnitTargets", !mapOptions.showUnitTargets)}
>
<OlCheckbox checked={mapOptions.showUnitTargets} onChange={() => {}}></OlCheckbox>
<span className="my-auto">Show Detection lines</span>
<span className="my-auto">Show detection lines</span>
</div>
<div
className={`
@ -136,7 +137,7 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
onClick={() => getApp().getMap().setOption("hideUnitsShortRangeRings", !mapOptions.hideUnitsShortRangeRings)}
>
<OlCheckbox checked={mapOptions.hideUnitsShortRangeRings} onChange={() => {}}></OlCheckbox>
<span className="my-auto">Hide Short range Rings</span>
<span className="my-auto">Hide short range rings</span>
</div>
<div
@ -148,7 +149,7 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
onClick={() => getApp().getMap().setOption("hideGroupMembers", !mapOptions.hideGroupMembers)}
>
<OlCheckbox checked={mapOptions.hideGroupMembers} onChange={() => {}}></OlCheckbox>
<span className="my-auto">Hide Group members</span>
<span className="my-auto">Hide group members</span>
</div>
<div
className={`
@ -161,6 +162,17 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
<OlCheckbox checked={mapOptions.showMinimap} onChange={() => {}}></OlCheckbox>
<span className="my-auto">Show minimap</span>
</div>
<div
className={`
group flex cursor-pointer flex-row content-center justify-start
gap-4 rounded-md p-2
dark:hover:bg-olympus-400
`}
onClick={() => getApp().getMap().setOption("showRacetracks", !mapOptions.showRacetracks)}
>
<OlCheckbox checked={mapOptions.showRacetracks} onChange={() => {}}></OlCheckbox>
<span className="my-auto">Show racetracks</span>
</div>
<div
className={`
group flex cursor-pointer flex-row content-center justify-start
@ -173,8 +185,20 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
mapOptions.AWACSCoalition === "red" && getApp().getMap().setOption("AWACSCoalition", "blue");
}}
>
<div className="flex flex-col gap-2">
<div className="flex content-center gap-4">
<OlCoalitionToggle onClick={() => {}} coalition={mapOptions.AWACSCoalition} />
<span className="my-auto">Coalition of unit bullseye info</span>
</div>
<div className="flex gap-1 text-sm text-gray-400">
<FaQuestionCircle className={`my-auto w-8`} />{" "}
<div
className={`my-auto ml-2`}
>
Change the coalition of the bullseye to use to provide bullseye information in the unit tooltip.
</div>
</div>
</div>
</div>
</OlAccordion>

View File

@ -67,7 +67,7 @@ import {
UnitSelectedEvent,
UnitUpdatedEvent,
} from "../events";
import { CoalitionAreaHandle } from "../map/coalitionarea/coalitionareahandle";
import { DraggableHandle } from "../map/coalitionarea/coalitionareahandle";
import { ArrowMarker } from "../map/markers/arrowmarker";
import { Spot } from "../mission/spot";
import { SpotEditMarker } from "../map/markers/spoteditmarker";
@ -171,7 +171,7 @@ export abstract class Unit extends CustomMarker {
#trailPolylines: Polyline[] = [];
#racetrackPolylines: Polyline[] = [];
#racetrackArcs: Polyline[] = [];
#racetrackAnchorMarkers: CoalitionAreaHandle[] = [new CoalitionAreaHandle(new LatLng(0, 0)), new CoalitionAreaHandle(new LatLng(0, 0))];
#racetrackAnchorMarkers: DraggableHandle[] = [new DraggableHandle(new LatLng(0, 0)), new DraggableHandle(new LatLng(0, 0))];
#racetrackArrow: ArrowMarker = new ArrowMarker(new LatLng(0, 0));
#inhibitRacetrackDraw: boolean = false;
#spotLines: { [key: number]: Polyline } = {};
@ -379,13 +379,13 @@ export abstract class Unit extends CustomMarker {
});
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 }),
new Polyline([], { color: colors.WHITE, weight: 3, smoothFactor: 1, dashArray: "5, 5" }),
new Polyline([], { color: colors.WHITE, weight: 3, smoothFactor: 1, dashArray: "5, 5" }),
];
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 }),
new Polyline([], { color: colors.WHITE, weight: 3, smoothFactor: 1, dashArray: "5, 5" }),
new Polyline([], { color: colors.WHITE, weight: 3, smoothFactor: 1, dashArray: "5, 5" }),
];
this.#racetrackAnchorMarkers[0].on("drag", (e: any) => {
@ -1724,84 +1724,88 @@ export abstract class Unit extends CustomMarker {
}
#drawRacetrack() {
let groundspeed = this.#speed;
this.#clearRacetrack();
// Determine racetrack length
let racetrackLength = this.#racetrackLength;
if (racetrackLength === 0) {
if (this.getIsActiveTanker())
racetrackLength = nmToM(50); // Default length for active tanker
else racetrackLength = this.#desiredSpeed * 30; // Default length based on desired speed
if (getApp().getMap().getOptions().showRacetracks) {
let groundspeed = this.#speed;
// Determine racetrack length
let racetrackLength = this.#racetrackLength;
if (racetrackLength === 0) {
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));
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);
// 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].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));
}
// 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.#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);
}
// Calculate the radius of the racetrack turns
const radius = Math.pow(groundspeed, 2) / 9.81 / Math.tan(deg2rad(22.5));
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);
// 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].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));
}
// 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.#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() {
@ -1993,8 +1997,8 @@ export abstract class Unit extends CustomMarker {
var color;
if (contactData.detectionMethod === VISUAL || contactData.detectionMethod === OPTIC) color = colors.MAGENTA;
else if (contactData.detectionMethod === RADAR || contactData.detectionMethod === IRST) color = colors.YELLOW;
else if (contactData.detectionMethod === RWR) color = colors.GREEN;
else color = colors.WHITE;
else if (contactData.detectionMethod === RWR) color = colors.GREEN_YELLOW;
else color = colors.LIGHT_GREY;
var contactPolyline = new Polyline([startLatLng, endLatLng], {
color: color,
weight: 3,