Fix native map services and add provider selection

This commit is contained in:
livio committed 2026-09-11 19:25:09 +02:00
1 parent 56211e4b25
commit 3ea51a6842
9 files changed
+1408 -129

No files matched your search

+163 -112
View File
@@ -8,33 +8,34 @@ const lmeState = {
imported: null,
drawings: null,
drawControl: null,
drawEventsBound: false,
gpsMarker: null,
gpsAccuracy: null,
gpsTrail: null,
currentParcelLayer: null,
gpsTrailEnabled: true,
gpsTrailEnabled: false,
gpsTrailPoints: [],
gpsWatchId: null,
lastPosition: null,
lastWfsLookupAt: 0,
wfsLookupInFlight: false,
fileImportBound: false,
resizeBound: false
resizeBound: false,
googleMapsLoading: null
};
window.lmeMap = {
init,
search,
locateParcelByPoint,
showSearchResult,
displayOfficialParcel,
exportCadastreView,
fitImported,
clearImported,
addProjectMarker,
enableDrawTools,
setDrawToolsVisible,
clearDrawings,
saveWorkspace,
loadWorkspace,
setBaseLayer,
configureGoogleMaps,
setCadastreOpacity,
toggleCadastreLayer,
startGps,
@@ -70,6 +71,31 @@ function init() {
attribution: "Tiles © Esri"
});
lmeState.baseLayers.topographic = L.tileLayer("https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}", {
maxNativeZoom: 19,
maxZoom: 22,
attribution: "Tiles © Esri"
});
lmeState.baseLayers["streets-esri"] = L.tileLayer("https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}", {
maxNativeZoom: 19,
maxZoom: 22,
attribution: "Tiles © Esri"
});
lmeState.baseLayers.terrain = L.tileLayer("https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png", {
maxNativeZoom: 17,
maxZoom: 22,
attribution: "Map data © OpenStreetMap contributors, SRTM | Map style © OpenTopoMap"
});
lmeState.baseLayers.carto = L.tileLayer("https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png", {
subdomains: "abcd",
maxNativeZoom: 20,
maxZoom: 22,
attribution: "© OpenStreetMap contributors © CARTO"
});
lmeState.currentBase = lmeState.baseLayers.satellite.addTo(lmeState.map);
lmeState.imported = L.featureGroup().addTo(lmeState.map);
lmeState.drawings = L.featureGroup().addTo(lmeState.map);
@@ -95,35 +121,19 @@ function init() {
setInfo("Mappa pronta: satellite + catasto AdE");
}
async function search(text) {
const query = (text || "").trim();
if (!query) {
setInfo("Inserisci una localita o coordinate.");
function showSearchResult(latitude, longitude, label) {
const lat = Number(latitude);
const lon = Number(longitude);
if (!Number.isFinite(lat) || !Number.isFinite(lon)) {
setInfo("La ricerca ha restituito coordinate non valide.");
return;
}
const coordinate = parseCoordinates(query);
if (coordinate) {
lmeState.map.setView(coordinate, 19);
setInfo(`Coordinate: ${coordinate[0].toFixed(6)}, ${coordinate[1].toFixed(6)}`);
return;
}
const response = await fetch(`https://nominatim.openstreetmap.org/search?format=json&limit=1&q=${encodeURIComponent(query)}`, {
headers: { "Accept": "application/json" }
});
const results = await response.json();
if (!results.length) {
setInfo("Localita non trovata.");
return;
}
const place = results[0];
lmeState.map.setView([Number(place.lat), Number(place.lon)], 18);
setInfo(place.display_name);
lmeState.map.setView([lat, lon], 18);
setInfo(label || `${lat.toFixed(6)}, ${lon.toFixed(6)}`);
}
async function locateParcelByPoint(latitude, longitude, parcel, label) {
function displayOfficialParcel(xmlText, latitude, longitude, parcel, label, shouldFit) {
const lat = Number(latitude);
const lon = Number(longitude);
if (!Number.isFinite(lat) || !Number.isFinite(lon)) {
@@ -131,21 +141,24 @@ async function locateParcelByPoint(latitude, longitude, parcel, label) {
return;
}
setInfo(`Recupero il confine ufficiale: ${label}`);
try {
const meters = 8;
const deltaLat = meters / 111320;
const deltaLon = meters / (111320 * Math.cos(lat * Math.PI / 180));
const geojson = await fetchOfficialParcelsByBbox(lat - deltaLat, lon - deltaLon, lat + deltaLat, lon + deltaLon);
const geojson = gmlParcelsToGeoJson(xmlText);
const normalizedParcel = normalizeParcel(parcel);
const feature = (geojson.features || []).find(item => {
const props = item.properties || {};
return String(props.LABEL || "").trim().toUpperCase() === normalizedParcel.toUpperCase()
|| String(props.NATIONALCADASTRALREFERENCE || "").toUpperCase().endsWith(`.${normalizedParcel.toUpperCase()}`);
}) || (geojson.features || []).find(item => geometryContainsPoint(item.geometry, [lon, lat])) || geojson.features?.[0];
const featureByNumber = normalizedParcel
? (geojson.features || []).find(item => {
const props = item.properties || {};
return String(props.LABEL || "").trim().toUpperCase() === normalizedParcel
|| String(props.NATIONALCADASTRALREFERENCE || "").toUpperCase().endsWith(`.${normalizedParcel}`);
})
: null;
const feature = featureByNumber
|| (geojson.features || []).find(item => geometryContainsPoint(item.geometry, [lon, lat]))
|| geojson.features?.[0];
if (!feature) {
lmeState.map.setView([lat, lon], 20);
if (shouldFit) {
lmeState.map.setView([lat, lon], 20);
}
setInfo("Posizione trovata, ma il servizio WFS dell'Agenzia non ha restituito il confine. Riprova tra poco.");
return;
}
@@ -154,13 +167,19 @@ async function locateParcelByPoint(latitude, longitude, parcel, label) {
const layer = L.geoJSON(feature, {
style: { color: "#ef4444", weight: 4, opacity: 1, fillColor: "#fbbf24", fillOpacity: 0.26 }
}).addTo(lmeState.currentParcelLayer);
fitLayer(layer);
if (shouldFit) {
fitLayer(layer);
}
const ref = feature.properties?.NATIONALCADASTRALREFERENCE || feature.properties?.INSPIREID_LOCALID || label;
setText("parcelState", `Particella selezionata: ${ref}`);
setInfo(`Trovata ${ref}. Ora puoi attivare il GPS e camminare dentro il confine evidenziato.`);
if (normalizedParcel) {
setText("parcelState", `Particella selezionata: ${ref}`);
setInfo(`Trovata ${ref}. Ora puoi attivare il GPS e camminare dentro il confine evidenziato.`);
} else {
setText("parcelState", `Dentro particella AdE: ${ref}`);
}
} catch (error) {
setInfo(`Servizio catastale temporaneamente non disponibile: ${error.message}`);
setInfo(`Geometria catastale non leggibile: ${error.message}`);
}
}
@@ -284,13 +303,36 @@ function enableDrawTools() {
}
});
lmeState.map.addControl(lmeState.drawControl);
lmeState.map.on(L.Draw.Event.CREATED, event => {
lmeState.drawings.addLayer(event.layer);
updateGpsInsideState();
});
if (!lmeState.drawEventsBound) {
lmeState.map.on(L.Draw.Event.CREATED, event => {
lmeState.drawings.addLayer(event.layer);
updateGpsInsideState();
});
lmeState.drawEventsBound = true;
}
setInfo("Disegno attivo: crea POI, linee e aree sulla mappa.");
}
function setDrawToolsVisible(visible) {
if (visible) {
enableDrawTools();
return;
}
if (!lmeState.drawControl) {
return;
}
Object.values(lmeState.drawControl._toolbars || {}).forEach(toolbar => {
if (typeof toolbar.disable === "function") {
toolbar.disable();
}
});
lmeState.map.removeControl(lmeState.drawControl);
lmeState.drawControl = null;
setInfo("Strumenti di disegno nascosti.");
}
function clearDrawings() {
const count = lmeState.drawings.getLayers().length;
if (!count) {
@@ -343,6 +385,62 @@ function setBaseLayer(name) {
}
lmeState.currentBase = selected.addTo(lmeState.map);
lmeState.currentBase.bringToBack();
const labels = {
satellite: "Satellite Esri",
osm: "OpenStreetMap",
"streets-esri": "Strade dettagliate Esri",
topographic: "Topografica Esri",
terrain: "OpenTopoMap",
carto: "CARTO Voyager",
"google-satellite": "Satellite Google Maps",
"google-roadmap": "Strade Google Maps"
};
setInfo(`Sfondo attivo: ${labels[name] || labels.satellite}. Il catasto AdE resta sovrapposto.`);
}
function configureGoogleMaps(apiKey) {
const key = (apiKey || "").trim();
if (!key) {
return Promise.reject(new Error("Chiave Google Maps mancante."));
}
if (window.google?.maps && L.gridLayer.googleMutant) {
createGoogleLayers();
return Promise.resolve();
}
if (lmeState.googleMapsLoading) {
return lmeState.googleMapsLoading;
}
lmeState.googleMapsLoading = new Promise((resolve, reject) => {
const callbackName = `lmeGoogleMapsReady${Date.now()}`;
const script = document.createElement("script");
window[callbackName] = () => {
delete window[callbackName];
createGoogleLayers();
resolve();
};
script.src = `https://maps.googleapis.com/maps/api/js?key=${encodeURIComponent(key)}&loading=async&callback=${callbackName}`;
script.async = true;
script.onerror = () => {
delete window[callbackName];
lmeState.googleMapsLoading = null;
reject(new Error("Impossibile caricare Google Maps. Controlla chiave, API e connessione."));
};
document.head.appendChild(script);
});
return lmeState.googleMapsLoading;
}
function createGoogleLayers() {
if (!L.gridLayer.googleMutant) {
throw new Error("Modulo Google Maps non disponibile.");
}
lmeState.baseLayers["google-satellite"] ||= L.gridLayer.googleMutant({ type: "hybrid", maxZoom: 22 });
lmeState.baseLayers["google-roadmap"] ||= L.gridLayer.googleMutant({ type: "roadmap", maxZoom: 22 });
}
function setCadastreOpacity(value) {
@@ -469,7 +567,6 @@ function onGpsPosition(position) {
setText("gpsState", `${latitude.toFixed(6)}, ${longitude.toFixed(6)} - precisione ${Math.round(accuracy)} m`);
updateGpsInsideState();
updateOfficialParcelFromPoint(latitude, longitude, accuracy);
}
function onGpsError(error) {
@@ -483,6 +580,16 @@ function updateGpsInsideState() {
}
const point = [lmeState.lastPosition[1], lmeState.lastPosition[0]];
const officialMatch = findContainingPolygon(point, lmeState.currentParcelLayer.toGeoJSON());
if (officialMatch) {
const reference = officialMatch.properties?.NATIONALCADASTRALREFERENCE
|| officialMatch.properties?.INSPIREID_LOCALID
|| officialMatch.properties?.LABEL
|| "particella AdE";
setText("parcelState", `Dentro particella AdE: ${reference}`);
return;
}
const match = findContainingPolygon(point, lmeState.imported.toGeoJSON()) || findContainingPolygon(point, lmeState.drawings.toGeoJSON());
if (match) {
@@ -491,51 +598,11 @@ function updateGpsInsideState() {
return;
}
setText("parcelState", "Fuori dai poligoni caricati");
}
async function updateOfficialParcelFromPoint(latitude, longitude, accuracy) {
const now = Date.now();
if (lmeState.wfsLookupInFlight || now - lmeState.lastWfsLookupAt < 2500) {
return;
}
lmeState.wfsLookupInFlight = true;
lmeState.lastWfsLookupAt = now;
const meters = Math.max(2, Math.min(Number(accuracy || 5), 25));
const deltaLat = meters / 111320;
const deltaLon = meters / (111320 * Math.cos(latitude * Math.PI / 180));
try {
const geojson = await fetchOfficialParcelsByBbox(latitude - deltaLat, longitude - deltaLon, latitude + deltaLat, longitude + deltaLon);
const feature = (geojson.features || []).find(item => geometryContainsPoint(item.geometry, [longitude, latitude])) || geojson.features?.[0];
if (!feature) {
return;
}
lmeState.currentParcelLayer.clearLayers();
L.geoJSON(feature, {
style: { color: "#ef4444", weight: 4, opacity: 1, fillColor: "#fbbf24", fillOpacity: 0.24 }
}).addTo(lmeState.currentParcelLayer);
const ref = feature.properties?.NATIONALCADASTRALREFERENCE || feature.properties?.INSPIREID_LOCALID || feature.properties?.LABEL || "particella AdE";
setText("parcelState", `Dentro particella AdE: ${ref}`);
} catch (error) {
setInfo(`Aggiornamento particella GPS non disponibile: ${error.message}`);
} finally {
lmeState.wfsLookupInFlight = false;
}
}
async function fetchOfficialParcelsByBbox(south, west, north, east) {
const url = `${wfsUrl()}language=ita&SERVICE=WFS&VERSION=2.0.0&TYPENAMES=CP:CadastralParcel&SRSNAME=urn:ogc:def:crs:EPSG::6706&BBOX=${south},${west},${north},${east}&REQUEST=GetFeature&COUNT=20`;
const response = await fetch(url);
if (!response.ok) {
return { type: "FeatureCollection", features: [] };
}
const xmlText = await response.text();
return gmlParcelsToGeoJson(xmlText);
setText(
"parcelState",
lmeState.currentParcelLayer.getLayers().length
? "Fuori dalla particella catastale evidenziata"
: "Fuori dai poligoni caricati");
}
function gmlParcelsToGeoJson(xmlText) {
@@ -634,18 +701,6 @@ async function queryFeatureInfo(latlng) {
}
}
function parseCoordinates(text) {
const parts = text.split(/[,\s;]+/).map(Number).filter(value => !Number.isNaN(value));
if (parts.length < 2) {
return null;
}
const [lat, lng] = parts;
if (Math.abs(lat) <= 90 && Math.abs(lng) <= 180) {
return [lat, lng];
}
return null;
}
function fitLayer(layer) {
try {
const bounds = layer.getBounds();
@@ -661,10 +716,6 @@ function cadastreUrl() {
return "https://wms.cartografia.agenziaentrate.gov.it/inspire/wms/ows01.php?language=ita&";
}
function wfsUrl() {
return "https://wfs.cartografia.agenziaentrate.gov.it/inspire/wfs/owfs01.php?";
}
function setInfo(text) {
setText("mapInfo", text);
}