/* * Copyright (c) 2026 by Christian Kellner. * Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause */ import React, { useEffect, useRef, useState } from 'react'; import maplibregl from 'maplibre-gl'; import 'maplibre-gl/dist/maplibre-gl.css'; import { useSelector, useActions } from '../../services/state/store.js'; import { Select, Space, Typography, Button, Popover, Divider, Switch, Banner } from '@douyinfe/semi-ui-19'; import { IconFilter } from '@douyinfe/semi-icons'; import no_image from '../../assets/no_image.jpg'; import RangeSlider from 'react-range-slider-input'; import 'react-range-slider-input/dist/style.css'; import './Map.less'; const { Text } = Typography; const GERMANY_BOUNDS = [ [5.866, 47.27], // Southwest coordinates [15.042, 55.059], // Northeast coordinates ]; 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 MapView() { const mapContainer = useRef(null); const map = useRef(null); const markers = useRef([]); const actions = useActions(); const listings = useSelector((state) => state.listingsData.mapListings); const [style, setStyle] = useState('STANDARD'); const [show3dBuildings, setShow3dBuildings] = useState(false); const jobs = useSelector((state) => state.jobsData.jobs); const [jobId, setJobId] = useState(null); const [priceRange, setPriceRange] = useState([0, 0]); const [showFilterBar, setShowFilterBar] = useState(false); useEffect(() => { setPriceRange([0, getMaxPrice()]); }, [listings]); const getMaxPrice = () => { return listings.reduce((max, item) => { const price = Number(item.price); return Number.isFinite(price) && price > max ? price : max; }, -Infinity); }; const filterListings = () => { const min = priceRange[0]; const max = priceRange[1] && priceRange[1] > 0 ? priceRange[1] : getMaxPrice(); return listings.filter((listing) => listing.price && listing.price >= min && listing.price <= max); }; useEffect(() => { if (map.current) return; map.current = new maplibregl.Map({ container: mapContainer.current, style: STYLES[style], center: [10.4515, 51.1657], // Center of Germany zoom: 4, maxBounds: GERMANY_BOUNDS, antialias: true, }); map.current.addControl( new maplibregl.NavigationControl({ showCompass: true, visualizePitch: true, visualizeRoll: true, }), 'top-right', ); map.current.addControl( new maplibregl.GeolocateControl({ positionOptions: { enableHighAccuracy: true, }, trackUserLocation: true, }), ); return () => { map.current.remove(); }; }, []); useEffect(() => { if (map.current) { map.current.setStyle(STYLES[style]); } }, [style]); useEffect(() => { if (show3dBuildings && style !== 'STANDARD') { setStyle('STANDARD'); } }, [show3dBuildings, style]); useEffect(() => { if (!map.current) return; map.current.setPitch(show3dBuildings ? 45 : 0); }, [show3dBuildings]); useEffect(() => { if (!map.current) return; const add3dLayer = () => { if (show3dBuildings) { if (!map.current.getSource('openfreemap')) { map.current.addSource('openfreemap', { type: 'vector', url: 'https://tiles.openfreemap.org/planet', }); } if (!map.current.getLayer('3d-buildings')) { const layers = map.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; } } map.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 (map.current.getLayer('3d-buildings')) { map.current.removeLayer('3d-buildings'); } } }; if (map.current.isStyleLoaded()) { add3dLayer(); } else { map.current.once('styledata', add3dLayer); } }, [show3dBuildings, style]); const setMapStyle = (value) => { setStyle(value); if (value === 'SATELLITE') { setShow3dBuildings(false); } }; const fetchListings = async () => { actions.listingsData.getListingsForMap({ jobId, }); }; useEffect(() => { fetchListings(); }, [jobId]); useEffect(() => { if (!map.current) return; markers.current.forEach((marker) => marker.remove()); markers.current = []; filterListings().forEach((listing) => { if ( listing.latitude != null && listing.longitude != null && listing.latitude !== -1 && listing.longitude !== -1 ) { const capitalizedProvider = listing.provider ? listing.provider.charAt(0).toUpperCase() + listing.provider.slice(1) : 'N/A'; const popup = new maplibregl.Popup({ offset: 25 }).setHTML( `