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

548 lines
12 KiB
JavaScript

// public/js/mapBaseLayers.js
// Basemap 2D Leaflet e selettore livelli.
(() => {
"use strict";
const STORAGE_KEY = "selectedMapStyle";
const DEFAULT_STYLE = "osm";
let currentLayer = null;
let layers = {};
let layerControl = null;
let initializedMap = null;
let terrainPlaceholderLayer = null;
let terrainStyleControl = null;
let styleChangeInProgress = false;
function getMapboxToken() {
return window.AppConfig?.mapboxToken || "";
}
function createOsmLayer() {
return L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
subdomains: "abc",
maxZoom: 19,
attribution: "© OpenStreetMap contributors",
});
}
function createOpenTopoLayer() {
return L.tileLayer("https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png", {
subdomains: "abc",
maxZoom: 17,
attribution:
"Map data: © OpenStreetMap contributors, SRTM | " +
"Map style: © OpenTopoMap",
});
}
function createMapboxLayer(styleId, token) {
if (!token) {
return null;
}
const encodedToken = encodeURIComponent(token);
const tileUrl =
`https://api.mapbox.com/styles/v1/mapbox/${styleId}` +
`/tiles/512/{z}/{x}/{y}@2x` +
`?access_token=${encodedToken}`;
return L.tileLayer(tileUrl, {
tileSize: 512,
zoomOffset: -1,
maxZoom: 22,
attribution: "© Mapbox © OpenStreetMap contributors",
});
}
function createLayers() {
const token = getMapboxToken();
const result = {
osm: {
label: "OpenStreetMap",
layer: createOsmLayer(),
engine: "leaflet",
},
opentopo: {
label: "Topografica",
layer: createOpenTopoLayer(),
engine: "leaflet",
},
};
if (!token) {
console.warn(
"[mapBaseLayers] Token Mapbox non configurato. " +
"Saranno disponibili solamente OpenStreetMap " +
"e OpenTopoMap.",
);
return result;
}
const mapboxDefinitions = {
mapboxStreets: {
label: "Mapbox Streets",
styleId: "streets-v12",
},
mapboxOutdoors: {
label: "Mapbox Outdoors",
styleId: "outdoors-v12",
},
mapboxSatellite: {
label: "Mapbox Satellite",
styleId: "satellite-v9",
},
mapboxSatelliteStreets: {
label: "Mapbox Satellite + strade",
styleId: "satellite-streets-v12",
},
};
for (const [id, definition] of Object.entries(mapboxDefinitions)) {
const layer = createMapboxLayer(definition.styleId, token);
if (!layer) {
continue;
}
result[id] = {
label: definition.label,
layer,
};
}
terrainPlaceholderLayer = L.layerGroup();
result.terrain3d = {
label: "Terrain 3D",
layer: terrainPlaceholderLayer,
engine: "mapbox",
};
return result;
}
function getSavedStyle() {
const savedStyle = localStorage.getItem(STORAGE_KEY);
if (savedStyle && layers[savedStyle]) {
return savedStyle;
}
return DEFAULT_STYLE;
}
function findStyleIdByLayer(selectedLayer) {
for (const [styleId, definition] of Object.entries(layers)) {
if (definition.layer === selectedLayer) {
return styleId;
}
}
return null;
}
function dispatchStyleChange(styleId, label) {
const definition = layers[styleId];
window.dispatchEvent(
new CustomEvent("mapstylechange", {
detail: {
engine: definition?.engine || "leaflet",
styleId,
label,
},
}),
);
}
async function setStyle(map, styleId, save = true) {
if (!map) {
console.error("[mapBaseLayers] Istanza Leaflet mancante");
return false;
}
if (styleChangeInProgress) {
return false;
}
const selectedStyle = layers[styleId] ? styleId : DEFAULT_STYLE;
const definition = layers[selectedStyle];
if (!definition) {
console.error("[mapBaseLayers] Basemap non trovata:", styleId);
return false;
}
styleChangeInProgress = true;
try {
// ===============================
// TERRAIN 3D
// ===============================
if (selectedStyle === "terrain3d") {
const success = await window.MapTerrain3D?.enable?.();
if (!success) {
console.warn("[mapBaseLayers] Terrain 3D non attivato");
return false;
}
currentLayer = terrainPlaceholderLayer;
if (save) {
localStorage.setItem(STORAGE_KEY, selectedStyle);
}
syncTerrainStyleControl(selectedStyle);
dispatchStyleChange(selectedStyle, definition.label);
return true;
}
// ===============================
// MAPPA LEAFLET
// ===============================
if (window.MapTerrain3D?.enabled?.()) {
window.MapTerrain3D?.disable?.();
}
if (
currentLayer &&
currentLayer !== terrainPlaceholderLayer &&
map.hasLayer(currentLayer)
) {
map.removeLayer(currentLayer);
}
if (terrainPlaceholderLayer && map.hasLayer(terrainPlaceholderLayer)) {
map.removeLayer(terrainPlaceholderLayer);
}
currentLayer = definition.layer;
if (currentLayer && !map.hasLayer(currentLayer)) {
currentLayer.addTo(map);
}
if (save) {
localStorage.setItem(STORAGE_KEY, selectedStyle);
}
syncTerrainStyleControl(selectedStyle);
dispatchStyleChange(selectedStyle, definition.label);
return true;
} finally {
styleChangeInProgress = false;
}
}
function createControl(map) {
if (layerControl) {
map.removeControl(layerControl);
layerControl = null;
}
const baseLayers = {};
for (const definition of Object.values(layers)) {
baseLayers[definition.label] = definition.layer;
}
layerControl = L.control.layers(baseLayers, null, {
position: "topright",
collapsed: true,
});
layerControl.addTo(map);
map.on("baselayerchange", handleBaseLayerChange);
}
async function handleBaseLayerChange(event) {
const styleId = findStyleIdByLayer(event.layer);
if (!styleId) {
return;
}
// ===============================
// TERRAIN 3D
// ===============================
if (styleId === "terrain3d") {
const success = await setStyle(initializedMap, "terrain3d", true);
if (!success) {
await setStyle(initializedMap, DEFAULT_STYLE, true);
}
return;
}
// ===============================
// LEAFLET
// ===============================
currentLayer = event.layer;
if (window.MapTerrain3D?.enabled?.()) {
window.MapTerrain3D?.disable?.();
}
localStorage.setItem(STORAGE_KEY, styleId);
syncTerrainStyleControl(styleId);
dispatchStyleChange(styleId, event.name);
}
async function waitForConfiguration() {
if (window.AppConfigPromise) {
try {
await window.AppConfigPromise;
} catch (error) {
console.error("[mapBaseLayers] Errore configurazione:", error);
}
}
return window.AppConfig || {};
}
function getSelectedStyle() {
if (window.MapTerrain3D?.enabled?.()) {
return "terrain3d";
}
return findStyleIdByLayer(currentLayer) || DEFAULT_STYLE;
}
function syncTerrainStyleControl(styleId) {
const select = terrainStyleControl?.querySelector?.("select");
if (!select) {
return;
}
if (layers[styleId]) {
select.value = styleId;
}
}
function removeTerrainStyleControl() {
if (!terrainStyleControl) {
return;
}
terrainStyleControl.remove();
terrainStyleControl = null;
}
function mountTerrainStyleControl(targetContainer) {
if (!targetContainer) {
return;
}
removeTerrainStyleControl();
const wrapper = document.createElement("div");
wrapper.className = "terrain-style-control";
const select = document.createElement("select");
select.className = "terrain-style-select";
select.setAttribute("aria-label", "Tipo di mappa");
for (const [id, definition] of Object.entries(layers)) {
const option = document.createElement("option");
option.value = id;
option.textContent = definition.label;
select.appendChild(option);
}
select.value = getSelectedStyle();
select.addEventListener("change", async () => {
const selectedStyle = select.value;
const success = await setStyle(initializedMap, selectedStyle, true);
if (!success) {
select.value = getSelectedStyle();
}
});
wrapper.appendChild(select);
targetContainer.appendChild(wrapper);
terrainStyleControl = wrapper;
}
async function initialize(map) {
if (!map) {
console.error("[mapBaseLayers] Istanza mappa non disponibile");
return false;
}
if (typeof window.L === "undefined") {
console.error("[mapBaseLayers] Leaflet non disponibile");
return false;
}
await waitForConfiguration();
/*
* Evita una doppia inizializzazione sulla stessa
* istanza Leaflet.
*/
if (initializedMap === map && Object.keys(layers).length > 0) {
return true;
}
/*
* Se il modulo viene inizializzato su una nuova mappa,
* rimuove il listener dalla precedente.
*/
if (initializedMap && initializedMap !== map) {
initializedMap.off("baselayerchange", handleBaseLayerChange);
}
initializedMap = map;
layers = createLayers();
const initialStyle = getSavedStyle();
const safeInitialStyle =
initialStyle === "terrain3d" ? DEFAULT_STYLE : initialStyle;
const styleApplied = await setStyle(map, safeInitialStyle, false);
createControl(map);
console.log("[mapBaseLayers] Basemap inizializzate", {
currentStyle: getCurrentStyle(),
availableStyles: getAvailableStyles().map((style) => style.id),
mapboxConfigured: Boolean(getMapboxToken()),
});
return styleApplied;
}
function getCurrentStyle() {
if (window.MapTerrain3D?.enabled?.()) {
return "terrain3d";
}
return findStyleIdByLayer(currentLayer) || DEFAULT_STYLE;
}
function getAvailableStyles() {
return Object.entries(layers).map(([id, definition]) => ({
id,
label: definition.label,
}));
}
function createLeafletLayer(
styleId,
) {
const token =
getMapboxToken();
switch (styleId) {
case "osm":
return createOsmLayer();
case "opentopo":
return createOpenTopoLayer();
case "mapboxStreets":
return createMapboxLayer(
"streets-v12",
token,
);
case "mapboxOutdoors":
return createMapboxLayer(
"outdoors-v12",
token,
);
case "mapboxSatellite":
return createMapboxLayer(
"satellite-v9",
token,
);
case "mapboxSatelliteStreets":
return createMapboxLayer(
"satellite-streets-v12",
token,
);
default:
return createOsmLayer();
}
}
function getMapboxStyleUrl(
styleId,
) {
switch (styleId) {
case "mapboxStreets":
return "mapbox://styles/mapbox/streets-v12";
case "mapboxSatellite":
return "mapbox://styles/mapbox/satellite-v9";
case "mapboxSatelliteStreets":
return "mapbox://styles/mapbox/satellite-streets-v12";
case "terrain3d":
case "mapboxOutdoors":
default:
return "mapbox://styles/mapbox/outdoors-v12";
}
}
window.MapBaseLayers =
Object.freeze({
initialize,
setStyle,
getCurrentStyle,
getAvailableStyles,
createLeafletLayer,
getMapboxStyleUrl,
mountTerrainStyleControl,
removeTerrainStyleControl,
});
})();