feat: started working at measuring tool

This commit is contained in:
Davide Passoni 2025-01-31 17:25:14 +01:00
parent a65a5a5bed
commit 627c4b5584
4 changed files with 168 additions and 4 deletions

View File

@ -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";

View File

@ -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);

View 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)`;
}
}
}
}

View 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;
}