// public/js/mapGlobal.js // =============================== // MAPPA GLOBALE // Controller generico Leaflet + MarkerCluster // =============================== window.globalMap = null; window.globalMarkers = null; document.addEventListener("DOMContentLoaded", () => { "use strict"; const openBtn = document.getElementById("openMapBtn"); if (!openBtn) { console.error("[mapGlobal] Pulsante #openMapBtn non trovato"); return; } const DISABLE_CLUSTER_AT_ZOOM = 18; const OPEN_STRIP_CHILDREN_MAX = 20; let mapInitializationPromise = null; openBtn.addEventListener("click", openGlobalMap); // =============================== // APRI / CHIUDI MAPPA // =============================== async function openGlobalMap() { const mapDiv = document.getElementById("globalMap"); const gallery = document.getElementById("gallery"); if (!mapDiv) { console.error("[mapGlobal] Contenitore #globalMap non trovato"); return; } const isOpen = mapDiv.classList.contains("open"); // Chiude la mappa. if (isOpen) { mapDiv.classList.remove("open"); gallery?.classList.remove("hidden"); window.closeBottomSheet?.(); window.clearActivityMapLayers?.(); return; } // Apre la mappa. mapDiv.classList.add("open"); gallery?.classList.add("hidden"); // Attende che il browser renda visibile il contenitore. await new Promise((resolve) => { requestAnimationFrame(resolve); }); try { if (!window.globalMap) { /* * Impedisce due inizializzazioni contemporanee * in caso di clic ripetuti. */ if (!mapInitializationPromise) { mapInitializationPromise = initGlobalMap(); } await mapInitializationPromise; } else { window.globalMap.invalidateSize(); } await window.redrawGlobalMap?.(); } catch (error) { console.error("[mapGlobal] Errore apertura mappa:", error); mapInitializationPromise = null; } } window.openGlobalMap = openGlobalMap; // =============================== // INIZIALIZZA MAPPA // =============================== async function initGlobalMap() { console.log("[mapGlobal] Inizializzo Leaflet + MarkerCluster"); if (typeof window.L === "undefined") { throw new Error("Leaflet non è stato caricato"); } if (typeof window.L.markerClusterGroup !== "function") { throw new Error("Leaflet MarkerCluster non è stato caricato"); } window.globalMap = L.map("globalMap", { zoomControl: true, attributionControl: true, }).setView([42.5, 12.5], 6); window.MapEngine?.init(window.globalMap); // =============================== // BASEMAP // =============================== if ( window.MapBaseLayers && typeof window.MapBaseLayers.initialize === "function" ) { /* * Attende che config.js abbia recuperato * il token Mapbox da /config. */ await window.MapBaseLayers.initialize(window.globalMap); } else { console.error( "[mapGlobal] MapBaseLayers non disponibile. " + "Uso OpenStreetMap come fallback.", ); L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { subdomains: "abc", maxZoom: 19, attribution: "© OpenStreetMap contributors", }).addTo(window.globalMap); } // =============================== // MARKER CLUSTER // =============================== window.globalMarkers = L.markerClusterGroup({ showCoverageOnHover: false, spiderfyOnMaxZoom: true, disableClusteringAtZoom: DISABLE_CLUSTER_AT_ZOOM, iconCreateFunction: clusterIconRenderer, }); window.MapEngine?.init?.(window.globalMap, window.globalMarkers); window.globalMarkers.on("clusterclick", onClusterClick); window.globalMap.addLayer(window.globalMarkers); window.MapEngine?.init(window.globalMap, window.globalMarkers); // =============================== // AGGIORNAMENTO BOTTOM SHEET // =============================== window.globalMap.on("moveend", refreshOpenBottomSheet); window.globalMap.on("moveend", () => { if (window.state?.mode === "photos") { window.renderPhotoMapLayer?.(); } }); /* * Leaflet viene creato mentre il contenitore * è appena diventato visibile. Un secondo * invalidateSize evita layout incompleti. */ await new Promise((resolve) => { requestAnimationFrame(resolve); }); window.globalMap.invalidateSize(); console.log("[mapGlobal] Mappa inizializzata", { currentStyle: window.MapBaseLayers?.getCurrentStyle?.() || "fallback-osm", styles: window.MapBaseLayers?.getAvailableStyles?.() || [], }); return window.globalMap; } // =============================== // AGGIORNA BOTTOM SHEET DOPO MOVE // =============================== function refreshOpenBottomSheet() { const bottomSheet = document.getElementById("bottomSheet"); if (!bottomSheet?.classList.contains("open")) { return; } if (!window.globalMap || !window.globalMarkers) { return; } const bounds = window.globalMap.getBounds(); const items = []; window.globalMarkers.eachLayer((marker) => { if (!marker.__item) { return; } if (typeof marker.getLatLng !== "function") { return; } if (bounds.contains(marker.getLatLng())) { items.push(marker.__item); } }); if (items.length > 1) { window.refreshBottomSheet?.(items); } } // =============================== // CLUSTER ICON GENERICO // =============================== function clusterIconRenderer(cluster) { if ( window.state?.mode === "photos" && typeof window.createPhotoClusterIcon === "function" ) { return window.createPhotoClusterIcon(cluster); } if ( window.state?.mode === "activities" && typeof window.createActivityClusterIcon === "function" ) { return window.createActivityClusterIcon(cluster); } const count = cluster.getChildCount(); return L.divIcon({ html: `
` + `${count}` + `
`, className: "marker-cluster-wrapper", iconSize: L.point(48, 48), }); } // =============================== // CLICK SU CLUSTER // =============================== function onClusterClick(event) { if (!event?.layer || !window.globalMap) { return; } const markers = event.layer.getAllChildMarkers(); const items = markers.map((marker) => marker.__item).filter(Boolean); const count = markers.length; if ( count <= OPEN_STRIP_CHILDREN_MAX || window.globalMap.getZoom() >= DISABLE_CLUSTER_AT_ZOOM - 1 ) { if (items.length > 1) { window.currentOpenCluster = event.layer; window.openBottomSheet?.(items); } else if (items.length === 1) { window.openMapItem?.(items[0]); } return; } window.globalMap.fitBounds(event.layer.getBounds(), { padding: [60, 60], maxZoom: DISABLE_CLUSTER_AT_ZOOM, animate: true, }); } // =============================== // APRI ELEMENTO DALLA MAPPA // =============================== window.openMapItem = function (item) { if (!item) { return; } window.closeBottomSheet?.(); if (window.state?.mode === "photos") { window.openPhotoMapItem?.(item); } else { window.openActivityModal?.(item); } }; // =============================== // RIDISEGNA MARKER E ATTIVITÀ // =============================== window.redrawGlobalMap = async function () { if (!window.globalMap || !window.globalMarkers) { return; } window.globalMarkers.clearLayers(); if (typeof window.clearActivityMapLayers === "function") { window.clearActivityMapLayers(); } if (window.state?.mode === "photos") { window.hideActivityRoutesToggle?.(); if (typeof window.renderPhotoMapLayer === "function") { await window.renderPhotoMapLayer(); } } else { if (typeof window.renderActivityMapLayer === "function") { await window.renderActivityMapLayer(); } } }; // =============================== // AGGANCIA REFRESH GALLERY // =============================== const originalRefresh = window.refreshGallery; if (typeof originalRefresh === "function") { window.refreshGallery = function (...args) { const result = originalRefresh.apply(this, args); // I dati sono cambiati: invalida l'indice spaziale window.MapSpatialIndex?.clear?.(); const mapDiv = document.getElementById("globalMap"); if (mapDiv?.classList.contains("open")) { Promise.resolve(window.redrawGlobalMap?.()).catch((error) => { console.error("[mapGlobal] Errore redraw dopo refresh:", error); }); } return result; }; } else { console.warn( "[mapGlobal] refreshGallery non disponibile " + "durante l'inizializzazione", ); } });