"use client"; import { Control, divIcon, 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-expect-error geojson hat keine Typen 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 { Heliport, Station } from "@repo/db"; import { useQuery } from "@tanstack/react-query"; import { getStationsAPI } from "_querys/stations"; import "./darkMapStyles.css"; import { getHeliportsAPI } from "_querys/heliports"; 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 ( } 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 HeliportsLayer = () => { const { data: heliports } = useQuery({ queryKey: ["heliports"], queryFn: () => getHeliportsAPI(), }); const [heliportsWithIcon, setHeliportsWithIcon] = useState<(Heliport & { icon?: string })[]>([]); const map = useMap(); const [isVisible, setIsVisible] = useState(true); const [boxContent, setBoxContent] = useState(null); // Übergangslösung const formatDate = (date: Date): string => { const year = date.getFullYear().toString().slice(-2); // Letzte 2 Stellen des Jahres const month = (date.getMonth() + 1).toString().padStart(2, "0"); // Monat (mit führender Null, falls notwendig) const day = date.getDate().toString().padStart(2, "0"); // Tag (mit führender Null, falls notwendig) return `${year}${month}${day}`; }; const replaceWithYesterdayDate = (url: string): string => { const yesterday = new Date(); yesterday.setDate(yesterday.getDate() - 2); // Einen Tag zurücksetzen const formattedDate = formatDate(yesterday); return url.replace(/\.at\/lo\/\d{6}/, `.at/lo/${formattedDate}`); }; const resetSelection = () => { setBoxContent(null); }; useMapEvent("click", () => { resetSelection(); }); useEffect(() => { const handleZoom = () => { setIsVisible(map.getZoom() > 8); }; handleZoom(); map.on("zoomend", handleZoom); const fetchIcons = async () => { if (!heliports) return; const urls = await Promise.all( heliports.map(async (heliport) => { return createCustomMarker(heliport.type); }), ); setHeliportsWithIcon( heliports.map((heliport, index) => ({ ...heliport, icon: urls[index] })), ); }; const filterVisibleHeliports = () => { const bounds = map.getBounds(); if (!heliports?.length) return; // Filtere die Heliports, die innerhalb der Kartenansicht liegen const visibleHeliports = heliports.filter((heliport) => { const coordinates: LatLngExpression = [heliport.lat, heliport.lng]; return bounds.contains(coordinates); // Überprüft, ob der Heliport innerhalb der aktuellen Bounds liegt }); setHeliportsWithIcon(visibleHeliports); }; if (heliports?.length) { fetchIcons(); filterVisibleHeliports(); } handleZoom(); map.on("zoomend", handleZoom); map.on("moveend", filterVisibleHeliports); return () => { map.off("zoomend", handleZoom); map.off("moveend", filterVisibleHeliports); }; }, [map, heliports]); const createCustomIcon = (heliportType: string) => { if (heliportType === "POI") { return divIcon({ className: "custom-marker no-pointer", // CSS-Klasse für Styling html: '
H
', iconSize: [15, 15], // Größe des Icons iconAnchor: [7.5, 15], // Ankerpunkt des Icons }); } // Heliport Typ: H-Icon if (heliportType === "HELIPAD") { return divIcon({ className: "custom-marker no-pointer", // CSS-Klasse für Styling html: '
H
', iconSize: [15, 15], // Größe des Icons (15x15 px Viereck) iconAnchor: [7.5, 15], // Ankerpunkt des Icons }); } // Mountain Typ: Kreis mit "M" if (heliportType === "MOUNTAIN") { return divIcon({ className: "custom-marker no-pointer", html: '
M
', iconSize: [15, 15], // Größe des Icons iconAnchor: [7.5, 15], // Ankerpunkt des Icons }); } // Falls kein Typ übereinstimmt, standardmäßig das POI-Icon mit Fragezeichen verwenden return divIcon({ className: "custom-marker no-pointer", html: '
?
', iconSize: [15, 15], iconAnchor: [7.5, 15], }); }; return ( <> {isVisible && heliportsWithIcon.map((heliport) => { const coordinates: LatLngExpression = [heliport.lat, heliport.lng]; const designatorLabel = heliport.designator.charAt(0).toUpperCase(); const heliportType = heliport.type; return ( { const tooltipContent = `${heliport.siteNameSub26} (${heliport.designator})`; e.target .bindTooltip(tooltipContent, { direction: "top", offset: [4, -15], }) .openTooltip(); }, mouseout: (e) => { e.target.closeTooltip(); }, click: () => { setBoxContent(

{heliport.siteNameSub26}

Designator: {heliport.designator}

{heliport.info?.startsWith("http") ? (

{heliport.info}

) : (

{heliport.info}

)}

{heliport.lat} °N, {heliport.lng} °E

, ); }, }} >
{heliport.designator} {` (${designatorLabel})`}
); })}
{boxContent &&
{boxContent}
} ); }; const StationsLayer = ({ attribution }: { attribution: Control.Attribution }) => { const { data: stations } = useQuery({ queryKey: ["stations"], queryFn: () => getStationsAPI(), }); const [selectedStations, setSelectedStations] = useState([]); 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]); } }; const [stationsWithIcon, setStationsWithIcon] = useState<(Station & { icon: string })[]>([]); useEffect(() => { if (!stations) { setStationsWithIcon([]); return; } const fetchIcons = async () => { const urls = await Promise.all( stations.map(async (station) => { return await createCustomMarker(station.operator); }), ); setStationsWithIcon(stations.map((station, index) => ({ ...station, icon: urls[index]! }))); }; fetchIcons(); }, [stations]); return ( {stationsWithIcon?.map((station) => { const coordinates: LatLngExpression = [station.latitude, station.longitude]; const typeLabel = station.bosUse?.charAt(0).toUpperCase(); return ( { if (!station.hideRangeRings) handleMarkerClick(station.id); }, add: () => attribution.addAttribution(attributionText), remove: () => attribution.removeAttribution(attributionText), }} >
{station.bosCallsign} {` (${typeLabel})`}
{[ station.hasWinch ? "W" : null, station.is24h ? "24h" : null, station.hasNvg ? "N" : null, ] .filter(Boolean) .join(", ")}
); })} {selectedStations.map((stationId) => { const station = stations?.find((s) => s.id === stationId); if (!station) return null; const center: LatLngExpression = [station.latitude, station.longitude]; return (
{(station.bosUse === "SECONDARY" || station.bosUse === "DUAL_USE") && ( )}
); })}
); }; const EsriSatellite = () => { const accessToken = process.env.NEXT_PUBLIC_ESRI_ACCESS; return ( <> {/* Satellite Imagery Layer; API KEY PROVIDED BY VAR0002 */} ); }; const StrassentexteEsri = () => { return ( ); }; const OpenAIP = () => { const ref = useRef(null); return ( { if (ref.current) { ref.current.bringToFront(); } }, }} ref={ref} attribution='© OpenAIP' url={`https://api.tiles.openaip.net/api/data/openaip/{z}/{x}/{y}.png?apiKey=${process.env.NEXT_PUBLIC_OPENAIP_ACCESS}`} /> ); }; const NiederschlagOverlay = () => { const tileLayerRef = useRef(null); return ( { 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 SlopesOverlay = () => { const tileLayerRef = useRef(null); return ( { tileLayerRef.current?.bringToFront(); }, }} attribution="Opensnowmap.org (CC-BY-SA)" url="http://tiles.opensnowmap.org/pistes/{z}/{x}/{y}.png?" transparent zIndex={1000} /> ); }; 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 ? ( } 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(); return ( ); };