diff --git a/apps/dispatch/app/(dispatch)/_components/map/AircraftMarker.tsx b/apps/dispatch/app/(dispatch)/_components/map/AircraftMarker.tsx new file mode 100644 index 00000000..de40dc02 --- /dev/null +++ b/apps/dispatch/app/(dispatch)/_components/map/AircraftMarker.tsx @@ -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 `
+
+
+ + ${aircraft.fmsStatus} + + + ${aircraft.bosName} + +
`; + }; + + return aircrafts.map((aircraft) => { + const markerRef = useRef(null); + const popupRef = useRef(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 ( + + + {openAircraftMarker.includes(aircraft.id) && ( + +
Alla
+
+ )} +
+ ); + }); +}; diff --git a/apps/dispatch/app/(dispatch)/_components/map/ContextMenu.tsx b/apps/dispatch/app/(dispatch)/_components/map/ContextMenu.tsx index 53820d6a..d82c1964 100644 --- a/apps/dispatch/app/(dispatch)/_components/map/ContextMenu.tsx +++ b/apps/dispatch/app/(dispatch)/_components/map/ContextMenu.tsx @@ -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) => { diff --git a/apps/dispatch/app/(dispatch)/_components/map/Map.tsx b/apps/dispatch/app/(dispatch)/_components/map/Map.tsx index 155d5d5d..08ac1299 100644 --- a/apps/dispatch/app/(dispatch)/_components/map/Map.tsx +++ b/apps/dispatch/app/(dispatch)/_components/map/Map.tsx @@ -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 ({}) => { + ); }; diff --git a/apps/dispatch/app/_store/aircraftsStore.ts b/apps/dispatch/app/_store/aircraftsStore.ts new file mode 100644 index 00000000..7c9a0462 --- /dev/null +++ b/apps/dispatch/app/_store/aircraftsStore.ts @@ -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((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] }; + } + }), +})); diff --git a/apps/dispatch/app/_store/mapStore.ts b/apps/dispatch/app/_store/mapStore.ts index 38589264..075364ad 100644 --- a/apps/dispatch/app/_store/mapStore.ts +++ b/apps/dispatch/app/_store/mapStore.ts @@ -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((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, diff --git a/apps/dispatch/app/globals.css b/apps/dispatch/app/globals.css index 4178859a..06986ec6 100644 --- a/apps/dispatch/app/globals.css +++ b/apps/dispatch/app/globals.css @@ -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; diff --git a/grafana/grafana.db b/grafana/grafana.db index 05a61d31..72bd3457 100644 Binary files a/grafana/grafana.db and b/grafana/grafana.db differ