Workaround for rounding errors in MapTransform.

This commit is contained in:
ClemensF 2016-01-30 11:50:53 +01:00
parent 3717081f18
commit 4a37e94d77
7 changed files with 98 additions and 47 deletions

View file

@ -79,7 +79,7 @@ namespace MapControl
if (mousePosition.HasValue) if (mousePosition.HasValue)
{ {
var position = e.GetPosition(this); var position = e.GetPosition(this);
TranslateMap((Point)(position - mousePosition.Value)); TranslateMap(position - mousePosition.Value);
mousePosition = position; mousePosition = position;
} }
} }
@ -96,7 +96,7 @@ namespace MapControl
base.OnManipulationDelta(e); base.OnManipulationDelta(e);
TransformMap(e.ManipulationOrigin, TransformMap(e.ManipulationOrigin,
(Point)e.DeltaManipulation.Translation, e.DeltaManipulation.Rotation, e.DeltaManipulation.Translation, e.DeltaManipulation.Rotation,
(e.DeltaManipulation.Scale.X + e.DeltaManipulation.Scale.Y) / 2d); (e.DeltaManipulation.Scale.X + e.DeltaManipulation.Scale.Y) / 2d);
} }
} }

View file

@ -70,11 +70,12 @@ namespace MapControl
MapOrigin = mapTransform.Transform(origin); MapOrigin = mapTransform.Transform(origin);
ViewportScale = Math.Pow(2d, ZoomLevel) * (double)TileSource.TileSize / 360d; ViewportScale = Math.Pow(2d, ZoomLevel) * (double)TileSource.TileSize / 360d;
viewportTransform.Matrix = var transform = new Matrix(1d, 0d, 0d, 1d, -MapOrigin.X, -MapOrigin.Y)
new Matrix(1d, 0d, 0d, 1d, -MapOrigin.X, -MapOrigin.Y) .Rotate(-Heading)
.Scale(ViewportScale, -ViewportScale) .Scale(ViewportScale, -ViewportScale)
.Rotate(Heading)
.Translate(ViewportOrigin.X, ViewportOrigin.Y); .Translate(ViewportOrigin.X, ViewportOrigin.Y);
viewportTransform.Matrix = transform;
} }
} }
} }

View file

@ -53,6 +53,24 @@ namespace MapControl
typeof(MapBase), new FrameworkPropertyMetadata(Brushes.Transparent)); typeof(MapBase), new FrameworkPropertyMetadata(Brushes.Transparent));
} }
/// <summary>
/// Changes the Center property according to the specified translation in viewport coordinates.
/// </summary>
public void TranslateMap(Vector translation)
{
TranslateMap((Point)translation);
}
/// <summary>
/// Changes the Center, Heading and ZoomLevel properties according to the specified
/// viewport coordinate translation, rotation and scale delta values. Rotation and scaling
/// is performed relative to the specified origin point in viewport coordinates.
/// </summary>
public void TransformMap(Point origin, Vector translation, double rotation, double scale)
{
TransformMap(origin, (Point)translation, rotation, scale);
}
partial void RemoveAnimation(DependencyProperty property) partial void RemoveAnimation(DependencyProperty property)
{ {
BeginAnimation(property, null); BeginAnimation(property, null);
@ -72,8 +90,8 @@ namespace MapControl
ViewportScale = Math.Pow(2d, ZoomLevel) * (double)TileSource.TileSize / 360d; ViewportScale = Math.Pow(2d, ZoomLevel) * (double)TileSource.TileSize / 360d;
var transform = new Matrix(1d, 0d, 0d, 1d, -MapOrigin.X, -MapOrigin.Y); var transform = new Matrix(1d, 0d, 0d, 1d, -MapOrigin.X, -MapOrigin.Y);
transform.Rotate(-Heading);
transform.Scale(ViewportScale, -ViewportScale); transform.Scale(ViewportScale, -ViewportScale);
transform.Rotate(Heading);
transform.Translate(ViewportOrigin.X, ViewportOrigin.Y); transform.Translate(ViewportOrigin.X, ViewportOrigin.Y);
viewportTransform.Matrix = transform; viewportTransform.Matrix = transform;

View file

@ -75,9 +75,6 @@ namespace MapControl
private DoubleAnimation headingAnimation; private DoubleAnimation headingAnimation;
private bool internalPropertyChange; private bool internalPropertyChange;
internal Point MapOrigin { get; private set; }
internal Point ViewportOrigin { get; private set; }
public MapBase() public MapBase()
{ {
Initialize(); Initialize();
@ -272,6 +269,9 @@ namespace MapControl
get { return scaleRotateTransform; } get { return scaleRotateTransform; }
} }
internal Point MapOrigin { get; private set; }
internal Point ViewportOrigin { get; private set; }
/// <summary> /// <summary>
/// Gets the scaling factor from cartesian map coordinates to viewport coordinates. /// Gets the scaling factor from cartesian map coordinates to viewport coordinates.
/// </summary> /// </summary>
@ -337,18 +337,32 @@ namespace MapControl
} }
/// <summary> /// <summary>
/// Changes the Center property according to the specified translation in viewport coordinates. /// Changes the Center property according to the specified map translation in viewport coordinates.
/// </summary> /// </summary>
public void TranslateMap(Point translation) public void TranslateMap(Point translation)
{ {
if (transformOrigin != null) if (transformOrigin != null)
{ {
ResetTransformOrigin(); ResetTransformOrigin();
UpdateTransform();
} }
if (translation.X != 0d || translation.Y != 0d) if (translation.X != 0d || translation.Y != 0d)
{ {
Center = ViewportPointToLocation(new Point(ViewportOrigin.X - translation.X, ViewportOrigin.Y - translation.Y)); if (Heading != 0d)
{
var cos = Math.Cos(Heading / 180d * Math.PI);
var sin = Math.Sin(Heading / 180d * Math.PI);
translation = new Point(
translation.X * cos + translation.Y * sin,
translation.Y * cos - translation.X * sin);
}
translation.X /= -ViewportScale;
translation.Y /= ViewportScale;
Center = mapTransform.Transform(Center, MapOrigin, translation);
} }
} }
@ -359,9 +373,10 @@ namespace MapControl
/// </summary> /// </summary>
public void TransformMap(Point origin, Point translation, double rotation, double scale) public void TransformMap(Point origin, Point translation, double rotation, double scale)
{ {
SetTransformOrigin(origin); if (rotation != 0d || scale != 1d)
{
ViewportOrigin = new Point(ViewportOrigin.X + translation.X, ViewportOrigin.Y + translation.Y); transformOrigin = ViewportPointToLocation(origin);
ViewportOrigin = new Point(origin.X + translation.X, origin.Y + translation.Y);
if (rotation != 0d) if (rotation != 0d)
{ {
@ -379,6 +394,11 @@ namespace MapControl
UpdateTransform(true); UpdateTransform(true);
} }
else
{
TranslateMap(translation); // more precise
}
}
/// <summary> /// <summary>
/// Sets the value of the TargetZoomLevel property while retaining the specified origin point /// Sets the value of the TargetZoomLevel property while retaining the specified origin point
@ -567,7 +587,6 @@ namespace MapControl
if (!internalPropertyChange) if (!internalPropertyChange)
{ {
AdjustCenterProperty(CenterProperty, ref center); AdjustCenterProperty(CenterProperty, ref center);
ResetTransformOrigin();
UpdateTransform(); UpdateTransform();
if (centerAnimation == null) if (centerAnimation == null)
@ -616,8 +635,6 @@ namespace MapControl
InternalSetValue(CenterProperty, TargetCenter); InternalSetValue(CenterProperty, TargetCenter);
InternalSetValue(CenterPointProperty, mapTransform.Transform(TargetCenter)); InternalSetValue(CenterPointProperty, mapTransform.Transform(TargetCenter));
RemoveAnimation(CenterPointProperty); // remove holding animation in WPF RemoveAnimation(CenterPointProperty); // remove holding animation in WPF
ResetTransformOrigin();
UpdateTransform(); UpdateTransform();
} }
} }
@ -628,7 +645,6 @@ namespace MapControl
{ {
centerPoint.X = Location.NormalizeLongitude(centerPoint.X); centerPoint.X = Location.NormalizeLongitude(centerPoint.X);
InternalSetValue(CenterProperty, mapTransform.Transform(centerPoint)); InternalSetValue(CenterProperty, mapTransform.Transform(centerPoint));
ResetTransformOrigin();
UpdateTransform(); UpdateTransform();
} }
} }
@ -720,7 +736,7 @@ namespace MapControl
InternalSetValue(ZoomLevelProperty, TargetZoomLevel); InternalSetValue(ZoomLevelProperty, TargetZoomLevel);
RemoveAnimation(ZoomLevelProperty); // remove holding animation in WPF RemoveAnimation(ZoomLevelProperty); // remove holding animation in WPF
UpdateTransform(true); Dispatcher.BeginInvoke(() => UpdateTransform(true));
} }
} }
@ -793,12 +809,11 @@ namespace MapControl
InternalSetValue(HeadingProperty, TargetHeading); InternalSetValue(HeadingProperty, TargetHeading);
RemoveAnimation(HeadingProperty); // remove holding animation in WPF RemoveAnimation(HeadingProperty); // remove holding animation in WPF
UpdateTransform(); UpdateTransform();
} }
} }
private void UpdateTransform(bool resetTransformOrigin = false) private void UpdateTransform(bool resetOrigin = false)
{ {
var center = transformOrigin ?? Center; var center = transformOrigin ?? Center;
@ -812,7 +827,7 @@ namespace MapControl
if (center.Latitude < -mapTransform.MaxLatitude || center.Latitude > mapTransform.MaxLatitude) if (center.Latitude < -mapTransform.MaxLatitude || center.Latitude > mapTransform.MaxLatitude)
{ {
center.Latitude = Math.Min(Math.Max(center.Latitude, -mapTransform.MaxLatitude), mapTransform.MaxLatitude); center.Latitude = Math.Min(Math.Max(center.Latitude, -mapTransform.MaxLatitude), mapTransform.MaxLatitude);
resetTransformOrigin = true; resetOrigin = true;
} }
InternalSetValue(CenterProperty, center); InternalSetValue(CenterProperty, center);
@ -823,7 +838,7 @@ namespace MapControl
InternalSetValue(CenterPointProperty, mapTransform.Transform(center)); InternalSetValue(CenterPointProperty, mapTransform.Transform(center));
} }
if (resetTransformOrigin) if (resetOrigin)
{ {
ResetTransformOrigin(); ResetTransformOrigin();
SetViewportTransform(center); SetViewportTransform(center);

View file

@ -3,6 +3,7 @@
// Licensed under the Microsoft Public License (Ms-PL) // Licensed under the Microsoft Public License (Ms-PL)
#if NETFX_CORE #if NETFX_CORE
using System;
using Windows.Foundation; using Windows.Foundation;
#else #else
using System.Windows; using System.Windows;
@ -25,21 +26,21 @@ namespace MapControl
public abstract double MaxLatitude { get; } public abstract double MaxLatitude { get; }
/// <summary> /// <summary>
/// Gets the scale factor of the map projection at the specified geographic location /// Gets the scale factor of the map projection at the specified
/// relative to the scale at latitude zero. /// geographic location relative to the scale at latitude zero.
/// </summary> /// </summary>
public abstract double RelativeScale(Location location); public abstract double RelativeScale(Location location);
/// <summary>
/// Transforms a cartesian coordinate point to a geographic location.
/// </summary>
public abstract Location Transform(Point point);
/// <summary> /// <summary>
/// Transforms a geographic location to a cartesian coordinate point. /// Transforms a geographic location to a cartesian coordinate point.
/// </summary> /// </summary>
public abstract Point Transform(Location location); public abstract Point Transform(Location location);
/// <summary>
/// Transforms a cartesian coordinate point to a geographic location.
/// </summary>
public abstract Location Transform(Point point);
/// <summary> /// <summary>
/// Transforms a geographic location to a cartesian coordinate point /// Transforms a geographic location to a cartesian coordinate point
/// with minumum distance to the specified reference longitude value. /// with minumum distance to the specified reference longitude value.
@ -61,5 +62,22 @@ namespace MapControl
return p; return p;
} }
/// <summary>
/// Transforms a geographic location by the specified translation in viewport coordinates.
/// </summary>
public Location Transform(Location origin, Point mapOrigin, Point translation)
{
#if NETFX_CORE
var latitudeTranslation = translation.Y / RelativeScale(origin);
if (Math.Abs(latitudeTranslation) < 1e-3) // avoid rounding errors
{
return new Location(origin.Latitude + latitudeTranslation, origin.Longitude + translation.X);
} }
#endif
return Transform(new Point(mapOrigin.X + translation.X, mapOrigin.Y + translation.Y));
}
}
} }

View file

@ -17,7 +17,7 @@ namespace MapControl
/// </summary> /// </summary>
public class MercatorTransform : MapTransform public class MercatorTransform : MapTransform
{ {
public static readonly double MaxLatitudeValue = Math.Atan(Math.Sinh(Math.PI)) / Math.PI * 180d; public static readonly double MaxLatitudeValue = YToLatitude(180d);
public static double RelativeScale(double latitude) public static double RelativeScale(double latitude)
{ {
@ -46,13 +46,12 @@ namespace MapControl
return double.PositiveInfinity; return double.PositiveInfinity;
} }
latitude *= Math.PI / 180d; return Math.Log(Math.Tan((latitude + 90d) * Math.PI / 360d)) / Math.PI * 180d;
return Math.Log(Math.Tan(latitude) + 1d / Math.Cos(latitude)) / Math.PI * 180d;
} }
public static double YToLatitude(double y) public static double YToLatitude(double y)
{ {
return Math.Atan(Math.Sinh(y * Math.PI / 180d)) / Math.PI * 180d; return Math.Atan(Math.Exp(y * Math.PI / 180d)) / Math.PI * 360d - 90d;
} }
public override double MaxLatitude public override double MaxLatitude

View file

@ -46,7 +46,7 @@ namespace MapControl
new Matrix(1d, 0d, 0d, 1d, TileSource.TileSize * TileGrid.XMin, TileSource.TileSize * TileGrid.YMin) new Matrix(1d, 0d, 0d, 1d, TileSource.TileSize * TileGrid.XMin, TileSource.TileSize * TileGrid.YMin)
.Scale(scale, scale) .Scale(scale, scale)
.Translate(offsetX, offsetY) .Translate(offsetX, offsetY)
.RotateAt(parentMap.Heading, parentMap.ViewportOrigin.X, parentMap.ViewportOrigin.Y); ; .RotateAt(parentMap.Heading, parentMap.ViewportOrigin.X, parentMap.ViewportOrigin.Y);
} }
} }
} }