galleria_photo_face_rknn_op.../public/js/map/terrain3d.js
2026-10-03 12:16:58 +02:00

960 lines
18 KiB
JavaScript

// public/js/map/terrain3d.js
// Switch Leaflet <-> Mapbox GL Terrain 3D
(() => {
"use strict";
let mapboxMap = null;
let enabled = false;
let savedLeafletView = null;
let savedLeafletMap = null;
let terrainMarkerRefreshTimer = null;
let terrainMarkerRefreshToken = 0;
let terrainEventsMap = null;
let destroying = false;
function getToken() {
return window.AppConfig?.mapboxToken || "";
}
function getContainer() {
return document.getElementById("terrainMap");
}
function getLeafletMap() {
if (window.MapEngine?.type === "mapbox") {
return savedLeafletMap;
}
return (
window.GlobalMap?.getMap?.() ||
window.MapEngine?.map ||
window.leafletMap ||
null
);
}
function createMapbox3D() {
const container = getContainer();
if (!container) {
console.error("[Terrain3D] #terrainMap non trovato");
return;
}
const token = getToken();
if (!token) {
console.error("[Terrain3D] Token Mapbox mancante");
return;
}
if (!window.mapboxgl) {
console.error("[Terrain3D] Mapbox GL JS non caricato");
return;
}
// Impedisce la creazione
// di una seconda istanza.
if (mapboxMap) {
console.warn("[Terrain3D] Mapbox già inizializzato");
return;
}
// =================================
// MOSTRA IL CONTENITORE MAPBOX
// =================================
container.classList.add("open");
container.style.display = "block";
container.style.visibility = "visible";
container.style.pointerEvents = "auto";
// =================================
// RECUPERA LA MAPPA LEAFLET
// =================================
if (!savedLeafletMap) {
savedLeafletMap = getLeafletMap();
}
const leafletMap = savedLeafletMap;
console.log("[Terrain3D] Leaflet trovato:", leafletMap);
// =================================
// VISTA INIZIALE
// =================================
let center = [12.4964, 41.9028];
let zoom = 6;
if (leafletMap) {
const leafletCenter = leafletMap.getCenter();
center = [leafletCenter.lng, leafletCenter.lat];
zoom = leafletMap.getZoom();
savedLeafletView = {
lat: leafletCenter.lat,
lng: leafletCenter.lng,
zoom: leafletMap.getZoom(),
};
console.log("[Terrain3D] Vista Leaflet salvata", savedLeafletView);
}
// =================================
// NASCONDE LEAFLET
// =================================
if (leafletMap) {
const leafletMapContainer = leafletMap.getContainer();
leafletMapContainer.style.visibility = "hidden";
leafletMapContainer.style.pointerEvents = "none";
console.log("[Terrain3D] Leaflet nascosto");
}
// =================================
// CREA MAPBOX
// =================================
mapboxgl.accessToken = token;
mapboxMap = new mapboxgl.Map({
container,
style: "mapbox://styles/mapbox/outdoors-v12",
center,
zoom,
pitch: 60,
bearing: -20,
antialias: true,
});
// =================================
// CARICAMENTO MAPBOX
// =================================
mapboxMap.on("load", () => {
if (!mapboxMap) {
return;
}
// -------------------------------
// SOURCE DEL TERRENO
// -------------------------------
if (!mapboxMap.getSource("mapbox-dem")) {
mapboxMap.addSource("mapbox-dem", {
type: "raster-dem",
url: "mapbox://mapbox.mapbox-terrain-dem-v1",
tileSize: 512,
maxzoom: 14,
});
}
// -------------------------------
// TERRENO 3D
// -------------------------------
mapboxMap.setTerrain({
source: "mapbox-dem",
exaggeration: 1.5,
});
// -------------------------------
// CONTROLLI MAPBOX
// -------------------------------
mapboxMap.addControl(
new mapboxgl.NavigationControl(),
"top-right",
);
// -------------------------------
// COLLEGA MAPENGINE
// -------------------------------
if (window.MapEngine) {
window.MapEngine.setMapbox(mapboxMap);
}
// -------------------------------
// COLLEGA MARKER CONTROLLER
// -------------------------------
window.MapboxMarkers?.setMap?.(mapboxMap);
window.MapBaseLayers?.mountTerrainStyleControl?.(container);
// -------------------------------
// ATTIVA MAPBOX 3D
// -------------------------------
/*
* enabled deve diventare true prima
* di registrare e utilizzare il refresh.
*/
enabled = true;
const activitiesMode =
window.state?.mode ===
"activities";
if (activitiesMode) {
window.showActivityRoutesToggle?.();
} else {
window.hideActivityRoutesToggle?.(
true,
);
}
// -------------------------------
// COLLEGA MOVEEND
// -------------------------------
/*
* Registra il listener nominato
* handleTerrainMoveEnd.
*
* Non devono esserci altri listener
* anonimi moveend in questa funzione.
*/
setupTerrainMapEvents();
// -------------------------------
// PRIMO REFRESH
// -------------------------------
/*
* Aspetta il completamento del primo
* rendering prima di caricare marker,
* cluster e route.
*/
mapboxMap.once("idle", () => {
scheduleTerrainMarkerRefresh("initial-idle");
});
console.log("[Terrain3D] Mapbox 3D attivo");
// -------------------------------
// AGGIORNA LE DIMENSIONI
// -------------------------------
requestAnimationFrame(() => {
mapboxMap?.resize?.();
});
});
// =================================
// ERRORI MAPBOX
// =================================
mapboxMap.on("error", (event) => {
console.error("[Terrain3D] errore Mapbox", event?.error || event);
});
}
function destroyMapbox3D() {
if (!mapboxMap) {
return;
}
destroying = true;
console.log("[Terrain3D] Chiusura MapboxGL");
const mapboxCenter = mapboxMap.getCenter?.();
const mapboxZoom = mapboxMap.getZoom?.();
if (
mapboxCenter &&
Number.isFinite(Number(mapboxCenter.lat)) &&
Number.isFinite(Number(mapboxCenter.lng)) &&
Number.isFinite(Number(mapboxZoom))
) {
savedLeafletView = {
lat: Number(mapboxCenter.lat),
lng: Number(mapboxCenter.lng),
zoom: Number(mapboxZoom),
};
}
removeTerrainMapEvents();
/*
* Pulisce marker e cluster mentre
* la vecchia mappa è ancora valida.
*/
window.MapboxMarkers?.clearMarkers?.();
window.MapBaseLayers?.removeTerrainStyleControl?.();
/*
* Soltanto dopo la pulizia viene
* distrutta l'istanza Mapbox.
*/
mapboxMap.remove();
mapboxMap = null;
enabled = false;
destroying = false;
const terrainContainer = document.getElementById("terrainMap");
if (terrainContainer) {
terrainContainer.classList.remove("open");
terrainContainer.style.display = "none";
}
const globalMapContainer = document.getElementById("globalMap");
if (globalMapContainer) {
globalMapContainer.style.visibility = "visible";
globalMapContainer.classList.add("open");
}
const leafletMap = savedLeafletMap;
if (leafletMap) {
const leafletMapContainer = leafletMap.getContainer();
leafletMapContainer.style.display = "block";
leafletMapContainer.style.visibility = "visible";
leafletMapContainer.style.pointerEvents = "auto";
leafletMap.invalidateSize();
if (savedLeafletView) {
leafletMap.setView(
[savedLeafletView.lat, savedLeafletView.lng],
savedLeafletView.zoom,
);
}
console.log("[Terrain3D] Leaflet ripristinato");
}
if (
savedLeafletMap &&
window.MapEngine
) {
window.MapEngine.type =
"leaflet";
window.MapEngine.map =
savedLeafletMap;
window.MapEngine.markerLayer =
window.globalMarkers;
}
requestAnimationFrame(
async () => {
if (!savedLeafletMap) {
return;
}
savedLeafletMap.invalidateSize?.();
if (
window.globalMarkers &&
!savedLeafletMap.hasLayer(
window.globalMarkers,
)
) {
savedLeafletMap.addLayer(
window.globalMarkers,
);
}
window.globalMarkers
?.clearLayers?.();
if (
window.state?.mode ===
"activities"
) {
await window
.renderActivityMapLayer?.();
} else {
await window
.renderPhotoMapLayer?.();
}
savedLeafletMap.invalidateSize?.();
console.log(
"[Terrain3D] Leaflet ridisegnato",
{
mode:
window.state?.mode,
markerLayers:
window.globalMarkers
?.getLayers?.()
?.length ?? null,
clusterOnMap:
!!(
window.globalMarkers &&
savedLeafletMap.hasLayer(
window.globalMarkers,
)
),
},
);
},
);
console.log(
"[Terrain3D] ritorno Leaflet",
);
}
function waitForMapboxReady(timeoutMs = 2500) {
return new Promise((resolve) => {
if (!mapboxMap) {
resolve(false);
return;
}
if (mapboxMap.isStyleLoaded?.() && !mapboxMap.isMoving?.()) {
resolve(true);
return;
}
let completed = false;
const finish = () => {
if (completed) {
return;
}
completed = true;
clearTimeout(timeout);
mapboxMap?.off?.("idle", handleReady);
resolve(!!mapboxMap && !!mapboxMap.isStyleLoaded?.());
};
const handleReady = () => {
if (!mapboxMap) {
finish();
return;
}
if (mapboxMap.isStyleLoaded?.() && !mapboxMap.isMoving?.()) {
finish();
}
};
const timeout = setTimeout(finish, timeoutMs);
mapboxMap.on("idle", handleReady);
});
}
function scheduleTerrainMarkerRefresh(reason = "moveend") {
if (!mapboxMap) {
return;
}
if (!enabled) {
return;
}
if (destroying) {
return;
}
if (terrainMarkerRefreshTimer) {
clearTimeout(terrainMarkerRefreshTimer);
}
const token = ++terrainMarkerRefreshToken;
terrainMarkerRefreshTimer = setTimeout(async () => {
terrainMarkerRefreshTimer = null;
if (!mapboxMap || !enabled) {
return;
}
if (token !== terrainMarkerRefreshToken) {
return;
}
/*
* Dopo moveend Mapbox può ancora
* avere isStyleLoaded() false.
* Aspettiamo il successivo idle.
*/
const ready = await waitForMapboxReady();
if (!ready || !mapboxMap || !enabled) {
console.warn("[Terrain3D] refresh annullato, Mapbox non pronto", {
reason,
token,
});
return;
}
if (token !== terrainMarkerRefreshToken) {
return;
}
if (typeof window.refreshTerrainMarkers !== "function") {
return;
}
console.log("[Terrain3D] aggiorno marker", {
reason,
token,
styleLoaded: mapboxMap.isStyleLoaded?.(),
moving: mapboxMap.isMoving?.(),
zoom: mapboxMap.getZoom?.(),
bounds: mapboxMap.getBounds?.()?.toArray?.(),
});
await window.refreshTerrainMarkers();
}, 180);
}
function handleTerrainMoveEnd() {
scheduleTerrainMarkerRefresh("moveend");
}
function setupTerrainMapEvents() {
if (!mapboxMap) {
return;
}
if (terrainEventsMap === mapboxMap) {
return;
}
removeTerrainMapEvents();
terrainEventsMap = mapboxMap;
terrainEventsMap.on("moveend", handleTerrainMoveEnd);
console.log("[Terrain3D] listener moveend collegato");
}
function removeTerrainMapEvents() {
if (terrainMarkerRefreshTimer) {
clearTimeout(terrainMarkerRefreshTimer);
terrainMarkerRefreshTimer = null;
}
terrainMarkerRefreshToken++;
if (!terrainEventsMap) {
return;
}
terrainEventsMap.off("moveend", handleTerrainMoveEnd);
terrainEventsMap = null;
console.log("[Terrain3D] listener moveend rimosso");
}
async function enable() {
// Mapbox è già attivo.
if (enabled && mapboxMap) {
mapboxMap.resize?.();
return true;
}
// Rimuove un'istanza rimasta
// parzialmente inizializzata.
if (mapboxMap && !enabled) {
console.warn("[Terrain3D] rimuovo istanza Mapbox incompleta");
try {
removeTerrainMapEvents();
window.MapBaseLayers?.removeTerrainStyleControl?.();
window.MapboxMarkers?.clearMarkers?.();
mapboxMap.remove();
} catch (error) {
console.warn("[Terrain3D] errore pulizia istanza incompleta", error);
}
mapboxMap = null;
}
// Avvia una nuova istanza Mapbox.
createMapbox3D();
const success = await new Promise((resolve) => {
let completed = false;
let check = null;
let timeout = null;
const finish = (result) => {
if (completed) {
return;
}
completed = true;
if (check !== null) {
clearInterval(check);
check = null;
}
if (timeout !== null) {
clearTimeout(timeout);
timeout = null;
}
resolve(result);
};
check = setInterval(() => {
if (!mapboxMap) {
return;
}
if (!enabled) {
return;
}
if (!mapboxMap.isStyleLoaded?.()) {
return;
}
mapboxMap.resize?.();
finish(true);
}, 50);
timeout = setTimeout(() => {
finish(false);
}, 10000);
});
window.updateTerrainButton?.(enabled);
if (!success) {
console.warn("[Terrain3D] attivazione non completata entro 10 secondi");
// Pulisce l'istanza che non ha
// completato correttamente il load.
if (mapboxMap) {
try {
removeTerrainMapEvents();
window.MapBaseLayers?.removeTerrainStyleControl?.();
window.MapboxMarkers?.clearMarkers?.();
mapboxMap.remove();
} catch (error) {
console.warn("[Terrain3D] errore pulizia dopo timeout", error);
}
}
mapboxMap = null;
enabled = false;
window.updateTerrainButton?.(false);
return false;
}
console.log("[Terrain3D] attivazione completata");
return true;
}
function disable() {
if (!mapboxMap) {
enabled = false;
window.updateTerrainButton?.(false);
return;
}
destroyMapbox3D();
enabled = false;
window.updateTerrainButton?.(false);
}
async function toggle() {
if (enabled) {
disable();
} else {
await enable();
}
}
function isEnabled() {
return enabled;
}
function isActivityItem(item) {
if (!item) {
return false;
}
const format = String(
item.format ||
item.file_extension ||
item.mime_type ||
item.file_name ||
item.name ||
"",
).toLowerCase();
return (
format.includes("fit") ||
format.includes("gpx") ||
format.includes("tcx") ||
item.distance_meters != null ||
item.duration_millis != null ||
item.track_preview != null
);
}
window.refreshTerrainMarkers = async function () {
if (!mapboxMap) {
return;
}
if (!enabled) {
return;
}
if (destroying) {
return;
}
const items =
typeof window.getLocalItems === "function" ? window.getLocalItems() : [];
const isActivitiesMode =
window.state?.mode ===
"activities";
let photos = [];
let activities = [];
let routes = [];
if (isActivitiesMode) {
window.showActivityRoutesToggle?.();
if (
window.MapboxPhotoSpiderfy
?.isOpen?.()
) {
window.MapboxPhotoSpiderfy
?.clear?.();
}
window.MapboxPhotoMarkers
?.clear?.();
window.MapboxPhotoClusters
?.clear?.();
window.MapboxMarkers
?.setPhotos?.([]);
activities =
typeof window
.getActivityMapGeoJSON ===
"function"
? window
.getActivityMapGeoJSON()
: [];
routes =
typeof window
.getActivityRoutesGeoJSON ===
"function"
? window
.getActivityRoutesGeoJSON()
: [];
if (
activities.length === 0 &&
typeof window
.renderActivityMapLayer ===
"function"
) {
console.log(
"[Terrain3D] caricamento iniziale attività",
{
localItems:
items.length,
},
);
await window
.renderActivityMapLayer();
activities =
typeof window
.getActivityMapGeoJSON ===
"function"
? window
.getActivityMapGeoJSON()
: [];
routes =
typeof window
.getActivityRoutesGeoJSON ===
"function"
? window
.getActivityRoutesGeoJSON()
: [];
}
window.MapboxMarkers
?.setActivities?.(
activities,
);
const showRoutes =
window
.areActivityRoutesVisible?.() ??
false;
window.MapboxMarkers
?.setRoutes?.(
showRoutes
? routes
: [],
);
} else {
window.hideActivityRoutesToggle?.(
true,
);
if (
window.MapboxActivitySpiderfy
?.isOpen?.()
) {
window.MapboxActivitySpiderfy
?.clear?.();
}
window.MapboxActivityMarkers
?.clear?.();
window.MapboxActivityClusters
?.clear?.();
window.MapboxMarkers
?.setActivities?.([]);
window.MapboxMarkers
?.setRoutes?.([]);
photos =
typeof window
.getPhotoMapGeoJSON ===
"function"
? window
.getPhotoMapGeoJSON()
: [];
window.MapboxMarkers
?.setPhotos?.(
photos,
);
}
console.log("[Terrain3D] marker aggiornati", {
mode: isActivitiesMode ? "activities" : "photos",
localItems: items.length,
photos: photos.length,
activities: activities.length,
routesAvailable: routes.length,
routesVisible: isActivitiesMode
? (window.areActivityRoutesVisible?.() ?? false)
: false,
styleLoaded: !!mapboxMap?.isStyleLoaded?.(),
moving: !!mapboxMap?.isMoving?.(),
});
};
window.MapTerrain3D = Object.freeze({
enable,
disable,
toggle,
enabled: isEnabled,
});
})();