Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc037a57d4 | ||
|
|
ce355f820c | ||
|
|
85e1c93d8a | ||
|
|
7b642afe2d | ||
|
|
342935d99d | ||
|
|
5ea424b46c | ||
|
|
29c3848a22 |
No files matched your search
+408
-5
@@ -3,6 +3,7 @@
|
|||||||
@inject PreciseLocationService LocationService
|
@inject PreciseLocationService LocationService
|
||||||
@inject CadastralCatalogService CatalogService
|
@inject CadastralCatalogService CatalogService
|
||||||
@inject OfficialCadastreService OfficialCadastreService
|
@inject OfficialCadastreService OfficialCadastreService
|
||||||
|
@inject FiducialPointService FiducialPointService
|
||||||
@inject LocationSearchService LocationSearchService
|
@inject LocationSearchService LocationSearchService
|
||||||
@inject NetworkLocationService NetworkLocationService
|
@inject NetworkLocationService NetworkLocationService
|
||||||
@inject AzureMapsService AzureMapsService
|
@inject AzureMapsService AzureMapsService
|
||||||
@@ -46,6 +47,7 @@
|
|||||||
<button @onclick='() => SelectTab("map")'>@L("menu.map")</button>
|
<button @onclick='() => SelectTab("map")'>@L("menu.map")</button>
|
||||||
<button @onclick='() => SelectTab("search")'>@L("menu.search")</button>
|
<button @onclick='() => SelectTab("search")'>@L("menu.search")</button>
|
||||||
<button @onclick='() => SelectTab("cadastre")'>@L("menu.cadastre")</button>
|
<button @onclick='() => SelectTab("cadastre")'>@L("menu.cadastre")</button>
|
||||||
|
<button @onclick='() => SelectTab("fiducials")'>Punti fiduciali</button>
|
||||||
<button @onclick='() => SelectTab("data")'>@L("menu.data")</button>
|
<button @onclick='() => SelectTab("data")'>@L("menu.data")</button>
|
||||||
<button @onclick='() => SelectTab("projects")'>@L("menu.projects")</button>
|
<button @onclick='() => SelectTab("projects")'>@L("menu.projects")</button>
|
||||||
<button @onclick='() => SelectTab("save")'>@L("menu.export")</button>
|
<button @onclick='() => SelectTab("save")'>@L("menu.export")</button>
|
||||||
@@ -94,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>
|
||||||
@@ -109,13 +121,18 @@
|
|||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="gps-overlay @(ActiveTab == "search" ? "search-open" : null)">
|
<div class="gps-overlay @(ActiveTab == "search" ? "search-open" : null) @(GpsOverlayCollapsed ? "is-collapsed" : null)">
|
||||||
<div class="gps-state">
|
<div class="gps-state">
|
||||||
<span id="gpsState">GPS non attivo</span>
|
<span id="gpsState">GPS non attivo</span>
|
||||||
<strong id="parcelState">Nessuna particella selezionata</strong>
|
<strong id="parcelState">Nessuna particella selezionata</strong>
|
||||||
</div>
|
</div>
|
||||||
<button class="gps-main" @onclick="ToggleGps">@(GpsActive ? "Ferma GPS" : "Avvia GPS")</button>
|
<div class="gps-actions">
|
||||||
<button class="gps-center" @onclick="CenterOnGps" aria-label="Centra posizione">Centra</button>
|
<button class="gps-main" @onclick="ToggleGps">@(GpsActive ? "Ferma GPS" : "Avvia GPS")</button>
|
||||||
|
<button class="gps-center" @onclick="CenterOnGps" aria-label="Centra posizione GPS">Centra</button>
|
||||||
|
</div>
|
||||||
|
<button class="gps-collapse" @onclick="ToggleGpsOverlay" aria-label="@(GpsOverlayCollapsed ? "Espandi pannello GPS" : "Riduci pannello GPS")" title="@(GpsOverlayCollapsed ? "Espandi pannello GPS" : "Riduci pannello GPS")">
|
||||||
|
<span aria-hidden="true">@(GpsOverlayCollapsed ? "+" : "−")</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (ActiveTab is not "map" and not "search")
|
@if (ActiveTab is not "map" and not "search")
|
||||||
@@ -196,6 +213,104 @@
|
|||||||
<MenuHelp Topic="cadastre" />
|
<MenuHelp Topic="cadastre" />
|
||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
|
else if (ActiveTab == "fiducials")
|
||||||
|
{
|
||||||
|
<section class="panel-content">
|
||||||
|
@if (!CanUse(AppFeatures.FiducialPoints))
|
||||||
|
{
|
||||||
|
<div class="service-note protected-service">
|
||||||
|
<strong>Funzione LME Pro</strong>
|
||||||
|
<p>La ricerca dei Punti Fiduciali e delle monografie ufficiali è disponibile con abbonamento Pro o chiave manuale valida.</p>
|
||||||
|
<button class="primary-command" @onclick='() => SelectTab("settings")'>Apri abbonamento Pro</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<label>
|
||||||
|
Provincia
|
||||||
|
<select value="@SelectedFiducialProvinceCode" @onchange="OnFiducialProvinceChanged" disabled="@FiducialBusy">
|
||||||
|
<option value="">Seleziona provincia</option>
|
||||||
|
@foreach (var province in FiducialProvinces)
|
||||||
|
{
|
||||||
|
<option value="@province.Code">@province.Name</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Comune catastale
|
||||||
|
<select value="@SelectedFiducialMunicipalityCode" @onchange="OnFiducialMunicipalityChanged" disabled="@(FiducialMunicipalities.Count == 0 || FiducialBusy)">
|
||||||
|
<option value="">Seleziona comune</option>
|
||||||
|
@foreach (var municipality in FiducialMunicipalities)
|
||||||
|
{
|
||||||
|
<option value="@municipality.Code">@municipality.Name (@municipality.Code)</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Foglio
|
||||||
|
<select @bind="SelectedFiducialSheet" disabled="@(FiducialSheets.Count == 0 || FiducialBusy)">
|
||||||
|
<option value="">Seleziona foglio</option>
|
||||||
|
@foreach (var sheet in FiducialSheets)
|
||||||
|
{
|
||||||
|
<option value="@sheet.Code">@sheet.Name</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<button class="primary-command" @onclick="SearchFiducials" disabled="@(string.IsNullOrWhiteSpace(SelectedFiducialSheet) || FiducialBusy)">
|
||||||
|
@(FiducialBusy ? "Ricerca..." : "Cerca Punti Fiduciali")
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<label class="toggle-line">
|
||||||
|
<input type="checkbox" @bind="KeepPreviousFiducialPoints" @bind:after="ApplyKeepPreviousFiducials" />
|
||||||
|
<span>Mantieni i PF dei fogli già caricati</span>
|
||||||
|
</label>
|
||||||
|
<small class="field-help">Ogni foglio resta un sottogruppo separato nel pannello Layer, da cui puoi nasconderlo o rimuoverlo.</small>
|
||||||
|
|
||||||
|
<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))
|
||||||
|
{
|
||||||
|
<div class="catalog-progress"><span>@FiducialMessage</span></div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (MappedFiducialPoints.Count > 0)
|
||||||
|
{
|
||||||
|
<section class="panel-section">
|
||||||
|
<h2>Punti cartografici (@MappedFiducialPoints.Count)</h2>
|
||||||
|
<button class="secondary-command" @onclick="FitFiducialPoints">Inquadra tutti i PF</button>
|
||||||
|
<div class="fiducial-list">
|
||||||
|
@foreach (var point in MappedFiducialPoints)
|
||||||
|
{
|
||||||
|
<article class="fiducial-item">
|
||||||
|
<strong>@point.Identifier</strong>
|
||||||
|
<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>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="service-note">
|
||||||
|
<strong>Monografie ufficiali, posizione sperimentale</strong>
|
||||||
|
<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>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
}
|
||||||
else if (ActiveTab == "data")
|
else if (ActiveTab == "data")
|
||||||
{
|
{
|
||||||
<section class="panel-content">
|
<section class="panel-content">
|
||||||
@@ -553,6 +668,11 @@
|
|||||||
Trasparenza catasto
|
Trasparenza catasto
|
||||||
<input type="range" min="0" max="1" step="0.05" value="@CadastralOpacity.ToString(System.Globalization.CultureInfo.InvariantCulture)" @onchange="ChangeCadastreOpacity" />
|
<input type="range" min="0" max="1" step="0.05" value="@CadastralOpacity.ToString(System.Globalization.CultureInfo.InvariantCulture)" @onchange="ChangeCadastreOpacity" />
|
||||||
</label>
|
</label>
|
||||||
|
<label>
|
||||||
|
Durata messaggi mappa (secondi)
|
||||||
|
<input type="number" min="0" max="30" step="1" @bind="MapMessageTimeoutSeconds" @bind:after="ApplyMapMessageOptions" />
|
||||||
|
</label>
|
||||||
|
<small class="field-help">Imposta 0 per lasciare i messaggi visibili fino alla chiusura manuale.</small>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="panel-section">
|
<section class="panel-section">
|
||||||
@@ -689,7 +809,10 @@
|
|||||||
</aside>
|
</aside>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div id="mapInfo" class="map-info">Pronto</div>
|
<div id="mapInfo" class="map-info" role="status" aria-live="polite">
|
||||||
|
<span id="mapInfoText" class="map-info-text">Pronto</span>
|
||||||
|
<button type="button" class="map-info-close" onclick="window.lmeMap.hideInfo()" aria-label="Chiudi messaggio" title="Chiudi messaggio">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="wmsPreview" class="wms-preview" hidden>
|
<div id="wmsPreview" class="wms-preview" hidden>
|
||||||
<section class="wms-preview-dialog" role="dialog" aria-modal="true" aria-labelledby="wmsPreviewTitle">
|
<section class="wms-preview-dialog" role="dialog" aria-modal="true" aria-labelledby="wmsPreviewTitle">
|
||||||
@@ -732,6 +855,12 @@
|
|||||||
private string MonthlyPrice { get; set; } = string.Empty;
|
private string MonthlyPrice { get; set; } = string.Empty;
|
||||||
private string YearlyPrice { get; set; } = string.Empty;
|
private string YearlyPrice { get; set; } = string.Empty;
|
||||||
private bool BillingBusy { get; set; }
|
private bool BillingBusy { get; set; }
|
||||||
|
private bool FiducialBusy { get; set; }
|
||||||
|
private string SelectedFiducialProvinceCode { get; set; } = string.Empty;
|
||||||
|
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 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;
|
||||||
@@ -743,10 +872,14 @@
|
|||||||
private string CatalogMessage { get; set; } = string.Empty;
|
private string CatalogMessage { get; set; } = string.Empty;
|
||||||
private double? CatalogFraction { get; set; }
|
private double? CatalogFraction { get; set; }
|
||||||
private double CadastralOpacity { get; set; } = 0.75;
|
private double CadastralOpacity { get; set; } = 0.75;
|
||||||
|
private int MapMessageTimeoutSeconds { get; set; } = 3;
|
||||||
private bool CatalogBusy { get; set; }
|
private bool CatalogBusy { get; set; }
|
||||||
private bool GpsActive { get; set; }
|
private bool GpsActive { 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 KeepPreviousFiducialPoints { 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; }
|
||||||
@@ -806,6 +939,11 @@
|
|||||||
private IReadOnlyList<MunicipalityOption> Municipalities { get; set; } = [];
|
private IReadOnlyList<MunicipalityOption> Municipalities { get; set; } = [];
|
||||||
private IReadOnlyList<string> Sheets { get; set; } = [];
|
private IReadOnlyList<string> Sheets { get; set; } = [];
|
||||||
private IReadOnlyList<ParcelOption> Parcels { get; set; } = [];
|
private IReadOnlyList<ParcelOption> Parcels { get; set; } = [];
|
||||||
|
private IReadOnlyList<FiducialAreaOption> FiducialProvinces { get; set; } = [];
|
||||||
|
private IReadOnlyList<FiducialAreaOption> FiducialMunicipalities { get; set; } = [];
|
||||||
|
private IReadOnlyList<FiducialAreaOption> FiducialSheets { 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);
|
||||||
@@ -819,6 +957,7 @@
|
|||||||
private string PanelTitle => ActiveTab switch
|
private string PanelTitle => ActiveTab switch
|
||||||
{
|
{
|
||||||
"cadastre" => "Ricerca catastale",
|
"cadastre" => "Ricerca catastale",
|
||||||
|
"fiducials" => "Punti fiduciali",
|
||||||
"data" => "Dati e disegni",
|
"data" => "Dati e disegni",
|
||||||
"projects" => "Archivio progetti",
|
"projects" => "Archivio progetti",
|
||||||
"save" => "Salvataggio ed esportazione",
|
"save" => "Salvataggio ed esportazione",
|
||||||
@@ -830,6 +969,7 @@
|
|||||||
private string PanelEyebrow => ActiveTab switch
|
private string PanelEyebrow => ActiveTab switch
|
||||||
{
|
{
|
||||||
"cadastre" => "Regione, Comune, Foglio, Particella",
|
"cadastre" => "Regione, Comune, Foglio, Particella",
|
||||||
|
"fiducials" => "Monografie ufficiali AdE",
|
||||||
"data" => "File GIS, misure e POI",
|
"data" => "File GIS, misure e POI",
|
||||||
"projects" => "Salva, apri e visualizza i tuoi lavori",
|
"projects" => "Salva, apri e visualizza i tuoi lavori",
|
||||||
"save" => "Esporta file GIS",
|
"save" => "Esporta file GIS",
|
||||||
@@ -1041,6 +1181,10 @@
|
|||||||
{
|
{
|
||||||
await RefreshProjectsAsync();
|
await RefreshProjectsAsync();
|
||||||
}
|
}
|
||||||
|
if (tab == "fiducials" && CanUse(AppFeatures.FiducialPoints) && FiducialProvinces.Count == 0)
|
||||||
|
{
|
||||||
|
await LoadFiducialProvincesAsync();
|
||||||
|
}
|
||||||
await Task.Delay(30);
|
await Task.Delay(30);
|
||||||
await JS.InvokeVoidAsync("lmeMap.resize");
|
await JS.InvokeVoidAsync("lmeMap.resize");
|
||||||
if (LayerPanelOpen)
|
if (LayerPanelOpen)
|
||||||
@@ -1120,6 +1264,235 @@
|
|||||||
: await CatalogService.GetMunicipalitiesAsync(SelectedRegionFile);
|
: await CatalogService.GetMunicipalitiesAsync(SelectedRegionFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task LoadFiducialProvincesAsync()
|
||||||
|
{
|
||||||
|
FiducialBusy = true;
|
||||||
|
FiducialMessage = "Carico l'elenco delle province...";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FiducialProvinces = await FiducialPointService.GetProvincesAsync();
|
||||||
|
FiducialMessage = string.Empty;
|
||||||
|
}
|
||||||
|
catch (Exception error)
|
||||||
|
{
|
||||||
|
FiducialMessage = $"Servizio monografie non disponibile: {error.GetBaseException().Message}";
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
FiducialBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnFiducialProvinceChanged(ChangeEventArgs args)
|
||||||
|
{
|
||||||
|
SelectedFiducialProvinceCode = args.Value?.ToString() ?? string.Empty;
|
||||||
|
SelectedFiducialMunicipalityCode = string.Empty;
|
||||||
|
SelectedFiducialSheet = string.Empty;
|
||||||
|
FiducialMunicipalities = [];
|
||||||
|
FiducialSheets = [];
|
||||||
|
FiducialPoints = [];
|
||||||
|
MappedFiducialPoints = [];
|
||||||
|
if (string.IsNullOrWhiteSpace(SelectedFiducialProvinceCode))
|
||||||
|
{
|
||||||
|
FiducialMessage = string.Empty;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FiducialBusy = true;
|
||||||
|
FiducialMessage = "Carico i Comuni catastali...";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FiducialMunicipalities = await FiducialPointService.GetMunicipalitiesAsync(SelectedFiducialProvinceCode);
|
||||||
|
FiducialMessage = string.Empty;
|
||||||
|
}
|
||||||
|
catch (Exception error)
|
||||||
|
{
|
||||||
|
FiducialMessage = $"Comuni non disponibili: {error.GetBaseException().Message}";
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
FiducialBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnFiducialMunicipalityChanged(ChangeEventArgs args)
|
||||||
|
{
|
||||||
|
SelectedFiducialMunicipalityCode = args.Value?.ToString() ?? string.Empty;
|
||||||
|
SelectedFiducialSheet = string.Empty;
|
||||||
|
FiducialSheets = [];
|
||||||
|
FiducialPoints = [];
|
||||||
|
MappedFiducialPoints = [];
|
||||||
|
if (string.IsNullOrWhiteSpace(SelectedFiducialMunicipalityCode))
|
||||||
|
{
|
||||||
|
FiducialMessage = string.Empty;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FiducialBusy = true;
|
||||||
|
FiducialMessage = "Carico i fogli disponibili...";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FiducialSheets = await FiducialPointService.GetSheetsAsync(
|
||||||
|
SelectedFiducialProvinceCode,
|
||||||
|
SelectedFiducialMunicipalityCode);
|
||||||
|
FiducialMessage = string.Empty;
|
||||||
|
}
|
||||||
|
catch (Exception error)
|
||||||
|
{
|
||||||
|
FiducialMessage = $"Fogli non disponibili: {error.GetBaseException().Message}";
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
FiducialBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SearchFiducials()
|
||||||
|
{
|
||||||
|
var municipality = FiducialMunicipalities.FirstOrDefault(item => item.Code == SelectedFiducialMunicipalityCode);
|
||||||
|
if (municipality is null || string.IsNullOrWhiteSpace(SelectedFiducialSheet))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await LoadFiducialsForSheetAsync(
|
||||||
|
municipality.Code,
|
||||||
|
SelectedFiducialSheet,
|
||||||
|
municipality.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadFiducialsForSheetAsync(
|
||||||
|
string municipalityCode,
|
||||||
|
string sheet,
|
||||||
|
string? municipalityName = null)
|
||||||
|
{
|
||||||
|
FiducialBusy = true;
|
||||||
|
FiducialPoints = [];
|
||||||
|
MappedFiducialPoints = [];
|
||||||
|
FiducialMessage = "Recupero monografie e coordinate dei PF...";
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string? monographError = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FiducialPoints = await FiducialPointService.GetFiducialsAsync(
|
||||||
|
municipalityCode,
|
||||||
|
sheet);
|
||||||
|
}
|
||||||
|
catch (Exception error)
|
||||||
|
{
|
||||||
|
monographError = error.GetBaseException().Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mapped = await FiducialPointService.GetMappedFiducialsAsync(
|
||||||
|
municipalityCode,
|
||||||
|
sheet);
|
||||||
|
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,
|
||||||
|
KeepPreviousFiducialPoints,
|
||||||
|
municipalityCode,
|
||||||
|
sheet,
|
||||||
|
string.IsNullOrWhiteSpace(municipalityName) ? municipalityCode : municipalityName);
|
||||||
|
FiducialMessage = MappedFiducialPoints.Count == 0
|
||||||
|
? "Nessun Punto Fiduciale georiferito per il foglio selezionato."
|
||||||
|
: $"Caricati {MappedFiducialPoints.Count} PF del foglio {NormalizeSheetLabel(sheet)} e {FiducialPoints.Count} monografie ufficiali."
|
||||||
|
+ (monographError is null ? string.Empty : " Le monografie non sono al momento raggiungibili.");
|
||||||
|
await JS.InvokeVoidAsync("lmeMap.showInfo", FiducialMessage);
|
||||||
|
}
|
||||||
|
catch (Exception error)
|
||||||
|
{
|
||||||
|
FiducialMessage = $"Ricerca PF non riuscita: {error.GetBaseException().Message}";
|
||||||
|
await JS.InvokeVoidAsync("lmeMap.showInfo", FiducialMessage);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
FiducialBusy = false;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JSInvokable]
|
||||||
|
public async Task DownloadFiducialsForMapSelection(
|
||||||
|
string municipalityCode,
|
||||||
|
string sheet,
|
||||||
|
string contextLabel)
|
||||||
|
{
|
||||||
|
if (!CanUse(AppFeatures.FiducialPoints))
|
||||||
|
{
|
||||||
|
await ShowProRequiredAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FiducialBusy)
|
||||||
|
{
|
||||||
|
await JS.InvokeVoidAsync("lmeMap.showInfo", "È già in corso una ricerca dei Punti Fiduciali.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await JS.InvokeVoidAsync("lmeMap.showInfo", $"Scarico i PF per {contextLabel}...");
|
||||||
|
var municipality = await CatalogService.FindMunicipalityAsync(municipalityCode);
|
||||||
|
await LoadFiducialsForSheetAsync(municipalityCode, sheet, municipality?.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeSheetLabel(string sheet) =>
|
||||||
|
int.TryParse(new string((sheet ?? string.Empty).Where(char.IsDigit).ToArray()), out var number)
|
||||||
|
? number.ToString(System.Globalization.CultureInfo.InvariantCulture)
|
||||||
|
: sheet ?? string.Empty;
|
||||||
|
|
||||||
|
private async Task DownloadFiducialMonograph(string monographUrl)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Launcher.Default.OpenAsync(new Uri(monographUrl));
|
||||||
|
}
|
||||||
|
catch (Exception error)
|
||||||
|
{
|
||||||
|
FiducialMessage = $"Impossibile aprire la monografia: {error.GetBaseException().Message}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ApplyFiducialVisibility()
|
||||||
|
{
|
||||||
|
SaveFlagPreferences();
|
||||||
|
await JS.InvokeVoidAsync("lmeMap.setFiducialPointsVisible", ShowFiducialPoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ApplyFiducialColor()
|
||||||
|
{
|
||||||
|
SaveFlagPreferences();
|
||||||
|
await JS.InvokeVoidAsync("lmeMap.setFiducialColor", FiducialColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task ApplyKeepPreviousFiducials()
|
||||||
|
{
|
||||||
|
SaveFlagPreferences();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
@@ -1305,7 +1678,7 @@
|
|||||||
Interlocked.Exchange(ref LastGpsLookupAt, now);
|
Interlocked.Exchange(ref LastGpsLookupAt, now);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var radius = Math.Clamp(location.Accuracy ?? 5, 2, 25);
|
var radius = Math.Clamp(location.Accuracy ?? 12, 12, 30);
|
||||||
var xml = await OfficialCadastreService.GetParcelsNearAsync(
|
var xml = await OfficialCadastreService.GetParcelsNearAsync(
|
||||||
location.Latitude,
|
location.Latitude,
|
||||||
location.Longitude,
|
location.Longitude,
|
||||||
@@ -2094,6 +2467,12 @@
|
|||||||
|
|
||||||
private Task CenterOnGps() => JS.InvokeVoidAsync("lmeMap.centerOnGps").AsTask();
|
private Task CenterOnGps() => JS.InvokeVoidAsync("lmeMap.centerOnGps").AsTask();
|
||||||
|
|
||||||
|
private void ToggleGpsOverlay()
|
||||||
|
{
|
||||||
|
GpsOverlayCollapsed = !GpsOverlayCollapsed;
|
||||||
|
SaveFlagPreferences();
|
||||||
|
}
|
||||||
|
|
||||||
private string L(string key) => L10n.T(key);
|
private string L(string key) => L10n.T(key);
|
||||||
|
|
||||||
private bool CanUse(string feature) => Entitlements.CanUse(feature);
|
private bool CanUse(string feature) => Entitlements.CanUse(feature);
|
||||||
@@ -2132,6 +2511,7 @@
|
|||||||
private Task ApplyEntitlementsToMapAsync() => JS.InvokeVoidAsync("lmeMap.setEntitlements", new
|
private Task ApplyEntitlementsToMapAsync() => JS.InvokeVoidAsync("lmeMap.setEntitlements", new
|
||||||
{
|
{
|
||||||
advancedGeometry = CanUse(AppFeatures.AdvancedGeometry),
|
advancedGeometry = CanUse(AppFeatures.AdvancedGeometry),
|
||||||
|
fiducialPoints = CanUse(AppFeatures.FiducialPoints),
|
||||||
maxDrawingObjects = Entitlements.HasProAccess ? (int?)null : EntitlementService.BaseDrawingObjectLimit,
|
maxDrawingObjects = Entitlements.HasProAccess ? (int?)null : EntitlementService.BaseDrawingObjectLimit,
|
||||||
drawingLimitMessage = L("drawings.base.limit.reached")
|
drawingLimitMessage = L("drawings.base.limit.reached")
|
||||||
}).AsTask();
|
}).AsTask();
|
||||||
@@ -2339,6 +2719,9 @@
|
|||||||
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);
|
||||||
|
KeepPreviousFiducialPoints = GetFlagPreference("keep_previous_fiducials", 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);
|
||||||
@@ -2356,6 +2739,11 @@
|
|||||||
OfflineExtraZoomLevels = Preferences.Default.Get($"{FlagPreferencePrefix}offline_zoom_levels", 2);
|
OfflineExtraZoomLevels = Preferences.Default.Get($"{FlagPreferencePrefix}offline_zoom_levels", 2);
|
||||||
GpsProximityEnabled = GetFlagPreference("gps_proximity", true);
|
GpsProximityEnabled = GetFlagPreference("gps_proximity", true);
|
||||||
GpsBoundaryAlertEnabled = GetFlagPreference("gps_boundary_alert", false);
|
GpsBoundaryAlertEnabled = GetFlagPreference("gps_boundary_alert", false);
|
||||||
|
GpsOverlayCollapsed = GetFlagPreference("gps_overlay_collapsed", false);
|
||||||
|
MapMessageTimeoutSeconds = Math.Clamp(
|
||||||
|
Preferences.Default.Get($"{FlagPreferencePrefix}map_message_timeout", 3),
|
||||||
|
0,
|
||||||
|
30);
|
||||||
|
|
||||||
foreach (var layer in CadastralLayers)
|
foreach (var layer in CadastralLayers)
|
||||||
{
|
{
|
||||||
@@ -2374,6 +2762,9 @@
|
|||||||
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);
|
||||||
|
SetFlagPreference("keep_previous_fiducials", KeepPreviousFiducialPoints);
|
||||||
|
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);
|
||||||
@@ -2391,6 +2782,8 @@
|
|||||||
Preferences.Default.Set($"{FlagPreferencePrefix}offline_zoom_levels", OfflineExtraZoomLevels);
|
Preferences.Default.Set($"{FlagPreferencePrefix}offline_zoom_levels", OfflineExtraZoomLevels);
|
||||||
SetFlagPreference("gps_proximity", GpsProximityEnabled);
|
SetFlagPreference("gps_proximity", GpsProximityEnabled);
|
||||||
SetFlagPreference("gps_boundary_alert", GpsBoundaryAlertEnabled);
|
SetFlagPreference("gps_boundary_alert", GpsBoundaryAlertEnabled);
|
||||||
|
SetFlagPreference("gps_overlay_collapsed", GpsOverlayCollapsed);
|
||||||
|
Preferences.Default.Set($"{FlagPreferencePrefix}map_message_timeout", MapMessageTimeoutSeconds);
|
||||||
|
|
||||||
foreach (var layer in CadastralLayers)
|
foreach (var layer in CadastralLayers)
|
||||||
{
|
{
|
||||||
@@ -2400,6 +2793,7 @@
|
|||||||
|
|
||||||
private async Task ApplyFlagPreferencesToMapAsync()
|
private async Task ApplyFlagPreferencesToMapAsync()
|
||||||
{
|
{
|
||||||
|
await JS.InvokeVoidAsync("lmeMap.setInfoTimeout", MapMessageTimeoutSeconds);
|
||||||
await JS.InvokeVoidAsync("lmeMap.setIncludeKmlSheetLayer", IncludeKmlSheetLayer);
|
await JS.InvokeVoidAsync("lmeMap.setIncludeKmlSheetLayer", IncludeKmlSheetLayer);
|
||||||
await JS.InvokeVoidAsync("lmeMap.setImportedLayerVisible", ShowImportedData);
|
await JS.InvokeVoidAsync("lmeMap.setImportedLayerVisible", ShowImportedData);
|
||||||
await JS.InvokeVoidAsync("lmeMap.setImportedSheetLayerVisible", ShowImportedSheetLayer);
|
await JS.InvokeVoidAsync("lmeMap.setImportedSheetLayerVisible", ShowImportedSheetLayer);
|
||||||
@@ -2408,6 +2802,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)
|
||||||
@@ -2416,6 +2812,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ApplyMapMessageOptions()
|
||||||
|
{
|
||||||
|
MapMessageTimeoutSeconds = Math.Clamp(MapMessageTimeoutSeconds, 0, 30);
|
||||||
|
SaveFlagPreferences();
|
||||||
|
await JS.InvokeVoidAsync("lmeMap.setInfoTimeout", MapMessageTimeoutSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ApplyAdvancedOptions()
|
private async Task ApplyAdvancedOptions()
|
||||||
{
|
{
|
||||||
SaveFlagPreferences();
|
SaveFlagPreferences();
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<EnableDefaultCssItems>false</EnableDefaultCssItems>
|
<EnableDefaultCssItems>false</EnableDefaultCssItems>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);obj/**;bin/**;obj-api36*/**;artifacts/**</DefaultItemExcludes>
|
||||||
|
|
||||||
<!-- Display name -->
|
<!-- Display name -->
|
||||||
<ApplicationTitle>LME Mappe Catastali Pro</ApplicationTitle>
|
<ApplicationTitle>LME Mappe Catastali Pro</ApplicationTitle>
|
||||||
@@ -28,8 +29,8 @@
|
|||||||
<ApplicationId>it.liviomerola.lmemappecatastali.pro</ApplicationId>
|
<ApplicationId>it.liviomerola.lmemappecatastali.pro</ApplicationId>
|
||||||
|
|
||||||
<!-- Versions -->
|
<!-- Versions -->
|
||||||
<ApplicationDisplayVersion>1.0.8</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>1.0.14</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>108</ApplicationVersion>
|
<ApplicationVersion>114</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 -->
|
||||||
@@ -61,6 +62,7 @@
|
|||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)' == 'Release' and $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
|
<PropertyGroup Condition="'$(Configuration)' == 'Release' and $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
|
||||||
<AndroidTargetSdkVersion>36</AndroidTargetSdkVersion>
|
<AndroidTargetSdkVersion>36</AndroidTargetSdkVersion>
|
||||||
|
<RuntimeIdentifiers>android-arm;android-arm64</RuntimeIdentifiers>
|
||||||
<AndroidPackageFormat>aab</AndroidPackageFormat>
|
<AndroidPackageFormat>aab</AndroidPackageFormat>
|
||||||
<AndroidUseAapt2>true</AndroidUseAapt2>
|
<AndroidUseAapt2>true</AndroidUseAapt2>
|
||||||
<AndroidCreatePackagePerAbi>false</AndroidCreatePackagePerAbi>
|
<AndroidCreatePackagePerAbi>false</AndroidCreatePackagePerAbi>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ public static class MauiProgram
|
|||||||
builder.Services.AddSingleton<PreciseLocationService>();
|
builder.Services.AddSingleton<PreciseLocationService>();
|
||||||
builder.Services.AddSingleton<CadastralCatalogService>();
|
builder.Services.AddSingleton<CadastralCatalogService>();
|
||||||
builder.Services.AddSingleton<OfficialCadastreService>();
|
builder.Services.AddSingleton<OfficialCadastreService>();
|
||||||
|
builder.Services.AddSingleton<FiducialPointService>();
|
||||||
builder.Services.AddSingleton<LocationSearchService>();
|
builder.Services.AddSingleton<LocationSearchService>();
|
||||||
builder.Services.AddSingleton<NetworkLocationService>();
|
builder.Services.AddSingleton<NetworkLocationService>();
|
||||||
builder.Services.AddSingleton<AzureMapsService>();
|
builder.Services.AddSingleton<AzureMapsService>();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
- Nome: LME Mappe Catastali Pro.
|
- Nome: LME Mappe Catastali Pro.
|
||||||
- Package name Android: `it.liviomerola.lmemappecatastali.pro`.
|
- Package name Android: `it.liviomerola.lmemappecatastali.pro`.
|
||||||
- Versione corrente da pubblicare: `1.0.8` / version code `108`.
|
- Versione corrente da pubblicare: `1.0.11` / version code `111`.
|
||||||
- Formato Release Android: Android App Bundle (`.aab`).
|
- Formato Release Android: Android App Bundle (`.aab`).
|
||||||
- Target SDK Release: Android 16 / API 36.
|
- Target SDK Release: Android 16 / API 36.
|
||||||
|
|
||||||
|
|||||||
@@ -209,17 +209,16 @@ public sealed class AndroidPlayBillingService : Java.Lang.Object, IPlayBillingSe
|
|||||||
throw new InvalidOperationException(ToMessage(result.Result));
|
throw new InvalidOperationException(ToMessage(result.Result));
|
||||||
}
|
}
|
||||||
|
|
||||||
var details = result.ProductDetailsList?.FirstOrDefault();
|
// The .NET async wrapper copies the Java result into the managed
|
||||||
|
// ProductDetails property; ProductDetailsList has no Java handle here.
|
||||||
|
var details = result.ProductDetails?.FirstOrDefault();
|
||||||
if (details is not null)
|
if (details is not null)
|
||||||
{
|
{
|
||||||
_lastCatalogFailure = null;
|
_lastCatalogFailure = null;
|
||||||
return details;
|
return details;
|
||||||
}
|
}
|
||||||
|
|
||||||
var unfetched = result.UnfetchedProductList?.FirstOrDefault();
|
_lastCatalogFailure = "Google Play ha restituito un catalogo vuoto per lme_pro. Verifica account Play Store, adesione al test e origine dell'installazione.";
|
||||||
_lastCatalogFailure = unfetched is null
|
|
||||||
? "Google Play ha restituito un catalogo vuoto per lme_pro. Verifica account Play Store, adesione al test e origine dell'installazione."
|
|
||||||
: $"Google Play non ha caricato lme_pro (codice catalogo {unfetched.StatusCodeValue}).";
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,7 +102,11 @@ public class MainActivity : MauiAppCompatActivity
|
|||||||
{
|
{
|
||||||
if (view is null || insets is null) return insets;
|
if (view is null || insets is null) return insets;
|
||||||
var systemBars = insets.GetInsets(WindowInsetsCompat.Type.SystemBars());
|
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;
|
return insets;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,22 @@ App .NET MAUI Blazor Hybrid per Android, iOS, Mac Catalyst e Windows, pensata pe
|
|||||||
|
|
||||||
Informativa privacy: [PRIVACY_POLICY.md](PRIVACY_POLICY.md).
|
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.
|
||||||
|
- I messaggi sulla mappa si chiudono automaticamente dopo un intervallo configurabile, predefinito a 3 secondi, e includono un pulsante di chiusura.
|
||||||
|
- La barra GPS mantiene sempre disponibili i comandi Avvia/Ferma e Centra, impilati sugli schermi stretti, e puo essere ridotta.
|
||||||
|
|
||||||
|
## Versione Pro 1.0.9 test
|
||||||
|
|
||||||
|
- Corregge la lettura del catalogo dal wrapper asincrono .NET di Google Play Billing, usando la raccolta managed `ProductDetails` popolata dal binding.
|
||||||
|
|
||||||
## Versione Pro 1.0.8 test
|
## Versione Pro 1.0.8 test
|
||||||
|
|
||||||
- Google Play Billing accetta tutte le offerte idonee restituite per i piani `monthly` e `yearly`, preferendo il piano base quando disponibile.
|
- Google Play Billing accetta tutte le offerte idonee restituite per i piani `monthly` e `yearly`, preferendo il piano base quando disponibile.
|
||||||
|
|||||||
@@ -40,6 +40,14 @@ public sealed class CadastralCatalogService
|
|||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<MunicipalityOption?> FindMunicipalityAsync(string municipalityCode)
|
||||||
|
{
|
||||||
|
var code = (municipalityCode ?? string.Empty).Trim();
|
||||||
|
var municipalities = await GetAllMunicipalitiesAsync();
|
||||||
|
return municipalities.FirstOrDefault(item =>
|
||||||
|
string.Equals(item.Code, code, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<IReadOnlyList<string>> GetSheetsAsync(
|
public async Task<IReadOnlyList<string>> GetSheetsAsync(
|
||||||
MunicipalityOption municipality,
|
MunicipalityOption municipality,
|
||||||
IProgress<CatalogProgress>? progress = null,
|
IProgress<CatalogProgress>? progress = null,
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ public sealed class EntitlementService(
|
|||||||
AppFeatures.CloudSync,
|
AppFeatures.CloudSync,
|
||||||
AppFeatures.OfflineMaps,
|
AppFeatures.OfflineMaps,
|
||||||
AppFeatures.AdvancedGeometry,
|
AppFeatures.AdvancedGeometry,
|
||||||
AppFeatures.GpsTrail
|
AppFeatures.GpsTrail,
|
||||||
|
AppFeatures.FiducialPoints
|
||||||
};
|
};
|
||||||
|
|
||||||
public bool IsGoogleProActive { get; private set; }
|
public bool IsGoogleProActive { get; private set; }
|
||||||
@@ -185,4 +186,5 @@ public static class AppFeatures
|
|||||||
public const string OfflineMaps = "offline-maps";
|
public const string OfflineMaps = "offline-maps";
|
||||||
public const string AdvancedGeometry = "advanced-geometry";
|
public const string AdvancedGeometry = "advanced-geometry";
|
||||||
public const string GpsTrail = "gps-trail";
|
public const string GpsTrail = "gps-trail";
|
||||||
|
public const string FiducialPoints = "fiducial-points";
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,292 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/1.0 (Android)");
|
||||||
|
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/html"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<FiducialAreaOption>> GetProvincesAsync(
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var html = await GetHtmlAsync("ricerca.php", cancellationToken);
|
||||||
|
return ParseOptions(html, "provincia");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<FiducialAreaOption>> GetMunicipalitiesAsync(
|
||||||
|
string provinceCode,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var code = NormalizeCode(provinceCode);
|
||||||
|
var html = await GetHtmlAsync($"ricerca.php?level=1&prov={Uri.EscapeDataString(code)}", cancellationToken);
|
||||||
|
return ParseOptions(html, "co");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<FiducialAreaOption>> GetSheetsAsync(
|
||||||
|
string provinceCode,
|
||||||
|
string municipalityCode,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var province = NormalizeCode(provinceCode);
|
||||||
|
var municipality = NormalizeCode(municipalityCode);
|
||||||
|
var path = $"ricerca.php?level=2&prov={Uri.EscapeDataString(province)}";
|
||||||
|
var html = await PostFormAsync(path, new Dictionary<string, string> { ["co"] = municipality }, cancellationToken);
|
||||||
|
return ParseOptions(html, "foglio");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<FiducialPointEntry>> GetFiducialsAsync(
|
||||||
|
string provinceName,
|
||||||
|
string municipalityName,
|
||||||
|
string municipalityCode,
|
||||||
|
string sheet,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var code = NormalizeCode(municipalityCode);
|
||||||
|
var normalizedSheet = sheet.Trim();
|
||||||
|
var query = string.Join("&",
|
||||||
|
[
|
||||||
|
$"provincia={Uri.EscapeDataString(provinceName.Trim())}",
|
||||||
|
$"comune={Uri.EscapeDataString(municipalityName.Trim())}",
|
||||||
|
$"co={Uri.EscapeDataString(code)}"
|
||||||
|
]);
|
||||||
|
var html = await PostFormAsync(
|
||||||
|
$"risultato.php?{query}",
|
||||||
|
new Dictionary<string, string> { ["foglio"] = normalizedSheet },
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
return ParseFiducialLinks(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<FiducialPointEntry>> GetFiducialsAsync(
|
||||||
|
string municipalityCode,
|
||||||
|
string sheet,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var code = NormalizeCode(municipalityCode);
|
||||||
|
var normalizedSheet = NormalizeSheet(sheet);
|
||||||
|
var html = await GetHtmlAsync(
|
||||||
|
$"risultato.php?co={Uri.EscapeDataString(code)}&foglio={Uri.EscapeDataString(normalizedSheet)}",
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
return ParseFiducialLinks(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<FiducialPointEntry> ParseFiducialLinks(string html) =>
|
||||||
|
DownloadLinkRegex().Matches(html)
|
||||||
|
.Select(match =>
|
||||||
|
{
|
||||||
|
var relativeUrl = WebUtility.HtmlDecode(match.Groups["url"].Value);
|
||||||
|
var fileName = WebUtility.HtmlDecode(match.Groups["name"].Value);
|
||||||
|
return new FiducialPointEntry(
|
||||||
|
FormatIdentifier(fileName),
|
||||||
|
new Uri(new Uri(ServiceBaseUrl), relativeUrl).AbsoluteUri);
|
||||||
|
})
|
||||||
|
.DistinctBy(item => item.Identifier, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.OrderBy(item => item.Identifier, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<MappedFiducialPoint>> GetMappedFiducialsAsync(
|
||||||
|
string municipalityCode,
|
||||||
|
string sheet,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var municipality = NormalizeCode(municipalityCode);
|
||||||
|
if (!int.TryParse(NormalizeSheet(sheet), 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 static string NormalizeSheet(string sheet)
|
||||||
|
{
|
||||||
|
var digits = new string((sheet ?? string.Empty).Where(char.IsDigit).ToArray());
|
||||||
|
if (string.IsNullOrWhiteSpace(digits))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Numero di foglio non valido.", nameof(sheet));
|
||||||
|
}
|
||||||
|
|
||||||
|
return int.TryParse(digits, out var number)
|
||||||
|
? number.ToString(System.Globalization.CultureInfo.InvariantCulture)
|
||||||
|
: digits;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> GetHtmlAsync(string relativeUrl, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
using var response = await _httpClient.GetAsync(ServiceBaseUrl + relativeUrl, cancellationToken);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
return await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> PostFormAsync(
|
||||||
|
string relativeUrl,
|
||||||
|
IReadOnlyDictionary<string, string> values,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
using var content = new FormUrlEncodedContent(values);
|
||||||
|
using var response = await _httpClient.PostAsync(ServiceBaseUrl + relativeUrl, content, cancellationToken);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
return await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<FiducialAreaOption> ParseOptions(string html, string selectId)
|
||||||
|
{
|
||||||
|
var select = SelectRegex().Matches(html)
|
||||||
|
.LastOrDefault(match => string.Equals(match.Groups["id"].Value, selectId, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (select is null)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return OptionRegex().Matches(select.Groups["body"].Value)
|
||||||
|
.Select(match => new FiducialAreaOption(
|
||||||
|
WebUtility.HtmlDecode(match.Groups["value"].Value).Trim(),
|
||||||
|
WebUtility.HtmlDecode(StripTagsRegex().Replace(match.Groups["label"].Value, string.Empty)).Trim()))
|
||||||
|
.Where(item => !string.IsNullOrWhiteSpace(item.Code) && !string.IsNullOrWhiteSpace(item.Name))
|
||||||
|
.DistinctBy(item => item.Code, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeCode(string value)
|
||||||
|
{
|
||||||
|
var normalized = value.Trim().ToUpperInvariant();
|
||||||
|
if (normalized.Length is < 2 or > 4 || normalized.Any(character => !char.IsLetterOrDigit(character)))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Codice territoriale non valido.", nameof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatIdentifier(string fileName)
|
||||||
|
{
|
||||||
|
var parts = fileName.Split('-', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||||
|
return parts.Length == 3 ? $"PF{parts[2]}/{parts[1]}/{parts[0]}" : fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
[GeneratedRegex("<select\\b[^>]*(?:id|name)=[\\\"'](?<id>[^\\\"']+)[\\\"'][^>]*>(?<body>[\\s\\S]*?)</select>", RegexOptions.IgnoreCase, matchTimeoutMilliseconds: 2000)]
|
||||||
|
private static partial Regex SelectRegex();
|
||||||
|
|
||||||
|
[GeneratedRegex("<option\\b[^>]*value=[\\\"'](?<value>[^\\\"']*)[\\\"'][^>]*>(?<label>[\\s\\S]*?)</option>", RegexOptions.IgnoreCase, matchTimeoutMilliseconds: 2000)]
|
||||||
|
private static partial Regex OptionRegex();
|
||||||
|
|
||||||
|
[GeneratedRegex("href=[\\\"'](?<url>download\\.php\\?[^\\\"']*namefile=(?<name>[A-Z0-9-]+))[^\\\"']*[\\\"']", RegexOptions.IgnoreCase, matchTimeoutMilliseconds: 2000)]
|
||||||
|
private static partial Regex DownloadLinkRegex();
|
||||||
|
|
||||||
|
[GeneratedRegex("<[^>]+>", RegexOptions.None, 2000)]
|
||||||
|
private static partial Regex StripTagsRegex();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = "");
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace LMEMappeCatastaliApp.Services;
|
namespace LMEMappeCatastaliApp.Services;
|
||||||
@@ -13,8 +15,9 @@ public sealed class OfficialCadastreService
|
|||||||
|
|
||||||
public OfficialCadastreService()
|
public OfficialCadastreService()
|
||||||
{
|
{
|
||||||
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/0.2");
|
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/1.0 (Android)");
|
||||||
_httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/gml+xml");
|
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/gml+xml"));
|
||||||
|
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml", 0.9));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetParcelsNearAsync(
|
public async Task<string> GetParcelsNearAsync(
|
||||||
@@ -22,14 +25,30 @@ public sealed class OfficialCadastreService
|
|||||||
double longitude,
|
double longitude,
|
||||||
double radiusMeters,
|
double radiusMeters,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
=> await GetFeaturesNearAsync(
|
{
|
||||||
|
var firstRadius = Math.Clamp(radiusMeters, 12, 30);
|
||||||
|
var xml = await GetFeaturesNearAsync(
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
radiusMeters,
|
firstRadius,
|
||||||
"CP:CadastralParcel",
|
"CP:CadastralParcel",
|
||||||
100,
|
100,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
|
if (firstRadius >= 30 || ContainsFeatures(xml))
|
||||||
|
{
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await GetFeaturesNearAsync(
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
30,
|
||||||
|
"CP:CadastralParcel",
|
||||||
|
100,
|
||||||
|
cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<string> GetSheetsNearAsync(
|
public async Task<string> GetSheetsNearAsync(
|
||||||
double latitude,
|
double latitude,
|
||||||
double longitude,
|
double longitude,
|
||||||
@@ -78,15 +97,65 @@ public sealed class OfficialCadastreService
|
|||||||
var url = WfsEndpoint + "?" + string.Join("&", query.Select(item =>
|
var url = WfsEndpoint + "?" + string.Join("&", query.Select(item =>
|
||||||
$"{Uri.EscapeDataString(item.Key)}={Uri.EscapeDataString(item.Value)}"));
|
$"{Uri.EscapeDataString(item.Key)}={Uri.EscapeDataString(item.Value)}"));
|
||||||
|
|
||||||
using var response = await _httpClient.GetAsync(url, HttpCompletionOption.ResponseContentRead, cancellationToken);
|
for (var attempt = 0; attempt < 3; attempt++)
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
{
|
||||||
throw new HttpRequestException($"Il servizio WFS AdE ha risposto {((int)response.StatusCode)}.");
|
try
|
||||||
|
{
|
||||||
|
using var request = new HttpRequestMessage(HttpMethod.Get, url)
|
||||||
|
{
|
||||||
|
Version = HttpVersion.Version11,
|
||||||
|
VersionPolicy = HttpVersionPolicy.RequestVersionOrLower
|
||||||
|
};
|
||||||
|
request.Headers.CacheControl = new CacheControlHeaderValue { NoCache = true };
|
||||||
|
|
||||||
|
using var response = await _httpClient.SendAsync(
|
||||||
|
request,
|
||||||
|
HttpCompletionOption.ResponseContentRead,
|
||||||
|
cancellationToken);
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var xml = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
|
ThrowIfExceptionReport(xml);
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attempt < 2 && IsTransient(response.StatusCode))
|
||||||
|
{
|
||||||
|
await Task.Delay(TimeSpan.FromMilliseconds(500 * (attempt + 1)), cancellationToken);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new HttpRequestException($"Il servizio WFS AdE ha risposto {((int)response.StatusCode)}.");
|
||||||
|
}
|
||||||
|
catch (HttpRequestException) when (attempt < 2)
|
||||||
|
{
|
||||||
|
await Task.Delay(TimeSpan.FromMilliseconds(500 * (attempt + 1)), cancellationToken);
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException) when (!cancellationToken.IsCancellationRequested && attempt < 2)
|
||||||
|
{
|
||||||
|
await Task.Delay(TimeSpan.FromMilliseconds(500 * (attempt + 1)), cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var xml = await response.Content.ReadAsStringAsync(cancellationToken);
|
throw new HttpRequestException("Il servizio WFS AdE non ha risposto dopo tre tentativi.");
|
||||||
ThrowIfExceptionReport(xml);
|
}
|
||||||
return xml;
|
|
||||||
|
private static bool IsTransient(HttpStatusCode statusCode) =>
|
||||||
|
statusCode is HttpStatusCode.RequestTimeout or HttpStatusCode.TooManyRequests
|
||||||
|
|| (int)statusCode >= 500;
|
||||||
|
|
||||||
|
private static bool ContainsFeatures(string xml)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var document = XDocument.Parse(xml);
|
||||||
|
return document.Descendants().Any(element =>
|
||||||
|
element.Name.LocalName is "member" or "featureMember");
|
||||||
|
}
|
||||||
|
catch (System.Xml.XmlException)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string Format(double value) => value.ToString("R", CultureInfo.InvariantCulture);
|
private static string Format(double value) => value.ToString("R", CultureInfo.InvariantCulture);
|
||||||
|
|||||||
+134
-8
@@ -1185,6 +1185,17 @@ input[type="checkbox"] {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lme-geometry-toolbar a.lme-fiducial-download {
|
||||||
|
font-size: 12px !important;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lme-geometry-toolbar a.is-disabled {
|
||||||
|
color: #8b949e;
|
||||||
|
background: #eef1f3;
|
||||||
|
opacity: 0.72;
|
||||||
|
}
|
||||||
|
|
||||||
.leaflet-help {
|
.leaflet-help {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
@@ -1245,6 +1256,41 @@ input[type="checkbox"] {
|
|||||||
transition: top 140ms ease;
|
transition: top 140ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gps-overlay.is-collapsed {
|
||||||
|
grid-template-columns: auto;
|
||||||
|
right: auto;
|
||||||
|
width: 48px;
|
||||||
|
max-width: 48px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gps-overlay.is-collapsed .gps-state,
|
||||||
|
.gps-overlay.is-collapsed .gps-actions {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fiducial-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fiducial-item {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 0;
|
||||||
|
border-bottom: 1px solid #d9e0e3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fiducial-item strong {
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fiducial-item .secondary-command {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.gps-overlay.search-open {
|
.gps-overlay.search-open {
|
||||||
top: 140px;
|
top: 140px;
|
||||||
}
|
}
|
||||||
@@ -1267,8 +1313,14 @@ input[type="checkbox"] {
|
|||||||
color: #a33227;
|
color: #a33227;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gps-actions {
|
||||||
|
display: grid;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.gps-main,
|
.gps-main,
|
||||||
.gps-center {
|
.gps-center,
|
||||||
|
.gps-collapse {
|
||||||
min-height: 38px;
|
min-height: 38px;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@@ -1279,6 +1331,15 @@ input[type="checkbox"] {
|
|||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gps-collapse {
|
||||||
|
width: 38px;
|
||||||
|
padding: 0;
|
||||||
|
background: #e7ecef;
|
||||||
|
color: var(--ink);
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.leaflet-top.leaflet-right {
|
.leaflet-top.leaflet-right {
|
||||||
top: 68px;
|
top: 68px;
|
||||||
right: 2px;
|
right: 2px;
|
||||||
@@ -1293,6 +1354,21 @@ input[type="checkbox"] {
|
|||||||
height: 38px;
|
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"] {
|
.lme-drawing-scope-actions a[aria-disabled="true"] {
|
||||||
opacity: 0.45;
|
opacity: 0.45;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
@@ -1566,11 +1642,36 @@ label {
|
|||||||
z-index: 820;
|
z-index: 820;
|
||||||
max-width: min(620px, calc(100% - 460px));
|
max-width: min(620px, calc(100% - 460px));
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 9px 11px;
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 7px 7px 7px 11px;
|
||||||
background: rgba(22, 31, 38, 0.88);
|
background: rgba(22, 31, 38, 0.88);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
pointer-events: none;
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-info[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-info-text {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 2px 0;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-info-close {
|
||||||
|
flex: 0 0 28px;
|
||||||
|
width: 28px;
|
||||||
|
min-height: 28px;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wms-preview[hidden] {
|
.wms-preview[hidden] {
|
||||||
@@ -1816,13 +1917,19 @@ footer {
|
|||||||
top: 136px;
|
top: 136px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gps-overlay.is-collapsed {
|
||||||
|
right: auto;
|
||||||
|
width: 48px;
|
||||||
|
max-width: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
.leaflet-top.leaflet-right {
|
.leaflet-top.leaflet-right {
|
||||||
top: 122px;
|
top: 174px;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-stage.search-active .leaflet-top.leaflet-right {
|
.map-stage.search-active .leaflet-top.leaflet-right {
|
||||||
top: 190px;
|
top: 242px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gps-main,
|
.gps-main,
|
||||||
@@ -1835,12 +1942,15 @@ footer {
|
|||||||
right: 8px;
|
right: 8px;
|
||||||
bottom: calc(var(--nav-height) + 116px);
|
bottom: calc(var(--nav-height) + 116px);
|
||||||
left: 8px;
|
left: 8px;
|
||||||
display: -webkit-box;
|
|
||||||
max-width: none;
|
max-width: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
overflow-wrap: anywhere;
|
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-info-text {
|
||||||
|
display: -webkit-box;
|
||||||
|
overflow: hidden;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
-webkit-line-clamp: 3;
|
-webkit-line-clamp: 3;
|
||||||
}
|
}
|
||||||
@@ -1864,8 +1974,24 @@ footer {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gps-overlay {
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gps-overlay.is-collapsed {
|
||||||
|
grid-template-columns: auto;
|
||||||
|
right: auto;
|
||||||
|
width: 48px;
|
||||||
|
max-width: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fiducial-item {
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gps-main,
|
||||||
.gps-center {
|
.gps-center {
|
||||||
display: none;
|
min-width: 86px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-tabs button {
|
.bottom-tabs button {
|
||||||
|
|||||||
+502
-33
@@ -5,8 +5,9 @@ const lmeState = {
|
|||||||
currentBaseName: "satellite",
|
currentBaseName: "satellite",
|
||||||
layerOrder: [
|
layerOrder: [
|
||||||
"gpsPosition",
|
"gpsPosition",
|
||||||
"drawings",
|
|
||||||
"gpsTrail",
|
"gpsTrail",
|
||||||
|
"fiducials",
|
||||||
|
"drawings",
|
||||||
"gpsParcels",
|
"gpsParcels",
|
||||||
"selectedParcels",
|
"selectedParcels",
|
||||||
"importedSheets",
|
"importedSheets",
|
||||||
@@ -14,8 +15,14 @@ const lmeState = {
|
|||||||
"cadastre"
|
"cadastre"
|
||||||
],
|
],
|
||||||
cadastre: null,
|
cadastre: null,
|
||||||
|
cadastreZoning: null,
|
||||||
cadastreLayers: ["CP.CadastralParcel", "codice_plla", "CP.CadastralZoning", "fabbricati"],
|
cadastreLayers: ["CP.CadastralParcel", "codice_plla", "CP.CadastralZoning", "fabbricati"],
|
||||||
cadastreOpacity: 0.75,
|
cadastreOpacity: 0.75,
|
||||||
|
navigationSheetLayer: null,
|
||||||
|
navigationSheetFeature: null,
|
||||||
|
navigationSheetLabel: null,
|
||||||
|
navigationSheetRefreshTimer: null,
|
||||||
|
navigationSheetRequestId: 0,
|
||||||
imported: null,
|
imported: null,
|
||||||
importedVisible: true,
|
importedVisible: true,
|
||||||
importedSheets: null,
|
importedSheets: null,
|
||||||
@@ -29,6 +36,7 @@ const lmeState = {
|
|||||||
drawingDeleteButton: null,
|
drawingDeleteButton: null,
|
||||||
drawingScopeMenu: null,
|
drawingScopeMenu: null,
|
||||||
geometryToolsControl: null,
|
geometryToolsControl: null,
|
||||||
|
fiducialDownloadButton: null,
|
||||||
ignoreMapClickUntil: 0,
|
ignoreMapClickUntil: 0,
|
||||||
activeDrawHandler: null,
|
activeDrawHandler: null,
|
||||||
drawEventsBound: false,
|
drawEventsBound: false,
|
||||||
@@ -44,12 +52,17 @@ 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: [],
|
||||||
gpsTrailStartedAt: null,
|
gpsTrailStartedAt: null,
|
||||||
gpsWatchId: null,
|
gpsWatchId: null,
|
||||||
lastPosition: null,
|
lastPosition: null,
|
||||||
|
infoTimeoutSeconds: 3,
|
||||||
|
infoTimer: null,
|
||||||
fileImportBound: false,
|
fileImportBound: false,
|
||||||
resizeBound: false,
|
resizeBound: false,
|
||||||
googleMapsLoading: null,
|
googleMapsLoading: null,
|
||||||
@@ -123,6 +136,10 @@ window.lmeMap = {
|
|||||||
closeCadastrePreview,
|
closeCadastrePreview,
|
||||||
clearSelectedParcels,
|
clearSelectedParcels,
|
||||||
setSelectedParcelsVisible,
|
setSelectedParcelsVisible,
|
||||||
|
setFiducialPoints,
|
||||||
|
setFiducialPointsVisible,
|
||||||
|
setFiducialColor,
|
||||||
|
fitFiducialPoints,
|
||||||
clearMapElements,
|
clearMapElements,
|
||||||
fitImported,
|
fitImported,
|
||||||
clearImported,
|
clearImported,
|
||||||
@@ -185,7 +202,9 @@ window.lmeMap = {
|
|||||||
setLocalization,
|
setLocalization,
|
||||||
setEntitlements,
|
setEntitlements,
|
||||||
importExternalGisFile,
|
importExternalGisFile,
|
||||||
showInfo: setInfo
|
showInfo: setInfo,
|
||||||
|
hideInfo,
|
||||||
|
setInfoTimeout
|
||||||
};
|
};
|
||||||
|
|
||||||
function setLocalization(options = {}) {
|
function setLocalization(options = {}) {
|
||||||
@@ -207,6 +226,7 @@ function setLocalization(options = {}) {
|
|||||||
|
|
||||||
function setEntitlements(options = {}) {
|
function setEntitlements(options = {}) {
|
||||||
lmeState.entitlements = { ...lmeState.entitlements, ...options };
|
lmeState.entitlements = { ...lmeState.entitlements, ...options };
|
||||||
|
updateFiducialDownloadButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
function requireProFeature(feature) {
|
function requireProFeature(feature) {
|
||||||
@@ -300,7 +320,15 @@ 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");
|
||||||
|
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([], {
|
lmeState.gpsTrail = L.polyline([], {
|
||||||
pane: "gpsTrailPane",
|
pane: "gpsTrailPane",
|
||||||
color: "#f97316",
|
color: "#f97316",
|
||||||
@@ -335,6 +363,7 @@ function init(dotNetReference) {
|
|||||||
lmeState.map.on("moveend", () => {
|
lmeState.map.on("moveend", () => {
|
||||||
void updateAzureAttribution();
|
void updateAzureAttribution();
|
||||||
scheduleMeasurementRefresh();
|
scheduleMeasurementRefresh();
|
||||||
|
scheduleNavigationSheetRefresh();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("closeWmsPreview")?.addEventListener("click", closeCadastrePreview);
|
document.getElementById("closeWmsPreview")?.addEventListener("click", closeCadastrePreview);
|
||||||
@@ -1321,6 +1350,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"
|
||||||
@@ -1351,7 +1381,8 @@ function restoreLayerOrder() {
|
|||||||
try {
|
try {
|
||||||
const saved = JSON.parse(localStorage.getItem("lmeMapLayerOrder") || "null");
|
const saved = JSON.parse(localStorage.getItem("lmeMapLayerOrder") || "null");
|
||||||
if (Array.isArray(saved)) {
|
if (Array.isArray(saved)) {
|
||||||
lmeState.layerOrder = saved;
|
const remaining = saved.filter(key => !["gpsPosition", "gpsTrail", "fiducials"].includes(key));
|
||||||
|
lmeState.layerOrder = ["gpsPosition", "gpsTrail", "fiducials", ...remaining];
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
}
|
}
|
||||||
@@ -1409,6 +1440,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), blocks: true },
|
||||||
{ 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) }
|
||||||
@@ -1596,11 +1628,11 @@ async function renameManagedBlock(category, source, records) {
|
|||||||
|
|
||||||
function managedCategoryCanRemove(category, records) {
|
function managedCategoryCanRemove(category, records) {
|
||||||
return records.length > 0
|
return records.length > 0
|
||||||
&& ["drawings", "imported", "importedSheets", "selectedParcels", "gpsParcels"].includes(category.key);
|
&& ["drawings", "imported", "importedSheets", "selectedParcels", "gpsParcels", "fiducials"].includes(category.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
function managedObjectCanRemove(record) {
|
function managedObjectCanRemove(record) {
|
||||||
return ["drawings", "imported", "importedSheets", "selectedParcels", "gpsParcels"].includes(record.categoryKey);
|
return ["drawings", "imported", "importedSheets", "selectedParcels", "gpsParcels", "fiducials"].includes(record.categoryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
function managedCategoryCanMove(categoryKey) {
|
function managedCategoryCanMove(categoryKey) {
|
||||||
@@ -1862,6 +1894,7 @@ function managedObjectDetails(layer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function refreshLayerManager() {
|
function refreshLayerManager() {
|
||||||
|
updateFiducialDownloadButton();
|
||||||
const container = document.getElementById("layerManagerTree");
|
const container = document.getElementById("layerManagerTree");
|
||||||
if (!container || !lmeState.map) {
|
if (!container || !lmeState.map) {
|
||||||
return;
|
return;
|
||||||
@@ -1997,8 +2030,12 @@ function renderLayerManagerBlock(category, source, records) {
|
|||||||
const blockName = managedBlockName(source, records);
|
const blockName = managedBlockName(source, records);
|
||||||
const rowActions = layerManagerRowActions(
|
const rowActions = layerManagerRowActions(
|
||||||
`<button type="button" class="layer-rename-button" aria-label="Rinomina gruppo" title="Rinomina">Rinomina</button>`,
|
`<button type="button" class="layer-rename-button" aria-label="Rinomina gruppo" title="Rinomina">Rinomina</button>`,
|
||||||
`<button type="button" class="layer-membership-button" aria-label="Sposta gruppo in un altro gruppo" title="Sposta nel gruppo">Sposta nel gruppo</button>`,
|
managedCategoryCanMove(category.key)
|
||||||
`<button type="button" class="layer-remove-button" aria-label="Rimuovi gruppo dal progetto" title="Rimuovi dal progetto">Rimuovi</button>`);
|
? `<button type="button" class="layer-membership-button" aria-label="Sposta gruppo in un altro gruppo" title="Sposta nel gruppo">Sposta nel gruppo</button>`
|
||||||
|
: "",
|
||||||
|
managedObjectCanRemove(records[0])
|
||||||
|
? `<button type="button" class="layer-remove-button" aria-label="Rimuovi gruppo dal progetto" title="Rimuovi dal progetto">Rimuovi</button>`
|
||||||
|
: "");
|
||||||
block.classList.add("layer-manager-with-actions");
|
block.classList.add("layer-manager-with-actions");
|
||||||
const blockLocked = records.length > 0 && records.every(record => Boolean(record.layer?.feature?.properties?._lmeLocked));
|
const blockLocked = records.length > 0 && records.every(record => Boolean(record.layer?.feature?.properties?._lmeLocked));
|
||||||
block.innerHTML = `<summary>${layerExpandButton(block.open, blockName)}<button type="button" class="layer-drag-handle" aria-label="Trascina blocco">☰</button><input type="checkbox" ${visible ? "checked" : ""} aria-label="Mostra ${escapeHtml(blockName)}"><span class="layer-manager-title">${escapeHtml(blockName)}</span><button type="button" class="layer-inline-lock ${blockLocked ? "is-locked" : ""}" aria-label="${blockLocked ? "Sblocca" : "Blocca"} gruppo" title="${blockLocked ? "Sblocca" : "Blocca"}">${blockLocked ? "🔒" : "🔓"}</button>${rowActions}${layerManagerMoveButtons("blocco")}<span class="layer-manager-count">${records.length}</span></summary><div class="layer-manager-children"></div>`;
|
block.innerHTML = `<summary>${layerExpandButton(block.open, blockName)}<button type="button" class="layer-drag-handle" aria-label="Trascina blocco">☰</button><input type="checkbox" ${visible ? "checked" : ""} aria-label="Mostra ${escapeHtml(blockName)}"><span class="layer-manager-title">${escapeHtml(blockName)}</span><button type="button" class="layer-inline-lock ${blockLocked ? "is-locked" : ""}" aria-label="${blockLocked ? "Sblocca" : "Blocca"} gruppo" title="${blockLocked ? "Sblocca" : "Blocca"}">${blockLocked ? "🔒" : "🔓"}</button>${rowActions}${layerManagerMoveButtons("blocco")}<span class="layer-manager-count">${records.length}</span></summary><div class="layer-manager-children"></div>`;
|
||||||
@@ -2019,12 +2056,12 @@ function renderLayerManagerBlock(category, source, records) {
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
renameManagedBlock(category, source, records);
|
renameManagedBlock(category, source, records);
|
||||||
});
|
});
|
||||||
block.querySelector(".layer-remove-button").addEventListener("click", event => {
|
block.querySelector(".layer-remove-button")?.addEventListener("click", event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
removeManagedRecords(records, `il gruppo '${blockName}'`);
|
removeManagedRecords(records, `il gruppo '${blockName}'`);
|
||||||
});
|
});
|
||||||
block.querySelector(".layer-membership-button").addEventListener("click", event => {
|
block.querySelector(".layer-membership-button")?.addEventListener("click", event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
showMoveManagedRecordsDialog(records, `il gruppo '${blockName}'`);
|
showMoveManagedRecordsDialog(records, `il gruppo '${blockName}'`);
|
||||||
@@ -2914,6 +2951,7 @@ function showLeafletToolbarHelp() {
|
|||||||
<div><dt>✓</dt><dd><strong>Controlla e ripara</strong>Verifica gli oggetti selezionati e prova a correggere coordinate duplicate o poligoni non validi. Esempio: usalo quando un contorno importato non si esporta o si riempie male; al termine compare un riepilogo.</dd></div>
|
<div><dt>✓</dt><dd><strong>Controlla e ripara</strong>Verifica gli oggetti selezionati e prova a correggere coordinate duplicate o poligoni non validi. Esempio: usalo quando un contorno importato non si esporta o si riempie male; al termine compare un riepilogo.</dd></div>
|
||||||
<div><dt>◉</dt><dd><strong>Crea foro</strong>Seleziona esattamente due poligoni: prima l'area esterna, poi quella interna. Esempio: sottrai il cortile dal perimetro di un fabbricato o un'isola esclusa da un'area di lavoro.</dd></div>
|
<div><dt>◉</dt><dd><strong>Crea foro</strong>Seleziona esattamente due poligoni: prima l'area esterna, poi quella interna. Esempio: sottrai il cortile dal perimetro di un fabbricato o un'isola esclusa da un'area di lavoro.</dd></div>
|
||||||
<div><dt>⇄</dt><dd><strong>Confronta due confini</strong>Seleziona esattamente due poligoni. L'app crea il solo primo in rosso, il solo secondo in blu e la sovrapposizione in verde, mostrando le rispettive aree.</dd></div>
|
<div><dt>⇄</dt><dd><strong>Confronta due confini</strong>Seleziona esattamente due poligoni. L'app crea il solo primo in rosso, il solo secondo in blu e la sovrapposizione in verde, mostrando le rispettive aree.</dd></div>
|
||||||
|
<div><dt>PF</dt><dd><strong>Scarica Punti Fiduciali</strong>Seleziona una particella o un foglio catastale; in alternativa usa la particella rilevata dal GPS. I PF vengono aggiunti come sottogruppo del relativo foglio.</dd></div>
|
||||||
<div><dt>?</dt><dd><strong>Guida</strong>Riapre questa descrizione. I risultati di riparazione e confronto vengono mostrati anche in una finestra, così l'operazione non resta senza riscontro.</dd></div>
|
<div><dt>?</dt><dd><strong>Guida</strong>Riapre questa descrizione. I risultati di riparazione e confronto vengono mostrati anche in una finestra, così l'operazione non resta senza riscontro.</dd></div>
|
||||||
</dl>
|
</dl>
|
||||||
</div>`);
|
</div>`);
|
||||||
@@ -2929,6 +2967,7 @@ function addGeometryToolsControl() {
|
|||||||
{ icon: "✓", title: "Controlla e ripara geometrie", run: validateAndRepairSelected },
|
{ icon: "✓", title: "Controlla e ripara geometrie", run: validateAndRepairSelected },
|
||||||
{ icon: "◉", title: "Crea foro da due poligoni", run: createHoleFromSelected },
|
{ icon: "◉", title: "Crea foro da due poligoni", run: createHoleFromSelected },
|
||||||
{ icon: "⇄", title: "Confronta due confini", run: compareSelectedGeometries },
|
{ icon: "⇄", title: "Confronta due confini", run: compareSelectedGeometries },
|
||||||
|
{ icon: "PF", title: "Scarica PF del foglio o della particella", run: downloadFiducialsForMapContext, fiducials: true },
|
||||||
{ icon: "?", title: "Guida strumenti mappa", run: showLeafletToolbarHelp }
|
{ icon: "?", title: "Guida strumenti mappa", run: showLeafletToolbarHelp }
|
||||||
];
|
];
|
||||||
actions.forEach(action => {
|
actions.forEach(action => {
|
||||||
@@ -2938,6 +2977,10 @@ function addGeometryToolsControl() {
|
|||||||
button.setAttribute("role", "button");
|
button.setAttribute("role", "button");
|
||||||
button.setAttribute("aria-label", action.title);
|
button.setAttribute("aria-label", action.title);
|
||||||
button.textContent = action.icon;
|
button.textContent = action.icon;
|
||||||
|
if (action.fiducials) {
|
||||||
|
button.classList.add("lme-fiducial-download");
|
||||||
|
lmeState.fiducialDownloadButton = button;
|
||||||
|
}
|
||||||
L.DomEvent.on(button, "click", event => { L.DomEvent.stop(event); action.run(); });
|
L.DomEvent.on(button, "click", event => { L.DomEvent.stop(event); action.run(); });
|
||||||
});
|
});
|
||||||
L.DomEvent.disableClickPropagation(container);
|
L.DomEvent.disableClickPropagation(container);
|
||||||
@@ -2945,6 +2988,88 @@ function addGeometryToolsControl() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
lmeState.geometryToolsControl = new GeometryTools().addTo(lmeState.map);
|
lmeState.geometryToolsControl = new GeometryTools().addTo(lmeState.map);
|
||||||
|
updateFiducialDownloadButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadFiducialsForMapContext() {
|
||||||
|
if (!requireProFeature("fiducialPoints")) return false;
|
||||||
|
const context = currentFiducialMapContext();
|
||||||
|
if (!context) {
|
||||||
|
setInfo("Seleziona una particella o un foglio catastale. Se il GPS è attivo puoi usare anche la particella corrente.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!lmeState.dotNetReference) {
|
||||||
|
setInfo("Servizio PF non ancora pronto. Riprova tra poco.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
void lmeState.dotNetReference.invokeMethodAsync(
|
||||||
|
"DownloadFiducialsForMapSelection",
|
||||||
|
context.municipalityCode,
|
||||||
|
context.sheet,
|
||||||
|
context.label);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentFiducialMapContext() {
|
||||||
|
const selected = selectedManagedRecords();
|
||||||
|
if (!selected.length && lmeState.managerHighlightedObjectId) {
|
||||||
|
const highlighted = lmeState.managerRecords?.get?.(lmeState.managerHighlightedObjectId);
|
||||||
|
if (highlighted) selected.push(highlighted);
|
||||||
|
}
|
||||||
|
const preferred = selected.filter(record => ["selectedParcels", "gpsParcels", "importedSheets", "imported"].includes(record.categoryKey));
|
||||||
|
for (const record of preferred) {
|
||||||
|
const context = fiducialContextFromLayer(record.layer);
|
||||||
|
if (context) return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gpsLayers = managedRecordsForCategory(layerManagerCategories().find(category => category.key === "gpsParcels"));
|
||||||
|
for (const record of gpsLayers) {
|
||||||
|
const context = fiducialContextFromLayer(record.layer);
|
||||||
|
if (context) return context;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fiducialContextFromLayer(layer) {
|
||||||
|
const properties = layer?.feature?.properties || {};
|
||||||
|
const municipalityCode = String(properties._lmeMunicipalityCode || "").trim().toUpperCase();
|
||||||
|
const sheet = normalizeFiducialSheet(properties._lmeSheet || properties.foglio || properties.FOGLIO || properties.sheet || properties.SHEET);
|
||||||
|
if (/^[A-Z][0-9A-Z]{3}$/.test(municipalityCode) && sheet) {
|
||||||
|
return { municipalityCode, sheet, label: `Comune ${municipalityCode}, foglio ${sheet}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
const references = [
|
||||||
|
properties.NATIONALCADASTRALREFERENCE,
|
||||||
|
properties.NATIONALCADASTRALZONINGREFERENCE,
|
||||||
|
properties.INSPIREID_LOCALID,
|
||||||
|
properties._lmeParcelReference,
|
||||||
|
properties.LABEL
|
||||||
|
].filter(Boolean).map(value => String(value).toUpperCase());
|
||||||
|
for (const reference of references) {
|
||||||
|
const match = reference.match(/(?:^|\.)([A-Z][0-9A-Z]{3})[_-](\d{4})(?:\d{2})?(?:[._-]|$)/);
|
||||||
|
if (match) {
|
||||||
|
const parsedSheet = normalizeFiducialSheet(match[2]);
|
||||||
|
return { municipalityCode: match[1], sheet: parsedSheet, label: `Comune ${match[1]}, foglio ${parsedSheet}` };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeFiducialSheet(value) {
|
||||||
|
const digits = String(value ?? "").match(/\d+/)?.[0] || "";
|
||||||
|
if (!digits) return "";
|
||||||
|
return String(Number.parseInt(digits, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFiducialDownloadButton() {
|
||||||
|
const button = lmeState.fiducialDownloadButton;
|
||||||
|
if (!button) return;
|
||||||
|
const enabled = Boolean(lmeState.entitlements?.fiducialPoints && currentFiducialMapContext());
|
||||||
|
button.classList.toggle("is-disabled", !enabled);
|
||||||
|
button.setAttribute("aria-disabled", String(!enabled));
|
||||||
|
button.title = enabled
|
||||||
|
? "Scarica PF del foglio o della particella"
|
||||||
|
: "Seleziona una particella o un foglio per scaricare i PF";
|
||||||
}
|
}
|
||||||
|
|
||||||
async function numericEditManagedRecord(record) {
|
async function numericEditManagedRecord(record) {
|
||||||
@@ -3057,7 +3182,8 @@ function restoreLastTrashItem() {
|
|||||||
if (lmeState.readOnly) { setInfo("Il progetto e' in sola lettura."); return false; }
|
if (lmeState.readOnly) { setInfo("Il progetto e' in sola lettura."); return false; }
|
||||||
const entry = lmeState.trash.pop();
|
const entry = lmeState.trash.pop();
|
||||||
if (!entry) { setInfo("Il cestino e' vuoto."); return false; }
|
if (!entry) { setInfo("Il cestino e' vuoto."); return false; }
|
||||||
const drawingsToRestore = entry.items.filter(item => !["imported", "importedSheets"].includes(item.categoryKey)).length;
|
const drawingsToRestore = entry.items.filter(item =>
|
||||||
|
!["imported", "importedSheets", "fiducials"].includes(item.categoryKey)).length;
|
||||||
if (!canAddDrawingObjects(drawingsToRestore)) {
|
if (!canAddDrawingObjects(drawingsToRestore)) {
|
||||||
lmeState.trash.push(entry);
|
lmeState.trash.push(entry);
|
||||||
return false;
|
return false;
|
||||||
@@ -3066,6 +3192,12 @@ function restoreLastTrashItem() {
|
|||||||
entry.items.forEach(item => {
|
entry.items.forEach(item => {
|
||||||
if (item.categoryKey === "imported") addImportedFeatures({ type: "FeatureCollection", features: [item.feature] });
|
if (item.categoryKey === "imported") addImportedFeatures({ type: "FeatureCollection", features: [item.feature] });
|
||||||
else if (item.categoryKey === "importedSheets") addImportedSheetFeatures({ type: "FeatureCollection", features: [item.feature] });
|
else if (item.categoryKey === "importedSheets") addImportedSheetFeatures({ type: "FeatureCollection", features: [item.feature] });
|
||||||
|
else if (item.categoryKey === "fiducials") {
|
||||||
|
L.geoJSON(item.feature, {
|
||||||
|
pointToLayer: (feature, latlng) => createFiducialMarker(feature, latlng),
|
||||||
|
onEachFeature: (feature, layer) => bindFiducialPopup(feature, layer)
|
||||||
|
}).eachLayer(layer => lmeState.fiducials.addLayer(layer));
|
||||||
|
}
|
||||||
else addDrawingFeature(item.feature);
|
else addDrawingFeature(item.feature);
|
||||||
});
|
});
|
||||||
refreshLayerManager();
|
refreshLayerManager();
|
||||||
@@ -3253,6 +3385,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;
|
||||||
}
|
}
|
||||||
@@ -3326,6 +3460,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4703,7 +4840,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,
|
||||||
@@ -4713,6 +4850,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")
|
||||||
@@ -4748,6 +4886,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(), {
|
||||||
@@ -4808,6 +4947,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();
|
||||||
@@ -4830,6 +4973,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) {
|
||||||
@@ -4880,6 +5024,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 },
|
||||||
@@ -4947,6 +5093,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();
|
||||||
@@ -5383,6 +5533,9 @@ function setCadastreOpacity(value) {
|
|||||||
if (lmeState.cadastre) {
|
if (lmeState.cadastre) {
|
||||||
lmeState.cadastre.setOpacity(lmeState.cadastreOpacity);
|
lmeState.cadastre.setOpacity(lmeState.cadastreOpacity);
|
||||||
}
|
}
|
||||||
|
if (lmeState.cadastreZoning) {
|
||||||
|
lmeState.cadastreZoning.setOpacity(Math.max(0.8, lmeState.cadastreOpacity));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleCadastreLayer(layer, enabled) {
|
function toggleCadastreLayer(layer, enabled) {
|
||||||
@@ -5394,6 +5547,13 @@ function toggleCadastreLayer(layer, enabled) {
|
|||||||
lmeState.cadastreLayers = lmeState.cadastreLayers.filter(item => item !== layer);
|
lmeState.cadastreLayers = lmeState.cadastreLayers.filter(item => item !== layer);
|
||||||
}
|
}
|
||||||
refreshCadastreLayer();
|
refreshCadastreLayer();
|
||||||
|
if (layer === "CP.CadastralZoning") {
|
||||||
|
if (enabled) {
|
||||||
|
scheduleNavigationSheetRefresh(true);
|
||||||
|
} else {
|
||||||
|
clearNavigationSheetOutline();
|
||||||
|
}
|
||||||
|
}
|
||||||
const layerNames = {
|
const layerNames = {
|
||||||
"CP.CadastralParcel": "Confini particelle",
|
"CP.CadastralParcel": "Confini particelle",
|
||||||
codice_plla: "Numeri particelle",
|
codice_plla: "Numeri particelle",
|
||||||
@@ -5409,43 +5569,189 @@ function toggleCadastreLayer(layer, enabled) {
|
|||||||
setInfo(`${layerNames[layer] || layer}: ${enabled ? "attivo" : "nascosto"}.`);
|
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() {
|
function refreshCadastreLayer() {
|
||||||
if (lmeState.cadastre) {
|
if (lmeState.cadastre) {
|
||||||
lmeState.map.removeLayer(lmeState.cadastre);
|
lmeState.map.removeLayer(lmeState.cadastre);
|
||||||
}
|
}
|
||||||
|
if (lmeState.cadastreZoning) {
|
||||||
const layers = activeCadastreLayers();
|
lmeState.map.removeLayer(lmeState.cadastreZoning);
|
||||||
lmeState.cadastre = null;
|
|
||||||
if (!layers.length) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const cadastreLayer = L.tileLayer.wms(cadastreUrl(), {
|
const zoningEnabled = lmeState.cadastreLayers.includes("CP.CadastralZoning");
|
||||||
pane: "cadastrePane",
|
const layers = activeCadastreLayers().filter(layer => layer !== "CP.CadastralZoning");
|
||||||
layers: layers.join(","),
|
lmeState.cadastre = null;
|
||||||
format: "image/png",
|
lmeState.cadastreZoning = null;
|
||||||
transparent: true,
|
|
||||||
version: "1.1.1",
|
|
||||||
crs: cadastreCrs,
|
|
||||||
maxZoom: 22,
|
|
||||||
opacity: lmeState.cadastreOpacity,
|
|
||||||
attribution: "Agenzia delle Entrate - CC BY 4.0"
|
|
||||||
});
|
|
||||||
|
|
||||||
|
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;
|
let errorShown = false;
|
||||||
cadastreLayer.on("tileerror", () => {
|
cadastreLayer.on("tileerror", event => {
|
||||||
|
const tile = event?.tile;
|
||||||
|
const retryCount = Number(tile?.dataset?.lmeRetry || 0);
|
||||||
|
if (tile && retryCount < 2) {
|
||||||
|
tile.dataset.lmeRetry = String(retryCount + 1);
|
||||||
|
window.setTimeout(() => {
|
||||||
|
const separator = tile.src.includes("?") ? "&" : "?";
|
||||||
|
tile.src = `${tile.src}${separator}_lme_retry=${Date.now()}`;
|
||||||
|
}, 450 * (retryCount + 1));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (errorShown) {
|
if (errorShown) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
errorShown = true;
|
errorShown = true;
|
||||||
setInfo("Layer catastali non caricati dal servizio AdE. Controlla la connessione e riprova.");
|
setInfo("Il servizio cartografico AdE non ha risposto. La connessione internet puo essere attiva: attendi qualche secondo e riprova.");
|
||||||
});
|
});
|
||||||
cadastreLayer.on("load", () => {
|
cadastreLayer.on("load", () => {
|
||||||
errorShown = false;
|
errorShown = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
lmeState.cadastre = cadastreLayer.addTo(lmeState.map);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function activeCadastreLayers() {
|
function activeCadastreLayers() {
|
||||||
@@ -5460,6 +5766,138 @@ function activeCadastreLayers() {
|
|||||||
return drawOrder.filter(layer => lmeState.cadastreLayers.includes(layer));
|
return drawOrder.filter(layer => lmeState.cadastreLayers.includes(layer));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setFiducialPoints(points, color, visible = true, keepPrevious = false, municipalityCode = "", sheet = "", municipalityName = "") {
|
||||||
|
if (!lmeState.fiducials) return;
|
||||||
|
const source = Array.isArray(points) ? points : [];
|
||||||
|
if (source.length || lmeState.fiducials.getLayers().length) rememberMapChange();
|
||||||
|
const normalizedMunicipality = String(municipalityCode || "").trim().toUpperCase();
|
||||||
|
const normalizedSheet = normalizeFiducialSheet(sheet);
|
||||||
|
const sheetKey = normalizedMunicipality && normalizedSheet
|
||||||
|
? `${normalizedMunicipality}:${normalizedSheet}`
|
||||||
|
: "fiducials-current";
|
||||||
|
const sheetLabel = `Foglio ${normalizedSheet || sheet}${municipalityName ? ` · ${municipalityName}` : ""}`;
|
||||||
|
if (!keepPrevious) {
|
||||||
|
managedRecordsForCategory(layerManagerCategories().find(category => category.key === "fiducials"))
|
||||||
|
.forEach(record => lmeState.managerSelectedObjectIds.delete(record.id));
|
||||||
|
lmeState.fiducials.clearLayers();
|
||||||
|
clearManagedCategoryState("fiducials");
|
||||||
|
} else {
|
||||||
|
managedRecordsForCategory(layerManagerCategories().find(category => category.key === "fiducials"))
|
||||||
|
.filter(record => record.layer?.feature?.properties?._lmeFiducialSheetKey === sheetKey)
|
||||||
|
.forEach(record => {
|
||||||
|
record.parent?.removeLayer?.(record.layer);
|
||||||
|
clearManagedObjectState(record.id);
|
||||||
|
lmeState.managerSelectedObjectIds.delete(record.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
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: sheetKey,
|
||||||
|
_lmeGroupName: sheetLabel,
|
||||||
|
_lmeFiducialSheetKey: sheetKey,
|
||||||
|
_lmeMunicipalityCode: normalizedMunicipality,
|
||||||
|
_lmeSheet: normalizedSheet,
|
||||||
|
_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();
|
||||||
@@ -5498,6 +5936,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
|
||||||
@@ -5506,7 +5945,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5514,6 +5953,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();
|
||||||
@@ -5905,7 +6345,36 @@ function cadastreUrl() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setInfo(text) {
|
function setInfo(text) {
|
||||||
setText("mapInfo", text);
|
const container = document.getElementById("mapInfo");
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
setText("mapInfoText", text);
|
||||||
|
container.hidden = false;
|
||||||
|
if (lmeState.infoTimer) {
|
||||||
|
window.clearTimeout(lmeState.infoTimer);
|
||||||
|
lmeState.infoTimer = null;
|
||||||
|
}
|
||||||
|
if (lmeState.infoTimeoutSeconds > 0) {
|
||||||
|
lmeState.infoTimer = window.setTimeout(hideInfo, lmeState.infoTimeoutSeconds * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideInfo() {
|
||||||
|
const container = document.getElementById("mapInfo");
|
||||||
|
if (container) container.hidden = true;
|
||||||
|
if (lmeState.infoTimer) {
|
||||||
|
window.clearTimeout(lmeState.infoTimer);
|
||||||
|
lmeState.infoTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setInfoTimeout(seconds) {
|
||||||
|
const value = Number(seconds);
|
||||||
|
lmeState.infoTimeoutSeconds = Number.isFinite(value) ? Math.min(30, Math.max(0, value)) : 3;
|
||||||
|
const container = document.getElementById("mapInfo");
|
||||||
|
if (container && !container.hidden) {
|
||||||
|
setInfo(document.getElementById("mapInfoText")?.textContent || "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setText(id, text) {
|
function setText(id, text) {
|
||||||
|
|||||||
Reference in new issue
Block a user