From 342935d99d2d662fc6b3b99e01c72d9d6ca13be0 Mon Sep 17 00:00:00 2001 From: livio Date: Fri, 2 Oct 2026 11:24:35 +0200 Subject: [PATCH] Show cadastral sheets while navigating --- LMEMappeCatastaliApp.csproj | 4 +- PLAY_STORE_PRO_CHECKLIST.md | 2 +- Platforms/Android/MainActivity.cs | 6 +- README.md | 6 + wwwroot/css/app.css | 15 +++ wwwroot/js/lme-map.js | 195 +++++++++++++++++++++++++++--- 6 files changed, 206 insertions(+), 22 deletions(-) diff --git a/LMEMappeCatastaliApp.csproj b/LMEMappeCatastaliApp.csproj index 478cb37..dc9af14 100644 --- a/LMEMappeCatastaliApp.csproj +++ b/LMEMappeCatastaliApp.csproj @@ -28,8 +28,8 @@ it.liviomerola.lmemappecatastali.pro - 1.0.10 - 110 + 1.0.11 + 111 $(ApplicationDisplayVersion) diff --git a/PLAY_STORE_PRO_CHECKLIST.md b/PLAY_STORE_PRO_CHECKLIST.md index 5a7dea7..eb7d268 100644 --- a/PLAY_STORE_PRO_CHECKLIST.md +++ b/PLAY_STORE_PRO_CHECKLIST.md @@ -4,7 +4,7 @@ - Nome: LME Mappe Catastali Pro. - Package name Android: `it.liviomerola.lmemappecatastali.pro`. -- Versione corrente da pubblicare: `1.0.10` / version code `110`. +- Versione corrente da pubblicare: `1.0.11` / version code `111`. - Formato Release Android: Android App Bundle (`.aab`). - Target SDK Release: Android 16 / API 36. diff --git a/Platforms/Android/MainActivity.cs b/Platforms/Android/MainActivity.cs index abf1583..50c0ab3 100644 --- a/Platforms/Android/MainActivity.cs +++ b/Platforms/Android/MainActivity.cs @@ -102,7 +102,11 @@ public class MainActivity : MauiAppCompatActivity { if (view is null || insets is null) return insets; var systemBars = insets.GetInsets(WindowInsetsCompat.Type.SystemBars()); - view.SetPadding(_left, _top, _right, systemBars?.Bottom ?? 0); + view.SetPadding( + _left, + systemBars?.Top ?? _top, + _right, + systemBars?.Bottom ?? 0); return insets; } } diff --git a/README.md b/README.md index 7bf6eea..bca8a1f 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,12 @@ App .NET MAUI Blazor Hybrid per Android, iOS, Mac Catalyst e Windows, pensata pe Informativa privacy: [PRIVACY_POLICY.md](PRIVACY_POLICY.md). +## Versione Pro 1.0.11 test + +- Il flag Fogli catastali mostra il WMS di tutti i fogli agli zoom panoramici e, in dettaglio, il confine ufficiale WFS del foglio corrente anche senza dati caricati. +- Se il foglio corrente e gia presente in un progetto o in un'importazione, l'overlay di navigazione non ne crea un duplicato. +- La toolbar superiore rispetta l'area occupata dalla barra di stato e dalle notifiche Android. + ## Versione Pro 1.0.10 test - Migliora la compatibilita del servizio catastale AdE sui dispositivi Android meno recenti con HTTP/1.1, tentativi automatici e una ricerca GPS piu tollerante. diff --git a/wwwroot/css/app.css b/wwwroot/css/app.css index 81c1eaf..76698c2 100644 --- a/wwwroot/css/app.css +++ b/wwwroot/css/app.css @@ -1318,6 +1318,21 @@ input[type="checkbox"] { height: 38px; } +.cadastral-sheet-label { + width: auto !important; + height: auto !important; + border: 1px solid #9a5b00; + border-radius: 4px; + padding: 3px 6px; + background: rgba(255, 251, 235, 0.94); + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.28); + color: #713f12; + font-size: 11px; + font-weight: 800; + line-height: 1.2; + white-space: nowrap; +} + .lme-drawing-scope-actions a[aria-disabled="true"] { opacity: 0.45; pointer-events: none; diff --git a/wwwroot/js/lme-map.js b/wwwroot/js/lme-map.js index 66f6f22..e637e56 100644 --- a/wwwroot/js/lme-map.js +++ b/wwwroot/js/lme-map.js @@ -14,8 +14,14 @@ const lmeState = { "cadastre" ], cadastre: null, + cadastreZoning: null, cadastreLayers: ["CP.CadastralParcel", "codice_plla", "CP.CadastralZoning", "fabbricati"], cadastreOpacity: 0.75, + navigationSheetLayer: null, + navigationSheetFeature: null, + navigationSheetLabel: null, + navigationSheetRefreshTimer: null, + navigationSheetRequestId: 0, imported: null, importedVisible: true, importedSheets: null, @@ -305,6 +311,13 @@ function init(dotNetReference) { lmeState.drawingMeasurements = L.layerGroup().addTo(lmeState.map); lmeState.selectedParcelsLayer = L.featureGroup().addTo(lmeState.map); lmeState.gpsParcelLayer = L.featureGroup().addTo(lmeState.map); + const navigationSheetPane = lmeState.map.createPane("navigationSheetPane"); + navigationSheetPane.style.zIndex = "465"; + navigationSheetPane.style.pointerEvents = "none"; + const cadastreZoningPane = lmeState.map.createPane("cadastreZoningPane"); + cadastreZoningPane.style.zIndex = "455"; + cadastreZoningPane.style.pointerEvents = "none"; + lmeState.navigationSheetLayer = L.layerGroup().addTo(lmeState.map); lmeState.gpsTrail = L.polyline([], { pane: "gpsTrailPane", color: "#f97316", @@ -339,6 +352,7 @@ function init(dotNetReference) { lmeState.map.on("moveend", () => { void updateAzureAttribution(); scheduleMeasurementRefresh(); + scheduleNavigationSheetRefresh(); }); document.getElementById("closeWmsPreview")?.addEventListener("click", closeCadastrePreview); @@ -5387,6 +5401,9 @@ function setCadastreOpacity(value) { if (lmeState.cadastre) { lmeState.cadastre.setOpacity(lmeState.cadastreOpacity); } + if (lmeState.cadastreZoning) { + lmeState.cadastreZoning.setOpacity(Math.max(0.8, lmeState.cadastreOpacity)); + } } function toggleCadastreLayer(layer, enabled) { @@ -5398,6 +5415,13 @@ function toggleCadastreLayer(layer, enabled) { lmeState.cadastreLayers = lmeState.cadastreLayers.filter(item => item !== layer); } refreshCadastreLayer(); + if (layer === "CP.CadastralZoning") { + if (enabled) { + scheduleNavigationSheetRefresh(true); + } else { + clearNavigationSheetOutline(); + } + } const layerNames = { "CP.CadastralParcel": "Confini particelle", codice_plla: "Numeri particelle", @@ -5413,29 +5437,166 @@ function toggleCadastreLayer(layer, enabled) { setInfo(`${layerNames[layer] || layer}: ${enabled ? "attivo" : "nascosto"}.`); } +function scheduleNavigationSheetRefresh(immediate = false) { + window.clearTimeout(lmeState.navigationSheetRefreshTimer); + if (!lmeState.map + || !lmeState.dotNetReference + || !lmeState.cadastreLayers.includes("CP.CadastralZoning")) { + return; + } + if (lmeState.map.getZoom() < 15) { + clearNavigationSheetOutline(); + return; + } + lmeState.navigationSheetRefreshTimer = window.setTimeout( + () => void refreshNavigationSheetOutline(), + immediate ? 0 : 650); +} + +async function refreshNavigationSheetOutline() { + if (!lmeState.map + || !lmeState.dotNetReference + || !lmeState.cadastreLayers.includes("CP.CadastralZoning")) { + return; + } + + const center = lmeState.map.getCenter(); + const point = [center.lng, center.lat]; + if (loadedCadastralSheetAt(point)) { + clearNavigationSheetOutline(); + return; + } + if (lmeState.navigationSheetFeature?.geometry + && geometryContainsPoint(lmeState.navigationSheetFeature.geometry, point)) { + lmeState.navigationSheetLabel?.setLatLng(center); + return; + } + + const requestId = ++lmeState.navigationSheetRequestId; + try { + const xml = await lmeState.dotNetReference.invokeMethodAsync( + "GetOfficialCadastralSheetsNear", + center.lat, + center.lng); + if (requestId !== lmeState.navigationSheetRequestId + || !lmeState.cadastreLayers.includes("CP.CadastralZoning")) { + return; + } + + const candidates = gmlParcelsToGeoJson(xml).features || []; + const sheet = findContainingPolygon(point, { type: "FeatureCollection", features: candidates }) + || candidates[0]; + if (!sheet) { + return; + } + + const reference = String(sheet.properties?.NATIONALCADASTRALZONINGREFERENCE + || sheet.properties?.LABEL + || "").trim(); + const previousReference = String( + lmeState.navigationSheetFeature?.properties?.NATIONALCADASTRALZONINGREFERENCE + || lmeState.navigationSheetFeature?.properties?.LABEL + || "").trim(); + lmeState.navigationSheetFeature = sheet; + lmeState.navigationSheetLayer.clearLayers(); + L.geoJSON(sheet, { + pane: "navigationSheetPane", + interactive: false, + style: { + color: "#f59e0b", + weight: 4, + opacity: 0.95, + dashArray: "10 6", + fillColor: "#fbbf24", + fillOpacity: 0.055 + } + }).addTo(lmeState.navigationSheetLayer); + lmeState.navigationSheetLabel = L.marker(center, { + pane: "navigationSheetPane", + interactive: false, + icon: L.divIcon({ + className: "cadastral-sheet-label", + html: escapeHtml(reference ? `Foglio ${reference}` : "Foglio catastale") + }) + }).addTo(lmeState.navigationSheetLayer); + if (reference && reference !== previousReference) { + setInfo(`Confine del foglio catastale ${reference} caricato.`); + } + } catch { + if (requestId === lmeState.navigationSheetRequestId) { + setInfo("Confine del foglio catastale non disponibile dal servizio AdE. Riprovo al prossimo spostamento."); + } + } +} + +function clearNavigationSheetOutline() { + window.clearTimeout(lmeState.navigationSheetRefreshTimer); + lmeState.navigationSheetRequestId++; + lmeState.navigationSheetFeature = null; + lmeState.navigationSheetLabel = null; + lmeState.navigationSheetLayer?.clearLayers(); +} + +function loadedCadastralSheetAt(point) { + const features = [ + ...(lmeState.importedSheets?.toGeoJSON?.()?.features || []), + ...(lmeState.imported?.toGeoJSON?.()?.features || []) + ].filter(feature => ["Polygon", "MultiPolygon"].includes(feature?.geometry?.type) + && (feature.properties?._lmeOfficialSheet + || feature.properties?.NATIONALCADASTRALZONINGREFERENCE + || isSheetOrOverlayFeature(feature))); + return findContainingPolygon(point, { type: "FeatureCollection", features }); +} + function refreshCadastreLayer() { if (lmeState.cadastre) { lmeState.map.removeLayer(lmeState.cadastre); } - - const layers = activeCadastreLayers(); - lmeState.cadastre = null; - if (!layers.length) { - return; + if (lmeState.cadastreZoning) { + lmeState.map.removeLayer(lmeState.cadastreZoning); } - const cadastreLayer = L.tileLayer.wms(cadastreUrl(), { - pane: "cadastrePane", - layers: layers.join(","), - format: "image/png", - transparent: true, - version: "1.1.1", - crs: cadastreCrs, - maxZoom: 22, - opacity: lmeState.cadastreOpacity, - attribution: "Agenzia delle Entrate - CC BY 4.0" - }); + const zoningEnabled = lmeState.cadastreLayers.includes("CP.CadastralZoning"); + const layers = activeCadastreLayers().filter(layer => layer !== "CP.CadastralZoning"); + lmeState.cadastre = null; + lmeState.cadastreZoning = null; + if (layers.length) { + const cadastreLayer = L.tileLayer.wms(cadastreUrl(), { + pane: "cadastrePane", + layers: layers.join(","), + format: "image/png", + transparent: true, + version: "1.1.1", + crs: cadastreCrs, + maxZoom: 22, + opacity: lmeState.cadastreOpacity, + attribution: "Agenzia delle Entrate - CC BY 4.0" + }); + attachCadastreTileRecovery(cadastreLayer); + lmeState.cadastre = cadastreLayer.addTo(lmeState.map); + } + + if (zoningEnabled) { + const zoningLayer = L.tileLayer.wms(cadastreUrl(), { + pane: "cadastreZoningPane", + layers: "CP.CadastralZoning", + format: "image/png", + transparent: true, + version: "1.1.1", + crs: cadastreCrs, + minZoom: 8, + maxZoom: 14, + opacity: Math.max(0.8, lmeState.cadastreOpacity), + attribution: "Agenzia delle Entrate - CC BY 4.0" + }); + attachCadastreTileRecovery(zoningLayer); + lmeState.cadastreZoning = zoningLayer.addTo(lmeState.map); + scheduleNavigationSheetRefresh(true); + } +} + +function attachCadastreTileRecovery(cadastreLayer) { let errorShown = false; cadastreLayer.on("tileerror", event => { const tile = event?.tile; @@ -5459,8 +5620,6 @@ function refreshCadastreLayer() { cadastreLayer.on("load", () => { errorShown = false; }); - - lmeState.cadastre = cadastreLayer.addTo(lmeState.map); } function activeCadastreLayers() {