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; using Avalonia.Media; using PathFigureCollection = Avalonia.Media.PathFigures; #endif namespace MapControl { /// /// Draws a graticule overlay. /// public partial class MapGraticule { private class Label(string latText, string lonText, double x, double y, double rotation) { public string LatitudeText { get; } = latText; public string LongitudeText { get; } = lonText; public double X { get; } = x; public double Y { get; } = y; public double Rotation { get; } = rotation; } private const double LineInterpolationResolution = 2d; public static readonly DependencyProperty MinLineDistanceProperty = DependencyPropertyHelper.Register(nameof(MinLineDistance), 150d); public static readonly DependencyProperty StrokeThicknessProperty = DependencyPropertyHelper.Register(nameof(StrokeThickness), 0.5); /// /// Minimum graticule line distance in pixels. The default value is 150. /// public double MinLineDistance { get => (double)GetValue(MinLineDistanceProperty); set => SetValue(MinLineDistanceProperty, value); } public double StrokeThickness { get => (double)GetValue(StrokeThicknessProperty); set => SetValue(StrokeThicknessProperty, value); } public Brush Foreground { get => (Brush)GetValue(ForegroundProperty); set => SetValue(ForegroundProperty, value); } public FontFamily FontFamily { get => (FontFamily)GetValue(FontFamilyProperty); set => SetValue(FontFamilyProperty, value); } public double FontSize { get => (double)GetValue(FontSizeProperty); set => SetValue(FontSizeProperty, value); } private double lineDistance; private string labelFormat; 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(List