Compare commits

...
8 changed files with 621 additions and 21 deletions

No files matched your search

+106
View File
@@ -673,6 +673,17 @@
<input type="number" min="0" max="30" step="1" @bind="MapMessageTimeoutSeconds" @bind:after="ApplyMapMessageOptions" /> <input type="number" min="0" max="30" step="1" @bind="MapMessageTimeoutSeconds" @bind:after="ApplyMapMessageOptions" />
</label> </label>
<small class="field-help">Imposta 0 per lasciare i messaggi visibili fino alla chiusura manuale.</small> <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>
<section class="panel-section"> <section class="panel-section">
@@ -877,6 +888,9 @@
private bool GpsActive { get; set; } private bool GpsActive { get; set; }
private bool GpsOverlayCollapsed { get; set; } private bool GpsOverlayCollapsed { get; set; }
private bool ShowGpsPosition { get; set; } = true; 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 ShowSelectedParcels { get; set; } = true;
private bool ShowFiducialPoints { get; set; } = true; private bool ShowFiducialPoints { get; set; } = true;
private bool KeepPreviousFiducialPoints { get; set; } = true; private bool KeepPreviousFiducialPoints { get; set; } = true;
@@ -1788,6 +1802,15 @@
return InvokeAsync(StateHasChanged); return InvokeAsync(StateHasChanged);
} }
[JSInvokable]
public async Task SetCompassFromLayerManager(bool enabled)
{
ShowCompass = enabled;
SaveFlagPreferences();
await ConfigureCompassSensorAsync();
await InvokeAsync(StateHasChanged);
}
[JSInvokable] [JSInvokable]
public Task<string> GetOfficialCadastralSheetsNear(double latitude, double longitude) => public Task<string> GetOfficialCadastralSheetsNear(double latitude, double longitude) =>
OfficialCadastreService.GetSheetsNearAsync(latitude, longitude); OfficialCadastreService.GetSheetsNearAsync(latitude, longitude);
@@ -2298,6 +2321,8 @@
ShowDrawingMeasurements = options.ShowDrawingMeasurements; ShowDrawingMeasurements = options.ShowDrawingMeasurements;
DrawToolsVisible = options.DrawToolsVisible; DrawToolsVisible = options.DrawToolsVisible;
ShowGpsPosition = options.GpsPositionVisible; ShowGpsPosition = options.GpsPositionVisible;
ShowCompass = options.CompassVisible;
CompassMode = options.CompassMode is "heading" ? "heading" : "fixed";
ShowSelectedParcels = options.SelectedParcelsVisible; ShowSelectedParcels = options.SelectedParcelsVisible;
TrailEnabled = options.TrailEnabled; TrailEnabled = options.TrailEnabled;
@@ -2307,6 +2332,7 @@
layer.Enabled = activeLayers.Contains(layer.Key); layer.Enabled = activeLayers.Contains(layer.Key);
} }
SaveFlagPreferences(); SaveFlagPreferences();
_ = ConfigureCompassSensorAsync();
} }
private async Task DeleteProject() private async Task DeleteProject()
@@ -2718,6 +2744,8 @@
ShowDrawingMeasurements = GetFlagPreference("show_drawing_measurements", true); ShowDrawingMeasurements = GetFlagPreference("show_drawing_measurements", true);
DrawToolsVisible = GetFlagPreference("draw_tools_visible", false); DrawToolsVisible = GetFlagPreference("draw_tools_visible", false);
ShowGpsPosition = GetFlagPreference("show_gps_position", true); 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); ShowSelectedParcels = GetFlagPreference("show_selected_parcels", true);
ShowFiducialPoints = GetFlagPreference("show_fiducial_points", true); ShowFiducialPoints = GetFlagPreference("show_fiducial_points", true);
KeepPreviousFiducialPoints = GetFlagPreference("keep_previous_fiducials", true); KeepPreviousFiducialPoints = GetFlagPreference("keep_previous_fiducials", true);
@@ -2761,6 +2789,8 @@
SetFlagPreference("show_drawing_measurements", ShowDrawingMeasurements); SetFlagPreference("show_drawing_measurements", ShowDrawingMeasurements);
SetFlagPreference("draw_tools_visible", DrawToolsVisible); SetFlagPreference("draw_tools_visible", DrawToolsVisible);
SetFlagPreference("show_gps_position", ShowGpsPosition); SetFlagPreference("show_gps_position", ShowGpsPosition);
SetFlagPreference("show_compass", ShowCompass);
Preferences.Default.Set($"{FlagPreferencePrefix}compass_mode", CompassMode);
SetFlagPreference("show_selected_parcels", ShowSelectedParcels); SetFlagPreference("show_selected_parcels", ShowSelectedParcels);
SetFlagPreference("show_fiducial_points", ShowFiducialPoints); SetFlagPreference("show_fiducial_points", ShowFiducialPoints);
SetFlagPreference("keep_previous_fiducials", KeepPreviousFiducialPoints); SetFlagPreference("keep_previous_fiducials", KeepPreviousFiducialPoints);
@@ -2801,6 +2831,8 @@
await JS.InvokeVoidAsync("lmeMap.setDrawingMeasurementsVisible", ShowDrawingMeasurements); await JS.InvokeVoidAsync("lmeMap.setDrawingMeasurementsVisible", ShowDrawingMeasurements);
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.setCompassOptions", ShowCompass, CompassMode);
await ConfigureCompassSensorAsync();
await JS.InvokeVoidAsync("lmeMap.setSelectedParcelsVisible", ShowSelectedParcels); await JS.InvokeVoidAsync("lmeMap.setSelectedParcelsVisible", ShowSelectedParcels);
await JS.InvokeVoidAsync("lmeMap.setFiducialColor", FiducialColor); await JS.InvokeVoidAsync("lmeMap.setFiducialColor", FiducialColor);
await JS.InvokeVoidAsync("lmeMap.setFiducialPointsVisible", ShowFiducialPoints); await JS.InvokeVoidAsync("lmeMap.setFiducialPointsVisible", ShowFiducialPoints);
@@ -2819,6 +2851,68 @@
await JS.InvokeVoidAsync("lmeMap.setInfoTimeout", MapMessageTimeoutSeconds); 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() private async Task ApplyAdvancedOptions()
{ {
SaveFlagPreferences(); SaveFlagPreferences();
@@ -2893,6 +2987,8 @@
public bool ShowDrawingMeasurements { get; init; } = true; public bool ShowDrawingMeasurements { get; init; } = true;
public bool DrawToolsVisible { get; init; } public bool DrawToolsVisible { get; init; }
public bool GpsPositionVisible { get; init; } = true; 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 SelectedParcelsVisible { get; init; } = true;
public bool TrailEnabled { get; init; } public bool TrailEnabled { get; init; }
} }
@@ -2922,6 +3018,16 @@
GpsLookupCancellation?.Dispose(); GpsLookupCancellation?.Dispose();
ProjectAutoSaveCancellation?.Cancel(); ProjectAutoSaveCancellation?.Cancel();
ProjectAutoSaveCancellation?.Dispose(); ProjectAutoSaveCancellation?.Dispose();
var compass = Microsoft.Maui.Devices.Sensors.Compass.Default;
if (CompassSensorSubscribed)
{
compass.ReadingChanged -= OnCompassReadingChanged;
if (compass.IsMonitoring)
{
compass.Stop();
}
CompassSensorSubscribed = false;
}
MapCallbackReference?.Dispose(); MapCallbackReference?.Dispose();
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }
+2 -2
View File
@@ -29,8 +29,8 @@
<ApplicationId>it.liviomerola.lmemappecatastali.pro</ApplicationId> <ApplicationId>it.liviomerola.lmemappecatastali.pro</ApplicationId>
<!-- Versions --> <!-- Versions -->
<ApplicationDisplayVersion>1.0.14</ApplicationDisplayVersion> <ApplicationDisplayVersion>1.0.18</ApplicationDisplayVersion>
<ApplicationVersion>114</ApplicationVersion> <ApplicationVersion>118</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 -->
+104
View File
@@ -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
+106
View File
@@ -1674,6 +1674,105 @@ label {
line-height: 1; 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] { .wms-preview[hidden] {
display: none; display: none;
} }
@@ -1948,6 +2047,13 @@ footer {
white-space: normal; white-space: normal;
} }
.map-compass {
--compass-radius: -27px;
left: 8px;
width: 72px;
height: 72px;
}
.map-info-text { .map-info-text {
display: -webkit-box; display: -webkit-box;
overflow: hidden; overflow: hidden;
+1
View File
@@ -27,6 +27,7 @@
<script src="_framework/blazor.webview.js" autostart="false"></script> <script src="_framework/blazor.webview.js" autostart="false"></script>
<script src="lib/leaflet/leaflet.js"></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/leaflet-draw/leaflet.draw.js"></script>
<script src="lib/googlemutant/Leaflet.GoogleMutant.js"></script> <script src="lib/googlemutant/Leaflet.GoogleMutant.js"></script>
<script src="lib/jszip/jszip.min.js"></script> <script src="lib/jszip/jszip.min.js"></script>
+279 -19
View File
@@ -12,6 +12,7 @@ const lmeState = {
"selectedParcels", "selectedParcels",
"importedSheets", "importedSheets",
"imported", "imported",
"compass",
"cadastre" "cadastre"
], ],
cadastre: null, cadastre: null,
@@ -61,6 +62,14 @@ const lmeState = {
gpsTrailStartedAt: null, gpsTrailStartedAt: null,
gpsWatchId: null, gpsWatchId: null,
lastPosition: null, lastPosition: null,
compassLayer: null,
compassElement: null,
compassVisible: false,
compassMode: "fixed",
compassHeading: 0,
compassPosition: null,
compassDragCleanup: null,
compassResizeHandler: null,
infoTimeoutSeconds: 3, infoTimeoutSeconds: 3,
infoTimer: null, infoTimer: null,
fileImportBound: false, fileImportBound: false,
@@ -185,6 +194,8 @@ window.lmeMap = {
getGpsTrailStats, getGpsTrailStats,
setNativeGpsPosition, setNativeGpsPosition,
setGpsPositionVisible, setGpsPositionVisible,
setCompassOptions,
setCompassHeading,
setGpsError, setGpsError,
saveGpsMeasurementPoint, saveGpsMeasurementPoint,
restoreLastTrashItem, restoreLastTrashItem,
@@ -259,7 +270,13 @@ function init(dotNetReference) {
lmeState.map = L.map("map", { lmeState.map = L.map("map", {
zoomControl: false, zoomControl: false,
attributionControl: false attributionControl: false,
rotate: true,
bearing: 0,
dragRotate: false,
touchRotate: false,
shiftKeyRotate: false,
rotateControl: false
}).setView([41.9028, 12.4964], 17); }).setView([41.9028, 12.4964], 17);
L.control.zoom({ position: "bottomright" }).addTo(lmeState.map); L.control.zoom({ position: "bottomright" }).addTo(lmeState.map);
@@ -303,13 +320,19 @@ function init(dotNetReference) {
lmeState.baseLayers.offline = createOfflineTileLayer(); lmeState.baseLayers.offline = createOfflineTileLayer();
lmeState.currentBase = lmeState.baseLayers.satellite.addTo(lmeState.map); lmeState.currentBase = lmeState.baseLayers.satellite.addTo(lmeState.map);
lmeState.compassLayer = createCompassLayer();
restoreLayerOrder(); restoreLayerOrder();
Object.values(managedLayerPanes()).forEach(paneName => { Object.values(managedLayerPanes()).forEach(paneName => {
if (!lmeState.map.getPane(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"; managerHighlightPane.style.zIndex = "650";
lmeState.drawingsRenderer = L.canvas({ pane: "drawingsPane", tolerance: 14 }); lmeState.drawingsRenderer = L.canvas({ pane: "drawingsPane", tolerance: 14 });
setLayerOrder(lmeState.layerOrder); setLayerOrder(lmeState.layerOrder);
@@ -322,10 +345,10 @@ function init(dotNetReference) {
lmeState.selectedParcelsLayer = L.featureGroup().addTo(lmeState.map); lmeState.selectedParcelsLayer = L.featureGroup().addTo(lmeState.map);
lmeState.fiducials = L.featureGroup().addTo(lmeState.map); lmeState.fiducials = L.featureGroup().addTo(lmeState.map);
lmeState.gpsParcelLayer = L.featureGroup().addTo(lmeState.map); lmeState.gpsParcelLayer = L.featureGroup().addTo(lmeState.map);
const navigationSheetPane = lmeState.map.createPane("navigationSheetPane"); const navigationSheetPane = lmeState.map.createPane("navigationSheetPane", lmeState.map._rotatePane || lmeState.map._mapPane);
navigationSheetPane.style.zIndex = "465"; navigationSheetPane.style.zIndex = "465";
navigationSheetPane.style.pointerEvents = "none"; 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.zIndex = "455";
cadastreZoningPane.style.pointerEvents = "none"; cadastreZoningPane.style.pointerEvents = "none";
lmeState.navigationSheetLayer = L.layerGroup().addTo(lmeState.map); lmeState.navigationSheetLayer = L.layerGroup().addTo(lmeState.map);
@@ -416,6 +439,199 @@ function init(dotNetReference) {
setInfo("Mappa pronta: satellite + catasto AdE"); 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) { function showSearchResult(latitude, longitude, label) {
const lat = Number(latitude); const lat = Number(latitude);
const lon = Number(longitude); const lon = Number(longitude);
@@ -1353,6 +1569,7 @@ function managedLayerPanes() {
fiducials: "fiducialsPane", fiducials: "fiducialsPane",
importedSheets: "importedSheetPane", importedSheets: "importedSheetPane",
imported: "importedPane", imported: "importedPane",
compass: "compassPane",
cadastre: "cadastrePane" cadastre: "cadastrePane"
}; };
} }
@@ -1366,10 +1583,14 @@ function setLayerOrder(order) {
return; return;
} }
normalized.forEach((key, index) => { normalized.forEach((key, index) => {
const pane = lmeState.map.getPane(panes[key]); const zIndex = String(590 - index * 20);
if (pane) { const paneNames = [panes[key]];
pane.style.zIndex = String(590 - index * 20); 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 { try {
localStorage.setItem("lmeMapLayerOrder", JSON.stringify(normalized)); localStorage.setItem("lmeMapLayerOrder", JSON.stringify(normalized));
@@ -1382,6 +1603,10 @@ function restoreLayerOrder() {
const saved = JSON.parse(localStorage.getItem("lmeMapLayerOrder") || "null"); const saved = JSON.parse(localStorage.getItem("lmeMapLayerOrder") || "null");
if (Array.isArray(saved)) { if (Array.isArray(saved)) {
const remaining = saved.filter(key => !["gpsPosition", "gpsTrail", "fiducials"].includes(key)); 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]; lmeState.layerOrder = ["gpsPosition", "gpsTrail", "fiducials", ...remaining];
} }
} catch { } catch {
@@ -1443,6 +1668,7 @@ function layerManagerCategories() {
{ key: "fiducials", label: "Punti fiduciali", roots: [lmeState.fiducials].filter(Boolean), blocks: true }, { 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: "compass", label: "Bussola", roots: [lmeState.compassLayer].filter(Boolean), uiOnly: true },
{ key: "cadastre", label: "Layer catastali AdE", roots: [lmeState.cadastre].filter(Boolean) } { key: "cadastre", label: "Layer catastali AdE", roots: [lmeState.cadastre].filter(Boolean) }
]; ];
} }
@@ -1480,6 +1706,9 @@ function collectManagedLeafLayers(layer, categoryKey, parent, records) {
} }
function managedRecordsForCategory(category) { function managedRecordsForCategory(category) {
if (category.uiOnly) {
return [];
}
const records = []; const records = [];
category.roots.forEach(root => collectManagedLeafLayers(root, category.key, lmeState.map, records)); category.roots.forEach(root => collectManagedLeafLayers(root, category.key, lmeState.map, records));
lmeState.managerHiddenObjects.forEach(record => { lmeState.managerHiddenObjects.forEach(record => {
@@ -2712,7 +2941,7 @@ function addDrawingFeature(feature, select = false) {
style: storedFeatureStyle, style: storedFeatureStyle,
pointToLayer: (item, latlng) => item?.properties?._lmeShape === "circle" && Number(item.properties._lmeRadius) > 0 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.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) => { onEachFeature: (item, layer) => {
annotateDrawingLayer(layer, item?.properties?._lmeShape); annotateDrawingLayer(layer, item?.properties?._lmeShape);
applyStyleFromProperties(layer); applyStyleFromProperties(layer);
@@ -3389,6 +3618,13 @@ function setManagedCategoryVisible(category, visible) {
lmeState.fiducialsVisible = visible; lmeState.fiducialsVisible = visible;
} else if (category.key === "gpsPosition") { } else if (category.key === "gpsPosition") {
lmeState.gpsPositionVisible = visible; 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) { if (visible) {
lmeState.managerHiddenCategories.delete(category.key); lmeState.managerHiddenCategories.delete(category.key);
@@ -3992,7 +4228,7 @@ function addProjectMarker(name) {
rememberMapChange(); rememberMapChange();
const center = lmeState.map.getCenter(); const center = lmeState.map.getCenter();
const label = (name || "Nuovo POI").trim(); 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.lmeManagerLabel = `POI: ${label}`;
marker.feature = { marker.feature = {
type: "Feature", type: "Feature",
@@ -4697,7 +4933,7 @@ function addMeasurementLabel(targetGroup, latLng, html, labelType, context) {
return; return;
} }
L.marker(latLng, { L.marker(latLng, {
pane: context.sourceType === "drawing" ? "drawingsPane" : "importedPane", pane: "tooltipPane",
interactive: false, interactive: false,
keyboard: false, keyboard: false,
icon: L.divIcon({ icon: L.divIcon({
@@ -4840,7 +5076,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: 6, schemaVersion: 7,
view: { view: {
latitude: center.lat, latitude: center.lat,
longitude: center.lng, longitude: center.lng,
@@ -4905,7 +5141,7 @@ function loadProjectSnapshot(raw, projectName) {
fillOpacity: 0.2 fillOpacity: 0.2
}); });
} }
return L.marker(latlng, { pane: "drawingsPane", draggable: false }); return L.marker(latlng, { pane: "drawingsMarkerPane", draggable: false });
}, },
onEachFeature: (feature, layer) => { onEachFeature: (feature, layer) => {
bindStoredFeaturePopup(feature, layer); bindStoredFeaturePopup(feature, layer);
@@ -4947,10 +5183,14 @@ function loadProjectSnapshot(raw, projectName) {
applyStyleFromProperties(layer); applyStyleFromProperties(layer);
} }
}).addTo(lmeState.selectedParcelsLayer); }).addTo(lmeState.selectedParcelsLayer);
L.geoJSON(data.fiducials || emptyFeatureCollection(), { const restoredFiducials = L.geoJSON(data.fiducials || emptyFeatureCollection(), {
pointToLayer: (feature, latlng) => createFiducialMarker(feature, latlng), pointToLayer: (feature, latlng) => createFiducialMarker(feature, latlng),
onEachFeature: (feature, layer) => bindFiducialPopup(feature, layer) onEachFeature: (feature, layer) => {
}).addTo(lmeState.fiducials); bindFiducialPopup(feature, layer);
bindManagedMapSelection(layer, "fiducials", lmeState.fiducials);
}
});
restoredFiducials.eachLayer(layer => lmeState.fiducials.addLayer(layer));
if (lmeState.importedSheetsVisible) { if (lmeState.importedSheetsVisible) {
void ensureImportedSheetOutlines().then(() => { void ensureImportedSheetOutlines().then(() => {
applyImportedSheetVisibility(); applyImportedSheetVisibility();
@@ -5023,6 +5263,8 @@ function captureDisplayOptions() {
showDrawingMeasurements: lmeState.showDrawingMeasurements, showDrawingMeasurements: lmeState.showDrawingMeasurements,
drawToolsVisible: Boolean(lmeState.drawControl), drawToolsVisible: Boolean(lmeState.drawControl),
gpsPositionVisible: lmeState.gpsPositionVisible, gpsPositionVisible: lmeState.gpsPositionVisible,
compassVisible: lmeState.compassVisible,
compassMode: lmeState.compassMode,
selectedParcelsVisible: lmeState.selectedParcelsVisible, selectedParcelsVisible: lmeState.selectedParcelsVisible,
fiducialsVisible: lmeState.fiducialsVisible, fiducialsVisible: lmeState.fiducialsVisible,
fiducialColor: lmeState.fiducialColor, fiducialColor: lmeState.fiducialColor,
@@ -5092,6 +5334,9 @@ function applyProjectDisplayOptions(options) {
setBaseLayer(options.baseLayer); setBaseLayer(options.baseLayer);
} }
setGpsPositionVisible(options.gpsPositionVisible !== false); setGpsPositionVisible(options.gpsPositionVisible !== false);
const compassVisible = Boolean(options.compassVisible)
&& !lmeState.managerHiddenCategories.has("compass");
setCompassOptions(compassVisible, options.compassMode);
setSelectedParcelsVisible(options.selectedParcelsVisible !== false); setSelectedParcelsVisible(options.selectedParcelsVisible !== false);
if (typeof options.fiducialColor === "string") { if (typeof options.fiducialColor === "string") {
setFiducialColor(options.fiducialColor); setFiducialColor(options.fiducialColor);
@@ -5145,6 +5390,19 @@ function emptyFeatureCollection() {
function managedGroupGeoJson(group, categoryKey) { function managedGroupGeoJson(group, categoryKey) {
const collection = group?.toGeoJSON?.() || emptyFeatureCollection(); const collection = group?.toGeoJSON?.() || emptyFeatureCollection();
const features = [...(collection.features || [])]; 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]; const categoryName = lmeState.managerCategoryNames[categoryKey];
if (categoryName) { if (categoryName) {
features.forEach(feature => { features.forEach(feature => {
@@ -5880,7 +6138,9 @@ function setFiducialPointsVisible(enabled) {
function setFiducialColor(color) { function setFiducialColor(color) {
if (!/^#[0-9a-f]{6}$/i.test(String(color))) return; if (!/^#[0-9a-f]{6}$/i.test(String(color))) return;
lmeState.fiducialColor = color; lmeState.fiducialColor = color;
lmeState.fiducials?.eachLayer(layer => { const category = layerManagerCategories().find(item => item.key === "fiducials");
managedRecordsForCategory(category).forEach(record => {
const layer = record.layer;
if (layer.feature?.properties) { if (layer.feature?.properties) {
layer.feature.properties._lmeColor = color; layer.feature.properties._lmeColor = color;
layer.feature.properties._lmeFillColor = color; layer.feature.properties._lmeFillColor = color;
@@ -6102,7 +6362,7 @@ function updateGpsPositionLayers(latlng, accuracy) {
lmeState.gpsMarker.lmeManagerId = "gps-position-marker"; lmeState.gpsMarker.lmeManagerId = "gps-position-marker";
lmeState.gpsMarker.lmeManagerLabel = "Posizione GPS"; lmeState.gpsMarker.lmeManagerLabel = "Posizione GPS";
lmeState.gpsAccuracy = L.circle(latlng, { lmeState.gpsAccuracy = L.circle(latlng, {
pane: "gpsPositionPane", pane: "gpsAccuracyPane",
radius: accuracy, radius: accuracy,
color: "#2563eb", color: "#2563eb",
weight: 2, weight: 2,
+21
View File
@@ -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.
+2
View File
@@ -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