server_photo_s85js/public/js/map/globalMap.js
2026-08-11 08:45:04 +02:00

358 lines
9 KiB
JavaScript

// 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: `<div class="activity-cluster">` + `${count}` + `</div>`,
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",
);
}
});