Files
ulapp/FieldLogger/Views/MapPage.xaml.cs
2026-08-23 20:54:03 -05:00

163 lines
4.7 KiB
C#

using FieldLogger.Models;
using FieldLogger.ViewModels;
#if WINDOWS
using System.Text.Json;
#else
using Microsoft.Maui.Controls.Maps;
using Microsoft.Maui.Maps;
using Map = Microsoft.Maui.Controls.Maps.Map;
#endif
namespace FieldLogger.Views;
public partial class MapPage : ContentPage
{
private readonly MapViewModel _viewModel;
#if WINDOWS
private WebView? _webView;
private bool _webMapReady;
private List<LoggedPoint>? _pendingPoints;
#else
private Map? _map;
#endif
public MapPage(MapViewModel viewModel, AppStatusViewModel appStatusViewModel)
{
InitializeComponent();
BindingContext = _viewModel = viewModel;
AppStatus.BindingContext = appStatusViewModel;
}
protected override async void OnAppearing()
{
base.OnAppearing();
await RefreshMapAsync();
}
private async void OnRefreshClicked(object? sender, EventArgs e)
{
await RefreshMapAsync();
}
private async Task RefreshMapAsync()
{
await EnsureMapCreatedAsync();
var points = await _viewModel.LoadPointsAsync();
ShowPoints(points);
}
#if WINDOWS
private async Task EnsureMapCreatedAsync()
{
if (_webView is not null)
return;
var apiKey = _viewModel.Settings.GoogleMapsApiKey;
if (string.IsNullOrWhiteSpace(apiKey))
{
MapHost.Content = new Label
{
Text = "Google Maps API key is not set.\nEnter your Maps JavaScript API key on the Settings page.",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
HorizontalTextAlignment = TextAlignment.Center,
};
return;
}
using var stream = await FileSystem.OpenAppPackageFileAsync("map.html");
using var reader = new StreamReader(stream);
var html = (await reader.ReadToEndAsync()).Replace("%%GOOGLE_MAPS_API_KEY%%", apiKey);
_webView = new WebView { Source = new HtmlWebViewSource { Html = html } };
_webView.Navigated += async (_, _) =>
{
_webMapReady = true;
if (_pendingPoints is not null)
{
var pts = _pendingPoints;
_pendingPoints = null;
await PushPointsToWebAsync(pts);
}
};
MapHost.Content = _webView;
}
private async void ShowPoints(List<LoggedPoint> points)
{
if (_webView is null)
return;
if (!_webMapReady)
{
_pendingPoints = points;
return;
}
await PushPointsToWebAsync(points);
}
private async Task PushPointsToWebAsync(List<LoggedPoint> points)
{
var payload = points.Select(p => new
{
id = p.Id,
lat = p.Latitude,
lng = p.Longitude,
utility = ((UmUtility)p.Utility).ToString(),
depth = p.DepthRaw,
current = p.CurrentRaw,
frequency = p.Frequency,
fix = ((GnssFixStatus)p.FixStatus).ToString(),
hrms = p.Hrms,
time = p.TimestampUtc.ToLocalTime().ToString("g"),
});
var json = JsonSerializer.Serialize(payload);
await _webView!.EvaluateJavaScriptAsync($"setPoints({json})");
}
#else
private Task EnsureMapCreatedAsync()
{
if (_map is null)
{
_map = new Map { MapType = MapType.Hybrid, IsShowingUser = false };
MapHost.Content = _map;
}
return Task.CompletedTask;
}
private void ShowPoints(List<LoggedPoint> points)
{
if (_map is null)
return;
_map.Pins.Clear();
foreach (var p in points)
{
_map.Pins.Add(new Pin
{
Label = $"#{p.Id} {(UmUtility)p.Utility} · depth {p.DepthRaw}",
Address = $"{(GnssFixStatus)p.FixStatus} · ±{p.Hrms:F3} m · {p.TimestampUtc.ToLocalTime():g}",
Location = new Location(p.Latitude, p.Longitude),
});
}
if (points.Count > 0)
{
var minLat = points.Min(p => p.Latitude);
var maxLat = points.Max(p => p.Latitude);
var minLon = points.Min(p => p.Longitude);
var maxLon = points.Max(p => p.Longitude);
var center = new Location((minLat + maxLat) / 2, (minLon + maxLon) / 2);
var spanKm = Math.Max(0.05,
Location.CalculateDistance(minLat, minLon, maxLat, maxLon, DistanceUnits.Kilometers) * 0.7);
_map.MoveToRegion(MapSpan.FromCenterAndRadius(center, Distance.FromKilometers(spanKm)));
}
}
#endif
}