started collision handeling for label
This commit is contained in:
239
apps/dispatch/app/(dispatch)/_components/map/AircraftMarker.tsx
Normal file
239
apps/dispatch/app/(dispatch)/_components/map/AircraftMarker.tsx
Normal 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>
|
||||
);
|
||||
});
|
||||
};
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
107
apps/dispatch/app/_store/aircraftsStore.ts
Normal file
107
apps/dispatch/app/_store/aircraftsStore.ts
Normal 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] };
|
||||
}
|
||||
}),
|
||||
}));
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user