// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control // Copyright © 2024 Clemens Fischer // Licensed under the Microsoft Public License (Ms-PL) using System; using System.Collections.Generic; using System.Globalization; using System.Linq; #if WPF using System.Windows; using System.Windows.Media; #elif UWP using Windows.UI.Xaml; using Windows.UI.Xaml.Media; #elif WINUI using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media; #elif AVALONIA using Avalonia.Media; using DependencyProperty = Avalonia.AvaloniaProperty; using PathFigureCollection = Avalonia.Media.PathFigures; #endif namespace MapControl { /// /// Draws a graticule overlay. /// public partial class MapGraticule { private class Label { public Label(string latText, string lonText, double x, double y, double rotation) { LatitudeText = latText; LongitudeText = lonText; X = x; Y = y; Rotation = rotation; } public string LatitudeText { get; } public string LongitudeText { get; } public double X { get; } public double Y { get; } public double Rotation { get; } } private const double LineInterpolationResolution = 2d; public static readonly DependencyProperty MinLineDistanceProperty = DependencyPropertyHelper.Register(nameof(MinLineDistance), 150d); private double lineDistance; private string labelFormat; /// /// Minimum graticule line distance in pixels. The default value is 150. /// public double MinLineDistance { get => (double)GetValue(MinLineDistanceProperty); set => SetValue(MinLineDistanceProperty, value); } private void SetLineDistance() { var minDistance = MinLineDistance / PixelPerLongitudeDegree(ParentMap.Center); var scale = minDistance < 1d / 60d ? 3600d : minDistance < 1d ? 60d : 1d; minDistance *= scale; var lineDistances = new double[] { 1d, 2d, 5d, 10d, 15d, 30d, 60d }; var i = 0; while (i < lineDistances.Length - 1 && lineDistances[i] < minDistance) { i++; } lineDistance = Math.Min(lineDistances[i] / scale, 30d); labelFormat = lineDistance < 1d / 60d ? "{0} {1}°{2:00}'{3:00}\"" : lineDistance < 1d ? "{0} {1}°{2:00}'" : "{0} {1}°"; } private double PixelPerLongitudeDegree(Location location) { return Math.Max(1d, // a reasonable lower limit ParentMap.GetScale(location).X * Math.Cos(location.Latitude * Math.PI / 180d) * MapProjection.Wgs84MeterPerDegree); } private string GetLabelText(double value, string hemispheres) { var hemisphere = hemispheres[0]; if (value < -1e-8) // ~1 mm { value = -value; hemisphere = hemispheres[1]; } var seconds = (int)Math.Round(value * 3600d); return string.Format(CultureInfo.InvariantCulture, labelFormat, hemisphere, seconds / 3600, seconds / 60 % 60, seconds % 60); } private void AddLabel(ICollection