Center map on startup location
This commit is contained in:
1 parent
7933b2e86a
commit
3327b51dcb
6 files changed
+155
-19
No files matched your search
@@ -291,6 +291,10 @@
|
||||
|
||||
<section class="panel-section">
|
||||
<h2>GPS e percorso</h2>
|
||||
<label class="toggle-line project-save-option">
|
||||
<input type="checkbox" checked="@ShowGpsPosition" @onchange="ToggleGpsPosition" />
|
||||
<span>Mostra punto e cerchio GPS</span>
|
||||
</label>
|
||||
<label class="toggle-line project-save-option">
|
||||
<input type="checkbox" checked="@TrailEnabled" @onchange="ToggleTrail" />
|
||||
<span>Registra la traccia mentre cammini</span>
|
||||
@@ -363,6 +367,7 @@
|
||||
private double? CatalogFraction { get; set; }
|
||||
private bool CatalogBusy { get; set; }
|
||||
private bool GpsActive { get; set; }
|
||||
private bool ShowGpsPosition { get; set; } = true;
|
||||
private bool DrawToolsVisible { get; set; }
|
||||
private bool TrailEnabled { get; set; }
|
||||
private bool LayerPanelOpen { get; set; }
|
||||
@@ -453,10 +458,36 @@
|
||||
if (firstRender)
|
||||
{
|
||||
await JS.InvokeVoidAsync("lmeMap.init");
|
||||
await CenterOnInitialLocationAsync();
|
||||
await MigrateLegacyProjectAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CenterOnInitialLocationAsync()
|
||||
{
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(12));
|
||||
try
|
||||
{
|
||||
var location = await LocationService.GetCurrentLocationAsync(timeout.Token);
|
||||
if (location is not null)
|
||||
{
|
||||
await JS.InvokeVoidAsync("lmeMap.showInitialPosition", location);
|
||||
}
|
||||
else
|
||||
{
|
||||
await JS.InvokeVoidAsync("lmeMap.showInfo", "Posizione iniziale non disponibile. La mappa resta utilizzabile normalmente.");
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
await JS.InvokeVoidAsync("lmeMap.showInfo", "Ricerca della posizione iniziale scaduta. Usa Avvia GPS per riprovare.");
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
await JS.InvokeVoidAsync("lmeMap.showInfo", $"Posizione iniziale non disponibile: {error.GetBaseException().Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task MigrateLegacyProjectAsync()
|
||||
{
|
||||
try
|
||||
@@ -680,6 +711,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ToggleGpsPosition(ChangeEventArgs args)
|
||||
{
|
||||
ShowGpsPosition = (bool?)args.Value == true;
|
||||
await JS.InvokeVoidAsync("lmeMap.setGpsPositionVisible", ShowGpsPosition);
|
||||
}
|
||||
|
||||
private async Task StartGps()
|
||||
{
|
||||
GpsActive = true;
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
<ApplicationId>com.companyname.lmemappecatastaliapp</ApplicationId>
|
||||
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>0.2.9</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>13</ApplicationVersion>
|
||||
<ApplicationDisplayVersion>0.2.10</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>14</ApplicationVersion>
|
||||
|
||||
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
|
||||
<WindowsPackageType>None</WindowsPackageType>
|
||||
|
||||
@@ -19,6 +19,8 @@ App .NET MAUI Blazor Hybrid per Android, iOS, Mac Catalyst e Windows, pensata pe
|
||||
- Archivio multiprogetto nella cartella privata dell'app, con nome, selezione da elenco, sovrascrittura ed eliminazione.
|
||||
- Ogni progetto salva file GIS, POI, disegni, traccia e inquadratura; l'inclusione delle particelle catastali rosse e' opzionale.
|
||||
- GPS nativo MAUI ad alta precisione con permessi Android/iOS.
|
||||
- Centratura automatica una tantum sulla posizione attuale all'apertura, senza mostrare il punto o il cerchio e senza avviare il tracking GPS continuo.
|
||||
- Punto e cerchio di precisione del GPS live possono essere mostrati o nascosti dalle impostazioni senza fermare il rilevamento.
|
||||
- Marker posizione, cerchio accuratezza in metri, tracciamento percorso.
|
||||
- Controllo se la posizione GPS cade dentro un poligono importato o disegnato.
|
||||
- Interrogazione WFS ufficiale Agenzia delle Entrate attorno alla posizione GPS per evidenziare la particella catastale corrente quando il servizio restituisce la geometria.
|
||||
|
||||
@@ -13,7 +13,7 @@ public sealed class AzureMapsService
|
||||
|
||||
public AzureMapsService()
|
||||
{
|
||||
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/0.2.9");
|
||||
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/0.2.10");
|
||||
_httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/json");
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,37 @@ public sealed class PreciseLocationService : IAsyncDisposable
|
||||
return status;
|
||||
}
|
||||
|
||||
public async Task<LocationSnapshot?> GetCurrentLocationAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var permission = await EnsurePermissionAsync();
|
||||
if (permission != PermissionStatus.Granted)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Location? location = null;
|
||||
try
|
||||
{
|
||||
var request = new GeolocationRequest(GeolocationAccuracy.Best, TimeSpan.FromSeconds(8));
|
||||
location = await Geolocation.Default.GetLocationAsync(request, cancellationToken);
|
||||
}
|
||||
catch (FeatureNotSupportedException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (location is null)
|
||||
{
|
||||
var lastKnown = await Geolocation.Default.GetLastKnownLocationAsync();
|
||||
if (lastKnown is not null && lastKnown.Timestamp >= DateTimeOffset.UtcNow.AddMinutes(-15))
|
||||
{
|
||||
location = lastKnown;
|
||||
}
|
||||
}
|
||||
|
||||
return location is null ? null : ToSnapshot(location);
|
||||
}
|
||||
|
||||
public async Task StartTrackingAsync(Func<LocationSnapshot, Task> onLocation, Func<string, Task> onError)
|
||||
{
|
||||
if (trackingCancellation is not null)
|
||||
@@ -47,14 +78,7 @@ public sealed class PreciseLocationService : IAsyncDisposable
|
||||
var location = await Geolocation.Default.GetLocationAsync(request, token);
|
||||
if (location is not null)
|
||||
{
|
||||
await onLocation(new LocationSnapshot(
|
||||
location.Latitude,
|
||||
location.Longitude,
|
||||
location.Accuracy,
|
||||
location.Altitude,
|
||||
location.Course,
|
||||
location.Speed,
|
||||
location.Timestamp));
|
||||
await onLocation(ToSnapshot(location));
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@@ -84,6 +108,15 @@ public sealed class PreciseLocationService : IAsyncDisposable
|
||||
StopTracking();
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
private static LocationSnapshot ToSnapshot(Location location) => new(
|
||||
location.Latitude,
|
||||
location.Longitude,
|
||||
location.Accuracy,
|
||||
location.Altitude,
|
||||
location.Course,
|
||||
location.Speed,
|
||||
location.Timestamp);
|
||||
}
|
||||
|
||||
public sealed record LocationSnapshot(
|
||||
|
||||
+72
-8
@@ -19,6 +19,9 @@ const lmeState = {
|
||||
measurementRefreshTimer: null,
|
||||
gpsMarker: null,
|
||||
gpsAccuracy: null,
|
||||
gpsPositionVisible: true,
|
||||
hasLiveGpsFix: false,
|
||||
lastAccuracy: 0,
|
||||
gpsTrail: null,
|
||||
selectedParcelsLayer: null,
|
||||
gpsParcelLayer: null,
|
||||
@@ -40,6 +43,7 @@ const cadastreCrs = L.extend({}, L.CRS.EPSG4326, { code: "EPSG:4258" });
|
||||
window.lmeMap = {
|
||||
init,
|
||||
showSearchResult,
|
||||
showInitialPosition,
|
||||
displayOfficialParcel,
|
||||
exportCadastreView,
|
||||
closeCadastrePreview,
|
||||
@@ -69,6 +73,7 @@ window.lmeMap = {
|
||||
centerOnGps,
|
||||
toggleTrail,
|
||||
setNativeGpsPosition,
|
||||
setGpsPositionVisible,
|
||||
setGpsError,
|
||||
resize,
|
||||
showInfo: setInfo
|
||||
@@ -178,6 +183,23 @@ function showSearchResult(latitude, longitude, label) {
|
||||
setInfo(label || `${lat.toFixed(6)}, ${lon.toFixed(6)}`);
|
||||
}
|
||||
|
||||
function showInitialPosition(location) {
|
||||
const latitude = Number(location?.latitude);
|
||||
const longitude = Number(location?.longitude);
|
||||
const accuracy = Math.max(0, Number(location?.accuracy) || 0);
|
||||
if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const latlng = [latitude, longitude];
|
||||
lmeState.lastPosition = latlng;
|
||||
lmeState.lastAccuracy = accuracy;
|
||||
lmeState.map.setView(latlng, accuracy > 80 ? 17 : 19);
|
||||
setText("gpsState", "Posizione iniziale acquisita - GPS non attivo");
|
||||
setInfo("Mappa centrata sulla posizione attuale. GPS continuo non attivo.");
|
||||
updateGpsInsideState();
|
||||
}
|
||||
|
||||
function displayOfficialParcel(xmlText, latitude, longitude, parcel, label, shouldFit) {
|
||||
const lat = Number(latitude);
|
||||
const lon = Number(longitude);
|
||||
@@ -1705,6 +1727,25 @@ function setNativeGpsPosition(location) {
|
||||
});
|
||||
}
|
||||
|
||||
function setGpsPositionVisible(enabled) {
|
||||
lmeState.gpsPositionVisible = Boolean(enabled);
|
||||
if (!lmeState.gpsPositionVisible) {
|
||||
if (lmeState.gpsMarker && lmeState.map.hasLayer(lmeState.gpsMarker)) {
|
||||
lmeState.map.removeLayer(lmeState.gpsMarker);
|
||||
}
|
||||
if (lmeState.gpsAccuracy && lmeState.map.hasLayer(lmeState.gpsAccuracy)) {
|
||||
lmeState.map.removeLayer(lmeState.gpsAccuracy);
|
||||
}
|
||||
setInfo("Punto e cerchio GPS nascosti. Il rilevamento continua.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (lmeState.hasLiveGpsFix && lmeState.lastPosition) {
|
||||
updateGpsPositionLayers(lmeState.lastPosition, lmeState.lastAccuracy);
|
||||
}
|
||||
setInfo("Punto e cerchio GPS visibili.");
|
||||
}
|
||||
|
||||
function setGpsError(error) {
|
||||
setText("gpsState", "GPS non disponibile");
|
||||
setInfo(error || "Errore GPS.");
|
||||
@@ -1729,14 +1770,12 @@ function onGpsPosition(position) {
|
||||
const { latitude, longitude, accuracy } = position.coords;
|
||||
const latlng = [latitude, longitude];
|
||||
lmeState.lastPosition = latlng;
|
||||
|
||||
if (!lmeState.gpsMarker) {
|
||||
lmeState.gpsMarker = L.marker(latlng, { title: "La tua posizione" }).addTo(lmeState.map);
|
||||
lmeState.gpsAccuracy = L.circle(latlng, { radius: accuracy, color: "#2563eb", weight: 2, fillColor: "#60a5fa", fillOpacity: 0.16 }).addTo(lmeState.map);
|
||||
lmeState.lastAccuracy = Math.max(0, Number(accuracy) || 0);
|
||||
const firstLiveFix = !lmeState.hasLiveGpsFix;
|
||||
lmeState.hasLiveGpsFix = true;
|
||||
updateGpsPositionLayers(latlng, lmeState.lastAccuracy);
|
||||
if (firstLiveFix) {
|
||||
lmeState.map.setView(latlng, 19);
|
||||
} else {
|
||||
lmeState.gpsMarker.setLatLng(latlng);
|
||||
lmeState.gpsAccuracy.setLatLng(latlng).setRadius(accuracy);
|
||||
}
|
||||
|
||||
if (lmeState.gpsTrailEnabled) {
|
||||
@@ -1744,10 +1783,35 @@ function onGpsPosition(position) {
|
||||
lmeState.gpsTrail.setLatLngs(lmeState.gpsTrailPoints);
|
||||
}
|
||||
|
||||
setText("gpsState", `${latitude.toFixed(6)}, ${longitude.toFixed(6)} - precisione ${Math.round(accuracy)} m`);
|
||||
setText("gpsState", `${latitude.toFixed(6)}, ${longitude.toFixed(6)} - precisione ${Math.round(lmeState.lastAccuracy)} m`);
|
||||
updateGpsInsideState();
|
||||
}
|
||||
|
||||
function updateGpsPositionLayers(latlng, accuracy) {
|
||||
if (!lmeState.gpsPositionVisible) {
|
||||
return;
|
||||
}
|
||||
if (!lmeState.gpsMarker) {
|
||||
lmeState.gpsMarker = L.marker(latlng, { title: "La tua posizione" });
|
||||
lmeState.gpsAccuracy = L.circle(latlng, {
|
||||
radius: accuracy,
|
||||
color: "#2563eb",
|
||||
weight: 2,
|
||||
fillColor: "#60a5fa",
|
||||
fillOpacity: 0.16
|
||||
});
|
||||
} else {
|
||||
lmeState.gpsMarker.setLatLng(latlng);
|
||||
lmeState.gpsAccuracy.setLatLng(latlng).setRadius(accuracy);
|
||||
}
|
||||
if (!lmeState.map.hasLayer(lmeState.gpsMarker)) {
|
||||
lmeState.gpsMarker.addTo(lmeState.map);
|
||||
}
|
||||
if (!lmeState.map.hasLayer(lmeState.gpsAccuracy)) {
|
||||
lmeState.gpsAccuracy.addTo(lmeState.map);
|
||||
}
|
||||
}
|
||||
|
||||
function onGpsError(error) {
|
||||
setText("gpsState", "GPS non autorizzato o non disponibile");
|
||||
setInfo(error.message || "Errore GPS.");
|
||||
|
||||
Reference in new issue
Block a user