From 85e1c93d8ab56b530438bcdafc52df9e662fd4d1 Mon Sep 17 00:00:00 2001 From: livio Date: Sun, 4 Oct 2026 15:18:42 +0200 Subject: [PATCH] Add mapped Pro fiducial point layer --- Components/Pages/Home.razor | 116 +++++++++++++++++++++---- LMEMappeCatastaliApp.csproj | 4 +- Services/FiducialPointService.cs | 114 ++++++++++++++++++++++++ wwwroot/js/lme-map.js | 145 ++++++++++++++++++++++++++++++- 4 files changed, 360 insertions(+), 19 deletions(-) diff --git a/Components/Pages/Home.razor b/Components/Pages/Home.razor index d883467..a059e41 100644 --- a/Components/Pages/Home.razor +++ b/Components/Pages/Home.razor @@ -96,6 +96,16 @@ Particelle e geometrie verdi di file e progetti + @if (CanUse(AppFeatures.FiducialPoints)) + { + + }
Ordine livelli e oggetti @@ -253,21 +263,35 @@ @(FiducialBusy ? "Ricerca..." : "Cerca Punti Fiduciali") + + + @if (!string.IsNullOrWhiteSpace(FiducialMessage)) {
@FiducialMessage
} - @if (FiducialPoints.Count > 0) + @if (MappedFiducialPoints.Count > 0) {
-

Punti trovati (@FiducialPoints.Count)

+

Punti cartografici (@MappedFiducialPoints.Count)

+
- @foreach (var point in FiducialPoints) + @foreach (var point in MappedFiducialPoints) {
@point.Identifier - + Nord/X: @FormatCoordinate(point.CadastralNorth) · Est/Y: @FormatCoordinate(point.CadastralEast) + @if (!string.IsNullOrWhiteSpace(point.MonographUrl)) + { + + }
}
@@ -275,8 +299,8 @@ }
- Fonte ufficiale Agenzia delle Entrate -

L'elenco e i PDF arrivano dal servizio pubblico delle monografie. Il layer sulla mappa sarà abilitato soltanto quando sono disponibili coordinate geografiche trasformate e verificabili.

+ Monografie ufficiali, posizione sperimentale +

Elenco e PDF provengono dall'Agenzia delle Entrate; il posizionamento sperimentale usa il servizio cartografico GloboGIS. Le coordinate sono un ausilio: verificale prima di qualsiasi uso topografico o professionale.

}
@@ -830,6 +854,7 @@ private string SelectedFiducialMunicipalityCode { get; set; } = string.Empty; private string SelectedFiducialSheet { get; set; } = string.Empty; private string FiducialMessage { get; set; } = string.Empty; + private string FiducialColor { get; set; } = "#dc2626"; private string Status { get; set; } = "online"; private string SelectedBaseLayer { get; set; } = "satellite"; private string GoogleMapsApiKey { get; set; } = string.Empty; @@ -847,6 +872,7 @@ private bool GpsOverlayCollapsed { get; set; } private bool ShowGpsPosition { get; set; } = true; private bool ShowSelectedParcels { get; set; } = true; + private bool ShowFiducialPoints { get; set; } = true; private bool ShowImportedData { get; set; } = true; private bool DrawToolsVisible { get; set; } private bool TrailEnabled { get; set; } @@ -910,6 +936,7 @@ private IReadOnlyList FiducialMunicipalities { get; set; } = []; private IReadOnlyList FiducialSheets { get; set; } = []; private IReadOnlyList FiducialPoints { get; set; } = []; + private IReadOnlyList MappedFiducialPoints { get; set; } = []; private IReadOnlyList Projects { get; set; } = []; private IReadOnlyList ProjectVersions { get; set; } = []; private MapProjectSummary? SelectedProject => Projects.FirstOrDefault(item => item.Id == SelectedProjectId); @@ -1257,6 +1284,7 @@ FiducialMunicipalities = []; FiducialSheets = []; FiducialPoints = []; + MappedFiducialPoints = []; if (string.IsNullOrWhiteSpace(SelectedFiducialProvinceCode)) { FiducialMessage = string.Empty; @@ -1286,6 +1314,7 @@ SelectedFiducialSheet = string.Empty; FiducialSheets = []; FiducialPoints = []; + MappedFiducialPoints = []; if (string.IsNullOrWhiteSpace(SelectedFiducialMunicipalityCode)) { FiducialMessage = string.Empty; @@ -1322,17 +1351,50 @@ FiducialBusy = true; FiducialPoints = []; - FiducialMessage = "Interrogo il servizio ufficiale delle monografie..."; + MappedFiducialPoints = []; + FiducialMessage = "Recupero monografie e coordinate dei PF..."; try { - FiducialPoints = await FiducialPointService.GetFiducialsAsync( - province.Name, - municipality.Name, + string? monographError = null; + try + { + FiducialPoints = await FiducialPointService.GetFiducialsAsync( + province.Name, + municipality.Name, + municipality.Code, + SelectedFiducialSheet); + } + catch (Exception error) + { + monographError = error.GetBaseException().Message; + } + + var mapped = await FiducialPointService.GetMappedFiducialsAsync( municipality.Code, SelectedFiducialSheet); - FiducialMessage = FiducialPoints.Count == 0 - ? "Nessun Punto Fiduciale pubblicato per il foglio selezionato." - : $"Trovati {FiducialPoints.Count} Punti Fiduciali."; + var monographs = FiducialPoints.ToDictionary( + item => item.Identifier, + item => item.MonographUrl, + StringComparer.OrdinalIgnoreCase); + var currentMappedPoints = monographs.Count == 0 + ? mapped + : mapped.Where(point => monographs.ContainsKey(point.Identifier)).ToArray(); + MappedFiducialPoints = currentMappedPoints + .Select(point => point with + { + MonographUrl = monographs.GetValueOrDefault(point.Identifier, string.Empty) + }) + .ToArray(); + + await JS.InvokeVoidAsync( + "lmeMap.setFiducialPoints", + MappedFiducialPoints, + FiducialColor, + ShowFiducialPoints); + FiducialMessage = MappedFiducialPoints.Count == 0 + ? "Nessun Punto Fiduciale georiferito per il foglio selezionato." + : $"Trovati {MappedFiducialPoints.Count} PF cartografici e {FiducialPoints.Count} monografie ufficiali." + + (monographError is null ? string.Empty : " Le monografie non sono al momento raggiungibili."); } catch (Exception error) { @@ -1344,11 +1406,11 @@ } } - private async Task DownloadFiducialMonograph(FiducialPointEntry point) + private async Task DownloadFiducialMonograph(string monographUrl) { try { - await Launcher.Default.OpenAsync(new Uri(point.MonographUrl)); + await Launcher.Default.OpenAsync(new Uri(monographUrl)); } catch (Exception error) { @@ -1356,6 +1418,24 @@ } } + private async Task ApplyFiducialVisibility() + { + SaveFlagPreferences(); + await JS.InvokeVoidAsync("lmeMap.setFiducialPointsVisible", ShowFiducialPoints); + } + + private async Task ApplyFiducialColor() + { + SaveFlagPreferences(); + await JS.InvokeVoidAsync("lmeMap.setFiducialColor", FiducialColor); + } + + private async Task FitFiducialPoints() => + await JS.InvokeVoidAsync("lmeMap.fitFiducialPoints"); + + private static string FormatCoordinate(double? value) => + value?.ToString("0.000", System.Globalization.CultureInfo.InvariantCulture) ?? "-"; + private async Task OnMunicipalityChanged(ChangeEventArgs args) { SelectedMunicipalityCode = args.Value?.ToString() ?? string.Empty; @@ -2581,6 +2661,8 @@ DrawToolsVisible = GetFlagPreference("draw_tools_visible", false); ShowGpsPosition = GetFlagPreference("show_gps_position", true); ShowSelectedParcels = GetFlagPreference("show_selected_parcels", true); + ShowFiducialPoints = GetFlagPreference("show_fiducial_points", true); + FiducialColor = Preferences.Default.Get($"{FlagPreferencePrefix}fiducial_color", "#dc2626"); TrailEnabled = GetFlagPreference("trail_enabled", false); ExportImported = GetFlagPreference("export_imported", true); ExportImportedSheets = GetFlagPreference("export_imported_sheets", true); @@ -2621,6 +2703,8 @@ SetFlagPreference("draw_tools_visible", DrawToolsVisible); SetFlagPreference("show_gps_position", ShowGpsPosition); SetFlagPreference("show_selected_parcels", ShowSelectedParcels); + SetFlagPreference("show_fiducial_points", ShowFiducialPoints); + Preferences.Default.Set($"{FlagPreferencePrefix}fiducial_color", FiducialColor); SetFlagPreference("trail_enabled", TrailEnabled); SetFlagPreference("export_imported", ExportImported); SetFlagPreference("export_imported_sheets", ExportImportedSheets); @@ -2658,6 +2742,8 @@ await JS.InvokeVoidAsync("lmeMap.setDrawToolsVisible", DrawToolsVisible); await JS.InvokeVoidAsync("lmeMap.setGpsPositionVisible", ShowGpsPosition); await JS.InvokeVoidAsync("lmeMap.setSelectedParcelsVisible", ShowSelectedParcels); + await JS.InvokeVoidAsync("lmeMap.setFiducialColor", FiducialColor); + await JS.InvokeVoidAsync("lmeMap.setFiducialPointsVisible", ShowFiducialPoints); await JS.InvokeVoidAsync("lmeMap.toggleTrail", TrailEnabled); foreach (var layer in CadastralLayers) diff --git a/LMEMappeCatastaliApp.csproj b/LMEMappeCatastaliApp.csproj index 77c4a49..8fc6e66 100644 --- a/LMEMappeCatastaliApp.csproj +++ b/LMEMappeCatastaliApp.csproj @@ -28,8 +28,8 @@ it.liviomerola.lmemappecatastali.pro - 1.0.12 - 112 + 1.0.13 + 113 $(ApplicationDisplayVersion) diff --git a/Services/FiducialPointService.cs b/Services/FiducialPointService.cs index 5abb72d..bfcfa7b 100644 --- a/Services/FiducialPointService.cs +++ b/Services/FiducialPointService.cs @@ -1,5 +1,6 @@ using System.Net; using System.Net.Http.Headers; +using System.Text.Json; using System.Text.RegularExpressions; namespace LMEMappeCatastaliApp.Services; @@ -7,6 +8,7 @@ namespace LMEMappeCatastaliApp.Services; public sealed partial class FiducialPointService { private const string ServiceBaseUrl = "https://www1.agenziaentrate.gov.it/servizi/Monografie/"; + private const string MappedPointsUrl = "https://cartografia.globogis.it/arcgis/rest/services/fiduciali/Punti_fiduciali/MapServer/2/query"; private readonly HttpClient _httpClient = new() { Timeout = TimeSpan.FromSeconds(30) }; public FiducialPointService() @@ -77,6 +79,105 @@ public sealed partial class FiducialPointService .ToArray(); } + public async Task> GetMappedFiducialsAsync( + string municipalityCode, + string sheet, + CancellationToken cancellationToken = default) + { + var municipality = NormalizeCode(municipalityCode); + if (!int.TryParse(new string(sheet.Where(char.IsDigit).ToArray()), out var sheetNumber)) + { + throw new ArgumentException("Numero di foglio non valido.", nameof(sheet)); + } + + var query = new Dictionary + { + ["f"] = "json", + ["where"] = $"COMUNENAZI='{municipality}' AND FOGLIO={sheetNumber}", + ["outFields"] = "ETICHETTA,PARTICELLA,NORD,EST,ATTENDIBIL,QUOTA,DATAAGGIOR,IDORIGINE,URL_AGENZI,FOGLIO,ALLEGATO,FIDUCIALE", + ["returnGeometry"] = "true", + ["outSR"] = "3857", + ["resultRecordCount"] = "500" + }; + var url = $"{MappedPointsUrl}?{string.Join('&', query.Select(item => $"{item.Key}={Uri.EscapeDataString(item.Value)}"))}"; + using var response = await _httpClient.GetAsync(url, cancellationToken); + response.EnsureSuccessStatusCode(); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken); + using var document = await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken); + if (document.RootElement.TryGetProperty("error", out var error)) + { + throw new InvalidOperationException(error.TryGetProperty("message", out var message) + ? message.GetString() + : "Servizio cartografico PF non disponibile."); + } + + if (!document.RootElement.TryGetProperty("features", out var features)) + { + return []; + } + + return features.EnumerateArray() + .Select(feature => ParseMappedPoint(feature, municipality)) + .Where(point => point is not null) + .Cast() + .DistinctBy(point => point.Identifier, StringComparer.OrdinalIgnoreCase) + .OrderBy(point => point.Identifier, StringComparer.OrdinalIgnoreCase) + .ToArray(); + } + + private static MappedFiducialPoint? ParseMappedPoint(JsonElement feature, string municipalityCode) + { + if (!feature.TryGetProperty("geometry", out var geometry) + || !TryGetDouble(geometry, "x", out var x) + || !TryGetDouble(geometry, "y", out var y)) + { + return null; + } + + var attributes = feature.GetProperty("attributes"); + var label = GetString(attributes, "ETICHETTA"); + var identifier = FormatIdentifier(label); + if (identifier == label) + { + var sheet = GetString(attributes, "FOGLIO").PadLeft(4, '0'); + var number = GetString(attributes, "FIDUCIALE").PadLeft(2, '0'); + identifier = $"PF{number}/{sheet}/{municipalityCode}"; + } + + const double earthRadius = 6378137d; + var longitude = x / earthRadius * 180d / Math.PI; + var latitude = (2d * Math.Atan(Math.Exp(y / earthRadius)) - Math.PI / 2d) * 180d / Math.PI; + return new MappedFiducialPoint( + identifier, + latitude, + longitude, + GetNullableDouble(attributes, "NORD"), + GetNullableDouble(attributes, "EST"), + GetString(attributes, "ATTENDIBIL"), + GetNullableDouble(attributes, "QUOTA"), + GetString(attributes, "PARTICELLA"), + GetString(attributes, "DATAAGGIOR"), + GetString(attributes, "IDORIGINE")); + } + + private static bool TryGetDouble(JsonElement element, string name, out double value) + { + value = 0; + return element.TryGetProperty(name, out var property) + && (property.ValueKind == JsonValueKind.Number + ? property.TryGetDouble(out value) + : double.TryParse(property.ToString(), System.Globalization.NumberStyles.Float, + System.Globalization.CultureInfo.InvariantCulture, out value)); + } + + private static double? GetNullableDouble(JsonElement element, string name) => + TryGetDouble(element, name, out var value) ? value : null; + + private static string GetString(JsonElement element, string name) => + element.TryGetProperty(name, out var property) && property.ValueKind != JsonValueKind.Null + ? property.ToString().Trim() + : string.Empty; + private async Task GetHtmlAsync(string relativeUrl, CancellationToken cancellationToken) { using var response = await _httpClient.GetAsync(ServiceBaseUrl + relativeUrl, cancellationToken); @@ -146,3 +247,16 @@ public sealed partial class FiducialPointService public sealed record FiducialAreaOption(string Code, string Name); public sealed record FiducialPointEntry(string Identifier, string MonographUrl); + +public sealed record MappedFiducialPoint( + string Identifier, + double Latitude, + double Longitude, + double? CadastralNorth, + double? CadastralEast, + string Reliability, + double? Elevation, + string Parcel, + string UpdatedAt, + string OriginId, + string MonographUrl = ""); diff --git a/wwwroot/js/lme-map.js b/wwwroot/js/lme-map.js index e637e56..2bd3a5f 100644 --- a/wwwroot/js/lme-map.js +++ b/wwwroot/js/lme-map.js @@ -9,6 +9,7 @@ const lmeState = { "gpsTrail", "gpsParcels", "selectedParcels", + "fiducials", "importedSheets", "imported", "cadastre" @@ -50,6 +51,9 @@ const lmeState = { gpsTrail: null, selectedParcelsLayer: null, selectedParcelsVisible: true, + fiducials: null, + fiducialsVisible: true, + fiducialColor: "#dc2626", gpsParcelLayer: null, gpsTrailEnabled: false, gpsTrailPoints: [], @@ -131,6 +135,10 @@ window.lmeMap = { closeCadastrePreview, clearSelectedParcels, setSelectedParcelsVisible, + setFiducialPoints, + setFiducialPointsVisible, + setFiducialColor, + fitFiducialPoints, clearMapElements, fitImported, clearImported, @@ -310,6 +318,7 @@ function init(dotNetReference) { lmeState.importedMeasurements = L.layerGroup().addTo(lmeState.map); lmeState.drawingMeasurements = L.layerGroup().addTo(lmeState.map); lmeState.selectedParcelsLayer = L.featureGroup().addTo(lmeState.map); + lmeState.fiducials = L.featureGroup().addTo(lmeState.map); lmeState.gpsParcelLayer = L.featureGroup().addTo(lmeState.map); const navigationSheetPane = lmeState.map.createPane("navigationSheetPane"); navigationSheetPane.style.zIndex = "465"; @@ -1339,6 +1348,7 @@ function managedLayerPanes() { gpsTrail: "gpsTrailPane", gpsParcels: "gpsParcelsPane", selectedParcels: "selectedParcelsPane", + fiducials: "fiducialsPane", importedSheets: "importedSheetPane", imported: "importedPane", cadastre: "cadastrePane" @@ -1427,6 +1437,7 @@ function layerManagerCategories() { { key: "gpsTrail", label: "Traccia GPS", roots: [lmeState.gpsTrail].filter(Boolean) }, { key: "gpsParcels", label: "Particella rilevata dal GPS", roots: [lmeState.gpsParcelLayer].filter(Boolean) }, { key: "selectedParcels", label: "Particelle catastali evidenziate", roots: [lmeState.selectedParcelsLayer].filter(Boolean) }, + { key: "fiducials", label: "Punti fiduciali", roots: [lmeState.fiducials].filter(Boolean) }, { key: "importedSheets", label: "Cornici e fogli importati", roots: [lmeState.importedSheets].filter(Boolean), blocks: true }, { key: "imported", label: "Progetti e dati importati", roots: [lmeState.imported].filter(Boolean), blocks: true }, { key: "cadastre", label: "Layer catastali AdE", roots: [lmeState.cadastre].filter(Boolean) } @@ -3271,6 +3282,8 @@ function setManagedCategoryVisible(category, visible) { lmeState.importedSheetsVisible = visible; } else if (category.key === "selectedParcels") { lmeState.selectedParcelsVisible = visible; + } else if (category.key === "fiducials") { + lmeState.fiducialsVisible = visible; } else if (category.key === "gpsPosition") { lmeState.gpsPositionVisible = visible; } @@ -3344,6 +3357,9 @@ function managedCategoryEnabled(categoryKey) { if (categoryKey === "selectedParcels") { return lmeState.selectedParcelsVisible; } + if (categoryKey === "fiducials") { + return lmeState.fiducialsVisible; + } return true; } @@ -4721,7 +4737,7 @@ function circleToPolygon(center, radiusMeters) { function createProjectSnapshot(includeCadastralParcels) { const center = lmeState.map.getCenter(); return JSON.stringify({ - schemaVersion: 5, + schemaVersion: 6, view: { latitude: center.lat, longitude: center.lng, @@ -4731,6 +4747,7 @@ function createProjectSnapshot(includeCadastralParcels) { imported: managedGroupGeoJson(lmeState.imported, "imported"), importedSheets: managedGroupGeoJson(lmeState.importedSheets, "importedSheets"), importedSheetsVisible: lmeState.importedSheetsVisible, + fiducials: managedGroupGeoJson(lmeState.fiducials, "fiducials"), displayOptions: captureDisplayOptions(), selectedParcels: includeCadastralParcels ? managedGroupGeoJson(lmeState.selectedParcelsLayer, "selectedParcels") @@ -4766,6 +4783,7 @@ function loadProjectSnapshot(raw, projectName) { lmeState.drawingMeasurements.clearLayers(); lmeState.importedMeasurements.clearLayers(); lmeState.selectedParcelsLayer.clearLayers(); + lmeState.fiducials.clearLayers(); lmeState.gpsParcelLayer.clearLayers(); const restoredDrawings = L.geoJSON(data.drawings || emptyFeatureCollection(), { @@ -4826,6 +4844,10 @@ function loadProjectSnapshot(raw, projectName) { applyStyleFromProperties(layer); } }).addTo(lmeState.selectedParcelsLayer); + L.geoJSON(data.fiducials || emptyFeatureCollection(), { + pointToLayer: (feature, latlng) => createFiducialMarker(feature, latlng), + onEachFeature: (feature, layer) => bindFiducialPopup(feature, layer) + }).addTo(lmeState.fiducials); if (lmeState.importedSheetsVisible) { void ensureImportedSheetOutlines().then(() => { applyImportedSheetVisibility(); @@ -4848,6 +4870,7 @@ function loadProjectSnapshot(raw, projectName) { lmeState.drawings, lmeState.imported, lmeState.selectedParcelsLayer, + lmeState.fiducials, lmeState.gpsTrail ]; if (lmeState.importedSheetsVisible) { @@ -4898,6 +4921,8 @@ function captureDisplayOptions() { drawToolsVisible: Boolean(lmeState.drawControl), gpsPositionVisible: lmeState.gpsPositionVisible, selectedParcelsVisible: lmeState.selectedParcelsVisible, + fiducialsVisible: lmeState.fiducialsVisible, + fiducialColor: lmeState.fiducialColor, layerOrder: [...lmeState.layerOrder], layerObjectOrder: { ...lmeState.managerObjectOrder }, layerCategoryNames: { ...lmeState.managerCategoryNames }, @@ -4965,6 +4990,10 @@ function applyProjectDisplayOptions(options) { } setGpsPositionVisible(options.gpsPositionVisible !== false); setSelectedParcelsVisible(options.selectedParcelsVisible !== false); + if (typeof options.fiducialColor === "string") { + setFiducialColor(options.fiducialColor); + } + setFiducialPointsVisible(options.fiducialsVisible !== false); toggleTrail(Boolean(options.trailEnabled)); setDrawToolsVisible(Boolean(options.drawToolsVisible)); applyStoredLayerManagerVisibility(); @@ -5634,6 +5663,116 @@ function activeCadastreLayers() { return drawOrder.filter(layer => lmeState.cadastreLayers.includes(layer)); } +function setFiducialPoints(points, color, visible = true) { + if (!lmeState.fiducials) return; + const source = Array.isArray(points) ? points : []; + if (source.length || lmeState.fiducials.getLayers().length) rememberMapChange(); + lmeState.fiducials.clearLayers(); + clearManagedCategoryState("fiducials"); + if (typeof color === "string" && /^#[0-9a-f]{6}$/i.test(color)) { + lmeState.fiducialColor = color; + } + source.forEach((point, index) => { + const latitude = Number(point.latitude ?? point.Latitude); + const longitude = Number(point.longitude ?? point.Longitude); + if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) return; + const feature = { + type: "Feature", + geometry: { type: "Point", coordinates: [longitude, latitude] }, + properties: { + name: String(point.identifier ?? point.Identifier ?? `PF ${index + 1}`), + _lmeDisplayName: String(point.identifier ?? point.Identifier ?? `PF ${index + 1}`), + _lmeSource: "Punti fiduciali georiferiti", + _lmeColor: lmeState.fiducialColor, + _lmeFillColor: lmeState.fiducialColor, + _lmeWeight: 3, + _lmeOpacity: 1, + _lmeFillOpacity: 0.72, + cadastralNorth: point.cadastralNorth ?? point.CadastralNorth ?? null, + cadastralEast: point.cadastralEast ?? point.CadastralEast ?? null, + reliability: String(point.reliability ?? point.Reliability ?? ""), + elevation: point.elevation ?? point.Elevation ?? null, + parcel: String(point.parcel ?? point.Parcel ?? ""), + updatedAt: String(point.updatedAt ?? point.UpdatedAt ?? ""), + originId: String(point.originId ?? point.OriginId ?? ""), + monographUrl: String(point.monographUrl ?? point.MonographUrl ?? "") + } + }; + const marker = createFiducialMarker(feature, L.latLng(latitude, longitude)); + marker.feature = feature; + bindFiducialPopup(feature, marker); + lmeState.fiducials.addLayer(marker); + }); + setFiducialPointsVisible(visible); + refreshLayerManager(); + if (source.length) finishMapChange(); +} + +function createFiducialMarker(feature, latlng) { + const properties = feature?.properties || {}; + const color = properties._lmeColor || lmeState.fiducialColor; + return L.circleMarker(latlng, { + pane: "fiducialsPane", + radius: 8, + color, + fillColor: properties._lmeFillColor || color, + weight: Number(properties._lmeWeight) || 3, + opacity: Number.isFinite(Number(properties._lmeOpacity)) ? Number(properties._lmeOpacity) : 1, + fillOpacity: Number.isFinite(Number(properties._lmeFillOpacity)) ? Number(properties._lmeFillOpacity) : 0.72 + }); +} + +function bindFiducialPopup(feature, layer) { + const properties = feature?.properties || {}; + const rows = []; + if (properties.cadastralNorth != null) rows.push(`Nord/X catastale: ${escapeHtml(properties.cadastralNorth)}`); + if (properties.cadastralEast != null) rows.push(`Est/Y catastale: ${escapeHtml(properties.cadastralEast)}`); + if (properties.reliability) rows.push(`Attendibilita: ${escapeHtml(properties.reliability)}`); + if (properties.elevation != null) rows.push(`Quota: ${escapeHtml(properties.elevation)}`); + if (properties.parcel) rows.push(`Particella: ${escapeHtml(properties.parcel)}`); + const url = String(properties.monographUrl || ""); + const monograph = /^https:\/\//i.test(url) + ? `

Apri monografia ufficiale

` + : ""; + layer.bindPopup(`${escapeHtml(properties._lmeDisplayName || properties.name || "Punto fiduciale")}

${rows.join("
")}

${monograph}Posizione cartografica sperimentale GloboGIS: verificare prima dell'uso topografico.`); + applyStyleFromProperties(layer); +} + +function setFiducialPointsVisible(enabled) { + lmeState.fiducialsVisible = Boolean(enabled); + if (!lmeState.map || !lmeState.fiducials) return; + if (lmeState.fiducialsVisible) { + lmeState.managerHiddenCategories.delete("fiducials"); + if (!lmeState.map.hasLayer(lmeState.fiducials)) lmeState.fiducials.addTo(lmeState.map); + lmeState.fiducials.bringToFront?.(); + } else { + lmeState.managerHiddenCategories.add("fiducials"); + if (lmeState.map.hasLayer(lmeState.fiducials)) lmeState.map.removeLayer(lmeState.fiducials); + } + refreshLayerManager(); +} + +function setFiducialColor(color) { + if (!/^#[0-9a-f]{6}$/i.test(String(color))) return; + lmeState.fiducialColor = color; + lmeState.fiducials?.eachLayer(layer => { + if (layer.feature?.properties) { + layer.feature.properties._lmeColor = color; + layer.feature.properties._lmeFillColor = color; + } + applyStyleFromProperties(layer); + }); + refreshLayerManager(); +} + +function fitFiducialPoints() { + if (!lmeState.fiducials?.getLayers().length) { + setInfo("Nessun Punto Fiduciale da inquadrare."); + return; + } + fitLayer(lmeState.fiducials); +} + function clearSelectedParcels() { const count = managedGroupGeoJson(lmeState.selectedParcelsLayer, "selectedParcels").features.length; if (count) rememberMapChange(); @@ -5672,6 +5811,7 @@ function setSelectedParcelsVisible(enabled) { function clearMapElements() { const count = managedGroupGeoJson(lmeState.selectedParcelsLayer, "selectedParcels").features.length + managedGroupGeoJson(lmeState.gpsParcelLayer, "gpsParcels").features.length + + managedGroupGeoJson(lmeState.fiducials, "fiducials").features.length + managedGroupGeoJson(lmeState.imported, "imported").features.length + managedGroupGeoJson(lmeState.importedSheets, "importedSheets").features.length + managedGroupGeoJson(lmeState.drawings, "drawings").features.length @@ -5680,7 +5820,7 @@ function clearMapElements() { setInfo("La mappa non contiene elementi da pulire."); return; } - if (!window.confirm("Rimuovere particelle evidenziate, file importati, disegni, POI e traccia GPS dalla mappa?")) { + if (!window.confirm("Rimuovere particelle evidenziate, Punti Fiduciali, file importati, disegni, POI e traccia GPS dalla mappa?")) { return; } @@ -5688,6 +5828,7 @@ function clearMapElements() { resetLayerManagerProjectState(); lmeState.selectedParcelsLayer.clearLayers(); lmeState.gpsParcelLayer.clearLayers(); + lmeState.fiducials.clearLayers(); lmeState.imported.clearLayers(); lmeState.importedSheets.clearLayers(); lmeState.drawings.clearLayers();