Fix native map services and add provider selection
This commit is contained in:
1 parent
56211e4b25
commit
3ea51a6842
9 files changed
+1408
-129
No files matched your search
+163
-112
@@ -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);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user