started collision handeling for label

This commit is contained in:
PxlLoewe
2025-04-10 21:24:05 -07:00
parent 9e430eeeec
commit b5e5aff084
7 changed files with 363 additions and 8 deletions

View File

@@ -0,0 +1,239 @@
import { Aircraft, useAircraftsStore } from "_store/aircraftsStore";
import { Marker, Popup, useMap } from "react-leaflet";
import { DivIcon, Marker as LMarker, Popup as LPopup } from "leaflet";
import { useMapStore } from "_store/mapStore";
import { Fragment, useEffect, useRef, useState } from "react";
import { cn } from "helpers/cn";
export const FMS_STATUS_COLORS: { [key: string]: string } = {
"0": "rgb(126,0,5)",
"1": "rgb(0,93,0)",
"2": "rgb(0,93,0)",
"3": "rgb(126,0,5)",
"4": "rgb(126,0,5)",
"5": "rgb(126,0,5)",
"6": "rgb(67,67,67)",
"7": "rgb(126,0,5)",
"8": "rgb(186,105,0)",
"9": "rgb(0,93,0)",
};
export const FMS_STATUS_TEXT_COLORS: { [key: string]: string } = {
"0": "rgb(255,39,57)",
"1": "rgb(42,217,42)",
"2": "rgb(42,217,42)",
"3": "rgb(255,39,57)",
"4": "rgb(255,39,57)",
"5": "rgb(255,39,57)",
"6": "rgb(211,211,211)",
"7": "rgb(255,39,57)",
"8": "rgb(255,143,0)",
"9": "rgb(42,217,42)",
};
export const AircraftMarker = (props: any) => {
const { openAircraftMarker, setOpenAircraftMarker } = useMapStore(
(state) => state,
);
const aircrafts = useAircraftsStore((state) => state.aircrafts);
const map = useMap();
const [markersAdjusted, setMarkersAdjusted] = useState(false);
const getMarkerHTML = (
aircraft: Aircraft,
anchor: "topleft" | "topright" | "bottomleft" | "bottomright",
) => {
return `<div
data-aircraft-id="${aircraft.id}"
id="aircraft-marker-${aircraft.id}"
class="${cn(
"aircraft-marker relative w-[140px] transform flex items-center gap-2 px-2 z-100",
anchor.includes("right") && "-translate-x-full",
anchor.includes("bottom") && "-translate-y-full",
)}"
style="
background-color: ${FMS_STATUS_COLORS[aircraft.fmsStatus]};
${openAircraftMarker.includes(aircraft.id) ? "opacity: 0;" : ""}
">
<div
class="${cn(
"absolute w-4 h-4 z-99",
anchor.includes("left") ? "-left-[2px]" : "-right-[2px]",
anchor.includes("top") ? "-top-[2px]" : "-bottom-[2px]",
)}"
style="
${anchor.includes("left") ? `border-left: 3px solid ${FMS_STATUS_TEXT_COLORS[aircraft.fmsStatus]};` : `border-right: 3px solid ${FMS_STATUS_TEXT_COLORS[aircraft.fmsStatus]};`}
${anchor.includes("top") ? `border-top: 3px solid ${FMS_STATUS_TEXT_COLORS[aircraft.fmsStatus]};` : `border-bottom: 3px solid ${FMS_STATUS_TEXT_COLORS[aircraft.fmsStatus]};`}
">
</div>
<span
class="font-semibold text-xl"
style="color: ${FMS_STATUS_TEXT_COLORS[aircraft.fmsStatus]};"
>
${aircraft.fmsStatus}
</span>
<span class="text-white text-[15px]">
${aircraft.bosName}
</span>
</div>`;
};
return aircrafts.map((aircraft) => {
const markerRef = useRef<LMarker>(null);
const popupRef = useRef<LPopup>(null);
useEffect(() => {
const handleClick = () => {
const open = openAircraftMarker.includes(aircraft.id);
if (open) {
setOpenAircraftMarker({
open: [],
close: [aircraft.id],
});
} else {
setOpenAircraftMarker({
open: [aircraft.id],
close: [],
});
}
};
markerRef.current?.on("click", handleClick);
return () => {
markerRef.current?.off("click", handleClick);
};
}, [markerRef.current, aircraft.id, openAircraftMarker]);
// TODO: Get Overlapping Markers and make them opientate away from the center
const [anchor, setAnchor] = useState<
"topleft" | "topright" | "bottomleft" | "bottomright"
>("topleft");
useEffect(() => {
/* const testRef = setInterval(() => {
positionMarker();
}, 1000); */
const handleZoom = () => {
setAnchor("topleft");
setMarkersAdjusted(false);
};
map.on("zoom", handleZoom);
return () => {
map.off("zoom", handleZoom);
};
/* return () => {
clearInterval(testRef);
}; */
}, [map]);
useEffect(() => {
if (markersAdjusted) return;
for (let i = 0; i < 3; i++) {
console.log(`Iteration ${i}`);
const otherMarkers = document.querySelectorAll(".aircraft-marker");
// get markers and check if they are overlapping
const ownMarker = document.querySelector(
`#aircraft-marker-${aircraft.id}`,
);
if (!otherMarkers || !ownMarker) return;
const overlappingMarkers = Array.from(otherMarkers).filter((marker) => {
// if (marker.id === `aircraft-marker-${aircraft.id}`) return false;
const rect1 = (marker as HTMLElement).getBoundingClientRect();
const rect2 = (ownMarker as HTMLElement).getBoundingClientRect();
return !(
rect1.right < rect2.left ||
rect1.left > rect2.right ||
rect1.bottom < rect2.top ||
rect1.top > rect2.bottom
);
});
// get the center of all overlapping markers
const markersPosition = overlappingMarkers.map((marker) => {
const rect = (marker as HTMLElement).getBoundingClientRect();
return {
x: rect.left + rect.width / 2,
y: rect.top + rect.height / 2,
};
});
const ownMarkerBounds = (
ownMarker as HTMLElement
).getBoundingClientRect();
const ownMarkerPosition = {
x: ownMarkerBounds.left + ownMarkerBounds.width / 2,
y: ownMarkerBounds.top + ownMarkerBounds.height / 2,
};
const centerOfOverlappingMarkers = markersPosition.reduce(
(acc, pos) => {
if (acc.x === 0 && acc.y === 0) return pos;
return {
x: (acc.x + pos.x) / 2,
y: (acc.y + pos.y) / 2,
};
},
{
x: 0,
y: 0,
},
);
console.log(
"overlapping markers",
centerOfOverlappingMarkers,
ownMarkerPosition,
);
if (overlappingMarkers.length >= 1) {
if (centerOfOverlappingMarkers.y > ownMarkerPosition.y) {
if (centerOfOverlappingMarkers.x > ownMarkerPosition.x) {
setAnchor("topright");
} else {
setAnchor("topleft");
}
} else {
if (centerOfOverlappingMarkers.x > ownMarkerPosition.x) {
setAnchor("bottomright");
} else {
setAnchor("bottomleft");
}
}
}
// TODO: Once markers are readjusted they dont overlap to these they did in the first iteration, but when all markers are adjusted they overlap whit ones they didnt in the first interation
}
setMarkersAdjusted(true);
}, [setAnchor, setMarkersAdjusted, aircraft.id, markersAdjusted]);
// EG. if 3 markers are overlapping and they are left and right
return (
<Fragment key={aircraft.id}>
<Marker
ref={markerRef}
position={[aircraft.location.lat, aircraft.location.lon]}
icon={
new DivIcon({
iconAnchor: [0, 0],
html: getMarkerHTML(aircraft, anchor),
})
}
/>
{openAircraftMarker.includes(aircraft.id) && (
<Popup
ref={popupRef}
position={[aircraft.location.lat, aircraft.location.lon]}
autoClose={false}
closeOnClick={false}
autoPan={false}
className="outline-red"
>
<div className="p-2 bg-white">Alla</div>
</Popup>
)}
</Fragment>
);
});
};

View File

@@ -5,14 +5,7 @@ import { Popup, useMap } from "react-leaflet";
export const ContextMenu = () => {
const map = useMap();
const {
contextMenu,
setContextMenu,
setSearchElements,
setSearchPopup,
setOpenMissionMarker,
openMissionMarker,
} = useMapStore();
const { contextMenu, setContextMenu, setSearchElements } = useMapStore();
useEffect(() => {
map.on("contextmenu", (e) => {

View File

@@ -6,6 +6,7 @@ import { BaseMaps } from "(dispatch)/_components/map/BaseMaps";
import { ContextMenu } from "(dispatch)/_components/map/ContextMenu";
import { MissionMarkers } from "(dispatch)/_components/map/MissionMarkers";
import { SearchElements } from "(dispatch)/_components/map/SearchElements";
import { AircraftMarker } from "(dispatch)/_components/map/AircraftMarker";
export default ({}) => {
const { map } = useMapStore();
@@ -16,6 +17,7 @@ export default ({}) => {
<SearchElements />
<ContextMenu />
<MissionMarkers />
<AircraftMarker />
</MapContainer>
);
};

View File

@@ -0,0 +1,107 @@
import { create } from "zustand";
export interface Aircraft {
id: string;
bosName: string;
bosNameShort: string;
fmsStatus: string;
fmsLog: {
status: string;
timestamp: string;
user: string;
}[];
location: {
lat: number;
lon: number;
altitude: number;
speed: number;
};
locationHistory: {
lat: number;
lon: number;
altitude: number;
speed: number;
timestamp: string;
}[];
}
interface AircraftStore {
aircrafts: Aircraft[];
setAircrafts: (aircrafts: Aircraft[]) => void;
setAircraft: (aircraft: Aircraft) => void;
}
export const useAircraftsStore = create<AircraftStore>((set) => ({
aircrafts: [
{
id: "1",
bosName: "Aircraft 1",
bosNameShort: "A1",
fmsStatus: "1",
fmsLog: [],
location: {
lat: 52.546781040592776,
lon: 13.369535209542219,
altitude: 0,
speed: 0,
},
locationHistory: [],
},
{
id: "2",
bosName: "Aircraft 2",
bosNameShort: "A2",
fmsStatus: "2",
fmsLog: [],
location: {
lat: 52.54588546048977,
lon: 13.46470691054384,
altitude: 0,
speed: 0,
},
locationHistory: [],
},
{
id: "3",
bosName: "Aircraft 3",
bosNameShort: "A3",
fmsStatus: "3",
fmsLog: [],
location: {
lat: 52.497519717230155,
lon: 13.342040806552554,
altitude: 0,
speed: 0,
},
locationHistory: [],
},
{
id: "4",
bosName: "Aircraft 4",
bosNameShort: "A4",
fmsStatus: "6",
fmsLog: [],
location: {
lat: 52.50175041192073,
lon: 13.478628701227349,
altitude: 0,
speed: 0,
},
locationHistory: [],
},
],
setAircrafts: (aircrafts) => set({ aircrafts }),
setAircraft: (aircraft) =>
set((state) => {
const existingAircraftIndex = state.aircrafts.findIndex(
(a) => a.id === aircraft.id,
);
if (existingAircraftIndex !== -1) {
const updatedAircrafts = [...state.aircrafts];
updatedAircrafts[existingAircraftIndex] = aircraft;
return { aircrafts: updatedAircrafts };
} else {
return { aircrafts: [...state.aircrafts, aircraft] };
}
}),
}));

View File

@@ -12,6 +12,8 @@ interface MapStore {
};
openMissionMarker: string[];
setOpenMissionMarker: (mission: { open: string[]; close: string[] }) => void;
openAircraftMarker: string[];
setOpenAircraftMarker: (mission: { open: string[]; close: string[] }) => void;
searchElements: {
id: number;
nodes: {
@@ -48,6 +50,14 @@ export const useMapStore = create<MapStore>((set, get) => ({
),
}));
},
openAircraftMarker: [],
setOpenAircraftMarker: ({ open, close }) => {
set((state) => ({
openAircraftMarker: [...state.openAircraftMarker, ...open].filter(
(id) => !close.includes(id),
),
}));
},
map: {
center: [51.5, 10.5],
zoom: 6,

View File

@@ -15,6 +15,10 @@
.leaflet-popup-content p {
margin: 0 !important;
}
.leaflet-div-icon {
background: inherit !important;
border: inherit !important;
}
.leaflet-popup-content-wrapper {
background: transparent !important;