/* global L */ // Initialisiert die Leaflet-Karte, verwaltet Marker-Clustering und // reagiert auf Store-Änderungen (places, selectedPlaceId). (function () { const { DEFAULT_MAP_CENTER } = EventMap.config; const { subscribe, getState, setState } = EventMap.store; const { getPlaceLatLng } = EventMap.models; const { createMarkerForPlace } = EventMap.map; let map = null; let clusterGroup = null; const markersByPlaceId = new Map(); let lastPlacesRef = null; let lastSelectedPlaceId = null; function hasValidCoords(place) { const { lat, lng } = getPlaceLatLng(place); return typeof lat === 'number' && typeof lng === 'number' && !Number.isNaN(lat) && !Number.isNaN(lng); } function renderMarkers(places) { clusterGroup.clearLayers(); markersByPlaceId.clear(); for (const place of places) { if (!hasValidCoords(place)) continue; const marker = createMarkerForPlace(place, { onSelect: (placeId) => setState({ selectedPlaceId: placeId }), }); markersByPlaceId.set(place.id, marker); clusterGroup.addLayer(marker); } } /** Passt den Kartenausschnitt an alle übergebenen Places an (Fallback: Default-Center). */ function fitToPlaces(places) { const valid = places.filter(hasValidCoords); if (valid.length === 0) { map.setView([DEFAULT_MAP_CENTER.lat, DEFAULT_MAP_CENTER.lng], DEFAULT_MAP_CENTER.zoom); return; } const bounds = L.latLngBounds( valid.map((place) => { const { lat, lng } = getPlaceLatLng(place); return [lat, lng]; }) ); map.fitBounds(bounds, { padding: [32, 32], maxZoom: 14 }); } /** Zentriert die Karte auf einen bestimmten Place und öffnet dessen Popup. */ function centerOnPlace(placeId) { const marker = markersByPlaceId.get(placeId); if (!marker || !map) return; map.setView(marker.getLatLng(), Math.max(map.getZoom(), 14)); marker.openPopup(); } /** Zentriert die Karte auf beliebige Koordinaten (z.B. Nutzerstandort). */ function centerOnCoords(lat, lng, zoom = 13) { if (!map) return; map.setView([lat, lng], zoom); } function getMapInstance() { return map; } /** Initialisiert die Karte im Element mit der übergebenen id. */ function initMap(containerId) { // Leaflet leert den Container beim Initialisieren nicht selbst - den // statischen "Karte wird geladen…"-Platzhalter aus index.html entfernen. const container = document.getElementById(containerId); if (container) { container.textContent = ''; } map = L.map(containerId, { center: [DEFAULT_MAP_CENTER.lat, DEFAULT_MAP_CENTER.lng], zoom: DEFAULT_MAP_CENTER.zoom, }); // tile.openstreetmap.org verlangt seit 2024 einen gültigen HTTP-Referer // (Tile Usage Policy). Seiten, die per file:// geöffnet werden, senden // browserseitig grundsätzlich keinen Referer mit ("Access blocked: Referer // is required"). Daher CARTO-Basemaps (kartographisch auf OSM-Daten // basierend, korrekt attributiert) statt des offiziellen OSM-Tile-Servers. L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', { maxZoom: 19, subdomains: 'abcd', attribution: '© OpenStreetMap-Mitwirkende ' + '© CARTO', }).addTo(map); clusterGroup = L.markerClusterGroup(); map.addLayer(clusterGroup); const initialState = getState(); renderMarkers(initialState.places); fitToPlaces(initialState.places); lastPlacesRef = initialState.places; subscribe((state) => { if (state.places !== lastPlacesRef) { lastPlacesRef = state.places; renderMarkers(state.places); } if (state.selectedPlaceId !== lastSelectedPlaceId) { lastSelectedPlaceId = state.selectedPlaceId; if (state.selectedPlaceId) { centerOnPlace(state.selectedPlaceId); } } }); return map; } EventMap.map.fitToPlaces = fitToPlaces; EventMap.map.centerOnPlace = centerOnPlace; EventMap.map.centerOnCoords = centerOnCoords; EventMap.map.getMapInstance = getMapInstance; EventMap.map.initMap = initMap; })();