/* * Copyright (c) 2026 by Christian Kellner. * Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause */ import { useEffect, useRef } from 'react'; import maplibregl from 'maplibre-gl'; import 'maplibre-gl/dist/maplibre-gl.css'; import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css'; import { fixMapboxDrawCompatibility, addDrawingControl, setupAreaFilterEventListeners } from './MapDrawingExtension.js'; import { getBoundsFromCoords } from '../../views/listings/mapUtils.js'; import './Map.less'; export const GERMANY_BOUNDS = [ [5.866, 47.27], // Southwest coordinates [15.042, 55.059], // Northeast coordinates ]; export const STYLES = { STANDARD: 'https://tiles.openfreemap.org/styles/bright', SATELLITE: { version: 8, sources: { 'satellite-tiles': { type: 'raster', tiles: ['https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'], tileSize: 256, attribution: 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community', }, 'satellite-labels': { type: 'raster', tiles: [ 'https://server.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}', ], tileSize: 256, attribution: '© Esri', }, }, layers: [ { id: 'satellite-tiles', type: 'raster', source: 'satellite-tiles', minzoom: 0, maxzoom: 19, }, { id: 'satellite-labels', type: 'raster', source: 'satellite-labels', minzoom: 0, maxzoom: 19, }, ], }, }; export default function Map({ style = 'STANDARD', show3dBuildings = false, onMapReady = null, enableDrawing = false, initialSpatialFilter = null, onDrawingChange = null, }) { const mapContainerRef = useRef(null); const mapRef = useRef(null); const drawRef = useRef(null); const hasFittedToInitialAreaRef = useRef(false); // Initialize map - ONLY when container changes, never reinitialize useEffect(() => { if (mapRef.current) return; // Map already exists, don't reinitialize mapRef.current = new maplibregl.Map({ container: mapContainerRef.current, style: STYLES[style], center: [10.4515, 51.1657], // Center of Germany zoom: 4, maxBounds: GERMANY_BOUNDS, antialias: true, }); mapRef.current.addControl( new maplibregl.NavigationControl({ showCompass: true, visualizePitch: true, visualizeRoll: true, }), 'top-right', ); mapRef.current.addControl( new maplibregl.GeolocateControl({ positionOptions: { enableHighAccuracy: true, }, trackUserLocation: true, }), ); // Initialize drawing extension only if enabled if (enableDrawing) { fixMapboxDrawCompatibility(); drawRef.current = addDrawingControl(mapRef.current); } // Call onMapReady callback if provided if (onMapReady) { onMapReady(mapRef.current); } return () => { if (mapRef.current) { mapRef.current.remove(); mapRef.current = null; } }; }, [mapContainerRef]); // ONLY depend on mapContainerRef - nothing else! // Load spatial filter and setup area filter event listeners useEffect(() => { if (!mapRef.current || !drawRef.current || !enableDrawing) return; // Load initial spatial filter if provided if (initialSpatialFilter) { try { drawRef.current.set(initialSpatialFilter); } catch (error) { console.error('Error loading spatial filter:', error); } if (!hasFittedToInitialAreaRef.current) { const coords = initialSpatialFilter.features.flatMap((feature) => feature.geometry?.type === 'Polygon' ? feature.geometry.coordinates.flat() : [], ); const bounds = getBoundsFromCoords(coords); if (bounds) { mapRef.current.fitBounds(bounds, { padding: 50, maxZoom: 15, duration: 0 }); hasFittedToInitialAreaRef.current = true; } } } // Setup drawing event listeners const cleanup = setupAreaFilterEventListeners(mapRef.current, drawRef.current, onDrawingChange); return cleanup; }, [initialSpatialFilter, onDrawingChange, enableDrawing]); // Handle style changes useEffect(() => { if (mapRef.current) { mapRef.current.setStyle(STYLES[style]); } }, [style]); // Handle 3D buildings layer useEffect(() => { if (!mapRef.current) return; const add3dLayer = () => { if (!mapRef.current || !mapRef.current.isStyleLoaded()) return; if (show3dBuildings) { if (!mapRef.current.getSource('openfreemap')) { mapRef.current.addSource('openfreemap', { type: 'vector', url: 'https://tiles.openfreemap.org/planet', }); } if (!mapRef.current.getLayer('3d-buildings')) { const layers = mapRef.current.getStyle().layers; let labelLayerId; for (let i = 0; i < layers.length; i++) { if (layers[i].type === 'symbol' && layers[i].layout?.['text-field']) { labelLayerId = layers[i].id; break; } } mapRef.current.addLayer( { id: '3d-buildings', source: 'openfreemap', 'source-layer': 'building', type: 'fill-extrusion', minzoom: 15, filter: ['!=', ['get', 'hide_3d'], true], paint: { 'fill-extrusion-color': [ 'interpolate', ['linear'], ['get', 'render_height'], 0, 'lightgray', 200, 'royalblue', 400, 'lightblue', ], 'fill-extrusion-height': ['interpolate', ['linear'], ['zoom'], 15, 0, 16, ['get', 'render_height']], 'fill-extrusion-base': ['case', ['>=', ['get', 'zoom'], 16], ['get', 'render_min_height'], 0], 'fill-extrusion-opacity': 0.6, }, }, labelLayerId, ); } } else { if (mapRef.current.getLayer('3d-buildings')) { mapRef.current.removeLayer('3d-buildings'); } } }; add3dLayer(); }, [show3dBuildings, style]); // Handle pitch for 3D useEffect(() => { if (!mapRef.current) return; mapRef.current.setPitch(show3dBuildings ? 45 : 0); }, [show3dBuildings]); return
; }