photo_server_json_flutter_c.../public/js/mapGlobal.js
2026-02-26 11:48:04 +01:00

224 lines
No EOL
8.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ===============================
// 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 lalias
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 allzoom 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 = toRad(lat2 - lat1);
const = toRad(lng2 - lng1);
const a = Math.sin(/2) ** 2 +
Math.cos(φ1) * Math.cos(φ2) *
Math.sin(/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();
}
};
});