2024-05-19 23:23:27 +02:00
|
|
|
|
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
|
|
|
|
|
|
// Copyright © 2024 Clemens Fischer
|
|
|
|
|
|
// Licensed under the Microsoft Public License (Ms-PL)
|
|
|
|
|
|
|
2024-05-27 11:08:50 +02:00
|
|
|
|
global using Avalonia;
|
|
|
|
|
|
global using Avalonia.Animation;
|
|
|
|
|
|
global using Avalonia.Animation.Easings;
|
|
|
|
|
|
global using Avalonia.Controls;
|
|
|
|
|
|
global using Avalonia.Controls.Documents;
|
|
|
|
|
|
global using Avalonia.Controls.Shapes;
|
|
|
|
|
|
global using Avalonia.Data;
|
|
|
|
|
|
global using Avalonia.Input;
|
|
|
|
|
|
global using Avalonia.Interactivity;
|
|
|
|
|
|
global using Avalonia.Media;
|
|
|
|
|
|
global using Avalonia.Media.Imaging;
|
|
|
|
|
|
global using Avalonia.Platform;
|
|
|
|
|
|
global using Avalonia.Styling;
|
|
|
|
|
|
global using Avalonia.Threading;
|
|
|
|
|
|
global using DependencyObject = Avalonia.AvaloniaObject;
|
|
|
|
|
|
global using DependencyProperty = Avalonia.AvaloniaProperty;
|
|
|
|
|
|
global using FrameworkElement = Avalonia.Controls.Control;
|
|
|
|
|
|
global using HorizontalAlignment = Avalonia.Layout.HorizontalAlignment;
|
|
|
|
|
|
global using VerticalAlignment = Avalonia.Layout.VerticalAlignment;
|
2024-05-27 16:35:02 +02:00
|
|
|
|
global using Brush = Avalonia.Media.IBrush;
|
|
|
|
|
|
global using ImageSource = Avalonia.Media.IImage;
|
2024-05-27 11:08:50 +02:00
|
|
|
|
global using PathFigureCollection = Avalonia.Media.PathFigures;
|
|
|
|
|
|
global using PointCollection = System.Collections.Generic.List<Avalonia.Point>;
|
|
|
|
|
|
global using PropertyPath = System.String;
|
|
|
|
|
|
|
2024-05-19 23:23:27 +02:00
|
|
|
|
using System.Threading;
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
|
|
|
|
|
namespace MapControl
|
|
|
|
|
|
{
|
2024-05-20 23:24:34 +02:00
|
|
|
|
public partial class MapBase
|
2024-05-19 23:23:27 +02:00
|
|
|
|
{
|
2024-05-27 11:18:14 +02:00
|
|
|
|
public static readonly StyledProperty<Brush> ForegroundProperty =
|
|
|
|
|
|
DependencyPropertyHelper.AddOwner<MapBase, Brush>(TextElement.ForegroundProperty);
|
2024-05-24 15:14:05 +02:00
|
|
|
|
|
2024-05-21 14:48:03 +02:00
|
|
|
|
public static readonly StyledProperty<Easing> AnimationEasingProperty =
|
|
|
|
|
|
DependencyPropertyHelper.Register<MapBase, Easing>(nameof(AnimationEasing), new QuadraticEaseOut());
|
|
|
|
|
|
|
2024-05-21 13:51:10 +02:00
|
|
|
|
public static readonly StyledProperty<Location> CenterProperty =
|
2024-05-23 18:22:52 +02:00
|
|
|
|
DependencyPropertyHelper.Register<MapBase, Location>(nameof(Center), new Location(),
|
2024-05-21 13:51:10 +02:00
|
|
|
|
(map, oldValue, newValue) => map.CenterPropertyChanged(newValue),
|
2024-05-23 18:22:52 +02:00
|
|
|
|
(map, value) => map.CoerceCenterProperty(value),
|
|
|
|
|
|
true);
|
2024-05-21 13:51:10 +02:00
|
|
|
|
|
|
|
|
|
|
public static readonly StyledProperty<Location> TargetCenterProperty =
|
2024-05-23 18:22:52 +02:00
|
|
|
|
DependencyPropertyHelper.Register<MapBase, Location>(nameof(TargetCenter), new Location(),
|
2024-05-21 13:51:10 +02:00
|
|
|
|
async (map, oldValue, newValue) => await map.TargetCenterPropertyChanged(newValue),
|
2024-05-23 18:22:52 +02:00
|
|
|
|
(map, value) => map.CoerceCenterProperty(value),
|
|
|
|
|
|
true);
|
2024-05-21 13:51:10 +02:00
|
|
|
|
|
|
|
|
|
|
public static readonly StyledProperty<double> MinZoomLevelProperty =
|
2024-05-24 22:00:16 +02:00
|
|
|
|
DependencyPropertyHelper.Register<MapBase, double>(nameof(MinZoomLevel), 1d,
|
2024-05-21 13:51:10 +02:00
|
|
|
|
(map, oldValue, newValue) => map.MinZoomLevelPropertyChanged(newValue),
|
|
|
|
|
|
(map, value) => map.CoerceMinZoomLevelProperty(value));
|
|
|
|
|
|
|
|
|
|
|
|
public static readonly StyledProperty<double> MaxZoomLevelProperty =
|
2024-05-23 18:22:52 +02:00
|
|
|
|
DependencyPropertyHelper.Register<MapBase, double>(nameof(MaxZoomLevel), 20d,
|
2024-05-21 13:51:10 +02:00
|
|
|
|
(map, oldValue, newValue) => map.MaxZoomLevelPropertyChanged(newValue),
|
2024-05-22 09:42:21 +02:00
|
|
|
|
(map, value) => map.CoerceMaxZoomLevelProperty(value));
|
2024-05-21 13:51:10 +02:00
|
|
|
|
|
|
|
|
|
|
public static readonly StyledProperty<double> ZoomLevelProperty =
|
2024-05-24 22:00:16 +02:00
|
|
|
|
DependencyPropertyHelper.Register<MapBase, double>(nameof(ZoomLevel), 1d,
|
2024-05-21 13:51:10 +02:00
|
|
|
|
(map, oldValue, newValue) => map.ZoomLevelPropertyChanged(newValue),
|
2024-05-23 18:22:52 +02:00
|
|
|
|
(map, value) => map.CoerceZoomLevelProperty(value),
|
|
|
|
|
|
true);
|
2024-05-21 13:51:10 +02:00
|
|
|
|
|
|
|
|
|
|
public static readonly StyledProperty<double> TargetZoomLevelProperty =
|
2024-05-24 22:00:16 +02:00
|
|
|
|
DependencyPropertyHelper.Register<MapBase, double>(nameof(TargetZoomLevel), 1d,
|
2024-05-21 13:51:10 +02:00
|
|
|
|
async (map, oldValue, newValue) => await map.TargetZoomLevelPropertyChanged(newValue),
|
2024-05-23 18:22:52 +02:00
|
|
|
|
(map, value) => map.CoerceZoomLevelProperty(value),
|
|
|
|
|
|
true);
|
2024-05-21 13:51:10 +02:00
|
|
|
|
|
|
|
|
|
|
public static readonly StyledProperty<double> HeadingProperty =
|
2024-05-23 18:22:52 +02:00
|
|
|
|
DependencyPropertyHelper.Register<MapBase, double>(nameof(Heading), 0d,
|
2024-05-21 13:51:10 +02:00
|
|
|
|
(map, oldValue, newValue) => map.HeadingPropertyChanged(newValue),
|
2024-05-23 18:22:52 +02:00
|
|
|
|
(map, value) => map.CoerceHeadingProperty(value),
|
|
|
|
|
|
true);
|
2024-05-21 13:51:10 +02:00
|
|
|
|
|
|
|
|
|
|
public static readonly StyledProperty<double> TargetHeadingProperty =
|
2024-05-23 18:22:52 +02:00
|
|
|
|
DependencyPropertyHelper.Register<MapBase, double>(nameof(TargetHeading), 0d,
|
2024-05-21 13:51:10 +02:00
|
|
|
|
async (map, oldValue, newValue) => await map.TargetHeadingPropertyChanged(newValue),
|
2024-05-23 18:22:52 +02:00
|
|
|
|
(map, value) => map.CoerceHeadingProperty(value),
|
|
|
|
|
|
true);
|
2024-05-21 13:51:10 +02:00
|
|
|
|
|
|
|
|
|
|
public static readonly DirectProperty<MapBase, double> ViewScaleProperty =
|
2024-05-22 10:16:22 +02:00
|
|
|
|
AvaloniaProperty.RegisterDirect<MapBase, double>(nameof(ViewScale), map => map.ViewTransform.Scale);
|
2024-05-19 23:23:27 +02:00
|
|
|
|
|
2024-05-20 23:24:34 +02:00
|
|
|
|
private CancellationTokenSource centerCts;
|
|
|
|
|
|
private CancellationTokenSource zoomLevelCts;
|
|
|
|
|
|
private CancellationTokenSource headingCts;
|
|
|
|
|
|
private Animation centerAnimation;
|
|
|
|
|
|
private Animation zoomLevelAnimation;
|
|
|
|
|
|
private Animation headingAnimation;
|
2024-05-19 23:23:27 +02:00
|
|
|
|
|
|
|
|
|
|
static MapBase()
|
|
|
|
|
|
{
|
2024-05-24 09:13:41 +02:00
|
|
|
|
BackgroundProperty.OverrideDefaultValue(typeof(MapBase), Brushes.White);
|
2024-05-20 23:24:34 +02:00
|
|
|
|
ClipToBoundsProperty.OverrideDefaultValue(typeof(MapBase), true);
|
2024-05-19 23:23:27 +02:00
|
|
|
|
|
2024-05-21 13:51:10 +02:00
|
|
|
|
Animation.RegisterCustomAnimator<Location, LocationAnimator>();
|
2024-05-19 23:23:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-20 23:24:34 +02:00
|
|
|
|
internal Size RenderSize => Bounds.Size;
|
2024-05-19 23:23:27 +02:00
|
|
|
|
|
2024-05-20 23:24:34 +02:00
|
|
|
|
protected override void OnSizeChanged(SizeChangedEventArgs e)
|
2024-05-19 23:23:27 +02:00
|
|
|
|
{
|
2024-05-20 23:24:34 +02:00
|
|
|
|
base.OnSizeChanged(e);
|
2024-05-19 23:23:27 +02:00
|
|
|
|
|
2024-05-20 23:24:34 +02:00
|
|
|
|
ResetTransformCenter();
|
|
|
|
|
|
UpdateTransform();
|
2024-05-19 23:23:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-21 14:48:03 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets or sets the Easing of the Center, ZoomLevel and Heading animations.
|
|
|
|
|
|
/// The default value is a QuadraticEaseOut.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public Easing AnimationEasing
|
|
|
|
|
|
{
|
|
|
|
|
|
get => GetValue(AnimationEasingProperty);
|
|
|
|
|
|
set => SetValue(AnimationEasingProperty, value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-19 23:23:27 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets the scaling factor from projected map coordinates to view coordinates,
|
|
|
|
|
|
/// as pixels per meter.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public double ViewScale
|
|
|
|
|
|
{
|
2024-05-25 18:58:51 +02:00
|
|
|
|
get => GetValue(ViewScaleProperty);
|
2024-05-22 09:42:21 +02:00
|
|
|
|
private set => RaisePropertyChanged(ViewScaleProperty, double.NaN, value);
|
2024-05-19 23:23:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-20 23:24:34 +02:00
|
|
|
|
private void CenterPropertyChanged(Location center)
|
2024-05-19 23:23:27 +02:00
|
|
|
|
{
|
2024-05-20 23:24:34 +02:00
|
|
|
|
if (!internalPropertyChange)
|
2024-05-19 23:23:27 +02:00
|
|
|
|
{
|
|
|
|
|
|
UpdateTransform();
|
2024-05-21 00:00:30 +02:00
|
|
|
|
|
|
|
|
|
|
if (centerAnimation == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
SetValueInternal(TargetCenterProperty, center);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task TargetCenterPropertyChanged(Location targetCenter)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!internalPropertyChange && !targetCenter.Equals(Center))
|
|
|
|
|
|
{
|
2024-05-21 17:39:03 +02:00
|
|
|
|
ResetTransformCenter();
|
|
|
|
|
|
|
2024-05-21 00:00:30 +02:00
|
|
|
|
centerCts?.Cancel();
|
|
|
|
|
|
|
|
|
|
|
|
centerAnimation = new Animation
|
|
|
|
|
|
{
|
|
|
|
|
|
FillMode = FillMode.Forward,
|
|
|
|
|
|
Duration = AnimationDuration,
|
2024-05-21 14:48:03 +02:00
|
|
|
|
Easing = AnimationEasing,
|
2024-05-21 00:00:30 +02:00
|
|
|
|
Children =
|
|
|
|
|
|
{
|
|
|
|
|
|
new KeyFrame
|
|
|
|
|
|
{
|
|
|
|
|
|
KeyTime = AnimationDuration,
|
2024-05-21 17:39:03 +02:00
|
|
|
|
Setters = { new Setter(CenterProperty, new Location(targetCenter.Latitude, CoerceLongitude(targetCenter.Longitude))) }
|
2024-05-21 00:00:30 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
centerCts = new CancellationTokenSource();
|
|
|
|
|
|
|
|
|
|
|
|
await centerAnimation.RunAsync(this, centerCts.Token);
|
|
|
|
|
|
|
2024-05-21 17:39:03 +02:00
|
|
|
|
if (!centerCts.IsCancellationRequested)
|
|
|
|
|
|
{
|
|
|
|
|
|
UpdateTransform();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-21 00:00:30 +02:00
|
|
|
|
centerCts.Dispose();
|
|
|
|
|
|
centerCts = null;
|
|
|
|
|
|
centerAnimation = null;
|
2024-05-19 23:23:27 +02:00
|
|
|
|
}
|
2024-05-20 23:24:34 +02:00
|
|
|
|
}
|
2024-05-19 23:23:27 +02:00
|
|
|
|
|
2024-05-21 13:51:10 +02:00
|
|
|
|
private void MinZoomLevelPropertyChanged(double minZoomLevel)
|
2024-05-19 23:23:27 +02:00
|
|
|
|
{
|
2024-05-21 13:51:10 +02:00
|
|
|
|
if (ZoomLevel < minZoomLevel)
|
|
|
|
|
|
{
|
|
|
|
|
|
ZoomLevel = minZoomLevel;
|
|
|
|
|
|
}
|
2024-05-20 23:24:34 +02:00
|
|
|
|
}
|
2024-05-19 23:23:27 +02:00
|
|
|
|
|
2024-05-21 13:51:10 +02:00
|
|
|
|
private void MaxZoomLevelPropertyChanged(double maxZoomLevel)
|
2024-05-20 23:24:34 +02:00
|
|
|
|
{
|
2024-05-21 13:51:10 +02:00
|
|
|
|
if (ZoomLevel > maxZoomLevel)
|
|
|
|
|
|
{
|
|
|
|
|
|
ZoomLevel = maxZoomLevel;
|
|
|
|
|
|
}
|
2024-05-20 23:24:34 +02:00
|
|
|
|
}
|
2024-05-19 23:23:27 +02:00
|
|
|
|
|
2024-05-20 23:24:34 +02:00
|
|
|
|
private void ZoomLevelPropertyChanged(double zoomLevel)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!internalPropertyChange)
|
|
|
|
|
|
{
|
|
|
|
|
|
UpdateTransform();
|
2024-05-19 23:23:27 +02:00
|
|
|
|
|
2024-05-20 23:24:34 +02:00
|
|
|
|
if (zoomLevelAnimation == null)
|
2024-05-19 23:23:27 +02:00
|
|
|
|
{
|
2024-05-20 23:24:34 +02:00
|
|
|
|
SetValueInternal(TargetZoomLevelProperty, zoomLevel);
|
2024-05-19 23:23:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-20 23:24:34 +02:00
|
|
|
|
private async Task TargetZoomLevelPropertyChanged(double targetZoomLevel)
|
2024-05-19 23:23:27 +02:00
|
|
|
|
{
|
2024-05-20 23:24:34 +02:00
|
|
|
|
if (!internalPropertyChange && targetZoomLevel != ZoomLevel)
|
2024-05-19 23:23:27 +02:00
|
|
|
|
{
|
2024-05-20 23:24:34 +02:00
|
|
|
|
zoomLevelCts?.Cancel();
|
2024-05-19 23:23:27 +02:00
|
|
|
|
|
2024-05-20 23:24:34 +02:00
|
|
|
|
zoomLevelAnimation = new Animation
|
2024-05-19 23:23:27 +02:00
|
|
|
|
{
|
|
|
|
|
|
FillMode = FillMode.Forward,
|
|
|
|
|
|
Duration = AnimationDuration,
|
2024-05-21 14:48:03 +02:00
|
|
|
|
Easing = AnimationEasing,
|
2024-05-19 23:23:27 +02:00
|
|
|
|
Children =
|
|
|
|
|
|
{
|
|
|
|
|
|
new KeyFrame
|
|
|
|
|
|
{
|
|
|
|
|
|
KeyTime = AnimationDuration,
|
2024-05-20 23:24:34 +02:00
|
|
|
|
Setters = { new Setter(ZoomLevelProperty, targetZoomLevel) }
|
2024-05-19 23:23:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2024-05-20 23:24:34 +02:00
|
|
|
|
zoomLevelCts = new CancellationTokenSource();
|
2024-05-19 23:23:27 +02:00
|
|
|
|
|
2024-05-20 23:24:34 +02:00
|
|
|
|
await zoomLevelAnimation.RunAsync(this, zoomLevelCts.Token);
|
2024-05-19 23:23:27 +02:00
|
|
|
|
|
2024-05-21 13:51:10 +02:00
|
|
|
|
if (!zoomLevelCts.IsCancellationRequested)
|
|
|
|
|
|
{
|
2024-05-21 17:39:03 +02:00
|
|
|
|
UpdateTransform(true); // reset transform center
|
2024-05-21 13:51:10 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-20 23:24:34 +02:00
|
|
|
|
zoomLevelCts.Dispose();
|
|
|
|
|
|
zoomLevelCts = null;
|
|
|
|
|
|
zoomLevelAnimation = null;
|
|
|
|
|
|
}
|
2024-05-19 23:23:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-20 23:24:34 +02:00
|
|
|
|
private void HeadingPropertyChanged(double heading)
|
2024-05-19 23:23:27 +02:00
|
|
|
|
{
|
2024-05-20 23:24:34 +02:00
|
|
|
|
if (!internalPropertyChange)
|
2024-05-19 23:23:27 +02:00
|
|
|
|
{
|
2024-05-20 23:24:34 +02:00
|
|
|
|
UpdateTransform();
|
2024-05-19 23:23:27 +02:00
|
|
|
|
|
2024-05-20 23:24:34 +02:00
|
|
|
|
if (headingAnimation == null)
|
2024-05-19 23:23:27 +02:00
|
|
|
|
{
|
2024-05-20 23:24:34 +02:00
|
|
|
|
SetValueInternal(TargetHeadingProperty, heading);
|
2024-05-19 23:23:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-20 23:24:34 +02:00
|
|
|
|
private async Task TargetHeadingPropertyChanged(double targetHeading)
|
2024-05-19 23:23:27 +02:00
|
|
|
|
{
|
2024-05-20 23:24:34 +02:00
|
|
|
|
if (!internalPropertyChange && targetHeading != Heading)
|
2024-05-19 23:23:27 +02:00
|
|
|
|
{
|
2024-05-20 23:24:34 +02:00
|
|
|
|
var delta = targetHeading - Heading;
|
2024-05-19 23:23:27 +02:00
|
|
|
|
|
2024-05-20 23:24:34 +02:00
|
|
|
|
if (delta > 180d)
|
2024-05-19 23:23:27 +02:00
|
|
|
|
{
|
2024-05-20 23:24:34 +02:00
|
|
|
|
delta -= 360d;
|
2024-05-19 23:23:27 +02:00
|
|
|
|
}
|
2024-05-20 23:24:34 +02:00
|
|
|
|
else if (delta < -180d)
|
2024-05-19 23:23:27 +02:00
|
|
|
|
{
|
2024-05-20 23:24:34 +02:00
|
|
|
|
delta += 360d;
|
2024-05-19 23:23:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-20 23:24:34 +02:00
|
|
|
|
targetHeading = Heading + delta;
|
2024-05-19 23:23:27 +02:00
|
|
|
|
|
2024-05-20 23:24:34 +02:00
|
|
|
|
headingCts?.Cancel();
|
2024-05-19 23:23:27 +02:00
|
|
|
|
|
2024-05-20 23:24:34 +02:00
|
|
|
|
headingAnimation = new Animation
|
2024-05-19 23:23:27 +02:00
|
|
|
|
{
|
2024-05-20 23:24:34 +02:00
|
|
|
|
FillMode = FillMode.Forward,
|
|
|
|
|
|
Duration = AnimationDuration,
|
2024-05-21 14:48:03 +02:00
|
|
|
|
Easing = AnimationEasing,
|
2024-05-20 23:24:34 +02:00
|
|
|
|
Children =
|
|
|
|
|
|
{
|
|
|
|
|
|
new KeyFrame
|
|
|
|
|
|
{
|
|
|
|
|
|
KeyTime = AnimationDuration,
|
|
|
|
|
|
Setters = { new Setter(HeadingProperty, targetHeading) }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2024-05-19 23:23:27 +02:00
|
|
|
|
|
2024-05-20 23:24:34 +02:00
|
|
|
|
headingCts = new CancellationTokenSource();
|
2024-05-19 23:23:27 +02:00
|
|
|
|
|
2024-05-20 23:24:34 +02:00
|
|
|
|
await headingAnimation.RunAsync(this, headingCts.Token);
|
2024-05-19 23:23:27 +02:00
|
|
|
|
|
2024-05-21 17:39:03 +02:00
|
|
|
|
if (!headingCts.IsCancellationRequested)
|
|
|
|
|
|
{
|
|
|
|
|
|
UpdateTransform();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-20 23:24:34 +02:00
|
|
|
|
headingCts.Dispose();
|
|
|
|
|
|
headingCts = null;
|
|
|
|
|
|
headingAnimation = null;
|
2024-05-19 23:23:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|