mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
feat: started working at measuring tool
This commit is contained in:
parent
a65a5a5bed
commit
627c4b5584
@ -325,7 +325,8 @@ export enum OlympusState {
|
||||
GAME_MASTER = "Game master",
|
||||
IMPORT_EXPORT = "Import/export",
|
||||
WARNING = "Warning modal",
|
||||
DATABASE_EDITOR = "Database editor"
|
||||
DATABASE_EDITOR = "Database editor",
|
||||
MEASURE = "Measure"
|
||||
}
|
||||
|
||||
export const NO_SUBSTATE = "No substate";
|
||||
|
||||
@ -3,7 +3,7 @@ import { getApp } from "../olympusapp";
|
||||
import { BoxSelect } from "./boxselect";
|
||||
import { Airbase } from "../mission/airbase";
|
||||
import { Unit } from "../unit/unit";
|
||||
import { areaContains, deepCopyTable, deg2rad, getGroundElevation } from "../other/utils";
|
||||
import { areaContains, bearing, bearingAndDistanceToLatLng, deepCopyTable, deg2rad, getGroundElevation, mToFt, mToNm, nmToM, rad2deg } from "../other/utils";
|
||||
import { TemporaryUnitMarker } from "./markers/temporaryunitmarker";
|
||||
import { ClickableMiniMap } from "./clickableminimap";
|
||||
import {
|
||||
@ -36,6 +36,7 @@ import "./markers/stylesheets/airbase.css";
|
||||
import "./markers/stylesheets/bullseye.css";
|
||||
import "./markers/stylesheets/units.css";
|
||||
import "./markers/stylesheets/spot.css";
|
||||
import "./markers/stylesheets/measure.css";
|
||||
import "./stylesheets/map.css";
|
||||
|
||||
import { initDraggablePath } from "./coalitionarea/draggablepath";
|
||||
@ -65,11 +66,12 @@ import {
|
||||
} from "../events";
|
||||
import { ContextActionSet } from "../unit/contextactionset";
|
||||
import { SmokeMarker } from "./markers/smokemarker";
|
||||
import { MeasureMarker } from "./markers/measuremarker";
|
||||
|
||||
/* Register the handler for the box selection */
|
||||
L.Map.addInitHook("addHandler", "boxSelect", BoxSelect);
|
||||
|
||||
initDraggablePath(L);
|
||||
initDraggablePath(L);
|
||||
|
||||
export class Map extends L.Map {
|
||||
/* Options */
|
||||
@ -154,6 +156,11 @@ export class Map extends L.Map {
|
||||
#targetPoint: TargetMarker | null = null;
|
||||
#IPToTargetLine: L.Polygon | null = null;
|
||||
|
||||
/* Measure tool */
|
||||
#measureReference: L.LatLng | null = null;
|
||||
#measureLines: L.Polyline[] = [];
|
||||
#measureMarkers: MeasureMarker[] = [];
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the map
|
||||
@ -322,6 +329,16 @@ export class Map extends L.Map {
|
||||
|
||||
getApp()
|
||||
.getShortcutManager()
|
||||
.addShortcut("measure", {
|
||||
label: "Toggle measurement tool",
|
||||
keyUpCallback: () => {
|
||||
getApp().getState() === OlympusState.MEASURE ? getApp().setState(OlympusState.IDLE) : getApp().setState(OlympusState.MEASURE);
|
||||
},
|
||||
code: "KeyM",
|
||||
shiftKey: false,
|
||||
altKey: false,
|
||||
ctrlKey: false,
|
||||
})
|
||||
.addShortcut("toggleUnitLabels", {
|
||||
label: "Hide/show labels",
|
||||
keyUpCallback: () => this.setOption("showUnitLabels", !this.getOptions().showUnitLabels),
|
||||
@ -837,7 +854,7 @@ export class Map extends L.Map {
|
||||
this.getContainer().classList.remove(`explosion-cursor`);
|
||||
["white", "blue", "red", "green", "orange"].forEach((color) => this.getContainer().classList.remove(`smoke-${color}-cursor`));
|
||||
this.getContainer().classList.remove(`plus-cursor`);
|
||||
|
||||
|
||||
/* Operations to perform when entering a state */
|
||||
if (state === OlympusState.IDLE) {
|
||||
getApp().getUnitsManager()?.deselectAllUnits();
|
||||
@ -904,6 +921,15 @@ export class Map extends L.Map {
|
||||
if (e.originalEvent?.button === 0) {
|
||||
this.#isLeftMouseDown = true;
|
||||
this.#leftMouseDownEpoch = Date.now();
|
||||
|
||||
/* If we are in the measure state there can only be a short click so immediately perform the action */
|
||||
if (getApp().getState() === OlympusState.MEASURE) {
|
||||
if (this.#measureLines.length > 0 && this.#measureReference)
|
||||
this.#measureLines[this.#measureLines.length - 1].setLatLngs([this.#measureReference, e.latlng]);
|
||||
this.#measureReference = e.latlng;
|
||||
this.#measureLines.push(new L.Polyline([this.#measureReference, e.latlng], { color: "magenta" }).addTo(this));
|
||||
this.#measureMarkers.push(new MeasureMarker(e.latlng, "", 0).addTo(this));
|
||||
}
|
||||
} else if (e.originalEvent?.button === 2) {
|
||||
this.#isRightMouseDown = true;
|
||||
this.#rightMouseDownEpoch = Date.now();
|
||||
@ -1033,6 +1059,8 @@ export class Map extends L.Map {
|
||||
if (this.#contextAction !== null) this.executeContextAction(null, e.latlng, e.originalEvent);
|
||||
else if (getApp().getSubState() === NO_SUBSTATE) getApp().setState(OlympusState.IDLE);
|
||||
else getApp().setState(OlympusState.UNIT_CONTROL);
|
||||
} else if (getApp().getState() === OlympusState.MEASURE) {
|
||||
/* Do nothing, we already clicked on the mouse down callback */
|
||||
} else {
|
||||
if (getApp().getSubState() === NO_SUBSTATE) getApp().setState(OlympusState.IDLE);
|
||||
else getApp().setState(OlympusState.UNIT_CONTROL);
|
||||
@ -1099,6 +1127,20 @@ export class Map extends L.Map {
|
||||
if (getApp().getState() === OlympusState.SPAWN) {
|
||||
if (this.#currentSpawnMarker) this.#currentSpawnMarker.setLatLng(e.latlng);
|
||||
if (this.#currentEffectMarker) this.#currentEffectMarker.setLatLng(e.latlng);
|
||||
} else if (getApp().getState() === OlympusState.MEASURE) {
|
||||
if (this.#measureLines.length > 0) this.#measureLines[this.#measureLines.length - 1].setLatLngs([this.#measureReference, e.latlng]);
|
||||
if (this.#measureMarkers.length > 0 && this.#measureReference) {
|
||||
const distance = this.#measureReference.distanceTo(e.latlng);
|
||||
let distanceString = ""
|
||||
if (distance > nmToM(1)) distanceString = `${mToNm(distance).toFixed(2)} NM`;
|
||||
else distanceString = `${mToFt(distance).toFixed(2)} ft`;
|
||||
const bearingTo = deg2rad(bearing(this.#measureReference.lat, this.#measureReference.lng, e.latlng.lat, e.latlng.lng, false));
|
||||
const halfPoint = bearingAndDistanceToLatLng(this.#measureReference.lat, this.#measureReference.lng, bearingTo, distance/2);
|
||||
const bearingString = `${(Math.floor(rad2deg(bearingTo) + 360) % 360)}°`;
|
||||
this.#measureMarkers[this.#measureMarkers.length - 1].setLatLng(halfPoint);
|
||||
this.#measureMarkers[this.#measureMarkers.length - 1].setRotationAngle(bearingTo + Math.PI / 2);
|
||||
this.#measureMarkers[this.#measureMarkers.length - 1].setTextValue(`${distanceString} - ${bearingString}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.#destionationWasRotated = true;
|
||||
@ -1166,6 +1208,11 @@ export class Map extends L.Map {
|
||||
});
|
||||
}
|
||||
|
||||
#clearMeasures() {
|
||||
this.#measureLines.forEach((line) => line.removeFrom(this));
|
||||
this.#measureMarkers.forEach((marker) => marker.removeFrom(this));
|
||||
}
|
||||
|
||||
/* */
|
||||
#panToUnit(unit: Unit) {
|
||||
var unitPosition = new L.LatLng(unit.getPosition().lat, unit.getPosition().lng);
|
||||
|
||||
90
frontend/react/src/map/markers/measuremarker.ts
Normal file
90
frontend/react/src/map/markers/measuremarker.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import { Marker, LatLng, DivIcon, Map } from "leaflet";
|
||||
|
||||
export class MeasureMarker extends Marker {
|
||||
#textValue: string;
|
||||
#isEditable: boolean = false;
|
||||
#rotationAngle: number; // Rotation angle in radians
|
||||
#previousValue: string;
|
||||
|
||||
onValueUpdated: (value: number) => void = () => {};
|
||||
onDeleteButtonClicked: () => void = () => {};
|
||||
|
||||
/**
|
||||
* Constructor for SpotEditMarker
|
||||
* @param {LatLng} latlng - The geographical position of the marker.
|
||||
* @param {string} textValue - The initial text value to display.
|
||||
* @param {number} rotationAngle - The initial rotation angle in radians.
|
||||
*/
|
||||
constructor(latlng: LatLng, textValue: string, rotationAngle: number = 0) {
|
||||
super(latlng, {
|
||||
icon: new DivIcon({
|
||||
className: "leaflet-measure-marker",
|
||||
html: `<div class="container">
|
||||
<div class="text">${textValue}</div>
|
||||
</div>`,
|
||||
}),
|
||||
});
|
||||
|
||||
this.#textValue = textValue;
|
||||
this.#rotationAngle = rotationAngle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text value of the marker.
|
||||
* @param {string} textValue - The new text value.
|
||||
*/
|
||||
setTextValue(textValue: string) {
|
||||
this.#textValue = textValue;
|
||||
const element = this.getElement();
|
||||
if (element) {
|
||||
const text = element.querySelector(".text");
|
||||
if (text) text.textContent = textValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the text value of the marker.
|
||||
* @returns {string} - The current text value.
|
||||
*/
|
||||
getTextValue() {
|
||||
return this.#textValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the rotation angle of the marker in radians.
|
||||
* @param {number} angle - The new rotation angle in radians.
|
||||
*/
|
||||
setRotationAngle(angle: number) {
|
||||
this.#rotationAngle = angle;
|
||||
if (!this.#isEditable) {
|
||||
this.#updateRotation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the rotation angle of the marker in radians.
|
||||
* @returns {number} - The current rotation angle in radians.
|
||||
*/
|
||||
getRotationAngle() {
|
||||
return this.#rotationAngle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the rotation angle to ensure the text is always readable.
|
||||
*/
|
||||
#updateRotation() {
|
||||
const element = this.getElement();
|
||||
if (element) {
|
||||
const container = element.querySelector(".container") as HTMLDivElement;
|
||||
if (container) {
|
||||
let angle = this.#rotationAngle % (2 * Math.PI);
|
||||
if (angle < 0) angle += 2 * Math.PI;
|
||||
if (angle > Math.PI / 2 && angle < (3 * Math.PI) / 2) {
|
||||
angle = (angle + Math.PI) % (2 * Math.PI); // Flip the text to be upright
|
||||
}
|
||||
const angleInDegrees = angle * (180 / Math.PI);
|
||||
container.style.transform = `rotate(${angleInDegrees}deg)`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
frontend/react/src/map/markers/stylesheets/measure.css
Normal file
26
frontend/react/src/map/markers/stylesheets/measure.css
Normal file
@ -0,0 +1,26 @@
|
||||
/* Container for the measure marker */
|
||||
.leaflet-measure-marker {
|
||||
text-align: center;
|
||||
display: flex !important;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Container for the measure marker content */
|
||||
.leaflet-measure-marker .container {
|
||||
min-width: 150px;
|
||||
transform-origin: center;
|
||||
background-color: var(--background-steel);
|
||||
color: white;
|
||||
border-radius: 999px;
|
||||
font-size: 13px;
|
||||
align-content: center;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
/* Text inside the measure marker */
|
||||
.leaflet-measure-marker .text {
|
||||
margin-left: 12px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user