From abc9516a0de4425d7b9622b0b5f4fdc09dfa1829 Mon Sep 17 00:00:00 2001 From: livio Date: Fri, 11 Sep 2026 20:42:40 +0200 Subject: [PATCH] Overlay selected parcels on WMS preview --- Components/Pages/Home.razor | 7 ++- LMEMappeCatastaliApp.csproj | 4 +- README.md | 1 + Services/AzureMapsService.cs | 2 +- wwwroot/css/app.css | 42 +++++++++++++++--- wwwroot/js/lme-map.js | 84 +++++++++++++++++++++++++++++++++--- 6 files changed, 125 insertions(+), 15 deletions(-) diff --git a/Components/Pages/Home.razor b/Components/Pages/Home.razor index 4444ed7..868b408 100644 --- a/Components/Pages/Home.razor +++ b/Components/Pages/Home.razor @@ -242,13 +242,16 @@
Vista WMS catastale - Immagine ufficiale AdE dell'area visibile, senza mappa di sfondo + Immagine ufficiale AdE con le particelle evidenziate dall'app
Caricamento... - Vista WMS catastale dell'area corrente +
diff --git a/LMEMappeCatastaliApp.csproj b/LMEMappeCatastaliApp.csproj index 07110b8..422416f 100644 --- a/LMEMappeCatastaliApp.csproj +++ b/LMEMappeCatastaliApp.csproj @@ -28,8 +28,8 @@ com.companyname.lmemappecatastaliapp - 0.2.4 - 8 + 0.2.5 + 9 None diff --git a/README.md b/README.md index ccf6abe..0c06560 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ App .NET MAUI Blazor Hybrid per Android, iOS, Mac Catalyst e Windows, pensata pe - Le particelle cercate possono essere aggiunte una dopo l'altra, mantenendo contemporaneamente sulla mappa tutti i confini selezionati. - Comandi separati per rimuovere le particelle selezionate o pulire importazioni, disegni, POI e traccia GPS. - Anteprima WMS ufficiale mostrata dentro l'app senza aprire una nuova finestra Android. +- Confini delle particelle selezionate e della particella GPS sovrapposti anche all'anteprima WMS corrente. - Collegamenti separati al Geoportale pubblico e all'Area riservata AdE per CAPTCHA, SPID/CIE e download ufficiali. ## Limite ufficiale AdE da considerare diff --git a/Services/AzureMapsService.cs b/Services/AzureMapsService.cs index 2824685..f53bfaa 100644 --- a/Services/AzureMapsService.cs +++ b/Services/AzureMapsService.cs @@ -13,7 +13,7 @@ public sealed class AzureMapsService public AzureMapsService() { - _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/0.2.4"); + _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/0.2.5"); _httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/json"); } diff --git a/wwwroot/css/app.css b/wwwroot/css/app.css index 2acb340..8381965 100644 --- a/wwwroot/css/app.css +++ b/wwwroot/css/app.css @@ -527,14 +527,46 @@ label { color: var(--muted); } -.wms-preview-body img { - display: block; - max-width: 100%; - max-height: 100%; - object-fit: contain; +.wms-preview-map { + position: relative; + flex: 0 0 auto; + overflow: hidden; background: #fff; } +.wms-preview-map[hidden] { + display: none; +} + +.wms-preview-map img, +.wms-preview-map svg { + display: block; + width: 100%; + height: 100%; +} + +.wms-preview-map svg { + position: absolute; + inset: 0; + pointer-events: none; +} + +.wms-preview-parcel { + fill: rgba(251, 191, 36, 0.28); + fill-rule: evenodd; + stroke: #ef4444; + stroke-width: 4px; + vector-effect: non-scaling-stroke; +} + +.wms-preview-gps-parcel { + fill: rgba(56, 189, 248, 0.24); + fill-rule: evenodd; + stroke: #1d4ed8; + stroke-width: 4px; + vector-effect: non-scaling-stroke; +} + .bottom-tabs { position: absolute; right: 0; diff --git a/wwwroot/js/lme-map.js b/wwwroot/js/lme-map.js index 56030ed..0902239 100644 --- a/wwwroot/js/lme-map.js +++ b/wwwroot/js/lme-map.js @@ -265,40 +265,114 @@ function exportCadastreView() { bbox }); const preview = document.getElementById("wmsPreview"); + const previewBody = preview?.querySelector(".wms-preview-body"); + const previewMap = document.getElementById("wmsPreviewMap"); const image = document.getElementById("wmsPreviewImage"); + const overlay = document.getElementById("wmsPreviewOverlay"); const loading = document.getElementById("wmsPreviewLoading"); - if (!preview || !image || !loading) { + if (!preview || !previewBody || !previewMap || !image || !overlay || !loading) { setInfo("Vista WMS interna non disponibile."); return; } + preview.hidden = false; + const availableWidth = Math.max(1, previewBody.clientWidth - 24); + const availableHeight = Math.max(1, previewBody.clientHeight - 24); + const displayScale = Math.min(1, availableWidth / width, availableHeight / height); + previewMap.style.width = `${Math.max(1, Math.round(width * displayScale))}px`; + previewMap.style.height = `${Math.max(1, Math.round(height * displayScale))}px`; + previewMap.hidden = true; + renderPreviewParcelOverlay(overlay, bounds, width, height); + loading.textContent = "Caricamento..."; loading.hidden = false; - image.hidden = true; image.onload = () => { loading.hidden = true; - image.hidden = false; - setInfo(`Vista WMS caricata con ${layers.length} layer.`); + previewMap.hidden = false; + const highlightedCount = lmeState.selectedParcelsLayer.getLayers().length + + lmeState.gpsParcelLayer.getLayers().length; + setInfo(`Vista WMS caricata con ${layers.length} layer e ${highlightedCount} particelle evidenziate.`); }; image.onerror = () => { + previewMap.hidden = true; loading.textContent = "Il servizio WMS non ha restituito l'immagine. Riprova tra poco."; setInfo("Vista WMS non disponibile."); }; - preview.hidden = false; image.src = `${cadastreUrl()}${params}`; } function closeCadastrePreview() { const preview = document.getElementById("wmsPreview"); + const previewMap = document.getElementById("wmsPreviewMap"); const image = document.getElementById("wmsPreviewImage"); + const overlay = document.getElementById("wmsPreviewOverlay"); if (preview) { preview.hidden = true; } + if (previewMap) { + previewMap.hidden = true; + } if (image) { image.onload = null; image.onerror = null; image.removeAttribute("src"); } + overlay?.replaceChildren(); +} + +function renderPreviewParcelOverlay(svg, bounds, width, height) { + svg.replaceChildren(); + svg.setAttribute("viewBox", `0 0 ${width} ${height}`); + appendPreviewGeometry(svg, lmeState.selectedParcelsLayer.toGeoJSON(), "wms-preview-parcel", bounds, width, height); + appendPreviewGeometry(svg, lmeState.gpsParcelLayer.toGeoJSON(), "wms-preview-gps-parcel", bounds, width, height); +} + +function appendPreviewGeometry(svg, featureCollection, className, bounds, width, height) { + const namespace = "http://www.w3.org/2000/svg"; + for (const feature of featureCollection?.features || []) { + for (const polygon of geometryPolygons(feature.geometry)) { + const pathData = polygon + .map(ring => ring + .map((coordinate, index) => { + const point = previewPixel(coordinate, bounds, width, height); + return `${index ? "L" : "M"}${point.x.toFixed(2)} ${point.y.toFixed(2)}`; + }) + .join(" ") + " Z") + .join(" "); + if (!pathData.trim()) { + continue; + } + const path = document.createElementNS(namespace, "path"); + path.setAttribute("d", pathData); + path.setAttribute("class", className); + svg.appendChild(path); + } + } +} + +function geometryPolygons(geometry) { + if (!geometry) { + return []; + } + if (geometry.type === "Polygon") { + return [geometry.coordinates || []]; + } + if (geometry.type === "MultiPolygon") { + return geometry.coordinates || []; + } + if (geometry.type === "GeometryCollection") { + return (geometry.geometries || []).flatMap(geometryPolygons); + } + return []; +} + +function previewPixel(coordinate, bounds, width, height) { + const longitude = Number(coordinate?.[0]); + const latitude = Number(coordinate?.[1]); + return { + x: ((longitude - bounds.getWest()) / (bounds.getEast() - bounds.getWest())) * width, + y: ((bounds.getNorth() - latitude) / (bounds.getNorth() - bounds.getSouth())) * height + }; } function bindFileImport() {