Initial commit: FieldLogger MAUI app with Maglink BLE support
Added Maglink RTK GNSS receiver integration with correct BLE UUIDs and device name filtering (ML-* prefix). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
151
FieldLogger/Views/MapPage.xaml.cs
Normal file
151
FieldLogger/Views/MapPage.xaml.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
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)
|
||||
{
|
||||
InitializeComponent();
|
||||
BindingContext = _viewModel = viewModel;
|
||||
}
|
||||
|
||||
protected override async void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user