Add mapped Pro fiducial point layer

This commit is contained in:
livio committed 2026-10-04 15:18:42 +02:00
1 parent 7b642afe2d
commit 85e1c93d8a
4 files changed
+357 -16

No files matched your search

+98 -12
View File
@@ -96,6 +96,16 @@
<small>Particelle e geometrie verdi di file e progetti</small> <small>Particelle e geometrie verdi di file e progetti</small>
</span> </span>
</label> </label>
@if (CanUse(AppFeatures.FiducialPoints))
{
<label class="map-layer-option">
<input type="checkbox" @bind="ShowFiducialPoints" @bind:after="ApplyFiducialVisibility" />
<span>
<strong>Punti fiduciali</strong>
<small>PF georiferiti caricati nella ricerca Pro</small>
</span>
</label>
}
<section class="layer-tree-section"> <section class="layer-tree-section">
<header> <header>
<strong>Ordine livelli e oggetti</strong> <strong>Ordine livelli e oggetti</strong>
@@ -253,21 +263,35 @@
@(FiducialBusy ? "Ricerca..." : "Cerca Punti Fiduciali") @(FiducialBusy ? "Ricerca..." : "Cerca Punti Fiduciali")
</button> </button>
<label class="toggle-line">
<input type="checkbox" @bind="ShowFiducialPoints" @bind:after="ApplyFiducialVisibility" />
<span>Mostra i PF sulla mappa</span>
</label>
<label>
Colore PF
<input type="color" @bind="FiducialColor" @bind:after="ApplyFiducialColor" />
</label>
@if (!string.IsNullOrWhiteSpace(FiducialMessage)) @if (!string.IsNullOrWhiteSpace(FiducialMessage))
{ {
<div class="catalog-progress"><span>@FiducialMessage</span></div> <div class="catalog-progress"><span>@FiducialMessage</span></div>
} }
@if (FiducialPoints.Count > 0) @if (MappedFiducialPoints.Count > 0)
{ {
<section class="panel-section"> <section class="panel-section">
<h2>Punti trovati (@FiducialPoints.Count)</h2> <h2>Punti cartografici (@MappedFiducialPoints.Count)</h2>
<button class="secondary-command" @onclick="FitFiducialPoints">Inquadra tutti i PF</button>
<div class="fiducial-list"> <div class="fiducial-list">
@foreach (var point in FiducialPoints) @foreach (var point in MappedFiducialPoints)
{ {
<article class="fiducial-item"> <article class="fiducial-item">
<strong>@point.Identifier</strong> <strong>@point.Identifier</strong>
<button class="secondary-command" @onclick="() => DownloadFiducialMonograph(point)">Scarica PDF monografia</button> <small>Nord/X: @FormatCoordinate(point.CadastralNorth) · Est/Y: @FormatCoordinate(point.CadastralEast)</small>
@if (!string.IsNullOrWhiteSpace(point.MonographUrl))
{
<button class="secondary-command" @onclick="() => DownloadFiducialMonograph(point.MonographUrl)">Scarica PDF monografia</button>
}
</article> </article>
} }
</div> </div>
@@ -275,8 +299,8 @@
} }
<div class="service-note"> <div class="service-note">
<strong>Fonte ufficiale Agenzia delle Entrate</strong> <strong>Monografie ufficiali, posizione sperimentale</strong>
<p>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.</p> <p>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.</p>
</div> </div>
} }
</section> </section>
@@ -830,6 +854,7 @@
private string SelectedFiducialMunicipalityCode { get; set; } = string.Empty; private string SelectedFiducialMunicipalityCode { get; set; } = string.Empty;
private string SelectedFiducialSheet { get; set; } = string.Empty; private string SelectedFiducialSheet { get; set; } = string.Empty;
private string FiducialMessage { 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 Status { get; set; } = "online";
private string SelectedBaseLayer { get; set; } = "satellite"; private string SelectedBaseLayer { get; set; } = "satellite";
private string GoogleMapsApiKey { get; set; } = string.Empty; private string GoogleMapsApiKey { get; set; } = string.Empty;
@@ -847,6 +872,7 @@
private bool GpsOverlayCollapsed { get; set; } private bool GpsOverlayCollapsed { get; set; }
private bool ShowGpsPosition { get; set; } = true; private bool ShowGpsPosition { get; set; } = true;
private bool ShowSelectedParcels { get; set; } = true; private bool ShowSelectedParcels { get; set; } = true;
private bool ShowFiducialPoints { get; set; } = true;
private bool ShowImportedData { get; set; } = true; private bool ShowImportedData { get; set; } = true;
private bool DrawToolsVisible { get; set; } private bool DrawToolsVisible { get; set; }
private bool TrailEnabled { get; set; } private bool TrailEnabled { get; set; }
@@ -910,6 +936,7 @@
private IReadOnlyList<FiducialAreaOption> FiducialMunicipalities { get; set; } = []; private IReadOnlyList<FiducialAreaOption> FiducialMunicipalities { get; set; } = [];
private IReadOnlyList<FiducialAreaOption> FiducialSheets { get; set; } = []; private IReadOnlyList<FiducialAreaOption> FiducialSheets { get; set; } = [];
private IReadOnlyList<FiducialPointEntry> FiducialPoints { get; set; } = []; private IReadOnlyList<FiducialPointEntry> FiducialPoints { get; set; } = [];
private IReadOnlyList<MappedFiducialPoint> MappedFiducialPoints { get; set; } = [];
private IReadOnlyList<MapProjectSummary> Projects { get; set; } = []; private IReadOnlyList<MapProjectSummary> Projects { get; set; } = [];
private IReadOnlyList<MapProjectVersion> ProjectVersions { get; set; } = []; private IReadOnlyList<MapProjectVersion> ProjectVersions { get; set; } = [];
private MapProjectSummary? SelectedProject => Projects.FirstOrDefault(item => item.Id == SelectedProjectId); private MapProjectSummary? SelectedProject => Projects.FirstOrDefault(item => item.Id == SelectedProjectId);
@@ -1257,6 +1284,7 @@
FiducialMunicipalities = []; FiducialMunicipalities = [];
FiducialSheets = []; FiducialSheets = [];
FiducialPoints = []; FiducialPoints = [];
MappedFiducialPoints = [];
if (string.IsNullOrWhiteSpace(SelectedFiducialProvinceCode)) if (string.IsNullOrWhiteSpace(SelectedFiducialProvinceCode))
{ {
FiducialMessage = string.Empty; FiducialMessage = string.Empty;
@@ -1286,6 +1314,7 @@
SelectedFiducialSheet = string.Empty; SelectedFiducialSheet = string.Empty;
FiducialSheets = []; FiducialSheets = [];
FiducialPoints = []; FiducialPoints = [];
MappedFiducialPoints = [];
if (string.IsNullOrWhiteSpace(SelectedFiducialMunicipalityCode)) if (string.IsNullOrWhiteSpace(SelectedFiducialMunicipalityCode))
{ {
FiducialMessage = string.Empty; FiducialMessage = string.Empty;
@@ -1322,7 +1351,11 @@
FiducialBusy = true; FiducialBusy = true;
FiducialPoints = []; FiducialPoints = [];
FiducialMessage = "Interrogo il servizio ufficiale delle monografie..."; MappedFiducialPoints = [];
FiducialMessage = "Recupero monografie e coordinate dei PF...";
try
{
string? monographError = null;
try try
{ {
FiducialPoints = await FiducialPointService.GetFiducialsAsync( FiducialPoints = await FiducialPointService.GetFiducialsAsync(
@@ -1330,9 +1363,38 @@
municipality.Name, municipality.Name,
municipality.Code, municipality.Code,
SelectedFiducialSheet); SelectedFiducialSheet);
FiducialMessage = FiducialPoints.Count == 0 }
? "Nessun Punto Fiduciale pubblicato per il foglio selezionato." catch (Exception error)
: $"Trovati {FiducialPoints.Count} Punti Fiduciali."; {
monographError = error.GetBaseException().Message;
}
var mapped = await FiducialPointService.GetMappedFiducialsAsync(
municipality.Code,
SelectedFiducialSheet);
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) catch (Exception error)
{ {
@@ -1344,11 +1406,11 @@
} }
} }
private async Task DownloadFiducialMonograph(FiducialPointEntry point) private async Task DownloadFiducialMonograph(string monographUrl)
{ {
try try
{ {
await Launcher.Default.OpenAsync(new Uri(point.MonographUrl)); await Launcher.Default.OpenAsync(new Uri(monographUrl));
} }
catch (Exception error) 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) private async Task OnMunicipalityChanged(ChangeEventArgs args)
{ {
SelectedMunicipalityCode = args.Value?.ToString() ?? string.Empty; SelectedMunicipalityCode = args.Value?.ToString() ?? string.Empty;
@@ -2581,6 +2661,8 @@
DrawToolsVisible = GetFlagPreference("draw_tools_visible", false); DrawToolsVisible = GetFlagPreference("draw_tools_visible", false);
ShowGpsPosition = GetFlagPreference("show_gps_position", true); ShowGpsPosition = GetFlagPreference("show_gps_position", true);
ShowSelectedParcels = GetFlagPreference("show_selected_parcels", 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); TrailEnabled = GetFlagPreference("trail_enabled", false);
ExportImported = GetFlagPreference("export_imported", true); ExportImported = GetFlagPreference("export_imported", true);
ExportImportedSheets = GetFlagPreference("export_imported_sheets", true); ExportImportedSheets = GetFlagPreference("export_imported_sheets", true);
@@ -2621,6 +2703,8 @@
SetFlagPreference("draw_tools_visible", DrawToolsVisible); SetFlagPreference("draw_tools_visible", DrawToolsVisible);
SetFlagPreference("show_gps_position", ShowGpsPosition); SetFlagPreference("show_gps_position", ShowGpsPosition);
SetFlagPreference("show_selected_parcels", ShowSelectedParcels); SetFlagPreference("show_selected_parcels", ShowSelectedParcels);
SetFlagPreference("show_fiducial_points", ShowFiducialPoints);
Preferences.Default.Set($"{FlagPreferencePrefix}fiducial_color", FiducialColor);
SetFlagPreference("trail_enabled", TrailEnabled); SetFlagPreference("trail_enabled", TrailEnabled);
SetFlagPreference("export_imported", ExportImported); SetFlagPreference("export_imported", ExportImported);
SetFlagPreference("export_imported_sheets", ExportImportedSheets); SetFlagPreference("export_imported_sheets", ExportImportedSheets);
@@ -2658,6 +2742,8 @@
await JS.InvokeVoidAsync("lmeMap.setDrawToolsVisible", DrawToolsVisible); await JS.InvokeVoidAsync("lmeMap.setDrawToolsVisible", DrawToolsVisible);
await JS.InvokeVoidAsync("lmeMap.setGpsPositionVisible", ShowGpsPosition); await JS.InvokeVoidAsync("lmeMap.setGpsPositionVisible", ShowGpsPosition);
await JS.InvokeVoidAsync("lmeMap.setSelectedParcelsVisible", ShowSelectedParcels); await JS.InvokeVoidAsync("lmeMap.setSelectedParcelsVisible", ShowSelectedParcels);
await JS.InvokeVoidAsync("lmeMap.setFiducialColor", FiducialColor);
await JS.InvokeVoidAsync("lmeMap.setFiducialPointsVisible", ShowFiducialPoints);
await JS.InvokeVoidAsync("lmeMap.toggleTrail", TrailEnabled); await JS.InvokeVoidAsync("lmeMap.toggleTrail", TrailEnabled);
foreach (var layer in CadastralLayers) foreach (var layer in CadastralLayers)
+2 -2
View File
@@ -28,8 +28,8 @@
<ApplicationId>it.liviomerola.lmemappecatastali.pro</ApplicationId> <ApplicationId>it.liviomerola.lmemappecatastali.pro</ApplicationId>
<!-- Versions --> <!-- Versions -->
<ApplicationDisplayVersion>1.0.12</ApplicationDisplayVersion> <ApplicationDisplayVersion>1.0.13</ApplicationDisplayVersion>
<ApplicationVersion>112</ApplicationVersion> <ApplicationVersion>113</ApplicationVersion>
<Version>$(ApplicationDisplayVersion)</Version> <Version>$(ApplicationDisplayVersion)</Version>
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged --> <!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
+114
View File
@@ -1,5 +1,6 @@
using System.Net; using System.Net;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Text.Json;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace LMEMappeCatastaliApp.Services; namespace LMEMappeCatastaliApp.Services;
@@ -7,6 +8,7 @@ namespace LMEMappeCatastaliApp.Services;
public sealed partial class FiducialPointService public sealed partial class FiducialPointService
{ {
private const string ServiceBaseUrl = "https://www1.agenziaentrate.gov.it/servizi/Monografie/"; 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) }; private readonly HttpClient _httpClient = new() { Timeout = TimeSpan.FromSeconds(30) };
public FiducialPointService() public FiducialPointService()
@@ -77,6 +79,105 @@ public sealed partial class FiducialPointService
.ToArray(); .ToArray();
} }
public async Task<IReadOnlyList<MappedFiducialPoint>> 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<string, string>
{
["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<MappedFiducialPoint>()
.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<string> GetHtmlAsync(string relativeUrl, CancellationToken cancellationToken) private async Task<string> GetHtmlAsync(string relativeUrl, CancellationToken cancellationToken)
{ {
using var response = await _httpClient.GetAsync(ServiceBaseUrl + relativeUrl, 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 FiducialAreaOption(string Code, string Name);
public sealed record FiducialPointEntry(string Identifier, string MonographUrl); 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 = "");
+143 -2
View File
@@ -9,6 +9,7 @@ const lmeState = {
"gpsTrail", "gpsTrail",
"gpsParcels", "gpsParcels",
"selectedParcels", "selectedParcels",
"fiducials",
"importedSheets", "importedSheets",
"imported", "imported",
"cadastre" "cadastre"
@@ -50,6 +51,9 @@ const lmeState = {
gpsTrail: null, gpsTrail: null,
selectedParcelsLayer: null, selectedParcelsLayer: null,
selectedParcelsVisible: true, selectedParcelsVisible: true,
fiducials: null,
fiducialsVisible: true,
fiducialColor: "#dc2626",
gpsParcelLayer: null, gpsParcelLayer: null,
gpsTrailEnabled: false, gpsTrailEnabled: false,
gpsTrailPoints: [], gpsTrailPoints: [],
@@ -131,6 +135,10 @@ window.lmeMap = {
closeCadastrePreview, closeCadastrePreview,
clearSelectedParcels, clearSelectedParcels,
setSelectedParcelsVisible, setSelectedParcelsVisible,
setFiducialPoints,
setFiducialPointsVisible,
setFiducialColor,
fitFiducialPoints,
clearMapElements, clearMapElements,
fitImported, fitImported,
clearImported, clearImported,
@@ -310,6 +318,7 @@ function init(dotNetReference) {
lmeState.importedMeasurements = L.layerGroup().addTo(lmeState.map); lmeState.importedMeasurements = L.layerGroup().addTo(lmeState.map);
lmeState.drawingMeasurements = L.layerGroup().addTo(lmeState.map); lmeState.drawingMeasurements = L.layerGroup().addTo(lmeState.map);
lmeState.selectedParcelsLayer = L.featureGroup().addTo(lmeState.map); lmeState.selectedParcelsLayer = L.featureGroup().addTo(lmeState.map);
lmeState.fiducials = L.featureGroup().addTo(lmeState.map);
lmeState.gpsParcelLayer = L.featureGroup().addTo(lmeState.map); lmeState.gpsParcelLayer = L.featureGroup().addTo(lmeState.map);
const navigationSheetPane = lmeState.map.createPane("navigationSheetPane"); const navigationSheetPane = lmeState.map.createPane("navigationSheetPane");
navigationSheetPane.style.zIndex = "465"; navigationSheetPane.style.zIndex = "465";
@@ -1339,6 +1348,7 @@ function managedLayerPanes() {
gpsTrail: "gpsTrailPane", gpsTrail: "gpsTrailPane",
gpsParcels: "gpsParcelsPane", gpsParcels: "gpsParcelsPane",
selectedParcels: "selectedParcelsPane", selectedParcels: "selectedParcelsPane",
fiducials: "fiducialsPane",
importedSheets: "importedSheetPane", importedSheets: "importedSheetPane",
imported: "importedPane", imported: "importedPane",
cadastre: "cadastrePane" cadastre: "cadastrePane"
@@ -1427,6 +1437,7 @@ function layerManagerCategories() {
{ key: "gpsTrail", label: "Traccia GPS", roots: [lmeState.gpsTrail].filter(Boolean) }, { key: "gpsTrail", label: "Traccia GPS", roots: [lmeState.gpsTrail].filter(Boolean) },
{ key: "gpsParcels", label: "Particella rilevata dal GPS", roots: [lmeState.gpsParcelLayer].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: "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: "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: "imported", label: "Progetti e dati importati", roots: [lmeState.imported].filter(Boolean), blocks: true },
{ key: "cadastre", label: "Layer catastali AdE", roots: [lmeState.cadastre].filter(Boolean) } { key: "cadastre", label: "Layer catastali AdE", roots: [lmeState.cadastre].filter(Boolean) }
@@ -3271,6 +3282,8 @@ function setManagedCategoryVisible(category, visible) {
lmeState.importedSheetsVisible = visible; lmeState.importedSheetsVisible = visible;
} else if (category.key === "selectedParcels") { } else if (category.key === "selectedParcels") {
lmeState.selectedParcelsVisible = visible; lmeState.selectedParcelsVisible = visible;
} else if (category.key === "fiducials") {
lmeState.fiducialsVisible = visible;
} else if (category.key === "gpsPosition") { } else if (category.key === "gpsPosition") {
lmeState.gpsPositionVisible = visible; lmeState.gpsPositionVisible = visible;
} }
@@ -3344,6 +3357,9 @@ function managedCategoryEnabled(categoryKey) {
if (categoryKey === "selectedParcels") { if (categoryKey === "selectedParcels") {
return lmeState.selectedParcelsVisible; return lmeState.selectedParcelsVisible;
} }
if (categoryKey === "fiducials") {
return lmeState.fiducialsVisible;
}
return true; return true;
} }
@@ -4721,7 +4737,7 @@ function circleToPolygon(center, radiusMeters) {
function createProjectSnapshot(includeCadastralParcels) { function createProjectSnapshot(includeCadastralParcels) {
const center = lmeState.map.getCenter(); const center = lmeState.map.getCenter();
return JSON.stringify({ return JSON.stringify({
schemaVersion: 5, schemaVersion: 6,
view: { view: {
latitude: center.lat, latitude: center.lat,
longitude: center.lng, longitude: center.lng,
@@ -4731,6 +4747,7 @@ function createProjectSnapshot(includeCadastralParcels) {
imported: managedGroupGeoJson(lmeState.imported, "imported"), imported: managedGroupGeoJson(lmeState.imported, "imported"),
importedSheets: managedGroupGeoJson(lmeState.importedSheets, "importedSheets"), importedSheets: managedGroupGeoJson(lmeState.importedSheets, "importedSheets"),
importedSheetsVisible: lmeState.importedSheetsVisible, importedSheetsVisible: lmeState.importedSheetsVisible,
fiducials: managedGroupGeoJson(lmeState.fiducials, "fiducials"),
displayOptions: captureDisplayOptions(), displayOptions: captureDisplayOptions(),
selectedParcels: includeCadastralParcels selectedParcels: includeCadastralParcels
? managedGroupGeoJson(lmeState.selectedParcelsLayer, "selectedParcels") ? managedGroupGeoJson(lmeState.selectedParcelsLayer, "selectedParcels")
@@ -4766,6 +4783,7 @@ function loadProjectSnapshot(raw, projectName) {
lmeState.drawingMeasurements.clearLayers(); lmeState.drawingMeasurements.clearLayers();
lmeState.importedMeasurements.clearLayers(); lmeState.importedMeasurements.clearLayers();
lmeState.selectedParcelsLayer.clearLayers(); lmeState.selectedParcelsLayer.clearLayers();
lmeState.fiducials.clearLayers();
lmeState.gpsParcelLayer.clearLayers(); lmeState.gpsParcelLayer.clearLayers();
const restoredDrawings = L.geoJSON(data.drawings || emptyFeatureCollection(), { const restoredDrawings = L.geoJSON(data.drawings || emptyFeatureCollection(), {
@@ -4826,6 +4844,10 @@ function loadProjectSnapshot(raw, projectName) {
applyStyleFromProperties(layer); applyStyleFromProperties(layer);
} }
}).addTo(lmeState.selectedParcelsLayer); }).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) { if (lmeState.importedSheetsVisible) {
void ensureImportedSheetOutlines().then(() => { void ensureImportedSheetOutlines().then(() => {
applyImportedSheetVisibility(); applyImportedSheetVisibility();
@@ -4848,6 +4870,7 @@ function loadProjectSnapshot(raw, projectName) {
lmeState.drawings, lmeState.drawings,
lmeState.imported, lmeState.imported,
lmeState.selectedParcelsLayer, lmeState.selectedParcelsLayer,
lmeState.fiducials,
lmeState.gpsTrail lmeState.gpsTrail
]; ];
if (lmeState.importedSheetsVisible) { if (lmeState.importedSheetsVisible) {
@@ -4898,6 +4921,8 @@ function captureDisplayOptions() {
drawToolsVisible: Boolean(lmeState.drawControl), drawToolsVisible: Boolean(lmeState.drawControl),
gpsPositionVisible: lmeState.gpsPositionVisible, gpsPositionVisible: lmeState.gpsPositionVisible,
selectedParcelsVisible: lmeState.selectedParcelsVisible, selectedParcelsVisible: lmeState.selectedParcelsVisible,
fiducialsVisible: lmeState.fiducialsVisible,
fiducialColor: lmeState.fiducialColor,
layerOrder: [...lmeState.layerOrder], layerOrder: [...lmeState.layerOrder],
layerObjectOrder: { ...lmeState.managerObjectOrder }, layerObjectOrder: { ...lmeState.managerObjectOrder },
layerCategoryNames: { ...lmeState.managerCategoryNames }, layerCategoryNames: { ...lmeState.managerCategoryNames },
@@ -4965,6 +4990,10 @@ function applyProjectDisplayOptions(options) {
} }
setGpsPositionVisible(options.gpsPositionVisible !== false); setGpsPositionVisible(options.gpsPositionVisible !== false);
setSelectedParcelsVisible(options.selectedParcelsVisible !== false); setSelectedParcelsVisible(options.selectedParcelsVisible !== false);
if (typeof options.fiducialColor === "string") {
setFiducialColor(options.fiducialColor);
}
setFiducialPointsVisible(options.fiducialsVisible !== false);
toggleTrail(Boolean(options.trailEnabled)); toggleTrail(Boolean(options.trailEnabled));
setDrawToolsVisible(Boolean(options.drawToolsVisible)); setDrawToolsVisible(Boolean(options.drawToolsVisible));
applyStoredLayerManagerVisibility(); applyStoredLayerManagerVisibility();
@@ -5634,6 +5663,116 @@ function activeCadastreLayers() {
return drawOrder.filter(layer => lmeState.cadastreLayers.includes(layer)); 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)
? `<p><a href="${escapeHtml(url)}" target="_blank" rel="noopener">Apri monografia ufficiale</a></p>`
: "";
layer.bindPopup(`<strong>${escapeHtml(properties._lmeDisplayName || properties.name || "Punto fiduciale")}</strong><p>${rows.join("<br>")}</p>${monograph}<small>Posizione cartografica sperimentale GloboGIS: verificare prima dell'uso topografico.</small>`);
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() { function clearSelectedParcels() {
const count = managedGroupGeoJson(lmeState.selectedParcelsLayer, "selectedParcels").features.length; const count = managedGroupGeoJson(lmeState.selectedParcelsLayer, "selectedParcels").features.length;
if (count) rememberMapChange(); if (count) rememberMapChange();
@@ -5672,6 +5811,7 @@ function setSelectedParcelsVisible(enabled) {
function clearMapElements() { function clearMapElements() {
const count = managedGroupGeoJson(lmeState.selectedParcelsLayer, "selectedParcels").features.length const count = managedGroupGeoJson(lmeState.selectedParcelsLayer, "selectedParcels").features.length
+ managedGroupGeoJson(lmeState.gpsParcelLayer, "gpsParcels").features.length + managedGroupGeoJson(lmeState.gpsParcelLayer, "gpsParcels").features.length
+ managedGroupGeoJson(lmeState.fiducials, "fiducials").features.length
+ managedGroupGeoJson(lmeState.imported, "imported").features.length + managedGroupGeoJson(lmeState.imported, "imported").features.length
+ managedGroupGeoJson(lmeState.importedSheets, "importedSheets").features.length + managedGroupGeoJson(lmeState.importedSheets, "importedSheets").features.length
+ managedGroupGeoJson(lmeState.drawings, "drawings").features.length + managedGroupGeoJson(lmeState.drawings, "drawings").features.length
@@ -5680,7 +5820,7 @@ function clearMapElements() {
setInfo("La mappa non contiene elementi da pulire."); setInfo("La mappa non contiene elementi da pulire.");
return; 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; return;
} }
@@ -5688,6 +5828,7 @@ function clearMapElements() {
resetLayerManagerProjectState(); resetLayerManagerProjectState();
lmeState.selectedParcelsLayer.clearLayers(); lmeState.selectedParcelsLayer.clearLayers();
lmeState.gpsParcelLayer.clearLayers(); lmeState.gpsParcelLayer.clearLayers();
lmeState.fiducials.clearLayers();
lmeState.imported.clearLayers(); lmeState.imported.clearLayers();
lmeState.importedSheets.clearLayers(); lmeState.importedSheets.clearLayers();
lmeState.drawings.clearLayers(); lmeState.drawings.clearLayers();