mirror of
https://github.com/ClemensFischer/XAML-Map-Control.git
synced 2026-04-05 14:37:01 +00:00
Added DependencyPropertyHelper
This commit is contained in:
parent
422f1dce0d
commit
3706709cfc
22 changed files with 967 additions and 1879 deletions
71
MapControl/Shared/DependencyPropertyHelper.cs
Normal file
71
MapControl/Shared/DependencyPropertyHelper.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// 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;
|
||||
#elif UWP
|
||||
using Windows.UI.Xaml;
|
||||
#else
|
||||
using System.Windows;
|
||||
#endif
|
||||
|
||||
namespace MapControl
|
||||
{
|
||||
public static class DependencyPropertyHelper
|
||||
{
|
||||
public static DependencyProperty Register<TOwner, TValue>(
|
||||
string name,
|
||||
TValue defaultValue = default,
|
||||
bool bindTwoWayByDefault = false,
|
||||
Action<TOwner, TValue, TValue> propertyChanged = null)
|
||||
where TOwner : DependencyObject
|
||||
{
|
||||
#if WINUI || UWP
|
||||
var metadata = propertyChanged != null
|
||||
? new PropertyMetadata(defaultValue, (o, e) => propertyChanged((TOwner)o, (TValue)e.OldValue, (TValue)e.NewValue))
|
||||
: new PropertyMetadata(defaultValue);
|
||||
|
||||
#else
|
||||
var metadata = new FrameworkPropertyMetadata
|
||||
{
|
||||
DefaultValue = defaultValue,
|
||||
BindsTwoWayByDefault = bindTwoWayByDefault
|
||||
};
|
||||
|
||||
if (propertyChanged != null)
|
||||
{
|
||||
metadata.PropertyChangedCallback = (o, e) => propertyChanged((TOwner)o, (TValue)e.OldValue, (TValue)e.NewValue);
|
||||
}
|
||||
#endif
|
||||
return DependencyProperty.Register(name, typeof(TValue), typeof(TOwner), metadata);
|
||||
}
|
||||
|
||||
public static DependencyProperty RegisterAttached<TOwner, TValue>(
|
||||
string name,
|
||||
TValue defaultValue = default,
|
||||
bool inherits = false,
|
||||
Action<FrameworkElement, TValue, TValue> propertyChanged = null)
|
||||
where TOwner : DependencyObject
|
||||
{
|
||||
#if WINUI || UWP
|
||||
var metadata = propertyChanged != null
|
||||
? new PropertyMetadata(defaultValue, (o, e) => propertyChanged((FrameworkElement)o, (TValue)e.OldValue, (TValue)e.NewValue))
|
||||
: new PropertyMetadata(defaultValue);
|
||||
#else
|
||||
var metadata = new FrameworkPropertyMetadata
|
||||
{
|
||||
DefaultValue = defaultValue,
|
||||
Inherits = inherits
|
||||
};
|
||||
|
||||
if (propertyChanged != null)
|
||||
{
|
||||
metadata.PropertyChangedCallback = (o, e) => propertyChanged((FrameworkElement)o, (TValue)e.OldValue, (TValue)e.NewValue);
|
||||
}
|
||||
#endif
|
||||
return DependencyProperty.RegisterAttached(name, typeof(TValue), typeof(TOwner), metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
// Copyright © 2024 Clemens Fischer
|
||||
// Licensed under the Microsoft Public License (Ms-PL)
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace MapControl
|
||||
|
|
@ -12,7 +11,7 @@ namespace MapControl
|
|||
public static string GetFullPath(string path)
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
return Path.GetFullPath(path, AppDomain.CurrentDomain.BaseDirectory);
|
||||
return Path.GetFullPath(path, System.AppDomain.CurrentDomain.BaseDirectory);
|
||||
#else
|
||||
return Path.GetFullPath(path);
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -4,14 +4,10 @@
|
|||
|
||||
using System;
|
||||
#if WINUI
|
||||
using Windows.Foundation;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
#elif UWP
|
||||
using Windows.Foundation;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Media;
|
||||
using Windows.UI.Xaml.Media.Animation;
|
||||
#else
|
||||
using System.Windows;
|
||||
|
|
@ -21,35 +17,8 @@ using System.Windows.Media.Animation;
|
|||
|
||||
namespace MapControl
|
||||
{
|
||||
public interface IMapLayer : IMapElement
|
||||
public partial class MapBase
|
||||
{
|
||||
Brush MapBackground { get; }
|
||||
Brush MapForeground { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The map control. Displays map content provided by one or more tile or image layers,
|
||||
/// such as MapTileLayerBase or MapImageLayer instances.
|
||||
/// The visible map area is defined by the Center and ZoomLevel properties.
|
||||
/// The map can be rotated by an angle that is given by the Heading property.
|
||||
/// MapBase can contain map overlay child elements like other MapPanels or MapItemsControls.
|
||||
/// </summary>
|
||||
public partial class MapBase : MapPanel
|
||||
{
|
||||
public static TimeSpan ImageFadeDuration { get; set; } = TimeSpan.FromSeconds(0.1);
|
||||
|
||||
public static readonly DependencyProperty MapLayerProperty = DependencyProperty.Register(
|
||||
nameof(MapLayer), typeof(UIElement), typeof(MapBase),
|
||||
new PropertyMetadata(null, (o, e) => ((MapBase)o).MapLayerPropertyChanged((UIElement)e.OldValue, (UIElement)e.NewValue)));
|
||||
|
||||
public static readonly DependencyProperty MapProjectionProperty = DependencyProperty.Register(
|
||||
nameof(MapProjection), typeof(MapProjection), typeof(MapBase),
|
||||
new PropertyMetadata(new WebMercatorProjection(), (o, e) => ((MapBase)o).MapProjectionPropertyChanged((MapProjection)e.NewValue)));
|
||||
|
||||
public static readonly DependencyProperty ProjectionCenterProperty = DependencyProperty.Register(
|
||||
nameof(ProjectionCenter), typeof(Location), typeof(MapBase),
|
||||
new PropertyMetadata(null, (o, e) => ((MapBase)o).ProjectionCenterPropertyChanged()));
|
||||
|
||||
public static readonly DependencyProperty MinZoomLevelProperty = DependencyProperty.Register(
|
||||
nameof(MinZoomLevel), typeof(double), typeof(MapBase),
|
||||
new PropertyMetadata(1d, (o, e) => ((MapBase)o).MinZoomLevelPropertyChanged((double)e.NewValue)));
|
||||
|
|
@ -58,10 +27,6 @@ namespace MapControl
|
|||
nameof(MaxZoomLevel), typeof(double), typeof(MapBase),
|
||||
new PropertyMetadata(20d, (o, e) => ((MapBase)o).MaxZoomLevelPropertyChanged((double)e.NewValue)));
|
||||
|
||||
public static readonly DependencyProperty AnimationDurationProperty = DependencyProperty.Register(
|
||||
nameof(AnimationDuration), typeof(TimeSpan), typeof(MapBase),
|
||||
new PropertyMetadata(TimeSpan.FromSeconds(0.3)));
|
||||
|
||||
public static readonly DependencyProperty AnimationEasingFunctionProperty = DependencyProperty.Register(
|
||||
nameof(AnimationEasingFunction), typeof(EasingFunctionBase), typeof(MapBase),
|
||||
new PropertyMetadata(new QuadraticEase { EasingMode = EasingMode.EaseOut }));
|
||||
|
|
@ -69,139 +34,6 @@ namespace MapControl
|
|||
private PointAnimation centerAnimation;
|
||||
private DoubleAnimation zoomLevelAnimation;
|
||||
private DoubleAnimation headingAnimation;
|
||||
private Location transformCenter;
|
||||
private Point viewCenter;
|
||||
private double centerLongitude;
|
||||
private double maxLatitude = 90d;
|
||||
private bool internalPropertyChange;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the current map viewport has changed.
|
||||
/// </summary>
|
||||
public event EventHandler<ViewportChangedEventArgs> ViewportChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the map foreground Brush.
|
||||
/// </summary>
|
||||
public Brush Foreground
|
||||
{
|
||||
get => (Brush)GetValue(ForegroundProperty);
|
||||
set => SetValue(ForegroundProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the base map layer, which is added as first element to the Children collection.
|
||||
/// If the layer implements IMapLayer (like MapTileLayer or MapImageLayer), its (non-null) MapBackground
|
||||
/// and MapForeground property values are used for the MapBase Background and Foreground properties.
|
||||
/// </summary>
|
||||
public UIElement MapLayer
|
||||
{
|
||||
get => (UIElement)GetValue(MapLayerProperty);
|
||||
set => SetValue(MapLayerProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the MapProjection used by the map control.
|
||||
/// </summary>
|
||||
public MapProjection MapProjection
|
||||
{
|
||||
get => (MapProjection)GetValue(MapProjectionProperty);
|
||||
set => SetValue(MapProjectionProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional center (reference point) for azimuthal projections.
|
||||
/// If ProjectionCenter is null, the Center property value will be used instead.
|
||||
/// </summary>
|
||||
public Location ProjectionCenter
|
||||
{
|
||||
get => (Location)GetValue(ProjectionCenterProperty);
|
||||
set => SetValue(ProjectionCenterProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the location of the center point of the map.
|
||||
/// </summary>
|
||||
public Location Center
|
||||
{
|
||||
get => (Location)GetValue(CenterProperty);
|
||||
set => SetValue(CenterProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the target value of a Center animation.
|
||||
/// </summary>
|
||||
public Location TargetCenter
|
||||
{
|
||||
get => (Location)GetValue(TargetCenterProperty);
|
||||
set => SetValue(TargetCenterProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum value of the ZoomLevel and TargetZoomLevel properties.
|
||||
/// Must not be less than zero or greater than MaxZoomLevel. The default value is 1.
|
||||
/// </summary>
|
||||
public double MinZoomLevel
|
||||
{
|
||||
get => (double)GetValue(MinZoomLevelProperty);
|
||||
set => SetValue(MinZoomLevelProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum value of the ZoomLevel and TargetZoomLevel properties.
|
||||
/// Must not be less than MinZoomLevel. The default value is 20.
|
||||
/// </summary>
|
||||
public double MaxZoomLevel
|
||||
{
|
||||
get => (double)GetValue(MaxZoomLevelProperty);
|
||||
set => SetValue(MaxZoomLevelProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the map zoom level.
|
||||
/// </summary>
|
||||
public double ZoomLevel
|
||||
{
|
||||
get => (double)GetValue(ZoomLevelProperty);
|
||||
set => SetValue(ZoomLevelProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the target value of a ZoomLevel animation.
|
||||
/// </summary>
|
||||
public double TargetZoomLevel
|
||||
{
|
||||
get => (double)GetValue(TargetZoomLevelProperty);
|
||||
set => SetValue(TargetZoomLevelProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the map heading, a counter-clockwise rotation angle in degrees.
|
||||
/// </summary>
|
||||
public double Heading
|
||||
{
|
||||
get => (double)GetValue(HeadingProperty);
|
||||
set => SetValue(HeadingProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the target value of a Heading animation.
|
||||
/// </summary>
|
||||
public double TargetHeading
|
||||
{
|
||||
get => (double)GetValue(TargetHeadingProperty);
|
||||
set => SetValue(TargetHeadingProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Duration of the Center, ZoomLevel and Heading animations.
|
||||
/// The default value is 0.3 seconds.
|
||||
/// </summary>
|
||||
public TimeSpan AnimationDuration
|
||||
{
|
||||
get => (TimeSpan)GetValue(AnimationDurationProperty);
|
||||
set => SetValue(AnimationDurationProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the EasingFunction of the Center, ZoomLevel and Heading animations.
|
||||
|
|
@ -219,23 +51,6 @@ namespace MapControl
|
|||
/// </summary>
|
||||
public double ViewScale => (double)GetValue(ViewScaleProperty);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ViewTransform instance that is used to transform between projected
|
||||
/// map coordinates and view coordinates.
|
||||
/// </summary>
|
||||
public ViewTransform ViewTransform { get; } = new ViewTransform();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the map scale as the horizontal and vertical scaling factors from geographic
|
||||
/// coordinates to view coordinates at the specified location, as pixels per meter.
|
||||
/// </summary>
|
||||
public Point GetScale(Location location)
|
||||
{
|
||||
var relativeScale = MapProjection.GetRelativeScale(location);
|
||||
|
||||
return new Point(ViewTransform.Scale * relativeScale.X, ViewTransform.Scale * relativeScale.Y);
|
||||
}
|
||||
|
||||
/// <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.
|
||||
|
|
@ -250,220 +65,6 @@ namespace MapControl
|
|||
return transform;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms a Location in geographic coordinates to a Point in view coordinates.
|
||||
/// </summary>
|
||||
public Point? LocationToView(Location location)
|
||||
{
|
||||
var point = MapProjection.LocationToMap(location);
|
||||
|
||||
if (!point.HasValue)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ViewTransform.MapToView(point.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms a Point in view coordinates to a Location in geographic coordinates.
|
||||
/// </summary>
|
||||
public Location ViewToLocation(Point point)
|
||||
{
|
||||
return MapProjection.MapToLocation(ViewTransform.ViewToMap(point));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms a Rect in view coordinates to a BoundingBox in geographic coordinates.
|
||||
/// </summary>
|
||||
public BoundingBox ViewRectToBoundingBox(Rect rect)
|
||||
{
|
||||
var p1 = ViewTransform.ViewToMap(new Point(rect.X, rect.Y));
|
||||
var p2 = ViewTransform.ViewToMap(new Point(rect.X, rect.Y + rect.Height));
|
||||
var p3 = ViewTransform.ViewToMap(new Point(rect.X + rect.Width, rect.Y));
|
||||
var p4 = ViewTransform.ViewToMap(new Point(rect.X + rect.Width, rect.Y + rect.Height));
|
||||
|
||||
var x1 = Math.Min(p1.X, Math.Min(p2.X, Math.Min(p3.X, p4.X)));
|
||||
var y1 = Math.Min(p1.Y, Math.Min(p2.Y, Math.Min(p3.Y, p4.Y)));
|
||||
var x2 = Math.Max(p1.X, Math.Max(p2.X, Math.Max(p3.X, p4.X)));
|
||||
var y2 = Math.Max(p1.Y, Math.Max(p2.Y, Math.Max(p3.Y, p4.Y)));
|
||||
|
||||
return MapProjection.MapToBoundingBox(new Rect(x1, y1, x2 - x1, y2 - y1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a temporary center point in view coordinates for scaling and rotation transformations.
|
||||
/// This center point is automatically reset when the Center property is set by application code
|
||||
/// or by the methods TranslateMap, TransformMap, ZoomMap and ZoomToBounds.
|
||||
/// </summary>
|
||||
public void SetTransformCenter(Point center)
|
||||
{
|
||||
transformCenter = ViewToLocation(center);
|
||||
viewCenter = transformCenter != null ? center : new Point(RenderSize.Width / 2d, RenderSize.Height / 2d);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the temporary transform center point set by SetTransformCenter.
|
||||
/// </summary>
|
||||
public void ResetTransformCenter()
|
||||
{
|
||||
transformCenter = null;
|
||||
viewCenter = new Point(RenderSize.Width / 2d, RenderSize.Height / 2d);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the Center property according to the specified translation in view coordinates.
|
||||
/// </summary>
|
||||
public void TranslateMap(Point translation)
|
||||
{
|
||||
if (transformCenter != null)
|
||||
{
|
||||
ResetTransformCenter();
|
||||
UpdateTransform();
|
||||
}
|
||||
|
||||
if (translation.X != 0d || translation.Y != 0d)
|
||||
{
|
||||
var center = ViewToLocation(new Point(viewCenter.X - translation.X, viewCenter.Y - translation.Y));
|
||||
|
||||
if (center != null)
|
||||
{
|
||||
Center = center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the Center, Heading and ZoomLevel properties according to the specified
|
||||
/// view coordinate translation, rotation and scale delta values. Rotation and scaling
|
||||
/// is performed relative to the specified center point in view coordinates.
|
||||
/// </summary>
|
||||
public void TransformMap(Point center, Point translation, double rotation, double scale)
|
||||
{
|
||||
if (rotation != 0d || scale != 1d)
|
||||
{
|
||||
SetTransformCenter(center);
|
||||
|
||||
viewCenter = new Point(viewCenter.X + translation.X, viewCenter.Y + translation.Y);
|
||||
|
||||
if (rotation != 0d)
|
||||
{
|
||||
var heading = (((Heading - rotation) % 360d) + 360d) % 360d;
|
||||
|
||||
SetValueInternal(HeadingProperty, heading);
|
||||
SetValueInternal(TargetHeadingProperty, heading);
|
||||
}
|
||||
|
||||
if (scale != 1d)
|
||||
{
|
||||
var zoomLevel = Math.Min(Math.Max(ZoomLevel + Math.Log(scale, 2d), MinZoomLevel), MaxZoomLevel);
|
||||
|
||||
SetValueInternal(ZoomLevelProperty, zoomLevel);
|
||||
SetValueInternal(TargetZoomLevelProperty, zoomLevel);
|
||||
}
|
||||
|
||||
UpdateTransform(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// More accurate than SetTransformCenter.
|
||||
//
|
||||
TranslateMap(translation);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of the TargetZoomLevel property while retaining the specified center point
|
||||
/// in view coordinates.
|
||||
/// </summary>
|
||||
public void ZoomMap(Point center, double zoomLevel)
|
||||
{
|
||||
zoomLevel = Math.Min(Math.Max(zoomLevel, MinZoomLevel), MaxZoomLevel);
|
||||
|
||||
if (TargetZoomLevel != zoomLevel)
|
||||
{
|
||||
SetTransformCenter(center);
|
||||
TargetZoomLevel = zoomLevel;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the TargetZoomLevel and TargetCenter properties so that the specified bounding box
|
||||
/// fits into the current view. The TargetHeading property is set to zero.
|
||||
/// </summary>
|
||||
public void ZoomToBounds(BoundingBox boundingBox)
|
||||
{
|
||||
var rect = MapProjection.BoundingBoxToMap(boundingBox);
|
||||
|
||||
if (rect.HasValue)
|
||||
{
|
||||
var rectCenter = new Point(rect.Value.X + rect.Value.Width / 2d, rect.Value.Y + rect.Value.Height / 2d);
|
||||
var targetCenter = MapProjection.MapToLocation(rectCenter);
|
||||
|
||||
if (targetCenter != null)
|
||||
{
|
||||
var scale = Math.Min(RenderSize.Width / rect.Value.Width, RenderSize.Height / rect.Value.Height);
|
||||
|
||||
TargetZoomLevel = ViewTransform.ScaleToZoomLevel(scale);
|
||||
TargetCenter = targetCenter;
|
||||
TargetHeading = 0d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal double ConstrainedLongitude(double longitude)
|
||||
{
|
||||
var offset = longitude - Center.Longitude;
|
||||
|
||||
if (offset > 180d)
|
||||
{
|
||||
longitude = Center.Longitude + (offset % 360d) - 360d;
|
||||
}
|
||||
else if (offset < -180d)
|
||||
{
|
||||
longitude = Center.Longitude + (offset % 360d) + 360d;
|
||||
}
|
||||
|
||||
return longitude;
|
||||
}
|
||||
|
||||
private void MapLayerPropertyChanged(UIElement oldLayer, UIElement newLayer)
|
||||
{
|
||||
if (oldLayer != null)
|
||||
{
|
||||
Children.Remove(oldLayer);
|
||||
|
||||
if (oldLayer is IMapLayer mapLayer)
|
||||
{
|
||||
if (mapLayer.MapBackground != null)
|
||||
{
|
||||
ClearValue(BackgroundProperty);
|
||||
}
|
||||
if (mapLayer.MapForeground != null)
|
||||
{
|
||||
ClearValue(ForegroundProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newLayer != null)
|
||||
{
|
||||
Children.Insert(0, newLayer);
|
||||
|
||||
if (newLayer is IMapLayer mapLayer)
|
||||
{
|
||||
if (mapLayer.MapBackground != null)
|
||||
{
|
||||
Background = mapLayer.MapBackground;
|
||||
}
|
||||
if (mapLayer.MapForeground != null)
|
||||
{
|
||||
Foreground = mapLayer.MapForeground;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MapProjectionPropertyChanged(MapProjection projection)
|
||||
{
|
||||
maxLatitude = 90d;
|
||||
|
|
@ -484,12 +85,6 @@ namespace MapControl
|
|||
UpdateTransform(false, true);
|
||||
}
|
||||
|
||||
private void ProjectionCenterPropertyChanged()
|
||||
{
|
||||
ResetTransformCenter();
|
||||
UpdateTransform();
|
||||
}
|
||||
|
||||
private Location CoerceCenterProperty(DependencyProperty property, Location center)
|
||||
{
|
||||
var c = center;
|
||||
|
|
@ -754,90 +349,5 @@ namespace MapControl
|
|||
this.BeginAnimation(HeadingProperty, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetValueInternal(DependencyProperty property, object value)
|
||||
{
|
||||
internalPropertyChange = true;
|
||||
|
||||
SetValue(property, value);
|
||||
|
||||
internalPropertyChange = false;
|
||||
}
|
||||
|
||||
private void UpdateTransform(bool resetTransformCenter = false, bool projectionChanged = false)
|
||||
{
|
||||
var transformCenterChanged = false;
|
||||
var viewScale = ViewTransform.ZoomLevelToScale(ZoomLevel);
|
||||
var projection = MapProjection;
|
||||
|
||||
projection.Center = ProjectionCenter ?? Center;
|
||||
|
||||
var mapCenter = projection.LocationToMap(transformCenter ?? Center);
|
||||
|
||||
if (mapCenter.HasValue)
|
||||
{
|
||||
ViewTransform.SetTransform(mapCenter.Value, viewCenter, viewScale, -Heading);
|
||||
|
||||
if (transformCenter != null)
|
||||
{
|
||||
var center = ViewToLocation(new Point(RenderSize.Width / 2d, RenderSize.Height / 2d));
|
||||
|
||||
if (center != null)
|
||||
{
|
||||
var centerLatitude = center.Latitude;
|
||||
var centerLongitude = Location.NormalizeLongitude(center.Longitude);
|
||||
|
||||
if (centerLatitude < -maxLatitude || centerLatitude > maxLatitude)
|
||||
{
|
||||
centerLatitude = Math.Min(Math.Max(centerLatitude, -maxLatitude), maxLatitude);
|
||||
resetTransformCenter = true;
|
||||
}
|
||||
|
||||
center = new Location(centerLatitude, centerLongitude);
|
||||
|
||||
SetValueInternal(CenterProperty, center);
|
||||
|
||||
if (centerAnimation == null)
|
||||
{
|
||||
SetValueInternal(TargetCenterProperty, center);
|
||||
}
|
||||
|
||||
if (resetTransformCenter)
|
||||
{
|
||||
// Check if transform center has moved across 180° longitude.
|
||||
//
|
||||
transformCenterChanged = Math.Abs(center.Longitude - transformCenter.Longitude) > 180d;
|
||||
|
||||
ResetTransformCenter();
|
||||
|
||||
projection.Center = ProjectionCenter ?? Center;
|
||||
|
||||
mapCenter = projection.LocationToMap(center);
|
||||
|
||||
if (mapCenter.HasValue)
|
||||
{
|
||||
ViewTransform.SetTransform(mapCenter.Value, viewCenter, viewScale, -Heading);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SetViewScale(ViewTransform.Scale);
|
||||
|
||||
// Check if view center has moved across 180° longitude.
|
||||
//
|
||||
transformCenterChanged = transformCenterChanged || Math.Abs(Center.Longitude - centerLongitude) > 180d;
|
||||
centerLongitude = Center.Longitude;
|
||||
|
||||
OnViewportChanged(new ViewportChangedEventArgs(projectionChanged, transformCenterChanged));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnViewportChanged(ViewportChangedEventArgs e)
|
||||
{
|
||||
base.OnViewportChanged(e);
|
||||
|
||||
ViewportChanged?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
519
MapControl/Shared/MapBaseCommon.cs
Normal file
519
MapControl/Shared/MapBaseCommon.cs
Normal file
|
|
@ -0,0 +1,519 @@
|
|||
// 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 AVALONIA
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Media;
|
||||
using DependencyProperty = Avalonia.AvaloniaProperty;
|
||||
using UIElement = Avalonia.Controls.Control;
|
||||
#elif WINUI
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
#elif UWP
|
||||
using Windows.UI;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Media;
|
||||
#else
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
#endif
|
||||
|
||||
namespace MapControl
|
||||
{
|
||||
public interface IMapLayer : IMapElement
|
||||
{
|
||||
Brush MapBackground { get; }
|
||||
Brush MapForeground { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The map control. Displays map content provided by one or more tile or image layers,
|
||||
/// such as MapTileLayerBase or MapImageLayer instances.
|
||||
/// The visible map area is defined by the Center and ZoomLevel properties.
|
||||
/// The map can be rotated by an angle that is given by the Heading property.
|
||||
/// MapBase can contain map overlay child elements like other MapPanels or MapItemsControls.
|
||||
/// </summary>
|
||||
public partial class MapBase : MapPanel
|
||||
{
|
||||
public static TimeSpan ImageFadeDuration { get; set; } = TimeSpan.FromSeconds(0.1);
|
||||
|
||||
public static readonly DependencyProperty ForegroundProperty =
|
||||
DependencyPropertyHelper.Register<MapBase, Brush>(nameof(Foreground), new SolidColorBrush(Colors.Black));
|
||||
|
||||
public static readonly DependencyProperty AnimationDurationProperty =
|
||||
DependencyPropertyHelper.Register<MapBase, TimeSpan>(nameof(AnimationDuration), TimeSpan.FromSeconds(0.3));
|
||||
|
||||
public static readonly DependencyProperty MapLayerProperty =
|
||||
DependencyPropertyHelper.Register<MapBase, UIElement>(nameof(MapLayer), null, false,
|
||||
(map, oldValue, newValue) => map.MapLayerPropertyChanged(oldValue, newValue));
|
||||
|
||||
public static readonly DependencyProperty MapProjectionProperty =
|
||||
DependencyPropertyHelper.Register<MapBase, MapProjection>(nameof(MapProjection), new WebMercatorProjection(), false,
|
||||
(map, oldValue, newValue) => map.MapProjectionPropertyChanged(newValue));
|
||||
|
||||
public static readonly DependencyProperty ProjectionCenterProperty =
|
||||
DependencyPropertyHelper.Register<MapBase, Location>(nameof(ProjectionCenter), null, false,
|
||||
(map, oldValue, newValue) => map.ProjectionCenterPropertyChanged());
|
||||
|
||||
private Location transformCenter;
|
||||
private Point viewCenter;
|
||||
private double centerLongitude;
|
||||
private double maxLatitude = 90d;
|
||||
private bool internalPropertyChange;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the current map viewport has changed.
|
||||
/// </summary>
|
||||
public event EventHandler<ViewportChangedEventArgs> ViewportChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the map foreground Brush.
|
||||
/// </summary>
|
||||
public Brush Foreground
|
||||
{
|
||||
get => (Brush)GetValue(ForegroundProperty);
|
||||
set => SetValue(ForegroundProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Duration of the Center, ZoomLevel and Heading animations.
|
||||
/// The default value is 0.3 seconds.
|
||||
/// </summary>
|
||||
public TimeSpan AnimationDuration
|
||||
{
|
||||
get => (TimeSpan)GetValue(AnimationDurationProperty);
|
||||
set => SetValue(AnimationDurationProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the base map layer, which is added as first element to the Children collection.
|
||||
/// If the layer implements IMapLayer (like MapTileLayer or MapImageLayer), its (non-null) MapBackground
|
||||
/// and MapForeground property values are used for the MapBase Background and Foreground properties.
|
||||
/// </summary>
|
||||
public UIElement MapLayer
|
||||
{
|
||||
get => (UIElement)GetValue(MapLayerProperty);
|
||||
set => SetValue(MapLayerProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the MapProjection used by the map control.
|
||||
/// </summary>
|
||||
public MapProjection MapProjection
|
||||
{
|
||||
get => (MapProjection)GetValue(MapProjectionProperty);
|
||||
set => SetValue(MapProjectionProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional center (reference point) for azimuthal projections.
|
||||
/// If ProjectionCenter is null, the Center property value will be used instead.
|
||||
/// </summary>
|
||||
public Location ProjectionCenter
|
||||
{
|
||||
get => (Location)GetValue(ProjectionCenterProperty);
|
||||
set => SetValue(ProjectionCenterProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the location of the center point of the map.
|
||||
/// </summary>
|
||||
public Location Center
|
||||
{
|
||||
get => (Location)GetValue(CenterProperty);
|
||||
set => SetValue(CenterProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the target value of a Center animation.
|
||||
/// </summary>
|
||||
public Location TargetCenter
|
||||
{
|
||||
get => (Location)GetValue(TargetCenterProperty);
|
||||
set => SetValue(TargetCenterProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum value of the ZoomLevel and TargetZoomLevel properties.
|
||||
/// Must not be less than zero or greater than MaxZoomLevel. The default value is 1.
|
||||
/// </summary>
|
||||
public double MinZoomLevel
|
||||
{
|
||||
get => (double)GetValue(MinZoomLevelProperty);
|
||||
set => SetValue(MinZoomLevelProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum value of the ZoomLevel and TargetZoomLevel properties.
|
||||
/// Must not be less than MinZoomLevel. The default value is 20.
|
||||
/// </summary>
|
||||
public double MaxZoomLevel
|
||||
{
|
||||
get => (double)GetValue(MaxZoomLevelProperty);
|
||||
set => SetValue(MaxZoomLevelProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the map zoom level.
|
||||
/// </summary>
|
||||
public double ZoomLevel
|
||||
{
|
||||
get => (double)GetValue(ZoomLevelProperty);
|
||||
set => SetValue(ZoomLevelProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the target value of a ZoomLevel animation.
|
||||
/// </summary>
|
||||
public double TargetZoomLevel
|
||||
{
|
||||
get => (double)GetValue(TargetZoomLevelProperty);
|
||||
set => SetValue(TargetZoomLevelProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the map heading, a counter-clockwise rotation angle in degrees.
|
||||
/// </summary>
|
||||
public double Heading
|
||||
{
|
||||
get => (double)GetValue(HeadingProperty);
|
||||
set => SetValue(HeadingProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the target value of a Heading animation.
|
||||
/// </summary>
|
||||
public double TargetHeading
|
||||
{
|
||||
get => (double)GetValue(TargetHeadingProperty);
|
||||
set => SetValue(TargetHeadingProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ViewTransform instance that is used to transform between projected
|
||||
/// map coordinates and view coordinates.
|
||||
/// </summary>
|
||||
public ViewTransform ViewTransform { get; } = new ViewTransform();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the map scale as the horizontal and vertical scaling factors from geographic
|
||||
/// coordinates to view coordinates at the specified location, as pixels per meter.
|
||||
/// </summary>
|
||||
public Point GetScale(Location location)
|
||||
{
|
||||
var relativeScale = MapProjection.GetRelativeScale(location);
|
||||
|
||||
return new Point(ViewTransform.Scale * relativeScale.X, ViewTransform.Scale * relativeScale.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms a Location in geographic coordinates to a Point in view coordinates.
|
||||
/// </summary>
|
||||
public Point? LocationToView(Location location)
|
||||
{
|
||||
var point = MapProjection.LocationToMap(location);
|
||||
|
||||
if (!point.HasValue)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ViewTransform.MapToView(point.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms a Point in view coordinates to a Location in geographic coordinates.
|
||||
/// </summary>
|
||||
public Location ViewToLocation(Point point)
|
||||
{
|
||||
return MapProjection.MapToLocation(ViewTransform.ViewToMap(point));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms a Rect in view coordinates to a BoundingBox in geographic coordinates.
|
||||
/// </summary>
|
||||
public BoundingBox ViewRectToBoundingBox(Rect rect)
|
||||
{
|
||||
var p1 = ViewTransform.ViewToMap(new Point(rect.X, rect.Y));
|
||||
var p2 = ViewTransform.ViewToMap(new Point(rect.X, rect.Y + rect.Height));
|
||||
var p3 = ViewTransform.ViewToMap(new Point(rect.X + rect.Width, rect.Y));
|
||||
var p4 = ViewTransform.ViewToMap(new Point(rect.X + rect.Width, rect.Y + rect.Height));
|
||||
|
||||
var x1 = Math.Min(p1.X, Math.Min(p2.X, Math.Min(p3.X, p4.X)));
|
||||
var y1 = Math.Min(p1.Y, Math.Min(p2.Y, Math.Min(p3.Y, p4.Y)));
|
||||
var x2 = Math.Max(p1.X, Math.Max(p2.X, Math.Max(p3.X, p4.X)));
|
||||
var y2 = Math.Max(p1.Y, Math.Max(p2.Y, Math.Max(p3.Y, p4.Y)));
|
||||
|
||||
return MapProjection.MapToBoundingBox(new Rect(x1, y1, x2 - x1, y2 - y1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a temporary center point in view coordinates for scaling and rotation transformations.
|
||||
/// This center point is automatically reset when the Center property is set by application code
|
||||
/// or by the methods TranslateMap, TransformMap, ZoomMap and ZoomToBounds.
|
||||
/// </summary>
|
||||
public void SetTransformCenter(Point center)
|
||||
{
|
||||
transformCenter = ViewToLocation(center);
|
||||
viewCenter = transformCenter != null ? center : new Point(RenderSize.Width / 2d, RenderSize.Height / 2d);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the temporary transform center point set by SetTransformCenter.
|
||||
/// </summary>
|
||||
public void ResetTransformCenter()
|
||||
{
|
||||
transformCenter = null;
|
||||
viewCenter = new Point(RenderSize.Width / 2d, RenderSize.Height / 2d);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the Center property according to the specified translation in view coordinates.
|
||||
/// </summary>
|
||||
public void TranslateMap(Point translation)
|
||||
{
|
||||
if (transformCenter != null)
|
||||
{
|
||||
ResetTransformCenter();
|
||||
UpdateTransform();
|
||||
}
|
||||
|
||||
if (translation.X != 0d || translation.Y != 0d)
|
||||
{
|
||||
var center = ViewToLocation(new Point(viewCenter.X - translation.X, viewCenter.Y - translation.Y));
|
||||
|
||||
if (center != null)
|
||||
{
|
||||
Center = center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the Center, Heading and ZoomLevel properties according to the specified
|
||||
/// view coordinate translation, rotation and scale delta values. Rotation and scaling
|
||||
/// is performed relative to the specified center point in view coordinates.
|
||||
/// </summary>
|
||||
public void TransformMap(Point center, Point translation, double rotation, double scale)
|
||||
{
|
||||
if (rotation != 0d || scale != 1d)
|
||||
{
|
||||
SetTransformCenter(center);
|
||||
|
||||
viewCenter = new Point(viewCenter.X + translation.X, viewCenter.Y + translation.Y);
|
||||
|
||||
if (rotation != 0d)
|
||||
{
|
||||
var heading = (((Heading - rotation) % 360d) + 360d) % 360d;
|
||||
|
||||
SetValueInternal(HeadingProperty, heading);
|
||||
SetValueInternal(TargetHeadingProperty, heading);
|
||||
}
|
||||
|
||||
if (scale != 1d)
|
||||
{
|
||||
var zoomLevel = Math.Min(Math.Max(ZoomLevel + Math.Log(scale, 2d), MinZoomLevel), MaxZoomLevel);
|
||||
|
||||
SetValueInternal(ZoomLevelProperty, zoomLevel);
|
||||
SetValueInternal(TargetZoomLevelProperty, zoomLevel);
|
||||
}
|
||||
|
||||
UpdateTransform(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// More accurate than SetTransformCenter.
|
||||
//
|
||||
TranslateMap(translation);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of the TargetZoomLevel property while retaining the specified center point
|
||||
/// in view coordinates.
|
||||
/// </summary>
|
||||
public void ZoomMap(Point center, double zoomLevel)
|
||||
{
|
||||
zoomLevel = Math.Min(Math.Max(zoomLevel, MinZoomLevel), MaxZoomLevel);
|
||||
|
||||
if (TargetZoomLevel != zoomLevel)
|
||||
{
|
||||
SetTransformCenter(center);
|
||||
|
||||
TargetZoomLevel = zoomLevel;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the TargetZoomLevel and TargetCenter properties so that the specified bounding box
|
||||
/// fits into the current view. The TargetHeading property is set to zero.
|
||||
/// </summary>
|
||||
public void ZoomToBounds(BoundingBox boundingBox)
|
||||
{
|
||||
var rect = MapProjection.BoundingBoxToMap(boundingBox);
|
||||
|
||||
if (rect.HasValue)
|
||||
{
|
||||
var rectCenter = new Point(rect.Value.X + rect.Value.Width / 2d, rect.Value.Y + rect.Value.Height / 2d);
|
||||
var targetCenter = MapProjection.MapToLocation(rectCenter);
|
||||
|
||||
if (targetCenter != null)
|
||||
{
|
||||
var scale = Math.Min(RenderSize.Width / rect.Value.Width, RenderSize.Height / rect.Value.Height);
|
||||
|
||||
TargetZoomLevel = ViewTransform.ScaleToZoomLevel(scale);
|
||||
TargetCenter = targetCenter;
|
||||
TargetHeading = 0d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal double ConstrainedLongitude(double longitude)
|
||||
{
|
||||
var offset = longitude - Center.Longitude;
|
||||
|
||||
if (offset > 180d)
|
||||
{
|
||||
longitude = Center.Longitude + (offset % 360d) - 360d;
|
||||
}
|
||||
else if (offset < -180d)
|
||||
{
|
||||
longitude = Center.Longitude + (offset % 360d) + 360d;
|
||||
}
|
||||
|
||||
return longitude;
|
||||
}
|
||||
|
||||
private void SetValueInternal(DependencyProperty property, object value)
|
||||
{
|
||||
internalPropertyChange = true;
|
||||
|
||||
SetValue(property, value);
|
||||
|
||||
internalPropertyChange = false;
|
||||
}
|
||||
|
||||
private void MapLayerPropertyChanged(UIElement oldLayer, UIElement newLayer)
|
||||
{
|
||||
if (oldLayer != null)
|
||||
{
|
||||
Children.Remove(oldLayer);
|
||||
|
||||
if (oldLayer is IMapLayer mapLayer)
|
||||
{
|
||||
if (mapLayer.MapBackground != null)
|
||||
{
|
||||
ClearValue(BackgroundProperty);
|
||||
}
|
||||
if (mapLayer.MapForeground != null)
|
||||
{
|
||||
ClearValue(ForegroundProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newLayer != null)
|
||||
{
|
||||
Children.Insert(0, newLayer);
|
||||
|
||||
if (newLayer is IMapLayer mapLayer)
|
||||
{
|
||||
if (mapLayer.MapBackground != null)
|
||||
{
|
||||
Background = mapLayer.MapBackground;
|
||||
}
|
||||
if (mapLayer.MapForeground != null)
|
||||
{
|
||||
Foreground = mapLayer.MapForeground;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProjectionCenterPropertyChanged()
|
||||
{
|
||||
ResetTransformCenter();
|
||||
UpdateTransform();
|
||||
}
|
||||
|
||||
private void UpdateTransform(bool resetTransformCenter = false, bool projectionChanged = false)
|
||||
{
|
||||
var transformCenterChanged = false;
|
||||
var viewScale = ViewTransform.ZoomLevelToScale(ZoomLevel);
|
||||
var projection = MapProjection;
|
||||
|
||||
projection.Center = ProjectionCenter ?? Center;
|
||||
|
||||
var mapCenter = projection.LocationToMap(transformCenter ?? Center);
|
||||
|
||||
if (mapCenter.HasValue)
|
||||
{
|
||||
ViewTransform.SetTransform(mapCenter.Value, viewCenter, viewScale, -Heading);
|
||||
|
||||
if (transformCenter != null)
|
||||
{
|
||||
var center = ViewToLocation(new Point(RenderSize.Width / 2d, RenderSize.Height / 2d));
|
||||
|
||||
if (center != null)
|
||||
{
|
||||
var centerLatitude = center.Latitude;
|
||||
var centerLongitude = Location.NormalizeLongitude(center.Longitude);
|
||||
|
||||
if (centerLatitude < -maxLatitude || centerLatitude > maxLatitude)
|
||||
{
|
||||
centerLatitude = Math.Min(Math.Max(centerLatitude, -maxLatitude), maxLatitude);
|
||||
resetTransformCenter = true;
|
||||
}
|
||||
|
||||
center = new Location(centerLatitude, centerLongitude);
|
||||
|
||||
SetValueInternal(CenterProperty, center);
|
||||
|
||||
if (centerAnimation == null)
|
||||
{
|
||||
SetValueInternal(TargetCenterProperty, center);
|
||||
}
|
||||
|
||||
if (resetTransformCenter)
|
||||
{
|
||||
// Check if transform center has moved across 180° longitude.
|
||||
//
|
||||
transformCenterChanged = Math.Abs(center.Longitude - transformCenter.Longitude) > 180d;
|
||||
|
||||
ResetTransformCenter();
|
||||
|
||||
projection.Center = ProjectionCenter ?? Center;
|
||||
|
||||
mapCenter = projection.LocationToMap(center);
|
||||
|
||||
if (mapCenter.HasValue)
|
||||
{
|
||||
ViewTransform.SetTransform(mapCenter.Value, viewCenter, viewScale, -Heading);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SetViewScale(ViewTransform.Scale);
|
||||
|
||||
// Check if view center has moved across 180° longitude.
|
||||
//
|
||||
transformCenterChanged = transformCenterChanged || Math.Abs(Center.Longitude - centerLongitude) > 180d;
|
||||
centerLongitude = Center.Longitude;
|
||||
|
||||
OnViewportChanged(new ViewportChangedEventArgs(projectionChanged, transformCenterChanged));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnViewportChanged(ViewportChangedEventArgs e)
|
||||
{
|
||||
base.OnViewportChanged(e);
|
||||
|
||||
ViewportChanged?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,14 @@
|
|||
// Licensed under the Microsoft Public License (Ms-PL)
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
#if WINUI
|
||||
#if AVALONIA
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using DependencyProperty = Avalonia.AvaloniaProperty;
|
||||
using FrameworkElement = Avalonia.Controls.Control;
|
||||
using HorizontalAlignment = Avalonia.Layout.HorizontalAlignment;
|
||||
using VerticalAlignment = Avalonia.Layout.VerticalAlignment;
|
||||
#elif WINUI
|
||||
using Windows.Foundation;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
|
@ -20,6 +26,10 @@ using System.Windows.Controls;
|
|||
using System.Windows.Media;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Arranges child elements on a Map at positions specified by the attached property Location,
|
||||
/// or in rectangles specified by the attached property BoundingBox.
|
||||
/// </summary>
|
||||
namespace MapControl
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -30,19 +40,31 @@ namespace MapControl
|
|||
MapBase ParentMap { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Arranges child elements on a Map at positions specified by the attached property Location,
|
||||
/// or in rectangles specified by the attached property BoundingBox.
|
||||
/// </summary>
|
||||
public partial class MapPanel : Panel, IMapElement
|
||||
{
|
||||
private static void ParentMapPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (obj is IMapElement mapElement)
|
||||
{
|
||||
mapElement.ParentMap = e.NewValue as MapBase;
|
||||
}
|
||||
}
|
||||
public static readonly DependencyProperty AutoCollapseProperty =
|
||||
DependencyPropertyHelper.RegisterAttached<MapPanel, bool>("AutoCollapse");
|
||||
|
||||
public static readonly DependencyProperty LocationProperty =
|
||||
DependencyPropertyHelper.RegisterAttached<MapPanel, Location>("Location", null, false,
|
||||
(obj, oldVale, newValue) => (obj.Parent as MapPanel)?.InvalidateArrange());
|
||||
|
||||
public static readonly DependencyProperty BoundingBoxProperty =
|
||||
DependencyPropertyHelper.RegisterAttached<MapPanel, BoundingBox>("BoundingBox", null, false,
|
||||
(obj, oldVale, newValue) => (obj.Parent as MapPanel)?.InvalidateArrange());
|
||||
|
||||
private static readonly DependencyProperty ViewPositionProperty =
|
||||
DependencyPropertyHelper.RegisterAttached<MapPanel, Point?>("ViewPosition");
|
||||
|
||||
private static readonly DependencyProperty ParentMapProperty =
|
||||
DependencyPropertyHelper.RegisterAttached<MapPanel, MapBase>("ParentMap", null, true,
|
||||
(obj, oldVale, newValue) =>
|
||||
{
|
||||
if (obj is IMapElement mapElement)
|
||||
{
|
||||
mapElement.ParentMap = newValue;
|
||||
}
|
||||
});
|
||||
|
||||
private MapBase parentMap;
|
||||
|
||||
|
|
@ -55,9 +77,6 @@ namespace MapControl
|
|||
set => SetParentMap(value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty AutoCollapseProperty = DependencyProperty.RegisterAttached(
|
||||
"AutoCollapse", typeof(bool), typeof(MapPanel), new PropertyMetadata(false));
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that controls whether an element's Visibility is automatically
|
||||
/// set to Collapsed when it is located outside the visible viewport area.
|
||||
|
|
@ -115,6 +134,16 @@ namespace MapControl
|
|||
return (Point?)element.GetValue(ViewPositionProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the attached ViewPosition property of an element. The method is called during
|
||||
/// ArrangeOverride and may be overridden to modify the actual view position value.
|
||||
/// An overridden method should call this method to set the attached property.
|
||||
/// </summary>
|
||||
protected virtual void SetViewPosition(FrameworkElement element, ref Point? position)
|
||||
{
|
||||
element.SetValue(ViewPositionProperty, position);
|
||||
}
|
||||
|
||||
protected virtual void SetParentMap(MapBase map)
|
||||
{
|
||||
if (parentMap != null && parentMap != this)
|
||||
|
|
@ -146,7 +175,7 @@ namespace MapControl
|
|||
{
|
||||
availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
|
||||
|
||||
foreach (var element in Children.OfType<FrameworkElement>())
|
||||
foreach (var element in ChildElements)
|
||||
{
|
||||
element.Measure(availableSize);
|
||||
}
|
||||
|
|
@ -158,7 +187,7 @@ namespace MapControl
|
|||
{
|
||||
if (parentMap != null)
|
||||
{
|
||||
foreach (var element in Children.OfType<FrameworkElement>())
|
||||
foreach (var element in ChildElements)
|
||||
{
|
||||
var location = GetLocation(element);
|
||||
var position = location != null ? GetViewPosition(location) : null;
|
||||
|
|
@ -167,8 +196,7 @@ namespace MapControl
|
|||
|
||||
if (GetAutoCollapse(element))
|
||||
{
|
||||
element.Visibility = position.HasValue && IsOutsideViewport(position.Value)
|
||||
? Visibility.Collapsed : Visibility.Visible;
|
||||
SetVisible(element, !(position.HasValue && IsOutsideViewport(position.Value)));
|
||||
}
|
||||
|
||||
if (position.HasValue)
|
||||
|
|
@ -230,11 +258,11 @@ namespace MapControl
|
|||
{
|
||||
var rectCenter = new Point(rect.X + rect.Width / 2d, rect.Y + rect.Height / 2d);
|
||||
var position = parentMap.ViewTransform.MapToView(rectCenter);
|
||||
var projection = parentMap.MapProjection;
|
||||
|
||||
if (parentMap.MapProjection.Type <= MapProjectionType.NormalCylindrical &&
|
||||
IsOutsideViewport(position))
|
||||
if (projection.Type <= MapProjectionType.NormalCylindrical && IsOutsideViewport(position))
|
||||
{
|
||||
var location = parentMap.MapProjection.MapToLocation(rectCenter);
|
||||
var location = projection.MapToLocation(rectCenter);
|
||||
|
||||
if (location != null)
|
||||
{
|
||||
|
|
@ -359,9 +387,7 @@ namespace MapControl
|
|||
}
|
||||
else if (rect.Rotation != 0d)
|
||||
{
|
||||
rotateTransform = new RotateTransform { Angle = rect.Rotation };
|
||||
element.RenderTransform = rotateTransform;
|
||||
element.RenderTransformOrigin = new Point(0.5, 0.5);
|
||||
SetRenderTransform(element, new RotateTransform { Angle = rect.Rotation }, 0.5, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -375,7 +401,7 @@ namespace MapControl
|
|||
element.Arrange(rect);
|
||||
}
|
||||
|
||||
internal static Size GetDesiredSize(UIElement element)
|
||||
internal static Size GetDesiredSize(FrameworkElement element)
|
||||
{
|
||||
var width = 0d;
|
||||
var height = 0d;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@
|
|||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
#if WINUI
|
||||
#if AVALONIA
|
||||
using Avalonia.Media;
|
||||
using DependencyProperty = Avalonia.AvaloniaProperty;
|
||||
#elif WINUI
|
||||
using Windows.Foundation;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
|
@ -24,6 +27,15 @@ namespace MapControl
|
|||
/// </summary>
|
||||
public class MapTileLayer : MapTileLayerBase
|
||||
{
|
||||
public static readonly DependencyProperty MinZoomLevelProperty =
|
||||
DependencyPropertyHelper.Register<MapTileLayer, int>(nameof(MinZoomLevel), 0);
|
||||
|
||||
public static readonly DependencyProperty MaxZoomLevelProperty =
|
||||
DependencyPropertyHelper.Register<MapTileLayer, int>(nameof(MaxZoomLevel), 19);
|
||||
|
||||
public static readonly DependencyProperty ZoomLevelOffsetProperty =
|
||||
DependencyPropertyHelper.Register<MapTileLayer, double>(nameof(ZoomLevelOffset), 0d);
|
||||
|
||||
private const int TileSize = 256;
|
||||
|
||||
private static readonly Point MapTopLeft = new Point(
|
||||
|
|
@ -39,15 +51,6 @@ namespace MapControl
|
|||
Description = "© [OpenStreetMap Contributors](http://www.openstreetmap.org/copyright)"
|
||||
};
|
||||
|
||||
public static readonly DependencyProperty MinZoomLevelProperty = DependencyProperty.Register(
|
||||
nameof(MinZoomLevel), typeof(int), typeof(MapTileLayer), new PropertyMetadata(0));
|
||||
|
||||
public static readonly DependencyProperty MaxZoomLevelProperty = DependencyProperty.Register(
|
||||
nameof(MaxZoomLevel), typeof(int), typeof(MapTileLayer), new PropertyMetadata(19));
|
||||
|
||||
public static readonly DependencyProperty ZoomLevelOffsetProperty = DependencyProperty.Register(
|
||||
nameof(ZoomLevelOffset), typeof(double), typeof(MapTileLayer), new PropertyMetadata(0d));
|
||||
|
||||
public TileMatrix TileMatrix { get; private set; }
|
||||
|
||||
public TileCollection Tiles { get; private set; } = new TileCollection();
|
||||
|
|
|
|||
|
|
@ -5,7 +5,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
#if WINUI
|
||||
#if AVALONIA
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Threading;
|
||||
using DependencyProperty = Avalonia.AvaloniaProperty;
|
||||
#elif WINUI
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
|
@ -23,36 +28,35 @@ using System.Windows.Threading;
|
|||
|
||||
namespace MapControl
|
||||
{
|
||||
public abstract class MapTileLayerBase : Panel, IMapLayer
|
||||
public abstract partial class MapTileLayerBase : Panel, IMapLayer
|
||||
{
|
||||
public static readonly DependencyProperty TileSourceProperty = DependencyProperty.Register(
|
||||
nameof(TileSource), typeof(TileSource), typeof(MapTileLayerBase),
|
||||
new PropertyMetadata(null, async (o, e) => await ((MapTileLayerBase)o).Update(true)));
|
||||
public static readonly DependencyProperty TileSourceProperty =
|
||||
DependencyPropertyHelper.Register<MapTileLayerBase, TileSource>(nameof(TileSource), null, false,
|
||||
async (obj, oldVale, newValue) => await obj.Update(true));
|
||||
|
||||
public static readonly DependencyProperty SourceNameProperty = DependencyProperty.Register(
|
||||
nameof(SourceName), typeof(string), typeof(MapTileLayerBase), new PropertyMetadata(null));
|
||||
public static readonly DependencyProperty SourceNameProperty =
|
||||
DependencyPropertyHelper.Register<MapTileLayerBase, string>(nameof(SourceName));
|
||||
|
||||
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register(
|
||||
nameof(Description), typeof(string), typeof(MapTileLayerBase), new PropertyMetadata(null));
|
||||
public static readonly DependencyProperty DescriptionProperty =
|
||||
DependencyPropertyHelper.Register<MapTileLayerBase, string>(nameof(Description));
|
||||
|
||||
public static readonly DependencyProperty MaxBackgroundLevelsProperty = DependencyProperty.Register(
|
||||
nameof(MaxBackgroundLevels), typeof(int), typeof(MapTileLayerBase), new PropertyMetadata(5));
|
||||
public static readonly DependencyProperty MaxBackgroundLevelsProperty =
|
||||
DependencyPropertyHelper.Register<MapTileLayerBase, int>(nameof(MaxBackgroundLevels), 5);
|
||||
|
||||
public static readonly DependencyProperty UpdateIntervalProperty = DependencyProperty.Register(
|
||||
nameof(UpdateInterval), typeof(TimeSpan), typeof(MapTileLayerBase),
|
||||
new PropertyMetadata(TimeSpan.FromSeconds(0.2), (o, e) => ((MapTileLayerBase)o).updateTimer.Interval = (TimeSpan)e.NewValue));
|
||||
public static readonly DependencyProperty UpdateIntervalProperty =
|
||||
DependencyPropertyHelper.Register<MapTileLayerBase, TimeSpan>(nameof(UpdateInterval), TimeSpan.FromSeconds(0.2));
|
||||
|
||||
public static readonly DependencyProperty UpdateWhileViewportChangingProperty = DependencyProperty.Register(
|
||||
nameof(UpdateWhileViewportChanging), typeof(bool), typeof(MapTileLayerBase), new PropertyMetadata(false));
|
||||
public static readonly DependencyProperty UpdateWhileViewportChangingProperty =
|
||||
DependencyPropertyHelper.Register<MapTileLayerBase, bool>(nameof(UpdateWhileViewportChanging));
|
||||
|
||||
public static readonly DependencyProperty MapBackgroundProperty = DependencyProperty.Register(
|
||||
nameof(MapBackground), typeof(Brush), typeof(MapTileLayerBase), new PropertyMetadata(null));
|
||||
public static readonly DependencyProperty MapBackgroundProperty =
|
||||
DependencyPropertyHelper.Register<MapTileLayerBase, Brush>(nameof(MapBackground));
|
||||
|
||||
public static readonly DependencyProperty MapForegroundProperty = DependencyProperty.Register(
|
||||
nameof(MapForeground), typeof(Brush), typeof(MapTileLayerBase), new PropertyMetadata(null));
|
||||
public static readonly DependencyProperty MapForegroundProperty =
|
||||
DependencyPropertyHelper.Register<MapTileLayerBase, Brush>(nameof(MapForeground));
|
||||
|
||||
public static readonly DependencyProperty LoadingProgressProperty = DependencyProperty.Register(
|
||||
nameof(LoadingProgress), typeof(double), typeof(MapTileLayerBase), new PropertyMetadata(1d));
|
||||
public static readonly DependencyProperty LoadingProgressProperty =
|
||||
DependencyPropertyHelper.Register<MapTileLayerBase, double>(nameof(LoadingProgress), 1d);
|
||||
|
||||
private readonly Progress<double> loadingProgress;
|
||||
private readonly DispatcherTimer updateTimer;
|
||||
|
|
@ -61,9 +65,9 @@ namespace MapControl
|
|||
|
||||
protected MapTileLayerBase()
|
||||
{
|
||||
RenderTransform = new MatrixTransform();
|
||||
MapPanel.SetRenderTransform(this, new MatrixTransform());
|
||||
|
||||
loadingProgress = new Progress<double>(p => LoadingProgress = p);
|
||||
loadingProgress = new Progress<double>(p => SetValue(LoadingProgressProperty, p));
|
||||
|
||||
updateTimer = this.CreateTimer(UpdateInterval);
|
||||
updateTimer.Tick += async (s, e) => await Update(false);
|
||||
|
|
@ -155,11 +159,7 @@ namespace MapControl
|
|||
/// <summary>
|
||||
/// Gets the progress of the TileImageLoader as a double value between 0 and 1.
|
||||
/// </summary>
|
||||
public double LoadingProgress
|
||||
{
|
||||
get => (double)GetValue(LoadingProgressProperty);
|
||||
private set => SetValue(LoadingProgressProperty, value);
|
||||
}
|
||||
public double LoadingProgress => (double)GetValue(LoadingProgressProperty);
|
||||
|
||||
/// <summary>
|
||||
/// Implements IMapElement.ParentMap.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue