mirror of
https://github.com/ClemensFischer/XAML-Map-Control.git
synced 2025-12-06 07:12:04 +01:00
354 lines
11 KiB
C#
354 lines
11 KiB
C#
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
|
|
// Copyright © 2024 Clemens Fischer
|
|
// Licensed under the Microsoft Public License (Ms-PL)
|
|
|
|
using System;
|
|
#if WINUI
|
|
using Microsoft.UI.Xaml;
|
|
using Microsoft.UI.Xaml.Media.Animation;
|
|
#elif UWP
|
|
using Windows.UI.Xaml;
|
|
using Windows.UI.Xaml.Media.Animation;
|
|
#else
|
|
using System.Windows;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Animation;
|
|
#endif
|
|
|
|
namespace MapControl
|
|
{
|
|
public partial class MapBase
|
|
{
|
|
public static readonly DependencyProperty MinZoomLevelProperty = DependencyProperty.Register(
|
|
nameof(MinZoomLevel), typeof(double), typeof(MapBase),
|
|
new PropertyMetadata(1d, (o, e) => ((MapBase)o).MinZoomLevelPropertyChanged((double)e.NewValue)));
|
|
|
|
public static readonly DependencyProperty MaxZoomLevelProperty = DependencyProperty.Register(
|
|
nameof(MaxZoomLevel), typeof(double), typeof(MapBase),
|
|
new PropertyMetadata(20d, (o, e) => ((MapBase)o).MaxZoomLevelPropertyChanged((double)e.NewValue)));
|
|
|
|
public static readonly DependencyProperty AnimationEasingFunctionProperty = DependencyProperty.Register(
|
|
nameof(AnimationEasingFunction), typeof(EasingFunctionBase), typeof(MapBase),
|
|
new PropertyMetadata(new QuadraticEase { EasingMode = EasingMode.EaseOut }));
|
|
|
|
private PointAnimation centerAnimation;
|
|
private DoubleAnimation zoomLevelAnimation;
|
|
private DoubleAnimation headingAnimation;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the EasingFunction of the Center, ZoomLevel and Heading animations.
|
|
/// The default value is a QuadraticEase with EasingMode.EaseOut.
|
|
/// </summary>
|
|
public EasingFunctionBase AnimationEasingFunction
|
|
{
|
|
get => (EasingFunctionBase)GetValue(AnimationEasingFunctionProperty);
|
|
set => SetValue(AnimationEasingFunctionProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the scaling factor from projected map coordinates to view coordinates,
|
|
/// as pixels per meter.
|
|
/// </summary>
|
|
public double ViewScale => (double)GetValue(ViewScaleProperty);
|
|
|
|
/// <summary>
|
|
/// Gets a transform Matrix for scaling and rotating objects that are anchored
|
|
/// at a Location from map coordinates (i.e. meters) to view coordinates.
|
|
/// </summary>
|
|
public Matrix GetMapTransform(Location location)
|
|
{
|
|
var scale = GetScale(location);
|
|
|
|
var transform = new Matrix(scale.X, 0d, 0d, scale.Y, 0d, 0d);
|
|
transform.Rotate(ViewTransform.Rotation);
|
|
|
|
return transform;
|
|
}
|
|
|
|
private void MapProjectionPropertyChanged(MapProjection projection)
|
|
{
|
|
maxLatitude = 90d;
|
|
|
|
if (projection.Type <= MapProjectionType.NormalCylindrical)
|
|
{
|
|
var maxLocation = projection.MapToLocation(new Point(0d, 180d * MapProjection.Wgs84MeterPerDegree));
|
|
|
|
if (maxLocation != null && maxLocation.Latitude < 90d)
|
|
{
|
|
maxLatitude = maxLocation.Latitude;
|
|
|
|
CoerceCenterProperty(CenterProperty, Center);
|
|
}
|
|
}
|
|
|
|
ResetTransformCenter();
|
|
UpdateTransform(false, true);
|
|
}
|
|
|
|
private Location CoerceCenterProperty(DependencyProperty property, Location center)
|
|
{
|
|
var c = center;
|
|
|
|
if (center == null)
|
|
{
|
|
center = new Location();
|
|
}
|
|
else if (
|
|
center.Latitude < -maxLatitude || center.Latitude > maxLatitude ||
|
|
center.Longitude < -180d || center.Longitude > 180d)
|
|
{
|
|
center = new Location(
|
|
Math.Min(Math.Max(center.Latitude, -maxLatitude), maxLatitude),
|
|
Location.NormalizeLongitude(center.Longitude));
|
|
}
|
|
|
|
if (center != c)
|
|
{
|
|
SetValueInternal(property, center);
|
|
}
|
|
|
|
return center;
|
|
}
|
|
|
|
private void CenterPropertyChanged(Location center)
|
|
{
|
|
if (!internalPropertyChange)
|
|
{
|
|
center = CoerceCenterProperty(CenterProperty, center);
|
|
|
|
UpdateTransform();
|
|
|
|
if (centerAnimation == null)
|
|
{
|
|
SetValueInternal(TargetCenterProperty, center);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void TargetCenterPropertyChanged(Location targetCenter)
|
|
{
|
|
if (!internalPropertyChange)
|
|
{
|
|
targetCenter = CoerceCenterProperty(TargetCenterProperty, targetCenter);
|
|
|
|
if (!targetCenter.Equals(Center))
|
|
{
|
|
if (centerAnimation != null)
|
|
{
|
|
centerAnimation.Completed -= CenterAnimationCompleted;
|
|
}
|
|
|
|
centerAnimation = new PointAnimation
|
|
{
|
|
From = new Point(Center.Longitude, Center.Latitude),
|
|
To = new Point(ConstrainedLongitude(targetCenter.Longitude), targetCenter.Latitude),
|
|
Duration = AnimationDuration,
|
|
EasingFunction = AnimationEasingFunction
|
|
};
|
|
|
|
centerAnimation.Completed += CenterAnimationCompleted;
|
|
|
|
this.BeginAnimation(CenterPointProperty, centerAnimation);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CenterAnimationCompleted(object sender, object e)
|
|
{
|
|
if (centerAnimation != null)
|
|
{
|
|
centerAnimation.Completed -= CenterAnimationCompleted;
|
|
centerAnimation = null;
|
|
|
|
this.BeginAnimation(CenterPointProperty, null);
|
|
}
|
|
}
|
|
|
|
private void CenterPointPropertyChanged(Location center)
|
|
{
|
|
if (centerAnimation != null)
|
|
{
|
|
SetValueInternal(CenterProperty, center);
|
|
UpdateTransform();
|
|
}
|
|
}
|
|
|
|
private void MinZoomLevelPropertyChanged(double minZoomLevel)
|
|
{
|
|
if (minZoomLevel < 0d || minZoomLevel > MaxZoomLevel)
|
|
{
|
|
minZoomLevel = Math.Min(Math.Max(minZoomLevel, 0d), MaxZoomLevel);
|
|
|
|
SetValueInternal(MinZoomLevelProperty, minZoomLevel);
|
|
}
|
|
|
|
if (ZoomLevel < minZoomLevel)
|
|
{
|
|
ZoomLevel = minZoomLevel;
|
|
}
|
|
}
|
|
|
|
private void MaxZoomLevelPropertyChanged(double maxZoomLevel)
|
|
{
|
|
if (maxZoomLevel < MinZoomLevel)
|
|
{
|
|
maxZoomLevel = MinZoomLevel;
|
|
|
|
SetValueInternal(MaxZoomLevelProperty, maxZoomLevel);
|
|
}
|
|
|
|
if (ZoomLevel > maxZoomLevel)
|
|
{
|
|
ZoomLevel = maxZoomLevel;
|
|
}
|
|
}
|
|
|
|
private double CoerceZoomLevelProperty(DependencyProperty property, double zoomLevel)
|
|
{
|
|
if (zoomLevel < MinZoomLevel || zoomLevel > MaxZoomLevel)
|
|
{
|
|
zoomLevel = Math.Min(Math.Max(zoomLevel, MinZoomLevel), MaxZoomLevel);
|
|
|
|
SetValueInternal(property, zoomLevel);
|
|
}
|
|
|
|
return zoomLevel;
|
|
}
|
|
|
|
private void ZoomLevelPropertyChanged(double zoomLevel)
|
|
{
|
|
if (!internalPropertyChange)
|
|
{
|
|
zoomLevel = CoerceZoomLevelProperty(ZoomLevelProperty, zoomLevel);
|
|
|
|
UpdateTransform();
|
|
|
|
if (zoomLevelAnimation == null)
|
|
{
|
|
SetValueInternal(TargetZoomLevelProperty, zoomLevel);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void TargetZoomLevelPropertyChanged(double targetZoomLevel)
|
|
{
|
|
if (!internalPropertyChange)
|
|
{
|
|
targetZoomLevel = CoerceZoomLevelProperty(TargetZoomLevelProperty, targetZoomLevel);
|
|
|
|
if (targetZoomLevel != ZoomLevel)
|
|
{
|
|
if (zoomLevelAnimation != null)
|
|
{
|
|
zoomLevelAnimation.Completed -= ZoomLevelAnimationCompleted;
|
|
}
|
|
|
|
zoomLevelAnimation = new DoubleAnimation
|
|
{
|
|
To = targetZoomLevel,
|
|
Duration = AnimationDuration,
|
|
EasingFunction = AnimationEasingFunction
|
|
};
|
|
|
|
zoomLevelAnimation.Completed += ZoomLevelAnimationCompleted;
|
|
|
|
this.BeginAnimation(ZoomLevelProperty, zoomLevelAnimation);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ZoomLevelAnimationCompleted(object sender, object e)
|
|
{
|
|
if (zoomLevelAnimation != null)
|
|
{
|
|
SetValueInternal(ZoomLevelProperty, TargetZoomLevel);
|
|
UpdateTransform(true);
|
|
|
|
zoomLevelAnimation.Completed -= ZoomLevelAnimationCompleted;
|
|
zoomLevelAnimation = null;
|
|
|
|
this.BeginAnimation(ZoomLevelProperty, null);
|
|
}
|
|
}
|
|
|
|
private double CoerceHeadingProperty(DependencyProperty property, double heading)
|
|
{
|
|
if (heading < 0d || heading > 360d)
|
|
{
|
|
heading = ((heading % 360d) + 360d) % 360d;
|
|
|
|
SetValueInternal(property, heading);
|
|
}
|
|
|
|
return heading;
|
|
}
|
|
|
|
private void HeadingPropertyChanged(double heading)
|
|
{
|
|
if (!internalPropertyChange)
|
|
{
|
|
heading = CoerceHeadingProperty(HeadingProperty, heading);
|
|
|
|
UpdateTransform();
|
|
|
|
if (headingAnimation == null)
|
|
{
|
|
SetValueInternal(TargetHeadingProperty, heading);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void TargetHeadingPropertyChanged(double targetHeading)
|
|
{
|
|
if (!internalPropertyChange)
|
|
{
|
|
targetHeading = CoerceHeadingProperty(TargetHeadingProperty, targetHeading);
|
|
|
|
if (targetHeading != Heading)
|
|
{
|
|
var delta = targetHeading - Heading;
|
|
|
|
if (delta > 180d)
|
|
{
|
|
delta -= 360d;
|
|
}
|
|
else if (delta < -180d)
|
|
{
|
|
delta += 360d;
|
|
}
|
|
|
|
if (headingAnimation != null)
|
|
{
|
|
headingAnimation.Completed -= HeadingAnimationCompleted;
|
|
}
|
|
|
|
headingAnimation = new DoubleAnimation
|
|
{
|
|
By = delta,
|
|
Duration = AnimationDuration,
|
|
EasingFunction = AnimationEasingFunction
|
|
};
|
|
|
|
headingAnimation.Completed += HeadingAnimationCompleted;
|
|
|
|
this.BeginAnimation(HeadingProperty, headingAnimation);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void HeadingAnimationCompleted(object sender, object e)
|
|
{
|
|
if (headingAnimation != null)
|
|
{
|
|
SetValueInternal(HeadingProperty, TargetHeading);
|
|
UpdateTransform();
|
|
|
|
headingAnimation.Completed -= HeadingAnimationCompleted;
|
|
headingAnimation = null;
|
|
|
|
this.BeginAnimation(HeadingProperty, null);
|
|
}
|
|
}
|
|
}
|
|
}
|