// public/js/mapBaseLayers.js // Basemap 2D Leaflet e selettore livelli. (() => { "use strict"; const STORAGE_KEY = "selectedMapStyle"; const DEFAULT_STYLE = "osm"; let currentLayer = null; let layers = {}; let layerControl = null; let initializedMap = null; function getMapboxToken() { return window.AppConfig?.mapboxToken || ""; } function createOsmLayer() { return L.tileLayer( "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { subdomains: "abc", maxZoom: 19, attribution: "© OpenStreetMap contributors" } ); } function createOpenTopoLayer() { return L.tileLayer( "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png", { subdomains: "abc", maxZoom: 17, attribution: "Map data: © OpenStreetMap contributors, SRTM | " + "Map style: © OpenTopoMap" } ); } function createMapboxLayer(styleId, token) { if (!token) { return null; } const encodedToken = encodeURIComponent(token); const tileUrl = `https://api.mapbox.com/styles/v1/mapbox/${styleId}` + `/tiles/512/{z}/{x}/{y}@2x` + `?access_token=${encodedToken}`; return L.tileLayer(tileUrl, { tileSize: 512, zoomOffset: -1, maxZoom: 22, attribution: "© Mapbox © OpenStreetMap contributors" }); } function createLayers() { const token = getMapboxToken(); const result = { osm: { label: "OpenStreetMap", layer: createOsmLayer() }, opentopo: { label: "Topografica", layer: createOpenTopoLayer() } }; if (!token) { console.warn( "[mapBaseLayers] Token Mapbox non configurato. " + "Saranno disponibili solamente OpenStreetMap " + "e OpenTopoMap." ); return result; } const mapboxDefinitions = { mapboxStreets: { label: "Mapbox Streets", styleId: "streets-v12" }, mapboxOutdoors: { label: "Mapbox Outdoors", styleId: "outdoors-v12" }, mapboxSatellite: { label: "Mapbox Satellite", styleId: "satellite-v9" }, mapboxSatelliteStreets: { label: "Mapbox Satellite + strade", styleId: "satellite-streets-v12" } }; for ( const [id, definition] of Object.entries(mapboxDefinitions) ) { const layer = createMapboxLayer( definition.styleId, token ); if (!layer) { continue; } result[id] = { label: definition.label, layer }; } return result; } function getSavedStyle() { const savedStyle = localStorage.getItem(STORAGE_KEY); if (savedStyle && layers[savedStyle]) { return savedStyle; } return DEFAULT_STYLE; } function findStyleIdByLayer(selectedLayer) { for ( const [styleId, definition] of Object.entries(layers) ) { if (definition.layer === selectedLayer) { return styleId; } } return null; } function dispatchStyleChange( styleId, label ) { window.dispatchEvent( new CustomEvent("mapstylechange", { detail: { engine: "leaflet", styleId, label } }) ); } function setStyle( map, styleId, save = true ) { if (!map) { console.error( "[mapBaseLayers] Istanza Leaflet mancante" ); return false; } const selectedStyle = layers[styleId] ? styleId : DEFAULT_STYLE; const definition = layers[selectedStyle]; if (!definition?.layer) { console.error( "[mapBaseLayers] Basemap non trovata:", styleId ); return false; } if ( currentLayer && map.hasLayer(currentLayer) ) { map.removeLayer(currentLayer); } currentLayer = definition.layer; currentLayer.addTo(map); if (save) { localStorage.setItem( STORAGE_KEY, selectedStyle ); } dispatchStyleChange( selectedStyle, definition.label ); return true; } function createControl(map) { if (layerControl) { map.removeControl(layerControl); layerControl = null; } const baseLayers = {}; for ( const definition of Object.values(layers) ) { baseLayers[definition.label] = definition.layer; } layerControl = L.control.layers( baseLayers, null, { position: "topright", collapsed: true } ); layerControl.addTo(map); map.on( "baselayerchange", handleBaseLayerChange ); } function handleBaseLayerChange(event) { const styleId = findStyleIdByLayer(event.layer); if (!styleId) { return; } currentLayer = event.layer; localStorage.setItem( STORAGE_KEY, styleId ); dispatchStyleChange( styleId, event.name ); } async function waitForConfiguration() { if (window.AppConfigPromise) { try { await window.AppConfigPromise; } catch (error) { console.error( "[mapBaseLayers] Errore configurazione:", error ); } } return window.AppConfig || {}; } async function initialize(map) { if (!map) { console.error( "[mapBaseLayers] Istanza mappa non disponibile" ); return false; } if (typeof window.L === "undefined") { console.error( "[mapBaseLayers] Leaflet non disponibile" ); return false; } await waitForConfiguration(); /* * Evita una doppia inizializzazione sulla stessa * istanza Leaflet. */ if ( initializedMap === map && Object.keys(layers).length > 0 ) { return true; } /* * Se il modulo viene inizializzato su una nuova mappa, * rimuove il listener dalla precedente. */ if ( initializedMap && initializedMap !== map ) { initializedMap.off( "baselayerchange", handleBaseLayerChange ); } initializedMap = map; layers = createLayers(); const initialStyle = getSavedStyle(); const styleApplied = setStyle( map, initialStyle, false ); createControl(map); console.log( "[mapBaseLayers] Basemap inizializzate", { currentStyle: getCurrentStyle(), availableStyles: getAvailableStyles().map( style => style.id ), mapboxConfigured: Boolean(getMapboxToken()) } ); return styleApplied; } function getCurrentStyle() { return ( findStyleIdByLayer(currentLayer) || DEFAULT_STYLE ); } function getAvailableStyles() { return Object.entries(layers).map( ([id, definition]) => ({ id, label: definition.label }) ); } window.MapBaseLayers = Object.freeze({ initialize, setStyle, getCurrentStyle, getAvailableStyles }); })();