394 lines
7.1 KiB
JavaScript
394 lines
7.1 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;
|
|
|
|
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()
|
|
},
|
|
|
|
opentopo: {
|
|
label: "Topografica",
|
|
layer: createOpenTopoLayer()
|
|
}
|
|
};
|
|
|
|
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
|
|
};
|
|
}
|
|
|
|
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
|
|
) {
|
|
window.dispatchEvent(
|
|
new CustomEvent("mapstylechange", {
|
|
detail: {
|
|
engine: "leaflet",
|
|
styleId,
|
|
label
|
|
}
|
|
})
|
|
);
|
|
}
|
|
|
|
function setStyle(
|
|
map,
|
|
styleId,
|
|
save = true
|
|
) {
|
|
if (!map) {
|
|
console.error(
|
|
"[mapBaseLayers] Istanza Leaflet mancante"
|
|
);
|
|
|
|
return false;
|
|
}
|
|
|
|
const selectedStyle =
|
|
layers[styleId]
|
|
? styleId
|
|
: DEFAULT_STYLE;
|
|
|
|
const definition =
|
|
layers[selectedStyle];
|
|
|
|
if (!definition?.layer) {
|
|
console.error(
|
|
"[mapBaseLayers] Basemap non trovata:",
|
|
styleId
|
|
);
|
|
|
|
return false;
|
|
}
|
|
|
|
if (
|
|
currentLayer &&
|
|
map.hasLayer(currentLayer)
|
|
) {
|
|
map.removeLayer(currentLayer);
|
|
}
|
|
|
|
currentLayer = definition.layer;
|
|
currentLayer.addTo(map);
|
|
|
|
if (save) {
|
|
localStorage.setItem(
|
|
STORAGE_KEY,
|
|
selectedStyle
|
|
);
|
|
}
|
|
|
|
dispatchStyleChange(
|
|
selectedStyle,
|
|
definition.label
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|
|
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
|
|
);
|
|
}
|
|
|
|
function handleBaseLayerChange(event) {
|
|
const styleId =
|
|
findStyleIdByLayer(event.layer);
|
|
|
|
if (!styleId) {
|
|
return;
|
|
}
|
|
|
|
currentLayer = event.layer;
|
|
|
|
localStorage.setItem(
|
|
STORAGE_KEY,
|
|
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 || {};
|
|
}
|
|
|
|
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 styleApplied = setStyle(
|
|
map,
|
|
initialStyle,
|
|
false
|
|
);
|
|
|
|
createControl(map);
|
|
|
|
console.log(
|
|
"[mapBaseLayers] Basemap inizializzate",
|
|
{
|
|
currentStyle: getCurrentStyle(),
|
|
availableStyles:
|
|
getAvailableStyles().map(
|
|
style => style.id
|
|
),
|
|
mapboxConfigured:
|
|
Boolean(getMapboxToken())
|
|
}
|
|
);
|
|
|
|
return styleApplied;
|
|
}
|
|
|
|
function getCurrentStyle() {
|
|
return (
|
|
findStyleIdByLayer(currentLayer) ||
|
|
DEFAULT_STYLE
|
|
);
|
|
}
|
|
|
|
function getAvailableStyles() {
|
|
return Object.entries(layers).map(
|
|
([id, definition]) => ({
|
|
id,
|
|
label: definition.label
|
|
})
|
|
);
|
|
}
|
|
|
|
window.MapBaseLayers = Object.freeze({
|
|
initialize,
|
|
setStyle,
|
|
getCurrentStyle,
|
|
getAvailableStyles
|
|
});
|
|
})();
|