365 lines
9.7 KiB
TypeScript
365 lines
9.7 KiB
TypeScript
"use client";
|
|
import { usePannelStore } from "_store/pannelStore";
|
|
import { Control, Icon, LatLngExpression } from "leaflet";
|
|
import { useEffect, useRef, useState } from "react";
|
|
import {
|
|
LayerGroup,
|
|
LayersControl,
|
|
TileLayer,
|
|
useMap,
|
|
WMSTileLayer,
|
|
GeoJSON,
|
|
Circle,
|
|
useMapEvent,
|
|
FeatureGroup,
|
|
Marker,
|
|
Tooltip,
|
|
} from "react-leaflet";
|
|
// @ts-ignore
|
|
import type { FeatureCollection, Geometry } from "geojson";
|
|
import L from "leaflet";
|
|
import LEITSTELLENBERECHE from "./_geojson/Leitstellen.json";
|
|
import WINDFARMS from "./_geojson/Windfarms.json";
|
|
import { createCustomMarker } from "_components/map/_components/createCustomMarker";
|
|
import { Station } from "@repo/db";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import { getStationsAPI } from "_querys/stations";
|
|
import "./darkMapStyles.css";
|
|
|
|
const RadioAreaLayer = () => {
|
|
const getColor = (randint: number) => {
|
|
switch (randint) {
|
|
case 1:
|
|
return "#2b6eff";
|
|
case 2:
|
|
return "#233ee5";
|
|
case 3:
|
|
return "#7BA5FF";
|
|
case 4:
|
|
return "#5087FF";
|
|
default:
|
|
return "#7f7f7f";
|
|
}
|
|
};
|
|
|
|
return (
|
|
<GeoJSON
|
|
data={LEITSTELLENBERECHE as FeatureCollection<Geometry>}
|
|
style={(feature) => {
|
|
if (!feature || !feature.properties) return {}; // Early return if feature or its properties are undefined
|
|
return {
|
|
color: getColor(feature.properties.randint),
|
|
weight: 1.5,
|
|
className: "no-pointer",
|
|
};
|
|
}}
|
|
onEachFeature={(feature, layer) => {
|
|
if (feature && feature.properties && feature.properties.name) {
|
|
layer.bindTooltip(
|
|
new L.Tooltip({
|
|
content: feature.properties.name,
|
|
direction: "top",
|
|
sticky: true,
|
|
}),
|
|
);
|
|
}
|
|
}}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const StationsLayer = ({ attribution }: { attribution: Control.Attribution }) => {
|
|
const { data: stations } = useQuery({
|
|
queryKey: ["stations"],
|
|
queryFn: () => getStationsAPI(),
|
|
});
|
|
|
|
const [selectedStations, setSelectedStations] = useState<Station["id"][]>([]);
|
|
const [stationsWithIcon, setStationsWithIcon] = useState<(Station & { icon?: string })[]>([]); // Zustand für die Stationen mit Icon
|
|
const attributionText = "";
|
|
|
|
const resetSelection = () => {
|
|
setSelectedStations([]);
|
|
};
|
|
|
|
useMapEvent("click", () => {
|
|
resetSelection();
|
|
});
|
|
|
|
const handleMarkerClick = (stationId: number) => {
|
|
if (selectedStations.includes(stationId)) {
|
|
setSelectedStations((prevStations) => prevStations.filter((s) => s !== stationId));
|
|
} else {
|
|
setSelectedStations((prevStations) => [...prevStations, stationId]);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
// Erstelle die Icons für alle Stationen
|
|
|
|
const fetchIcons = async () => {
|
|
if (!stations) return;
|
|
const urls = await Promise.all(
|
|
stations.map(async (station) => {
|
|
return createCustomMarker(station.operator);
|
|
}),
|
|
);
|
|
setStationsWithIcon(stations.map((station, index) => ({ ...station, icon: urls[index] })));
|
|
};
|
|
|
|
fetchIcons();
|
|
}, [stations]);
|
|
|
|
return (
|
|
<FeatureGroup>
|
|
{stationsWithIcon.map((station) => {
|
|
const coordinates: LatLngExpression = [station.latitude, station.longitude];
|
|
const typeLabel = station.bosUse.charAt(0).toUpperCase();
|
|
|
|
return (
|
|
<Marker
|
|
key={`marker-${station.id}`}
|
|
position={coordinates}
|
|
icon={
|
|
new Icon({
|
|
iconUrl: station.icon,
|
|
iconSize: [30, 30],
|
|
iconAnchor: [15, 15],
|
|
tooltipAnchor: [0, 20],
|
|
className: station.hideRangeRings ? "no-pointer" : "pointer",
|
|
})
|
|
}
|
|
eventHandlers={{
|
|
click: () => {
|
|
if (!station.hideRangeRings) handleMarkerClick(station.id);
|
|
},
|
|
add: () => attribution.addAttribution(attributionText),
|
|
remove: () => attribution.removeAttribution(attributionText),
|
|
}}
|
|
>
|
|
<Tooltip direction="top" sticky>
|
|
<div style={{ textAlign: "center" }}>
|
|
<strong>{station.bosCallsign}</strong>
|
|
<small style={{ fontWeight: "bold", fontSize: "0.7em" }}>{` (${typeLabel})`}</small>
|
|
<br />
|
|
<small>
|
|
{[
|
|
station.hasWinch ? "W" : null,
|
|
station.is24h ? "24h" : null,
|
|
station.hasNvg ? "N" : null,
|
|
]
|
|
.filter(Boolean)
|
|
.join(", ")}
|
|
</small>
|
|
</div>
|
|
</Tooltip>
|
|
</Marker>
|
|
);
|
|
})}
|
|
|
|
{selectedStations.map((stationId) => {
|
|
const station = stations?.find((s) => s.id === stationId);
|
|
|
|
if (!station) return null;
|
|
|
|
const center: LatLngExpression = [station.latitude, station.longitude];
|
|
return (
|
|
<div key={`marker-${stationId}`}>
|
|
<Circle
|
|
center={center}
|
|
radius={(station.aircraftSpeed * 1000) / 6}
|
|
color="#0e0ecf"
|
|
fillOpacity={0}
|
|
weight={2}
|
|
/>
|
|
<Circle
|
|
center={center}
|
|
radius={(station.aircraftSpeed * 1000) / 3}
|
|
color="navy"
|
|
fillOpacity={0}
|
|
weight={2}
|
|
/>
|
|
{(station.bosUse === "SECONDARY" || station.bosUse === "DUAL_USE") && (
|
|
<Circle
|
|
center={center}
|
|
radius={station.aircraftSpeed * 1000}
|
|
color="maroon"
|
|
fillOpacity={0}
|
|
weight={1}
|
|
dashArray="40,30"
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</FeatureGroup>
|
|
);
|
|
};
|
|
|
|
const EsriSatellite = ({ attribution }: { attribution: Control.Attribution }) => {
|
|
const accessToken = process.env.NEXT_PUBLIC_ESRI_ACCESS;
|
|
return (
|
|
<>
|
|
{/* Satellite Imagery Layer; API KEY PROVIDED BY VAR0002 */}
|
|
<TileLayer
|
|
attribution="Sources: Esri, TomTom, Garmin, FAO, NOAA, USGS"
|
|
url={`https://services.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}?token=${accessToken}`}
|
|
tileSize={256}
|
|
/>
|
|
</>
|
|
);
|
|
};
|
|
|
|
const StrassentexteEsri = () => {
|
|
return (
|
|
<WMSTileLayer
|
|
url="https://tiledbasemaps.arcgis.com/arcgis/rest/services/Reference/World_Transportation/MapServer/tile/{z}/{y}/{x}"
|
|
format="image/png"
|
|
transparent
|
|
/>
|
|
);
|
|
};
|
|
|
|
const OpenAIP = ({ attribution }: { attribution: Control.Attribution }) => {
|
|
const accessToken = process.env.NEXT_PUBLIC_OPENAIP_ACCESS;
|
|
const ref = useRef<L.TileLayer | null>(null);
|
|
|
|
return (
|
|
<TileLayer
|
|
eventHandlers={{
|
|
add: () => {
|
|
if (ref.current) {
|
|
ref.current.bringToFront();
|
|
}
|
|
},
|
|
}}
|
|
ref={ref}
|
|
attribution='© <a href="https://www.openaip.net" target="_blank">OpenAIP</a>'
|
|
url={`https://api.tiles.openaip.net/api/data/openaip/{z}/{x}/{y}.png?apiKey=${process.env.NEXT_PUBLIC_OPENAIP_ACCESS}`}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const NiederschlagOverlay = ({ attribution }: { attribution: Control.Attribution }) => {
|
|
const tileLayerRef = useRef<L.TileLayer.WMS | null>(null);
|
|
|
|
return (
|
|
<WMSTileLayer
|
|
ref={tileLayerRef}
|
|
eventHandlers={{
|
|
add: () => {
|
|
tileLayerRef.current?.bringToFront();
|
|
},
|
|
}}
|
|
attribution="Quelle: Deutscher Wetterdienst"
|
|
url="https://maps.dwd.de/geoserver/wms?"
|
|
format="image/png"
|
|
layers="dwd:Niederschlagsradar"
|
|
transparent
|
|
opacity={0.7}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const WindfarmOutlineLayer = () => {
|
|
const map = useMap();
|
|
const [isVisible, setIsVisible] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const handleZoom = () => {
|
|
setIsVisible(map.getZoom() < 13);
|
|
};
|
|
|
|
// Initial check and event listener
|
|
handleZoom();
|
|
map.on("zoomend", handleZoom);
|
|
|
|
// Cleanup on unmount
|
|
return () => {
|
|
map.off("zoomend", handleZoom);
|
|
};
|
|
}, [map]);
|
|
|
|
return isVisible ? (
|
|
<GeoJSON
|
|
data={WINDFARMS as FeatureCollection<Geometry>}
|
|
style={() => ({
|
|
color: "#233EE5",
|
|
weight: 1.5,
|
|
dashArray: "7, 10",
|
|
className: "no-pointer",
|
|
})}
|
|
onEachFeature={(feature, layer) => {
|
|
layer.bindTooltip(
|
|
new L.Tooltip({
|
|
content: feature.properties.Cluster_NR,
|
|
direction: "top",
|
|
sticky: true,
|
|
}),
|
|
);
|
|
}}
|
|
/>
|
|
) : null;
|
|
};
|
|
|
|
export const BaseMaps = () => {
|
|
const map = useMap();
|
|
const isPannelOpen = usePannelStore((state) => state.isOpen);
|
|
|
|
useEffect(() => {
|
|
setTimeout(() => {
|
|
map.invalidateSize();
|
|
}, 600);
|
|
}, [isPannelOpen]);
|
|
|
|
return (
|
|
<LayersControl position="topleft">
|
|
<LayersControl.Overlay name={"Funknetzbereiche"}>
|
|
<RadioAreaLayer />
|
|
</LayersControl.Overlay>
|
|
<LayersControl.Overlay name={"Niederschlag"}>
|
|
<NiederschlagOverlay attribution={map.attributionControl} />
|
|
</LayersControl.Overlay>
|
|
|
|
<LayersControl.Overlay name={"Windkraftanlagen offshore"}>
|
|
<WindfarmOutlineLayer />
|
|
</LayersControl.Overlay>
|
|
|
|
<LayersControl.Overlay name={"LRZs"}>
|
|
<StationsLayer attribution={map.attributionControl} />
|
|
</LayersControl.Overlay>
|
|
<LayersControl.Overlay name={"OpenAIP"}>
|
|
<OpenAIP attribution={map.attributionControl} />
|
|
</LayersControl.Overlay>
|
|
|
|
<LayersControl.BaseLayer name="OpenStreetMap Dark" checked>
|
|
<TileLayer
|
|
zIndex={-1}
|
|
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
|
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
|
className="invert-100 grayscale"
|
|
/>
|
|
</LayersControl.BaseLayer>
|
|
<LayersControl.BaseLayer name="OpenStreetMap">
|
|
<TileLayer
|
|
zIndex={-1}
|
|
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
|
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
|
/>
|
|
</LayersControl.BaseLayer>
|
|
<LayersControl.BaseLayer name="OpenTopoMap">
|
|
<TileLayer
|
|
attribution='map data: © <a href="https://opentopomap.org/copyright">OpenStreetMap</a> contributors, <a href="http://viewfinderpanoramas.org">SRTM</a> | map style: © <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
|
|
url="https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png"
|
|
/>
|
|
</LayersControl.BaseLayer>
|
|
<LayersControl.BaseLayer name="ESRI Satellite">
|
|
<LayerGroup>
|
|
<EsriSatellite attribution={map.attributionControl} />
|
|
<StrassentexteEsri />
|
|
</LayerGroup>
|
|
</LayersControl.BaseLayer>
|
|
</LayersControl>
|
|
);
|
|
};
|