// =============================== // MAPPA GLOBALE — stile Google Photos Web // - Cluster numerici che si spezzano con lo zoom // - Click su cluster → zoom progressivo e, quando "pochi", strip in basso // - Click su marker singolo → MODAL immediato (niente bottom) // - Raggruppamento dinamico basato su raggio in pixel → metri // =============================== window.globalMap = null; window.globalMarkers = null; // qui sarà un MarkerClusterGroup document.addEventListener("DOMContentLoaded", () => { const openBtn = document.getElementById("openMapBtn"); if (!openBtn) { console.error("openMapBtn non trovato nel DOM"); return; } openBtn.addEventListener("click", openGlobalMap); // —— Parametri "alla GP" regolabili ———————————————————————————————— const RADIUS_PX = 50; // raggio "visivo" sullo schermo per raggruppare vicini const DISABLE_CLUSTER_AT_ZOOM = 18; // oltre questo zoom i cluster si spaccano const OPEN_STRIP_CHILDREN_MAX = 20; // se un cluster ha <= N marker → apri strip invece di continuare a zoomare // ———————————————————————————————————————————————————————————————— async function openGlobalMap() { const mapDiv = document.getElementById("globalMap"); const gallery = document.getElementById("gallery"); if (!mapDiv) { console.error("globalMap DIV non trovato"); return; } const isOpen = mapDiv.classList.contains("open"); // Chiudi mappa if (isOpen) { mapDiv.classList.remove("open"); gallery?.classList.remove("hidden"); window.closeBottomSheet?.(); return; } // Apri mappa e nascondi galleria mapDiv.classList.add("open"); gallery?.classList.add("hidden"); // 👇 NUOVO: se la mappa è già stata creata in passato, riallinea l’alias if (window.globalMap) { window.leafletMapInstance = window.globalMap; } // Attendi dimensioni reali (evita init a height:0) await new Promise(r => requestAnimationFrame(r)); let tries = 0; while (mapDiv.getBoundingClientRect().height < 50 && tries < 10) { await new Promise(r => setTimeout(r, 30)); tries++; } // Inizializza solo la prima volta if (window.globalMap === null) { console.log("Inizializzo mappa Leaflet + MarkerCluster…"); window.globalMap = L.map("globalMap", { zoomControl: true, attributionControl: true }).setView([42.5, 12.5], 6); L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { maxZoom: 19 }).addTo(window.globalMap); // ✅ CLUSTER come Google Photos window.globalMarkers = L.markerClusterGroup({ showCoverageOnHover: false, spiderfyOnMaxZoom: true, disableClusteringAtZoom: DISABLE_CLUSTER_AT_ZOOM }); // Listener "clusterclick": zooma oppure, quando pochi, apri strip window.globalMarkers.on("clusterclick", (a) => { const childMarkers = a.layer.getAllChildMarkers(); const count = childMarkers.length; if (count <= OPEN_STRIP_CHILDREN_MAX || window.globalMap.getZoom() >= DISABLE_CLUSTER_AT_ZOOM - 1) { // Converti i child markers in lista foto e apri la strip const photos = childMarkers .map(m => m.__photo) .filter(Boolean); if (photos.length > 1) { window.openBottomSheet?.(photos); } else if (photos.length === 1) { // Singola → modal diretto openPhotoModal(photos[0]); } } else { // Continua a zoomare sui bounds del cluster window.globalMap.fitBounds(a.layer.getBounds(), { padding: [60, 60], maxZoom: DISABLE_CLUSTER_AT_ZOOM, animate: true }); } }); window.globalMap.addLayer(window.globalMarkers); // Disegna i marker redrawPhotoMarkers(); } // Fix dimensioni dopo apertura setTimeout(() => window.globalMap?.invalidateSize?.(), 120); } // —— Icona thumbnail per i marker ——————————————————————————————— function createPhotoIcon(photo) { const thumb = (typeof toAbsoluteUrl === "function") ? toAbsoluteUrl(photo?.thub2 || photo?.thub1) : (photo?.thub2 || photo?.thub1); return L.icon({ iconUrl: thumb || "", iconSize: [56, 56], iconAnchor: [28, 28], className: "photo-marker" }); } // —— Modal diretto per singola foto ———————————————————————————— function openPhotoModal(photo) { const thumb = (typeof toAbsoluteUrl === "function") ? toAbsoluteUrl(photo?.thub2 || photo?.thub1 || photo?.path) : (photo?.thub2 || photo?.thub1 || photo?.path); const original = (typeof toAbsoluteUrl === "function") ? toAbsoluteUrl(photo?.path) : photo?.path; window.closeBottomSheet?.(); window.openModal?.(original, thumb, photo); } // —— Raggio in metri a partire da N pixel all’zoom attuale —————————— function radiusMetersAtZoom(latlng, px) { if (!window.globalMap) return 0; const p = window.globalMap.latLngToContainerPoint(latlng); const p2 = L.point(p.x + px, p.y); const ll2 = window.globalMap.containerPointToLatLng(p2); return window.globalMap.distance(latlng, ll2); } // —— Distanza (metri) ———————————————————————————————————————— function distanceMeters(lat1, lng1, lat2, lng2) { const toRad = d => d * Math.PI / 180; const R = 6371000; const φ1 = toRad(lat1), φ2 = toRad(lat2); const dφ = toRad(lat2 - lat1); const dλ = toRad(lng2 - lng1); const a = Math.sin(dφ/2) ** 2 + Math.cos(φ1) * Math.cos(φ2) * Math.sin(dλ/2) ** 2; return 2 * R * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); } // —— Raggruppa foto entro raggio (metri) —————————————————————— function buildGroupByRadius(lat, lng, data, radiusM) { return data.filter(p => { const plat = +p?.gps?.lat; const plng = +p?.gps?.lng; if (!plat || !plng) return false; return distanceMeters(lat, lng, plat, plng) <= radiusM; }); } // —— Disegno/refresh marker ———————————————————————————————— function redrawPhotoMarkers() { if (!window.globalMarkers || !window.globalMap) return; window.globalMarkers.clearLayers(); const data = Array.isArray(window.photosData) ? window.photosData : []; data.forEach(photo => { const lat = +photo?.gps?.lat; const lng = +photo?.gps?.lng; if (!lat || !lng) return; const marker = L.marker([lat, lng], { icon: createPhotoIcon(photo), title: photo?.name || "" }); // Alleghiamo la foto al marker per recuperarla in eventi cluster marker.__photo = photo; // Click su marker: calcola raggio dinamico e apri strip o modal marker.on("click", () => { const here = L.latLng(lat, lng); const radiusM = radiusMetersAtZoom(here, RADIUS_PX); const dataAll = Array.isArray(window.photosData) ? window.photosData : []; const group = buildGroupByRadius(lat, lng, dataAll, radiusM); if (group.length > 1) { window.openBottomSheet?.(group); } else if (group.length === 1) { openPhotoModal(group[0]); } else { openPhotoModal(photo); } }); window.globalMarkers.addLayer(marker); }); } // Se la gallery si aggiorna (loadPhotos), ridisegna marker const originalRefresh = window.refreshGallery; window.refreshGallery = function wrappedRefreshGallery(...args) { try { originalRefresh?.apply(this, args); } catch (_) {} if (window.globalMap && window.globalMarkers) { redrawPhotoMarkers(); } }; });