// public/js/map/mapboxMarkers.js // MAPBOX MARKERS CONTROLLER (() => { "use strict"; let mapboxMap = null; let eventsMap = null; let idleRefreshScheduled = false; const state = { photos: [], activities: [], routes: [], }; function setMap(map) { if (!map) { console.error("[MapboxMarkers] mappa non valida"); return; } if (mapboxMap === map) { connectModules(); if (mapboxMap.isStyleLoaded?.()) { initializeModules(); } return; } /* * Scollega soltanto gli eventi del * controller dalla vecchia mappa. * * Non richiama clearHtmlMarkers() qui, * perché i singoli moduli potrebbero * puntare ancora a una mappa Mapbox * già distrutta. */ detachMapEvents(); mapboxMap = map; console.log("[MapboxMarkers] mappa collegata"); connectModules(); setupMapEvents(); if (mapboxMap.isStyleLoaded?.()) { initializeModules(); } else { mapboxMap.once("load", handleInitialLoad); } } function connectModules() { if (!mapboxMap) { return; } window.MapboxPhotoClusters?.setMap?.(mapboxMap); window.MapboxPhotoMarkers?.setMap?.(mapboxMap); window.MapboxPhotoSpiderfy?.setMap?.(mapboxMap); window.MapboxActivityClusters?.setMap?.(mapboxMap); window.MapboxActivityMarkers?.setMap?.(mapboxMap); window.MapboxActivitySpiderfy?.setMap?.(mapboxMap); } function handleInitialLoad() { if (!mapboxMap) { return; } initializeModules(); } function initializeModules() { if (!mapboxMap?.isStyleLoaded?.()) { return; } const mode = window.state?.mode; if (mode === "photos") { window.MapboxActivityMarkers ?.clear?.(); window.MapboxActivityClusters ?.clear?.(); window.PhotoSource?.update?.( mapboxMap, state.photos, ); window.MapboxPhotoClusters ?.createLayer?.(); return; } if (mode === "activities") { window.MapboxPhotoMarkers ?.clear?.(); window.MapboxPhotoClusters ?.clear?.(); window.ActivitySource?.update?.( mapboxMap, state.activities, ); window.MapboxActivityClusters ?.createLayer?.(); updateRoutesSourceAndLayer(); } } function setupMapEvents() { if (!mapboxMap) { return; } if (eventsMap === mapboxMap) { return; } detachMapEvents(); eventsMap = mapboxMap; eventsMap.on("style.load", handleStyleLoad); eventsMap.on("sourcedata", handleSourceData); eventsMap.on("idle", handleIdle); } function detachMapEvents() { if (!eventsMap) { idleRefreshScheduled = false; return; } eventsMap.off("style.load", handleStyleLoad); eventsMap.off("sourcedata", handleSourceData); eventsMap.off("idle", handleIdle); eventsMap.off("load", handleInitialLoad); eventsMap = null; idleRefreshScheduled = false; } function handleStyleLoad() { if (!mapboxMap?.isStyleLoaded?.()) { return; } initializeModules(); } function scheduleRenderedRefresh() { if (!mapboxMap?.isStyleLoaded?.()) { return; } if (idleRefreshScheduled) { return; } idleRefreshScheduled = true; requestAnimationFrame(() => { idleRefreshScheduled = false; if (!mapboxMap?.isStyleLoaded?.()) { return; } refreshRenderedMarkers(); }); } function ensureMapInfrastructure() { if (!mapboxMap?.isStyleLoaded?.()) { return; } const mode = window.state?.mode; if (mode === "photos") { if ( state.photos.length > 0 && !mapboxMap.getSource( "photos", ) ) { console.log( "[MapboxMarkers] creazione source photos", ); window.PhotoSource?.create?.( mapboxMap, state.photos, ); } if ( mapboxMap.getSource( "photos", ) && !mapboxMap.getLayer( "photos-clusters", ) ) { console.log( "[MapboxMarkers] creazione layer photos", ); window.MapboxPhotoClusters ?.createLayer?.(); } return; } if (mode === "activities") { if (mode === "activities") { window.ActivitySource?.update?.( mapboxMap, state.activities, ); if ( mapboxMap.getSource( "activities", ) && !mapboxMap.getLayer( "activities-clusters", ) ) { console.log( "[MapboxMarkers] creazione layer activities", ); window.MapboxActivityClusters ?.createLayer?.(); } } if ( mapboxMap.getSource( "activities", ) && !mapboxMap.getLayer( "activities-clusters", ) ) { console.log( "[MapboxMarkers] creazione layer activities", ); window.MapboxActivityClusters ?.createLayer?.(); } } } function handleIdle() { if (!mapboxMap?.isStyleLoaded?.()) { return; } if (idleRefreshScheduled) { return; } idleRefreshScheduled = true; requestAnimationFrame(() => { idleRefreshScheduled = false; if (!mapboxMap?.isStyleLoaded?.()) { return; } ensureMapInfrastructure(); refreshRenderedMarkers(); }); } function handleSourceData(event) { if (!mapboxMap?.isStyleLoaded?.()) { return; } if (event.dataType !== "source") { return; } const sourceId = event.sourceId; const mode = window.state?.mode; if ( mode === "photos" && sourceId !== "photos" ) { return; } if ( mode === "activities" && sourceId !== "activities" ) { return; } if (sourceId !== "photos" && sourceId !== "activities") { return; } /* * Aspettiamo che Mapbox abbia terminato * di caricare e indicizzare la source. */ const sourceLoaded = typeof mapboxMap.isSourceLoaded === "function" ? mapboxMap.isSourceLoaded(sourceId) : event.isSourceLoaded === true; if (!sourceLoaded) { return; } scheduleRenderedRefresh(); } function refreshRenderedMarkers() { if (!mapboxMap?.isStyleLoaded?.()) { return; } const mode = window.state?.mode; if (mode === "photos") { window.MapboxActivityMarkers ?.clear?.(); window.MapboxActivityClusters ?.clear?.(); if ( mapboxMap.getSource( "photos", ) && !window.MapboxPhotoSpiderfy ?.isOpen?.() ) { window.MapboxPhotoClusters ?.update?.(); window.MapboxPhotoMarkers ?.update?.( state.photos, ); } return; } if (mode === "activities") { window.MapboxPhotoMarkers ?.clear?.(); window.MapboxPhotoClusters ?.clear?.(); if ( mapboxMap.getSource( "activities", ) && !window.MapboxActivitySpiderfy ?.isOpen?.() ) { window.MapboxActivityClusters ?.update?.(); window.MapboxActivityMarkers ?.update?.( state.activities, ); } } } function refreshPhotos() { if ( window.state?.mode !== "photos" ) { return; } if (!mapboxMap) { return; } if (!mapboxMap.isStyleLoaded?.()) { return; } if ( window.MapboxPhotoSpiderfy ?.isOpen?.() ) { return; } if ( !mapboxMap.getSource( "photos", ) ) { return; } window.MapboxPhotoClusters ?.update?.(); window.MapboxPhotoMarkers ?.update?.( state.photos, ); } function refreshActivities() { if ( window.state?.mode !== "activities" ) { return; } if (!mapboxMap?.isStyleLoaded?.()) { return; } if ( window.MapboxActivitySpiderfy ?.isOpen?.() ) { return; } if ( !mapboxMap.getSource( "activities", ) ) { return; } window.MapboxActivityClusters ?.update?.(); window.MapboxActivityMarkers ?.update?.( state.activities, ); } function refreshRoutes() { updateRoutesSourceAndLayer(); } function refreshAll() { if (!mapboxMap?.isStyleLoaded?.()) { return; } initializeModules(); } function setPhotos(features = []) { replaceStateArray( state.photos, features, ); const photosMode = window.state?.mode === "photos"; console.log( "[MapboxMarkers] photos", { features: state.photos.length, photosMode, styleLoaded: !!mapboxMap ?.isStyleLoaded?.(), PhotoSource: !!window.PhotoSource, PhotoClusters: !!window .MapboxPhotoClusters, }, ); if (!mapboxMap) { return; } if ( !mapboxMap .isStyleLoaded?.() ) { return; } if (!window.PhotoSource) { console.warn( "[MapboxMarkers] PhotoSource non disponibile", ); return; } if (!photosMode) { window.MapboxPhotoMarkers ?.clear?.(); window.MapboxPhotoClusters ?.clear?.(); const existingSource = mapboxMap.getSource?.( "photos", ); if (existingSource) { window.PhotoSource.update( mapboxMap, [], ); } return; } window.PhotoSource.update( mapboxMap, state.photos, ); const sourceExists = !!mapboxMap.getSource?.( "photos", ); console.log( "[MapboxMarkers] photos source", { exists: sourceExists, features: state.photos.length, }, ); if (!sourceExists) { console.warn( "[MapboxMarkers] source photos non creata", ); return; } window.MapboxPhotoClusters ?.createLayer?.(); console.log( "[MapboxMarkers] photos layers", { clusters: !!mapboxMap.getLayer?.( "photos-clusters", ), count: !!mapboxMap.getLayer?.( "photos-cluster-count", ), }, ); scheduleRenderedRefresh(); } function setActivities(features = []) { replaceStateArray( state.activities, features, ); const activitiesMode = window.state?.mode === "activities"; console.log( "[MapboxMarkers] activities", { features: state.activities.length, activitiesMode, styleLoaded: !!mapboxMap ?.isStyleLoaded?.(), }, ); if (!mapboxMap) { return; } if ( !mapboxMap.isStyleLoaded?.() ) { return; } if (!window.ActivitySource) { console.warn( "[MapboxMarkers] ActivitySource non disponibile", ); return; } if (!activitiesMode) { window.MapboxActivityMarkers ?.clear?.(); window.MapboxActivityClusters ?.clear?.(); if ( mapboxMap.getSource?.( "activities", ) ) { window.ActivitySource.update( mapboxMap, [], ); } return; } window.ActivitySource.update( mapboxMap, state.activities, ); if ( !mapboxMap.getSource?.( "activities", ) ) { console.warn( "[MapboxMarkers] source activities non creata", ); return; } window.MapboxActivityClusters ?.createLayer?.(); scheduleRenderedRefresh(); } function setRoutes(features = []) { replaceStateArray( state.routes, features, ); console.log( "[MapboxMarkers] routes", state.routes.length, ); if ( window.state?.mode !== "activities" ) { return; } if ( !mapboxMap || !mapboxMap .isStyleLoaded?.() ) { return; } updateRoutesSourceAndLayer(); } function replaceStateArray(target, features) { target.length = 0; if (Array.isArray(features)) { target.push(...features); } } function updateRoutesSourceAndLayer() { if (!mapboxMap?.isStyleLoaded?.()) { return; } const data = { type: "FeatureCollection", features: state.routes, }; const source = mapboxMap.getSource("routes"); if (source) { source.setData(data); } else { mapboxMap.addSource("routes", { type: "geojson", data, }); } if (!mapboxMap.getLayer("routes-line")) { mapboxMap.addLayer({ id: "routes-line", type: "line", source: "routes", paint: { "line-color": "#ff6b00", "line-width": 4, "line-opacity": 0.8, }, }); } } function clearHtmlMarkers() { if (window.MapboxPhotoSpiderfy?.isOpen?.()) { window.MapboxPhotoSpiderfy?.clear?.(); } window.MapboxPhotoMarkers?.clear?.(); window.MapboxPhotoClusters?.clear?.(); if (window.MapboxActivitySpiderfy?.isOpen?.()) { window.MapboxActivitySpiderfy?.clear?.(); } window.MapboxActivityMarkers?.clear?.(); window.MapboxActivityClusters?.clear?.(); } function clearState() { // Svuota lo stato interno. state.photos.length = 0; state.activities.length = 0; state.routes.length = 0; // Rimuove marker, cluster e spiderfy HTML. clearHtmlMarkers(); // La mappa potrebbe non esistere più. if (!mapboxMap) { return; } // Evita chiamate Mapbox quando lo stile // è stato rimosso o non è disponibile. let styleLoaded = false; try { styleLoaded = !!mapboxMap.isStyleLoaded?.(); } catch (error) { console.warn( "[MapboxMarkers] mappa non più disponibile durante clearState", error, ); return; } if (!styleLoaded) { return; } // Svuota la source Photos. try { if (mapboxMap.getSource?.("photos")) { window.PhotoSource?.update?.(mapboxMap, []); } } catch (error) { console.warn("[MapboxMarkers] errore pulizia source photos", error); } // Svuota la source Activities. try { if (mapboxMap.getSource?.("activities")) { window.ActivitySource?.update?.(mapboxMap, []); } } catch (error) { console.warn("[MapboxMarkers] errore pulizia source activities", error); } // Svuota la source Routes. try { const routesSource = mapboxMap.getSource?.("routes"); routesSource?.setData?.({ type: "FeatureCollection", features: [], }); } catch (error) { console.warn("[MapboxMarkers] errore pulizia source routes", error); } } function getPhotos() { return state.photos; } function getActivities() { return state.activities; } function getRoutes() { return state.routes; } function getMap() { return mapboxMap; } function debug() { const result = { map: !!mapboxMap, styleLoaded: !!mapboxMap?.isStyleLoaded?.(), eventsConnected: eventsMap === mapboxMap, idleRefreshScheduled, photos: state.photos.length, activities: state.activities.length, routes: state.routes.length, photoSource: !!mapboxMap?.getSource?.("photos"), photoClustersLayer: !!mapboxMap?.getLayer?.("photos-clusters"), photoCountLayer: !!mapboxMap?.getLayer?.("photos-cluster-count"), photoMarkers: window.MapboxPhotoMarkers?.size?.() ?? 0, photoClusterMarkers: window.MapboxPhotoClusters?.size?.() ?? 0, activitySource: !!mapboxMap?.getSource?.("activities"), activityClustersLayer: !!mapboxMap?.getLayer?.("activities-clusters"), activityCountLayer: !!mapboxMap?.getLayer?.("activities-cluster-count"), activityMarkers: window.MapboxActivityMarkers?.size?.() ?? 0, activityClusterMarkers: window.MapboxActivityClusters?.size?.() ?? 0, activitySpiderfyOpen: window.MapboxActivitySpiderfy?.isOpen?.() ?? false, activitySpiderMarkers: window.MapboxActivitySpiderfy?.size?.() ?? 0, routesSource: !!mapboxMap?.getSource?.("routes"), routesLayer: !!mapboxMap?.getLayer?.("routes-line"), }; console.log("[MapboxMarkers DEBUG]", result); return result; } window.debugMapboxMarkers = debug; window.MapboxMarkers = { setMap, getMap, setPhotos, setActivities, setRoutes, getPhotos, getActivities, getRoutes, refreshAll, refreshPhotos, refreshActivities, refreshRoutes, clearMarkers: clearHtmlMarkers, clearState, debug, }; })();