Compare commits
8
Commits
7b642afe2d
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb69b9fd0d | ||
|
|
260e0a3df5 | ||
|
|
a15c8e0b36 | ||
|
|
aa389f7581 | ||
|
|
812420b86a | ||
|
|
bc037a57d4 | ||
|
|
ce355f820c | ||
|
|
85e1c93d8a |
No files matched your search
+270
-18
@@ -96,6 +96,16 @@
|
||||
<small>Particelle e geometrie verdi di file e progetti</small>
|
||||
</span>
|
||||
</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">
|
||||
<header>
|
||||
<strong>Ordine livelli e oggetti</strong>
|
||||
@@ -253,21 +263,41 @@
|
||||
@(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 (FiducialPoints.Count > 0)
|
||||
@if (MappedFiducialPoints.Count > 0)
|
||||
{
|
||||
<section class="panel-section">
|
||||
<h2>Punti trovati (@FiducialPoints.Count)</h2>
|
||||
<h2>Punti cartografici (@MappedFiducialPoints.Count)</h2>
|
||||
<button class="secondary-command" @onclick="FitFiducialPoints">Inquadra tutti i PF</button>
|
||||
<div class="fiducial-list">
|
||||
@foreach (var point in FiducialPoints)
|
||||
@foreach (var point in MappedFiducialPoints)
|
||||
{
|
||||
<article class="fiducial-item">
|
||||
<strong>@point.Identifier</strong>
|
||||
<button class="secondary-command" @onclick="() => DownloadFiducialMonograph(point)">Scarica PDF monografia</button>
|
||||
<small>Nord/X: @FormatCoordinate(point.CadastralNorth) · Est/Y: @FormatCoordinate(point.CadastralEast)</small>
|
||||
@if (!string.IsNullOrWhiteSpace(point.MonographUrl))
|
||||
{
|
||||
<button class="secondary-command" @onclick="() => DownloadFiducialMonograph(point.MonographUrl)">Scarica PDF monografia</button>
|
||||
}
|
||||
</article>
|
||||
}
|
||||
</div>
|
||||
@@ -275,8 +305,8 @@
|
||||
}
|
||||
|
||||
<div class="service-note">
|
||||
<strong>Fonte ufficiale Agenzia delle Entrate</strong>
|
||||
<p>L'elenco e i PDF arrivano dal servizio pubblico delle monografie. Il layer sulla mappa sarà abilitato soltanto quando sono disponibili coordinate geografiche trasformate e verificabili.</p>
|
||||
<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>
|
||||
@@ -643,6 +673,17 @@
|
||||
<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>
|
||||
<label class="toggle-line project-save-option">
|
||||
<input type="checkbox" @bind="ShowCompass" @bind:after="ApplyCompassOptions" />
|
||||
<span>Mostra bussola sulla mappa</span>
|
||||
</label>
|
||||
<label>
|
||||
Orientamento bussola
|
||||
<select @bind="CompassMode" @bind:after="ApplyCompassOptions" disabled="@(!ShowCompass)">
|
||||
<option value="fixed">Mappa: nord fisso in alto</option>
|
||||
<option value="heading">Navigatore: direzione del dispositivo in alto</option>
|
||||
</select>
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<section class="panel-section">
|
||||
@@ -830,6 +871,7 @@
|
||||
private string SelectedFiducialMunicipalityCode { get; set; } = string.Empty;
|
||||
private string SelectedFiducialSheet { get; set; } = string.Empty;
|
||||
private string FiducialMessage { get; set; } = string.Empty;
|
||||
private string FiducialColor { get; set; } = "#dc2626";
|
||||
private string Status { get; set; } = "online";
|
||||
private string SelectedBaseLayer { get; set; } = "satellite";
|
||||
private string GoogleMapsApiKey { get; set; } = string.Empty;
|
||||
@@ -846,7 +888,12 @@
|
||||
private bool GpsActive { get; set; }
|
||||
private bool GpsOverlayCollapsed { get; set; }
|
||||
private bool ShowGpsPosition { get; set; } = true;
|
||||
private bool ShowCompass { get; set; }
|
||||
private string CompassMode { get; set; } = "fixed";
|
||||
private bool CompassSensorSubscribed { get; set; }
|
||||
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 DrawToolsVisible { get; set; }
|
||||
private bool TrailEnabled { get; set; }
|
||||
@@ -910,6 +957,7 @@
|
||||
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<MapProjectVersion> ProjectVersions { get; set; } = [];
|
||||
private MapProjectSummary? SelectedProject => Projects.FirstOrDefault(item => item.Id == SelectedProjectId);
|
||||
@@ -1257,6 +1305,7 @@
|
||||
FiducialMunicipalities = [];
|
||||
FiducialSheets = [];
|
||||
FiducialPoints = [];
|
||||
MappedFiducialPoints = [];
|
||||
if (string.IsNullOrWhiteSpace(SelectedFiducialProvinceCode))
|
||||
{
|
||||
FiducialMessage = string.Empty;
|
||||
@@ -1286,6 +1335,7 @@
|
||||
SelectedFiducialSheet = string.Empty;
|
||||
FiducialSheets = [];
|
||||
FiducialPoints = [];
|
||||
MappedFiducialPoints = [];
|
||||
if (string.IsNullOrWhiteSpace(SelectedFiducialMunicipalityCode))
|
||||
{
|
||||
FiducialMessage = string.Empty;
|
||||
@@ -1313,42 +1363,119 @@
|
||||
|
||||
private async Task SearchFiducials()
|
||||
{
|
||||
var province = FiducialProvinces.FirstOrDefault(item => item.Code == SelectedFiducialProvinceCode);
|
||||
var municipality = FiducialMunicipalities.FirstOrDefault(item => item.Code == SelectedFiducialMunicipalityCode);
|
||||
if (province is null || municipality is null || string.IsNullOrWhiteSpace(SelectedFiducialSheet))
|
||||
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 = [];
|
||||
FiducialMessage = "Interrogo il servizio ufficiale delle monografie...";
|
||||
MappedFiducialPoints = [];
|
||||
FiducialMessage = "Recupero monografie e coordinate dei PF...";
|
||||
await InvokeAsync(StateHasChanged);
|
||||
try
|
||||
{
|
||||
string? monographError = null;
|
||||
try
|
||||
{
|
||||
FiducialPoints = await FiducialPointService.GetFiducialsAsync(
|
||||
province.Name,
|
||||
municipality.Name,
|
||||
municipality.Code,
|
||||
SelectedFiducialSheet);
|
||||
FiducialMessage = FiducialPoints.Count == 0
|
||||
? "Nessun Punto Fiduciale pubblicato per il foglio selezionato."
|
||||
: $"Trovati {FiducialPoints.Count} Punti Fiduciali.";
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DownloadFiducialMonograph(FiducialPointEntry point)
|
||||
[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(point.MonographUrl));
|
||||
await Launcher.Default.OpenAsync(new Uri(monographUrl));
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
@@ -1356,6 +1483,30 @@
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
SelectedMunicipalityCode = args.Value?.ToString() ?? string.Empty;
|
||||
@@ -1651,6 +1802,15 @@
|
||||
return InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public async Task SetCompassFromLayerManager(bool enabled)
|
||||
{
|
||||
ShowCompass = enabled;
|
||||
SaveFlagPreferences();
|
||||
await ConfigureCompassSensorAsync();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public Task<string> GetOfficialCadastralSheetsNear(double latitude, double longitude) =>
|
||||
OfficialCadastreService.GetSheetsNearAsync(latitude, longitude);
|
||||
@@ -2161,6 +2321,8 @@
|
||||
ShowDrawingMeasurements = options.ShowDrawingMeasurements;
|
||||
DrawToolsVisible = options.DrawToolsVisible;
|
||||
ShowGpsPosition = options.GpsPositionVisible;
|
||||
ShowCompass = options.CompassVisible;
|
||||
CompassMode = options.CompassMode is "heading" ? "heading" : "fixed";
|
||||
ShowSelectedParcels = options.SelectedParcelsVisible;
|
||||
TrailEnabled = options.TrailEnabled;
|
||||
|
||||
@@ -2170,6 +2332,7 @@
|
||||
layer.Enabled = activeLayers.Contains(layer.Key);
|
||||
}
|
||||
SaveFlagPreferences();
|
||||
_ = ConfigureCompassSensorAsync();
|
||||
}
|
||||
|
||||
private async Task DeleteProject()
|
||||
@@ -2374,6 +2537,7 @@
|
||||
private Task ApplyEntitlementsToMapAsync() => JS.InvokeVoidAsync("lmeMap.setEntitlements", new
|
||||
{
|
||||
advancedGeometry = CanUse(AppFeatures.AdvancedGeometry),
|
||||
fiducialPoints = CanUse(AppFeatures.FiducialPoints),
|
||||
maxDrawingObjects = Entitlements.HasProAccess ? (int?)null : EntitlementService.BaseDrawingObjectLimit,
|
||||
drawingLimitMessage = L("drawings.base.limit.reached")
|
||||
}).AsTask();
|
||||
@@ -2580,7 +2744,12 @@
|
||||
ShowDrawingMeasurements = GetFlagPreference("show_drawing_measurements", true);
|
||||
DrawToolsVisible = GetFlagPreference("draw_tools_visible", false);
|
||||
ShowGpsPosition = GetFlagPreference("show_gps_position", true);
|
||||
ShowCompass = GetFlagPreference("show_compass", false);
|
||||
CompassMode = Preferences.Default.Get($"{FlagPreferencePrefix}compass_mode", "fixed") is "heading" ? "heading" : "fixed";
|
||||
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);
|
||||
ExportImported = GetFlagPreference("export_imported", true);
|
||||
ExportImportedSheets = GetFlagPreference("export_imported_sheets", true);
|
||||
@@ -2620,7 +2789,12 @@
|
||||
SetFlagPreference("show_drawing_measurements", ShowDrawingMeasurements);
|
||||
SetFlagPreference("draw_tools_visible", DrawToolsVisible);
|
||||
SetFlagPreference("show_gps_position", ShowGpsPosition);
|
||||
SetFlagPreference("show_compass", ShowCompass);
|
||||
Preferences.Default.Set($"{FlagPreferencePrefix}compass_mode", CompassMode);
|
||||
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("export_imported", ExportImported);
|
||||
SetFlagPreference("export_imported_sheets", ExportImportedSheets);
|
||||
@@ -2657,7 +2831,11 @@
|
||||
await JS.InvokeVoidAsync("lmeMap.setDrawingMeasurementsVisible", ShowDrawingMeasurements);
|
||||
await JS.InvokeVoidAsync("lmeMap.setDrawToolsVisible", DrawToolsVisible);
|
||||
await JS.InvokeVoidAsync("lmeMap.setGpsPositionVisible", ShowGpsPosition);
|
||||
await JS.InvokeVoidAsync("lmeMap.setCompassOptions", ShowCompass, CompassMode);
|
||||
await ConfigureCompassSensorAsync();
|
||||
await JS.InvokeVoidAsync("lmeMap.setSelectedParcelsVisible", ShowSelectedParcels);
|
||||
await JS.InvokeVoidAsync("lmeMap.setFiducialColor", FiducialColor);
|
||||
await JS.InvokeVoidAsync("lmeMap.setFiducialPointsVisible", ShowFiducialPoints);
|
||||
await JS.InvokeVoidAsync("lmeMap.toggleTrail", TrailEnabled);
|
||||
|
||||
foreach (var layer in CadastralLayers)
|
||||
@@ -2673,6 +2851,68 @@
|
||||
await JS.InvokeVoidAsync("lmeMap.setInfoTimeout", MapMessageTimeoutSeconds);
|
||||
}
|
||||
|
||||
private async Task ApplyCompassOptions()
|
||||
{
|
||||
CompassMode = CompassMode is "heading" ? "heading" : "fixed";
|
||||
SaveFlagPreferences();
|
||||
await JS.InvokeVoidAsync("lmeMap.setCompassOptions", ShowCompass, CompassMode);
|
||||
await ConfigureCompassSensorAsync();
|
||||
}
|
||||
|
||||
private Task ConfigureCompassSensorAsync()
|
||||
{
|
||||
var compass = Microsoft.Maui.Devices.Sensors.Compass.Default;
|
||||
var shouldMonitor = ShowCompass && compass.IsSupported;
|
||||
try
|
||||
{
|
||||
if (shouldMonitor && !CompassSensorSubscribed)
|
||||
{
|
||||
compass.ReadingChanged += OnCompassReadingChanged;
|
||||
CompassSensorSubscribed = true;
|
||||
compass.Start(Microsoft.Maui.Devices.Sensors.SensorSpeed.UI);
|
||||
}
|
||||
else if (!shouldMonitor && CompassSensorSubscribed)
|
||||
{
|
||||
compass.ReadingChanged -= OnCompassReadingChanged;
|
||||
CompassSensorSubscribed = false;
|
||||
if (compass.IsMonitoring)
|
||||
{
|
||||
compass.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (CompassSensorSubscribed)
|
||||
{
|
||||
compass.ReadingChanged -= OnCompassReadingChanged;
|
||||
CompassSensorSubscribed = false;
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async void OnCompassReadingChanged(
|
||||
object? sender,
|
||||
Microsoft.Maui.Devices.Sensors.CompassChangedEventArgs args)
|
||||
{
|
||||
if (!MapReady || !ShowCompass)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await InvokeAsync(() => JS.InvokeVoidAsync(
|
||||
"lmeMap.setCompassHeading",
|
||||
args.Reading.HeadingMagneticNorth).AsTask());
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApplyAdvancedOptions()
|
||||
{
|
||||
SaveFlagPreferences();
|
||||
@@ -2747,6 +2987,8 @@
|
||||
public bool ShowDrawingMeasurements { get; init; } = true;
|
||||
public bool DrawToolsVisible { get; init; }
|
||||
public bool GpsPositionVisible { get; init; } = true;
|
||||
public bool CompassVisible { get; init; }
|
||||
public string CompassMode { get; init; } = "fixed";
|
||||
public bool SelectedParcelsVisible { get; init; } = true;
|
||||
public bool TrailEnabled { get; init; }
|
||||
}
|
||||
@@ -2776,6 +3018,16 @@
|
||||
GpsLookupCancellation?.Dispose();
|
||||
ProjectAutoSaveCancellation?.Cancel();
|
||||
ProjectAutoSaveCancellation?.Dispose();
|
||||
var compass = Microsoft.Maui.Devices.Sensors.Compass.Default;
|
||||
if (CompassSensorSubscribed)
|
||||
{
|
||||
compass.ReadingChanged -= OnCompassReadingChanged;
|
||||
if (compass.IsMonitoring)
|
||||
{
|
||||
compass.Stop();
|
||||
}
|
||||
CompassSensorSubscribed = false;
|
||||
}
|
||||
MapCallbackReference?.Dispose();
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<EnableDefaultCssItems>false</EnableDefaultCssItems>
|
||||
<Nullable>enable</Nullable>
|
||||
<DefaultItemExcludes>$(DefaultItemExcludes);obj/**;bin/**;obj-api36*/**;artifacts/**</DefaultItemExcludes>
|
||||
|
||||
<!-- Display name -->
|
||||
<ApplicationTitle>LME Mappe Catastali Pro</ApplicationTitle>
|
||||
@@ -28,8 +29,8 @@
|
||||
<ApplicationId>it.liviomerola.lmemappecatastali.pro</ApplicationId>
|
||||
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>1.0.12</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>112</ApplicationVersion>
|
||||
<ApplicationDisplayVersion>1.0.18</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>118</ApplicationVersion>
|
||||
<Version>$(ApplicationDisplayVersion)</Version>
|
||||
|
||||
<!-- 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'">
|
||||
<AndroidTargetSdkVersion>36</AndroidTargetSdkVersion>
|
||||
<RuntimeIdentifiers>android-arm;android-arm64</RuntimeIdentifiers>
|
||||
<AndroidPackageFormat>aab</AndroidPackageFormat>
|
||||
<AndroidUseAapt2>true</AndroidUseAapt2>
|
||||
<AndroidCreatePackagePerAbi>false</AndroidCreatePackagePerAbi>
|
||||
|
||||
@@ -40,6 +40,14 @@ public sealed class CadastralCatalogService
|
||||
.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(
|
||||
MunicipalityOption municipality,
|
||||
IProgress<CatalogProgress>? progress = null,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace LMEMappeCatastaliApp.Services;
|
||||
@@ -7,6 +8,7 @@ namespace LMEMappeCatastaliApp.Services;
|
||||
public sealed partial class FiducialPointService
|
||||
{
|
||||
private const string ServiceBaseUrl = "https://www1.agenziaentrate.gov.it/servizi/Monografie/";
|
||||
private const string MappedPointsUrl = "https://cartografia.globogis.it/arcgis/rest/services/fiduciali/Punti_fiduciali/MapServer/2/query";
|
||||
private readonly HttpClient _httpClient = new() { Timeout = TimeSpan.FromSeconds(30) };
|
||||
|
||||
public FiducialPointService()
|
||||
@@ -63,7 +65,25 @@ public sealed partial class FiducialPointService
|
||||
new Dictionary<string, string> { ["foglio"] = normalizedSheet },
|
||||
cancellationToken);
|
||||
|
||||
return DownloadLinkRegex().Matches(html)
|
||||
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);
|
||||
@@ -75,6 +95,117 @@ public sealed partial class FiducialPointService
|
||||
.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)
|
||||
@@ -146,3 +277,16 @@ public sealed partial class FiducialPointService
|
||||
public sealed record FiducialAreaOption(string Code, string Name);
|
||||
|
||||
public sealed record FiducialPointEntry(string Identifier, string MonographUrl);
|
||||
|
||||
public sealed record MappedFiducialPoint(
|
||||
string Identifier,
|
||||
double Latitude,
|
||||
double Longitude,
|
||||
double? CadastralNorth,
|
||||
double? CadastralEast,
|
||||
string Reliability,
|
||||
double? Elevation,
|
||||
string Parcel,
|
||||
string UpdatedAt,
|
||||
string OriginId,
|
||||
string MonographUrl = "");
|
||||
@@ -0,0 +1,104 @@
|
||||
param(
|
||||
[string]$Configuration = "Release"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$projectRoot = Split-Path -Parent $PSScriptRoot
|
||||
$dotnet = Join-Path $projectRoot "artifacts\tooling\dotnet10\dotnet.exe"
|
||||
$androidSdk = Join-Path $env:LOCALAPPDATA "Android\Sdk"
|
||||
$documentsDirectory = Split-Path -Parent (Split-Path -Parent $projectRoot)
|
||||
$privateDirectory = Join-Path $documentsDirectory "LMEMappeCatastali-Private\GooglePlay"
|
||||
$keyStore = Join-Path $privateDirectory "lme-mappe-catastali-pro-upload.jks"
|
||||
$recoveryFile = Join-Path $privateDirectory "UPLOAD-KEY-RECOVERY.txt"
|
||||
$outputDirectory = Join-Path $projectRoot "artifacts\play-store"
|
||||
$projectPath = Join-Path $projectRoot "LMEMappeCatastaliApp.csproj"
|
||||
|
||||
foreach ($requiredPath in @($dotnet, $androidSdk, $keyStore, $recoveryFile, $projectPath)) {
|
||||
if (-not (Test-Path -LiteralPath $requiredPath)) {
|
||||
throw "Componente richiesto non trovato: $requiredPath"
|
||||
}
|
||||
}
|
||||
|
||||
[xml]$projectXml = Get-Content -Raw -LiteralPath $projectPath
|
||||
$propertyGroup = $projectXml.Project.PropertyGroup |
|
||||
Where-Object { $_.ApplicationDisplayVersion -and $_.ApplicationVersion } |
|
||||
Select-Object -First 1
|
||||
$displayVersion = [string]$propertyGroup.ApplicationDisplayVersion
|
||||
$versionCode = [string]$propertyGroup.ApplicationVersion
|
||||
if ([string]::IsNullOrWhiteSpace($displayVersion) -or [string]::IsNullOrWhiteSpace($versionCode)) {
|
||||
throw "Versione applicazione non trovata nel progetto."
|
||||
}
|
||||
|
||||
$recovery = Get-Content -LiteralPath $recoveryFile
|
||||
function Read-RecoveryValue([string]$label) {
|
||||
$line = $recovery | Where-Object { $_ -like "${label}:*" } | Select-Object -First 1
|
||||
if ([string]::IsNullOrWhiteSpace($line)) {
|
||||
throw "Valore $label non trovato nel file di recupero."
|
||||
}
|
||||
return ($line -split ":", 2)[1].Trim()
|
||||
}
|
||||
|
||||
$storePassword = Read-RecoveryValue "Store password"
|
||||
$keyPassword = Read-RecoveryValue "Key password"
|
||||
$env:LME_ANDROID_UPLOAD_STORE_PASS = $storePassword
|
||||
$env:LME_ANDROID_UPLOAD_KEY_PASS = $keyPassword
|
||||
$packages = [System.Collections.Generic.List[System.IO.FileInfo]]::new()
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $outputDirectory | Out-Null
|
||||
|
||||
& $dotnet restore $projectPath `
|
||||
-p:Configuration=$Configuration `
|
||||
-p:TargetFrameworks=net10.0-android `
|
||||
-p:AndroidSdkDirectory=$androidSdk `
|
||||
-p:UseDefaultPublishRuntimeIdentifier=false
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Ripristino delle dipendenze Android non riuscito."
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ($format in @("aab", "apk")) {
|
||||
& $dotnet publish $projectPath `
|
||||
-f net10.0-android `
|
||||
-c $Configuration `
|
||||
--no-restore `
|
||||
-p:AndroidSdkDirectory=$androidSdk `
|
||||
-p:UseDefaultPublishRuntimeIdentifier=false `
|
||||
-p:AndroidPackageFormat=$format `
|
||||
-p:AndroidKeyStore=true `
|
||||
-p:AndroidSigningKeyStore=$keyStore `
|
||||
-p:AndroidSigningKeyAlias=lme-upload `
|
||||
-p:AndroidSigningStorePass=env:LME_ANDROID_UPLOAD_STORE_PASS `
|
||||
-p:AndroidSigningKeyPass=env:LME_ANDROID_UPLOAD_KEY_PASS
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Pubblicazione Android $format non riuscita."
|
||||
}
|
||||
|
||||
$publishDirectory = Join-Path $projectRoot "bin\$Configuration\net10.0-android\publish"
|
||||
$package = Get-ChildItem -LiteralPath $publishDirectory -Filter "*-Signed.$format" -File |
|
||||
Sort-Object LastWriteTime -Descending |
|
||||
Select-Object -First 1
|
||||
if ($null -eq $package) {
|
||||
throw "Nessun pacchetto $format firmato prodotto in $publishDirectory"
|
||||
}
|
||||
|
||||
$destination = Join-Path $outputDirectory "LME-Mappe-Catastali-Pro-$displayVersion-$versionCode.$format"
|
||||
Copy-Item -LiteralPath $package.FullName -Destination $destination -Force
|
||||
$packages.Add((Get-Item -LiteralPath $destination))
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Remove-Item Env:LME_ANDROID_UPLOAD_STORE_PASS -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:LME_ANDROID_UPLOAD_KEY_PASS -ErrorAction SilentlyContinue
|
||||
$storePassword = $null
|
||||
$keyPassword = $null
|
||||
}
|
||||
|
||||
$hashLines = $packages | ForEach-Object {
|
||||
$hash = Get-FileHash -Algorithm SHA256 -LiteralPath $_.FullName
|
||||
"$($hash.Hash.ToLowerInvariant()) $($_.Name)"
|
||||
}
|
||||
$hashFile = Join-Path $outputDirectory "LME-Mappe-Catastali-Pro-$displayVersion-$versionCode-SHA256SUMS.txt"
|
||||
$hashLines | Set-Content -LiteralPath $hashFile
|
||||
|
||||
$packages | Select-Object Name, Length, LastWriteTime
|
||||
Get-Item -LiteralPath $hashFile | Select-Object Name, Length, LastWriteTime
|
||||
@@ -1185,6 +1185,17 @@ input[type="checkbox"] {
|
||||
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 {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
@@ -1663,6 +1674,105 @@ label {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.map-compass {
|
||||
--compass-rotation: 0deg;
|
||||
--compass-radius: -31px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 12px;
|
||||
z-index: 810;
|
||||
width: 82px;
|
||||
height: 82px;
|
||||
transform: translateY(-50%);
|
||||
border: 2px solid rgba(255, 255, 255, 0.92);
|
||||
border-radius: 50%;
|
||||
background: rgba(22, 31, 38, 0.86);
|
||||
box-shadow: 0 2px 9px rgba(0, 0, 0, 0.3);
|
||||
color: #fff;
|
||||
cursor: grab;
|
||||
pointer-events: auto;
|
||||
touch-action: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.map-compass.is-dragging {
|
||||
cursor: grabbing;
|
||||
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.42);
|
||||
}
|
||||
|
||||
.map-compass-rose {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.map-compass-direction {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 20px;
|
||||
height: 12px;
|
||||
transform: translate(-50%, -50%) rotate(var(--direction-angle)) translateY(var(--compass-radius));
|
||||
color: #f8fafc;
|
||||
font-size: 9px;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.map-compass-direction > span {
|
||||
display: block;
|
||||
transform: rotate(var(--label-counter));
|
||||
}
|
||||
|
||||
.map-compass-direction.is-intermediate {
|
||||
color: #cbd5e1;
|
||||
font-size: 7px;
|
||||
}
|
||||
|
||||
.map-compass-direction.is-north {
|
||||
color: #fca5a5;
|
||||
font-size: 11px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.map-compass-needle {
|
||||
position: absolute;
|
||||
bottom: 50%;
|
||||
left: 50%;
|
||||
width: 4px;
|
||||
height: 23px;
|
||||
transform: translateX(-50%) rotate(var(--compass-rotation));
|
||||
transform-origin: 50% 100%;
|
||||
border-radius: 4px 4px 1px 1px;
|
||||
background: #ef4444;
|
||||
transition: transform 120ms linear;
|
||||
}
|
||||
|
||||
.map-compass-needle::after {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
left: 50%;
|
||||
width: 0;
|
||||
height: 0;
|
||||
transform: translateX(-50%);
|
||||
border-right: 5px solid transparent;
|
||||
border-bottom: 7px solid #ef4444;
|
||||
border-left: 5px solid transparent;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.map-compass-center {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
transform: translate(-50%, -50%);
|
||||
border: 2px solid #fff;
|
||||
border-radius: 50%;
|
||||
background: #17212b;
|
||||
}
|
||||
|
||||
.wms-preview[hidden] {
|
||||
display: none;
|
||||
}
|
||||
@@ -1937,6 +2047,13 @@ footer {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.map-compass {
|
||||
--compass-radius: -27px;
|
||||
left: 8px;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
}
|
||||
|
||||
.map-info-text {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
<script src="_framework/blazor.webview.js" autostart="false"></script>
|
||||
<script src="lib/leaflet/leaflet.js"></script>
|
||||
<script src="lib/leaflet-rotate/leaflet-rotate.umd.min.js"></script>
|
||||
<script src="lib/leaflet-draw/leaflet.draw.js"></script>
|
||||
<script src="lib/googlemutant/Leaflet.GoogleMutant.js"></script>
|
||||
<script src="lib/jszip/jszip.min.js"></script>
|
||||
|
||||
+551
-25
@@ -5,12 +5,14 @@ const lmeState = {
|
||||
currentBaseName: "satellite",
|
||||
layerOrder: [
|
||||
"gpsPosition",
|
||||
"drawings",
|
||||
"gpsTrail",
|
||||
"fiducials",
|
||||
"drawings",
|
||||
"gpsParcels",
|
||||
"selectedParcels",
|
||||
"importedSheets",
|
||||
"imported",
|
||||
"compass",
|
||||
"cadastre"
|
||||
],
|
||||
cadastre: null,
|
||||
@@ -35,6 +37,7 @@ const lmeState = {
|
||||
drawingDeleteButton: null,
|
||||
drawingScopeMenu: null,
|
||||
geometryToolsControl: null,
|
||||
fiducialDownloadButton: null,
|
||||
ignoreMapClickUntil: 0,
|
||||
activeDrawHandler: null,
|
||||
drawEventsBound: false,
|
||||
@@ -50,12 +53,23 @@ const lmeState = {
|
||||
gpsTrail: null,
|
||||
selectedParcelsLayer: null,
|
||||
selectedParcelsVisible: true,
|
||||
fiducials: null,
|
||||
fiducialsVisible: true,
|
||||
fiducialColor: "#dc2626",
|
||||
gpsParcelLayer: null,
|
||||
gpsTrailEnabled: false,
|
||||
gpsTrailPoints: [],
|
||||
gpsTrailStartedAt: null,
|
||||
gpsWatchId: null,
|
||||
lastPosition: null,
|
||||
compassLayer: null,
|
||||
compassElement: null,
|
||||
compassVisible: false,
|
||||
compassMode: "fixed",
|
||||
compassHeading: 0,
|
||||
compassPosition: null,
|
||||
compassDragCleanup: null,
|
||||
compassResizeHandler: null,
|
||||
infoTimeoutSeconds: 3,
|
||||
infoTimer: null,
|
||||
fileImportBound: false,
|
||||
@@ -131,6 +145,10 @@ window.lmeMap = {
|
||||
closeCadastrePreview,
|
||||
clearSelectedParcels,
|
||||
setSelectedParcelsVisible,
|
||||
setFiducialPoints,
|
||||
setFiducialPointsVisible,
|
||||
setFiducialColor,
|
||||
fitFiducialPoints,
|
||||
clearMapElements,
|
||||
fitImported,
|
||||
clearImported,
|
||||
@@ -176,6 +194,8 @@ window.lmeMap = {
|
||||
getGpsTrailStats,
|
||||
setNativeGpsPosition,
|
||||
setGpsPositionVisible,
|
||||
setCompassOptions,
|
||||
setCompassHeading,
|
||||
setGpsError,
|
||||
saveGpsMeasurementPoint,
|
||||
restoreLastTrashItem,
|
||||
@@ -217,6 +237,7 @@ function setLocalization(options = {}) {
|
||||
|
||||
function setEntitlements(options = {}) {
|
||||
lmeState.entitlements = { ...lmeState.entitlements, ...options };
|
||||
updateFiducialDownloadButton();
|
||||
}
|
||||
|
||||
function requireProFeature(feature) {
|
||||
@@ -249,7 +270,13 @@ function init(dotNetReference) {
|
||||
|
||||
lmeState.map = L.map("map", {
|
||||
zoomControl: false,
|
||||
attributionControl: false
|
||||
attributionControl: false,
|
||||
rotate: true,
|
||||
bearing: 0,
|
||||
dragRotate: false,
|
||||
touchRotate: false,
|
||||
shiftKeyRotate: false,
|
||||
rotateControl: false
|
||||
}).setView([41.9028, 12.4964], 17);
|
||||
|
||||
L.control.zoom({ position: "bottomright" }).addTo(lmeState.map);
|
||||
@@ -293,13 +320,19 @@ function init(dotNetReference) {
|
||||
lmeState.baseLayers.offline = createOfflineTileLayer();
|
||||
|
||||
lmeState.currentBase = lmeState.baseLayers.satellite.addTo(lmeState.map);
|
||||
lmeState.compassLayer = createCompassLayer();
|
||||
restoreLayerOrder();
|
||||
Object.values(managedLayerPanes()).forEach(paneName => {
|
||||
if (!lmeState.map.getPane(paneName)) {
|
||||
lmeState.map.createPane(paneName);
|
||||
const noRotation = paneName === "gpsPositionPane";
|
||||
lmeState.map.createPane(paneName, noRotation
|
||||
? (lmeState.map._norotatePane || lmeState.map._mapPane)
|
||||
: (lmeState.map._rotatePane || lmeState.map._mapPane));
|
||||
}
|
||||
});
|
||||
const managerHighlightPane = lmeState.map.createPane("managerHighlightPane");
|
||||
lmeState.map.createPane("drawingsMarkerPane", lmeState.map._norotatePane || lmeState.map._mapPane);
|
||||
lmeState.map.createPane("gpsAccuracyPane", lmeState.map._rotatePane || lmeState.map._mapPane);
|
||||
const managerHighlightPane = lmeState.map.createPane("managerHighlightPane", lmeState.map._rotatePane || lmeState.map._mapPane);
|
||||
managerHighlightPane.style.zIndex = "650";
|
||||
lmeState.drawingsRenderer = L.canvas({ pane: "drawingsPane", tolerance: 14 });
|
||||
setLayerOrder(lmeState.layerOrder);
|
||||
@@ -310,11 +343,12 @@ function init(dotNetReference) {
|
||||
lmeState.importedMeasurements = L.layerGroup().addTo(lmeState.map);
|
||||
lmeState.drawingMeasurements = L.layerGroup().addTo(lmeState.map);
|
||||
lmeState.selectedParcelsLayer = L.featureGroup().addTo(lmeState.map);
|
||||
lmeState.fiducials = L.featureGroup().addTo(lmeState.map);
|
||||
lmeState.gpsParcelLayer = L.featureGroup().addTo(lmeState.map);
|
||||
const navigationSheetPane = lmeState.map.createPane("navigationSheetPane");
|
||||
const navigationSheetPane = lmeState.map.createPane("navigationSheetPane", lmeState.map._rotatePane || lmeState.map._mapPane);
|
||||
navigationSheetPane.style.zIndex = "465";
|
||||
navigationSheetPane.style.pointerEvents = "none";
|
||||
const cadastreZoningPane = lmeState.map.createPane("cadastreZoningPane");
|
||||
const cadastreZoningPane = lmeState.map.createPane("cadastreZoningPane", lmeState.map._rotatePane || lmeState.map._mapPane);
|
||||
cadastreZoningPane.style.zIndex = "455";
|
||||
cadastreZoningPane.style.pointerEvents = "none";
|
||||
lmeState.navigationSheetLayer = L.layerGroup().addTo(lmeState.map);
|
||||
@@ -405,6 +439,199 @@ function init(dotNetReference) {
|
||||
setInfo("Mappa pronta: satellite + catasto AdE");
|
||||
}
|
||||
|
||||
function createCompassLayer() {
|
||||
return new (L.Layer.extend({
|
||||
onAdd(map) {
|
||||
const element = L.DomUtil.create("div", "map-compass");
|
||||
element.setAttribute("role", "img");
|
||||
element.setAttribute("aria-label", "Bussola: nord");
|
||||
element.setAttribute("title", "Trascina per spostare la bussola");
|
||||
element.innerHTML = `<span class="map-compass-rose" aria-hidden="true">
|
||||
<span class="map-compass-direction is-north" style="--direction-angle:0deg;--label-counter:0deg"><span>N</span></span>
|
||||
<span class="map-compass-direction is-intermediate" style="--direction-angle:45deg;--label-counter:-45deg"><span>NE</span></span>
|
||||
<span class="map-compass-direction" style="--direction-angle:90deg;--label-counter:-90deg"><span>E</span></span>
|
||||
<span class="map-compass-direction is-intermediate" style="--direction-angle:135deg;--label-counter:-135deg"><span>SE</span></span>
|
||||
<span class="map-compass-direction" style="--direction-angle:180deg;--label-counter:-180deg"><span>S</span></span>
|
||||
<span class="map-compass-direction is-intermediate" style="--direction-angle:225deg;--label-counter:-225deg"><span>SW</span></span>
|
||||
<span class="map-compass-direction" style="--direction-angle:270deg;--label-counter:-270deg"><span>W</span></span>
|
||||
<span class="map-compass-direction is-intermediate" style="--direction-angle:315deg;--label-counter:-315deg"><span>NW</span></span>
|
||||
<span class="map-compass-needle"></span>
|
||||
</span><span class="map-compass-center" aria-hidden="true"></span>`;
|
||||
L.DomEvent.disableClickPropagation(element);
|
||||
L.DomEvent.disableScrollPropagation(element);
|
||||
map.getContainer().appendChild(element);
|
||||
lmeState.compassElement = element;
|
||||
restoreCompassPosition();
|
||||
bindCompassDragging(element);
|
||||
lmeState.compassResizeHandler = () => applyCompassPosition();
|
||||
map.on("resize", lmeState.compassResizeHandler);
|
||||
requestAnimationFrame(() => applyCompassPosition());
|
||||
updateCompassDisplay();
|
||||
},
|
||||
onRemove(map) {
|
||||
lmeState.compassDragCleanup?.();
|
||||
lmeState.compassDragCleanup = null;
|
||||
if (lmeState.compassResizeHandler) {
|
||||
map.off("resize", lmeState.compassResizeHandler);
|
||||
lmeState.compassResizeHandler = null;
|
||||
}
|
||||
lmeState.compassElement?.remove();
|
||||
lmeState.compassElement = null;
|
||||
}
|
||||
}))();
|
||||
}
|
||||
|
||||
function restoreCompassPosition() {
|
||||
if (lmeState.compassPosition) return;
|
||||
try {
|
||||
const saved = JSON.parse(localStorage.getItem("lmeCompassPosition") || "null");
|
||||
const x = Number(saved?.x);
|
||||
const y = Number(saved?.y);
|
||||
if (Number.isFinite(x) && Number.isFinite(y)) {
|
||||
lmeState.compassPosition = {
|
||||
x: Math.max(0, Math.min(1, x)),
|
||||
y: Math.max(0, Math.min(1, y))
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
function applyCompassPosition() {
|
||||
const element = lmeState.compassElement;
|
||||
const container = lmeState.map?.getContainer();
|
||||
if (!element || !container) return;
|
||||
const width = container.clientWidth;
|
||||
const height = container.clientHeight;
|
||||
const elementWidth = element.offsetWidth;
|
||||
const elementHeight = element.offsetHeight;
|
||||
if (!width || !height || !elementWidth || !elementHeight) return;
|
||||
|
||||
const halfWidth = elementWidth / 2;
|
||||
const halfHeight = elementHeight / 2;
|
||||
const position = lmeState.compassPosition || {
|
||||
x: (12 + halfWidth) / width,
|
||||
y: 0.5
|
||||
};
|
||||
const centerX = Math.max(halfWidth + 4, Math.min(width - halfWidth - 4, position.x * width));
|
||||
const centerY = Math.max(halfHeight + 4, Math.min(height - halfHeight - 4, position.y * height));
|
||||
element.style.left = `${centerX - halfWidth}px`;
|
||||
element.style.top = `${centerY - halfHeight}px`;
|
||||
element.style.transform = "none";
|
||||
}
|
||||
|
||||
function bindCompassDragging(element) {
|
||||
let pointerId = null;
|
||||
let offsetX = 0;
|
||||
let offsetY = 0;
|
||||
|
||||
const move = event => {
|
||||
if (event.pointerId !== pointerId) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const container = lmeState.map?.getContainer();
|
||||
if (!container) return;
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
const elementWidth = element.offsetWidth;
|
||||
const elementHeight = element.offsetHeight;
|
||||
const left = event.clientX - containerRect.left - offsetX;
|
||||
const top = event.clientY - containerRect.top - offsetY;
|
||||
const centerX = Math.max(elementWidth / 2 + 4,
|
||||
Math.min(containerRect.width - elementWidth / 2 - 4, left + elementWidth / 2));
|
||||
const centerY = Math.max(elementHeight / 2 + 4,
|
||||
Math.min(containerRect.height - elementHeight / 2 - 4, top + elementHeight / 2));
|
||||
lmeState.compassPosition = {
|
||||
x: centerX / containerRect.width,
|
||||
y: centerY / containerRect.height
|
||||
};
|
||||
applyCompassPosition();
|
||||
};
|
||||
|
||||
const finish = event => {
|
||||
if (event.pointerId !== pointerId) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
element.classList.remove("is-dragging");
|
||||
if (element.hasPointerCapture?.(pointerId)) {
|
||||
element.releasePointerCapture(pointerId);
|
||||
}
|
||||
pointerId = null;
|
||||
try {
|
||||
localStorage.setItem("lmeCompassPosition", JSON.stringify(lmeState.compassPosition));
|
||||
} catch {
|
||||
}
|
||||
};
|
||||
|
||||
const start = event => {
|
||||
if (event.pointerType === "mouse" && event.button !== 0) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const rect = element.getBoundingClientRect();
|
||||
pointerId = event.pointerId;
|
||||
offsetX = event.clientX - rect.left;
|
||||
offsetY = event.clientY - rect.top;
|
||||
element.classList.add("is-dragging");
|
||||
element.setPointerCapture?.(pointerId);
|
||||
};
|
||||
|
||||
element.addEventListener("pointerdown", start);
|
||||
element.addEventListener("pointermove", move);
|
||||
element.addEventListener("pointerup", finish);
|
||||
element.addEventListener("pointercancel", finish);
|
||||
lmeState.compassDragCleanup = () => {
|
||||
element.removeEventListener("pointerdown", start);
|
||||
element.removeEventListener("pointermove", move);
|
||||
element.removeEventListener("pointerup", finish);
|
||||
element.removeEventListener("pointercancel", finish);
|
||||
};
|
||||
}
|
||||
|
||||
function setCompassOptions(visible, mode = "fixed") {
|
||||
lmeState.compassVisible = Boolean(visible);
|
||||
lmeState.compassMode = mode === "heading" ? "heading" : "fixed";
|
||||
if (!lmeState.map || !lmeState.compassLayer) return;
|
||||
if (lmeState.compassVisible) {
|
||||
lmeState.managerHiddenCategories.delete("compass");
|
||||
if (!lmeState.map.hasLayer(lmeState.compassLayer)) {
|
||||
lmeState.compassLayer.addTo(lmeState.map);
|
||||
}
|
||||
} else {
|
||||
lmeState.managerHiddenCategories.add("compass");
|
||||
if (lmeState.map.hasLayer(lmeState.compassLayer)) {
|
||||
lmeState.map.removeLayer(lmeState.compassLayer);
|
||||
}
|
||||
}
|
||||
if (!lmeState.compassVisible || lmeState.compassMode === "fixed") {
|
||||
lmeState.map.setHeading?.(null);
|
||||
lmeState.map.setBearing?.(0);
|
||||
} else if (Number.isFinite(lmeState.compassHeading)) {
|
||||
lmeState.map.setHeading?.(lmeState.compassHeading, { ease: 0.2, deadzone: 1 });
|
||||
}
|
||||
updateCompassDisplay();
|
||||
refreshLayerManager();
|
||||
}
|
||||
|
||||
function setCompassHeading(heading) {
|
||||
const parsed = Number(heading);
|
||||
if (!Number.isFinite(parsed)) return;
|
||||
lmeState.compassHeading = ((parsed % 360) + 360) % 360;
|
||||
if (lmeState.compassVisible && lmeState.compassMode === "heading") {
|
||||
lmeState.map?.setHeading?.(lmeState.compassHeading, { ease: 0.2, deadzone: 1 });
|
||||
}
|
||||
updateCompassDisplay();
|
||||
}
|
||||
|
||||
function updateCompassDisplay() {
|
||||
const element = lmeState.compassElement;
|
||||
if (!element) return;
|
||||
const rotation = -lmeState.compassHeading;
|
||||
element.classList.toggle("is-fixed", lmeState.compassMode === "fixed");
|
||||
element.style.setProperty("--compass-rotation", `${rotation}deg`);
|
||||
element.setAttribute("aria-label", lmeState.compassMode === "fixed"
|
||||
? `Bussola: mappa con nord fisso, orientamento ${Math.round(lmeState.compassHeading)} gradi`
|
||||
: `Bussola: nord, orientamento ${Math.round(lmeState.compassHeading)} gradi`);
|
||||
}
|
||||
|
||||
function showSearchResult(latitude, longitude, label) {
|
||||
const lat = Number(latitude);
|
||||
const lon = Number(longitude);
|
||||
@@ -1339,8 +1566,10 @@ function managedLayerPanes() {
|
||||
gpsTrail: "gpsTrailPane",
|
||||
gpsParcels: "gpsParcelsPane",
|
||||
selectedParcels: "selectedParcelsPane",
|
||||
fiducials: "fiducialsPane",
|
||||
importedSheets: "importedSheetPane",
|
||||
imported: "importedPane",
|
||||
compass: "compassPane",
|
||||
cadastre: "cadastrePane"
|
||||
};
|
||||
}
|
||||
@@ -1354,10 +1583,14 @@ function setLayerOrder(order) {
|
||||
return;
|
||||
}
|
||||
normalized.forEach((key, index) => {
|
||||
const pane = lmeState.map.getPane(panes[key]);
|
||||
if (pane) {
|
||||
pane.style.zIndex = String(590 - index * 20);
|
||||
}
|
||||
const zIndex = String(590 - index * 20);
|
||||
const paneNames = [panes[key]];
|
||||
if (key === "drawings") paneNames.push("drawingsMarkerPane");
|
||||
if (key === "gpsPosition") paneNames.push("gpsAccuracyPane");
|
||||
paneNames.forEach(paneName => {
|
||||
const pane = lmeState.map.getPane(paneName);
|
||||
if (pane) pane.style.zIndex = zIndex;
|
||||
});
|
||||
});
|
||||
try {
|
||||
localStorage.setItem("lmeMapLayerOrder", JSON.stringify(normalized));
|
||||
@@ -1369,7 +1602,12 @@ function restoreLayerOrder() {
|
||||
try {
|
||||
const saved = JSON.parse(localStorage.getItem("lmeMapLayerOrder") || "null");
|
||||
if (Array.isArray(saved)) {
|
||||
lmeState.layerOrder = saved;
|
||||
const remaining = saved.filter(key => !["gpsPosition", "gpsTrail", "fiducials"].includes(key));
|
||||
if (!remaining.includes("compass")) {
|
||||
const cadastreIndex = remaining.indexOf("cadastre");
|
||||
remaining.splice(cadastreIndex >= 0 ? cadastreIndex : remaining.length, 0, "compass");
|
||||
}
|
||||
lmeState.layerOrder = ["gpsPosition", "gpsTrail", "fiducials", ...remaining];
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
@@ -1427,8 +1665,10 @@ function layerManagerCategories() {
|
||||
{ key: "gpsTrail", label: "Traccia GPS", roots: [lmeState.gpsTrail].filter(Boolean) },
|
||||
{ key: "gpsParcels", label: "Particella rilevata dal GPS", roots: [lmeState.gpsParcelLayer].filter(Boolean) },
|
||||
{ key: "selectedParcels", label: "Particelle catastali evidenziate", roots: [lmeState.selectedParcelsLayer].filter(Boolean) },
|
||||
{ key: "fiducials", label: "Punti fiduciali", roots: [lmeState.fiducials].filter(Boolean), 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: "compass", label: "Bussola", roots: [lmeState.compassLayer].filter(Boolean), uiOnly: true },
|
||||
{ key: "cadastre", label: "Layer catastali AdE", roots: [lmeState.cadastre].filter(Boolean) }
|
||||
];
|
||||
}
|
||||
@@ -1466,6 +1706,9 @@ function collectManagedLeafLayers(layer, categoryKey, parent, records) {
|
||||
}
|
||||
|
||||
function managedRecordsForCategory(category) {
|
||||
if (category.uiOnly) {
|
||||
return [];
|
||||
}
|
||||
const records = [];
|
||||
category.roots.forEach(root => collectManagedLeafLayers(root, category.key, lmeState.map, records));
|
||||
lmeState.managerHiddenObjects.forEach(record => {
|
||||
@@ -1614,11 +1857,11 @@ async function renameManagedBlock(category, source, records) {
|
||||
|
||||
function managedCategoryCanRemove(category, records) {
|
||||
return records.length > 0
|
||||
&& ["drawings", "imported", "importedSheets", "selectedParcels", "gpsParcels"].includes(category.key);
|
||||
&& ["drawings", "imported", "importedSheets", "selectedParcels", "gpsParcels", "fiducials"].includes(category.key);
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -1880,6 +2123,7 @@ function managedObjectDetails(layer) {
|
||||
}
|
||||
|
||||
function refreshLayerManager() {
|
||||
updateFiducialDownloadButton();
|
||||
const container = document.getElementById("layerManagerTree");
|
||||
if (!container || !lmeState.map) {
|
||||
return;
|
||||
@@ -2015,8 +2259,12 @@ function renderLayerManagerBlock(category, source, records) {
|
||||
const blockName = managedBlockName(source, records);
|
||||
const rowActions = layerManagerRowActions(
|
||||
`<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>`,
|
||||
`<button type="button" class="layer-remove-button" aria-label="Rimuovi gruppo dal progetto" title="Rimuovi dal progetto">Rimuovi</button>`);
|
||||
managedCategoryCanMove(category.key)
|
||||
? `<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");
|
||||
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>`;
|
||||
@@ -2037,12 +2285,12 @@ function renderLayerManagerBlock(category, source, records) {
|
||||
event.stopPropagation();
|
||||
renameManagedBlock(category, source, records);
|
||||
});
|
||||
block.querySelector(".layer-remove-button").addEventListener("click", event => {
|
||||
block.querySelector(".layer-remove-button")?.addEventListener("click", event => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
removeManagedRecords(records, `il gruppo '${blockName}'`);
|
||||
});
|
||||
block.querySelector(".layer-membership-button").addEventListener("click", event => {
|
||||
block.querySelector(".layer-membership-button")?.addEventListener("click", event => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
showMoveManagedRecordsDialog(records, `il gruppo '${blockName}'`);
|
||||
@@ -2693,7 +2941,7 @@ function addDrawingFeature(feature, select = false) {
|
||||
style: storedFeatureStyle,
|
||||
pointToLayer: (item, latlng) => item?.properties?._lmeShape === "circle" && Number(item.properties._lmeRadius) > 0
|
||||
? L.circle(latlng, { pane: "drawingsPane", renderer: lmeState.drawingsRenderer, radius: Number(item.properties._lmeRadius) })
|
||||
: L.marker(latlng, { pane: "drawingsPane", draggable: false }),
|
||||
: L.marker(latlng, { pane: "drawingsMarkerPane", draggable: false }),
|
||||
onEachFeature: (item, layer) => {
|
||||
annotateDrawingLayer(layer, item?.properties?._lmeShape);
|
||||
applyStyleFromProperties(layer);
|
||||
@@ -2932,6 +3180,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>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>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>
|
||||
</dl>
|
||||
</div>`);
|
||||
@@ -2947,6 +3196,7 @@ function addGeometryToolsControl() {
|
||||
{ icon: "✓", title: "Controlla e ripara geometrie", run: validateAndRepairSelected },
|
||||
{ icon: "◉", title: "Crea foro da due poligoni", run: createHoleFromSelected },
|
||||
{ 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 }
|
||||
];
|
||||
actions.forEach(action => {
|
||||
@@ -2956,6 +3206,10 @@ function addGeometryToolsControl() {
|
||||
button.setAttribute("role", "button");
|
||||
button.setAttribute("aria-label", action.title);
|
||||
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.disableClickPropagation(container);
|
||||
@@ -2963,6 +3217,88 @@ function addGeometryToolsControl() {
|
||||
}
|
||||
});
|
||||
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) {
|
||||
@@ -3075,7 +3411,8 @@ function restoreLastTrashItem() {
|
||||
if (lmeState.readOnly) { setInfo("Il progetto e' in sola lettura."); return false; }
|
||||
const entry = lmeState.trash.pop();
|
||||
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)) {
|
||||
lmeState.trash.push(entry);
|
||||
return false;
|
||||
@@ -3084,6 +3421,12 @@ function restoreLastTrashItem() {
|
||||
entry.items.forEach(item => {
|
||||
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 === "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);
|
||||
});
|
||||
refreshLayerManager();
|
||||
@@ -3271,8 +3614,17 @@ function setManagedCategoryVisible(category, visible) {
|
||||
lmeState.importedSheetsVisible = visible;
|
||||
} else if (category.key === "selectedParcels") {
|
||||
lmeState.selectedParcelsVisible = visible;
|
||||
} else if (category.key === "fiducials") {
|
||||
lmeState.fiducialsVisible = visible;
|
||||
} else if (category.key === "gpsPosition") {
|
||||
lmeState.gpsPositionVisible = visible;
|
||||
} else if (category.key === "compass") {
|
||||
lmeState.compassVisible = visible;
|
||||
void lmeState.dotNetReference?.invokeMethodAsync("SetCompassFromLayerManager", visible);
|
||||
if (!visible) {
|
||||
lmeState.map.setHeading?.(null);
|
||||
lmeState.map.setBearing?.(0);
|
||||
}
|
||||
}
|
||||
if (visible) {
|
||||
lmeState.managerHiddenCategories.delete(category.key);
|
||||
@@ -3344,6 +3696,9 @@ function managedCategoryEnabled(categoryKey) {
|
||||
if (categoryKey === "selectedParcels") {
|
||||
return lmeState.selectedParcelsVisible;
|
||||
}
|
||||
if (categoryKey === "fiducials") {
|
||||
return lmeState.fiducialsVisible;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3873,7 +4228,7 @@ function addProjectMarker(name) {
|
||||
rememberMapChange();
|
||||
const center = lmeState.map.getCenter();
|
||||
const label = (name || "Nuovo POI").trim();
|
||||
const marker = L.marker(center, { pane: "drawingsPane", draggable: false }).addTo(lmeState.drawings);
|
||||
const marker = L.marker(center, { pane: "drawingsMarkerPane", draggable: false }).addTo(lmeState.drawings);
|
||||
marker.lmeManagerLabel = `POI: ${label}`;
|
||||
marker.feature = {
|
||||
type: "Feature",
|
||||
@@ -4578,7 +4933,7 @@ function addMeasurementLabel(targetGroup, latLng, html, labelType, context) {
|
||||
return;
|
||||
}
|
||||
L.marker(latLng, {
|
||||
pane: context.sourceType === "drawing" ? "drawingsPane" : "importedPane",
|
||||
pane: "tooltipPane",
|
||||
interactive: false,
|
||||
keyboard: false,
|
||||
icon: L.divIcon({
|
||||
@@ -4721,7 +5076,7 @@ function circleToPolygon(center, radiusMeters) {
|
||||
function createProjectSnapshot(includeCadastralParcels) {
|
||||
const center = lmeState.map.getCenter();
|
||||
return JSON.stringify({
|
||||
schemaVersion: 5,
|
||||
schemaVersion: 7,
|
||||
view: {
|
||||
latitude: center.lat,
|
||||
longitude: center.lng,
|
||||
@@ -4731,6 +5086,7 @@ function createProjectSnapshot(includeCadastralParcels) {
|
||||
imported: managedGroupGeoJson(lmeState.imported, "imported"),
|
||||
importedSheets: managedGroupGeoJson(lmeState.importedSheets, "importedSheets"),
|
||||
importedSheetsVisible: lmeState.importedSheetsVisible,
|
||||
fiducials: managedGroupGeoJson(lmeState.fiducials, "fiducials"),
|
||||
displayOptions: captureDisplayOptions(),
|
||||
selectedParcels: includeCadastralParcels
|
||||
? managedGroupGeoJson(lmeState.selectedParcelsLayer, "selectedParcels")
|
||||
@@ -4766,6 +5122,7 @@ function loadProjectSnapshot(raw, projectName) {
|
||||
lmeState.drawingMeasurements.clearLayers();
|
||||
lmeState.importedMeasurements.clearLayers();
|
||||
lmeState.selectedParcelsLayer.clearLayers();
|
||||
lmeState.fiducials.clearLayers();
|
||||
lmeState.gpsParcelLayer.clearLayers();
|
||||
|
||||
const restoredDrawings = L.geoJSON(data.drawings || emptyFeatureCollection(), {
|
||||
@@ -4784,7 +5141,7 @@ function loadProjectSnapshot(raw, projectName) {
|
||||
fillOpacity: 0.2
|
||||
});
|
||||
}
|
||||
return L.marker(latlng, { pane: "drawingsPane", draggable: false });
|
||||
return L.marker(latlng, { pane: "drawingsMarkerPane", draggable: false });
|
||||
},
|
||||
onEachFeature: (feature, layer) => {
|
||||
bindStoredFeaturePopup(feature, layer);
|
||||
@@ -4826,6 +5183,14 @@ function loadProjectSnapshot(raw, projectName) {
|
||||
applyStyleFromProperties(layer);
|
||||
}
|
||||
}).addTo(lmeState.selectedParcelsLayer);
|
||||
const restoredFiducials = L.geoJSON(data.fiducials || emptyFeatureCollection(), {
|
||||
pointToLayer: (feature, latlng) => createFiducialMarker(feature, latlng),
|
||||
onEachFeature: (feature, layer) => {
|
||||
bindFiducialPopup(feature, layer);
|
||||
bindManagedMapSelection(layer, "fiducials", lmeState.fiducials);
|
||||
}
|
||||
});
|
||||
restoredFiducials.eachLayer(layer => lmeState.fiducials.addLayer(layer));
|
||||
if (lmeState.importedSheetsVisible) {
|
||||
void ensureImportedSheetOutlines().then(() => {
|
||||
applyImportedSheetVisibility();
|
||||
@@ -4848,6 +5213,7 @@ function loadProjectSnapshot(raw, projectName) {
|
||||
lmeState.drawings,
|
||||
lmeState.imported,
|
||||
lmeState.selectedParcelsLayer,
|
||||
lmeState.fiducials,
|
||||
lmeState.gpsTrail
|
||||
];
|
||||
if (lmeState.importedSheetsVisible) {
|
||||
@@ -4897,7 +5263,11 @@ function captureDisplayOptions() {
|
||||
showDrawingMeasurements: lmeState.showDrawingMeasurements,
|
||||
drawToolsVisible: Boolean(lmeState.drawControl),
|
||||
gpsPositionVisible: lmeState.gpsPositionVisible,
|
||||
compassVisible: lmeState.compassVisible,
|
||||
compassMode: lmeState.compassMode,
|
||||
selectedParcelsVisible: lmeState.selectedParcelsVisible,
|
||||
fiducialsVisible: lmeState.fiducialsVisible,
|
||||
fiducialColor: lmeState.fiducialColor,
|
||||
layerOrder: [...lmeState.layerOrder],
|
||||
layerObjectOrder: { ...lmeState.managerObjectOrder },
|
||||
layerCategoryNames: { ...lmeState.managerCategoryNames },
|
||||
@@ -4964,7 +5334,14 @@ function applyProjectDisplayOptions(options) {
|
||||
setBaseLayer(options.baseLayer);
|
||||
}
|
||||
setGpsPositionVisible(options.gpsPositionVisible !== false);
|
||||
const compassVisible = Boolean(options.compassVisible)
|
||||
&& !lmeState.managerHiddenCategories.has("compass");
|
||||
setCompassOptions(compassVisible, options.compassMode);
|
||||
setSelectedParcelsVisible(options.selectedParcelsVisible !== false);
|
||||
if (typeof options.fiducialColor === "string") {
|
||||
setFiducialColor(options.fiducialColor);
|
||||
}
|
||||
setFiducialPointsVisible(options.fiducialsVisible !== false);
|
||||
toggleTrail(Boolean(options.trailEnabled));
|
||||
setDrawToolsVisible(Boolean(options.drawToolsVisible));
|
||||
applyStoredLayerManagerVisibility();
|
||||
@@ -5013,6 +5390,19 @@ function emptyFeatureCollection() {
|
||||
function managedGroupGeoJson(group, categoryKey) {
|
||||
const collection = group?.toGeoJSON?.() || emptyFeatureCollection();
|
||||
const features = [...(collection.features || [])];
|
||||
const knownIds = new Set(features.map(feature => feature?.properties?._lmeManagerId).filter(Boolean));
|
||||
lmeState.managerHiddenObjects.forEach(record => {
|
||||
if (record.categoryKey !== categoryKey || typeof record.layer?.toGeoJSON !== "function") return;
|
||||
const stored = record.layer.toGeoJSON();
|
||||
const hiddenFeatures = stored?.type === "FeatureCollection" ? stored.features : [stored];
|
||||
hiddenFeatures.filter(feature => feature?.type === "Feature").forEach(feature => {
|
||||
const id = feature?.properties?._lmeManagerId;
|
||||
if (!id || !knownIds.has(id)) {
|
||||
features.push(feature);
|
||||
if (id) knownIds.add(id);
|
||||
}
|
||||
});
|
||||
});
|
||||
const categoryName = lmeState.managerCategoryNames[categoryKey];
|
||||
if (categoryName) {
|
||||
features.forEach(feature => {
|
||||
@@ -5634,6 +6024,140 @@ function activeCadastreLayers() {
|
||||
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;
|
||||
const category = layerManagerCategories().find(item => item.key === "fiducials");
|
||||
managedRecordsForCategory(category).forEach(record => {
|
||||
const layer = record.layer;
|
||||
if (layer.feature?.properties) {
|
||||
layer.feature.properties._lmeColor = color;
|
||||
layer.feature.properties._lmeFillColor = color;
|
||||
}
|
||||
applyStyleFromProperties(layer);
|
||||
});
|
||||
refreshLayerManager();
|
||||
}
|
||||
|
||||
function fitFiducialPoints() {
|
||||
if (!lmeState.fiducials?.getLayers().length) {
|
||||
setInfo("Nessun Punto Fiduciale da inquadrare.");
|
||||
return;
|
||||
}
|
||||
fitLayer(lmeState.fiducials);
|
||||
}
|
||||
|
||||
function clearSelectedParcels() {
|
||||
const count = managedGroupGeoJson(lmeState.selectedParcelsLayer, "selectedParcels").features.length;
|
||||
if (count) rememberMapChange();
|
||||
@@ -5672,6 +6196,7 @@ function setSelectedParcelsVisible(enabled) {
|
||||
function clearMapElements() {
|
||||
const count = managedGroupGeoJson(lmeState.selectedParcelsLayer, "selectedParcels").features.length
|
||||
+ managedGroupGeoJson(lmeState.gpsParcelLayer, "gpsParcels").features.length
|
||||
+ managedGroupGeoJson(lmeState.fiducials, "fiducials").features.length
|
||||
+ managedGroupGeoJson(lmeState.imported, "imported").features.length
|
||||
+ managedGroupGeoJson(lmeState.importedSheets, "importedSheets").features.length
|
||||
+ managedGroupGeoJson(lmeState.drawings, "drawings").features.length
|
||||
@@ -5680,7 +6205,7 @@ function clearMapElements() {
|
||||
setInfo("La mappa non contiene elementi da pulire.");
|
||||
return;
|
||||
}
|
||||
if (!window.confirm("Rimuovere particelle evidenziate, file importati, disegni, POI e traccia GPS dalla mappa?")) {
|
||||
if (!window.confirm("Rimuovere particelle evidenziate, Punti Fiduciali, file importati, disegni, POI e traccia GPS dalla mappa?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5688,6 +6213,7 @@ function clearMapElements() {
|
||||
resetLayerManagerProjectState();
|
||||
lmeState.selectedParcelsLayer.clearLayers();
|
||||
lmeState.gpsParcelLayer.clearLayers();
|
||||
lmeState.fiducials.clearLayers();
|
||||
lmeState.imported.clearLayers();
|
||||
lmeState.importedSheets.clearLayers();
|
||||
lmeState.drawings.clearLayers();
|
||||
@@ -5836,7 +6362,7 @@ function updateGpsPositionLayers(latlng, accuracy) {
|
||||
lmeState.gpsMarker.lmeManagerId = "gps-position-marker";
|
||||
lmeState.gpsMarker.lmeManagerLabel = "Posizione GPS";
|
||||
lmeState.gpsAccuracy = L.circle(latlng, {
|
||||
pane: "gpsPositionPane",
|
||||
pane: "gpsAccuracyPane",
|
||||
radius: accuracy,
|
||||
color: "#2563eb",
|
||||
weight: 2,
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Grzegorz Tomicki
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,2 @@
|
||||
/*! @tomickigrzegorz/leaflet-rotate v0.3.0 | MIT */
|
||||
!function(t,i){"object"==typeof exports&&"undefined"!=typeof module?i(require("leaflet")):"function"==typeof define&&define.amd?define(["leaflet"],i):i((t="undefined"!=typeof globalThis?globalThis:t||self).L)}(this,function(t){"use strict";t.Point.prototype.rotate=function(i){var e=Math.cos(i),o=Math.sin(i);return new t.Point(this.x*e-this.y*o,this.x*o+this.y*e)},t.Point.prototype.rotateFrom=function(t,i){return i?this.subtract(i).rotate(t).add(i):this.rotate(t)};const i=Math.PI/180,e=180/Math.PI;function o(t){return(t%360+360)%360}function a(t){return o(t+180)-180}function n(t){var i=2*Math.PI;return((t+Math.PI)%i+i)%i-Math.PI}const s=1e3/60;function r(t,i){return 1-Math.pow(1-t,i/s)}function h(){return"undefined"!=typeof performance&&performance.now?performance.now():Date.now()}var _=t.Map.prototype;t.Map.mergeOptions({rotate:!1,bearing:0,touchRotate:!1,shiftKeyRotate:!1,dragRotate:!0,rotateControl:!1,rotateClockwise:!0});var p=_.initialize;_.initialize=function(t,i){i&&i.rotate&&(this._rotate=!0,this._bearing=0,this._bearingRad=0),p.call(this,t,i),this._rotate&&this.setBearing(i.bearing||0)};var l=_._initPanes;_._initPanes=function(){if(l.call(this),this._rotate){var i=this._mapPane;this._rotatePane=t.DomUtil.create("div","leaflet-rotate-pane",i),this._norotatePane=t.DomUtil.create("div","leaflet-norotate-pane",i),this._rotatePane.appendChild(this._panes.tilePane),this._rotatePane.appendChild(this._panes.overlayPane),this._norotatePane.appendChild(this._panes.shadowPane),this._norotatePane.appendChild(this._panes.markerPane),this._norotatePane.appendChild(this._panes.tooltipPane),this._norotatePane.appendChild(this._panes.popupPane),t.DomUtil.addClass(this._rotatePane,"leaflet-proxy leaflet-zoom-animated")}},_.setBearing=function(e){if(!this._rotate||!isFinite(e))return this;var a=this._bearing||0,n=o(e);if(n===a)return this;if(this._commitRotatePan(),this._bearing=n,this._bearingRad=n*i,this._updateRotatePaneTransform(),0===a!=(0===n))for(var s in this._layers){var r=this._layers[s];r instanceof t.Renderer&&r._update()}return this.fire("rotate")},_.getBearing=function(){return this._bearing||0},_._updateRotatePaneTransform=function(){if(this._rotatePane){if(!this._bearing)return this._rotatePane.style[t.DomUtil.TRANSFORM]="",void(this._rotatePane.style[t.DomUtil.TRANSFORM+"Origin"]="");var i=this.getSize().divideBy(2);this._rotatePane.style[t.DomUtil.TRANSFORM+"Origin"]=i.x+"px "+i.y+"px",this._rotatePane.style[t.DomUtil.TRANSFORM]="rotate("+this._bearingRad+"rad)"}},_._commitRotatePan=function(){if(this._rotate&&!this._committingRotatePan){var t=this._getMapPanePos();!t||0===t.x&&0===t.y||(this._committingRotatePan=!0,this._resetView(this.getCenter(),this.getZoom(),!0),this._committingRotatePan=!1)}};var m=_.containerPointToLayerPoint;_.containerPointToLayerPoint=function(i){if(!this._rotate||!this._bearing)return m.call(this,i);var e=t.point(i),o=this._getMapPanePos(),a=this.getSize().divideBy(2);return e.subtract(o).subtract(a).rotate(-this._bearingRad).add(a)};var d=_.layerPointToContainerPoint;_.layerPointToContainerPoint=function(i){if(!this._rotate||!this._bearing)return d.call(this,i);var e=t.point(i),o=this._getMapPanePos(),a=this.getSize().divideBy(2);return e.subtract(a).rotate(this._bearingRad).add(a).add(o)},_.rotatedPointToMapPanePoint=function(i){if(!this._bearing)return t.point(i);var e=this.getSize().divideBy(2);return t.point(i).rotateFrom(this._bearingRad,e)},_.mapPanePointToRotatedPoint=function(i){if(!this._bearing)return t.point(i);var e=this.getSize().divideBy(2);return t.point(i).rotateFrom(-this._bearingRad,e)};var c=_._getCenterOffset;_._getCenterOffset=function(t){return this._rotate&&this._bearing?this.project(t).subtract(this.project(this.getCenter())).rotate(this._bearingRad):c.call(this,t)};var u=_.getBounds;_.getBounds=function(){if(!this._rotate||!this._bearing)return u.call(this);var i=this.getSize(),e=t.latLngBounds();return e.extend(this.containerPointToLatLng(t.point(0,0))),e.extend(this.containerPointToLatLng(t.point(i.x,0))),e.extend(this.containerPointToLatLng(t.point(i.x,i.y))),e.extend(this.containerPointToLatLng(t.point(0,i.y))),e},_.mapBoundsToContainerBounds=function(i){return t.bounds([this.latLngToContainerPoint(i.getNorthWest()),this.latLngToContainerPoint(i.getNorthEast()),this.latLngToContainerPoint(i.getSouthEast()),this.latLngToContainerPoint(i.getSouthWest())])};var g=_.getBoundsZoom;_.getBoundsZoom=function(i,e,o){if(!this._rotate||!this._bearing)return g.call(this,i,e,o);i=t.latLngBounds(i),o=t.point(o||[0,0]);var a=this.getZoom()||0,n=this.getMinZoom(),s=this.getMaxZoom(),r=this.getSize().subtract(o);if(r.x<=0||r.y<=0)return a;var h=this.mapBoundsToContainerBounds(i).getSize(),_=this.options.zoomSnap,p=r.x/h.x,l=r.y/h.y,m=e?Math.max(p,l):Math.min(p,l);return a=this.getScaleZoom(m,a),_&&(a=Math.round(a/_)*_),Math.max(n,Math.min(s,a))},_._animateZoomNoDelay=function(t,i,e){this._mapPane&&(e&&(this._animatingZoom=!0,this._animateToCenter=t,this._animLine truncated
|
||||
Reference in new issue
Block a user