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)
{
var position = e.GetPosition(this);
TranslateMap((Point)(position - mousePosition.Value));
TranslateMap(position - mousePosition.Value);
mousePosition = position;
}
}
@ -96,7 +96,7 @@ namespace MapControl
base.OnManipulationDelta(e);
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);
}
}

View file

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

View file

@ -53,6 +53,24 @@ namespace MapControl
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)
{
BeginAnimation(property, null);
@ -72,8 +90,8 @@ namespace MapControl
ViewportScale = Math.Pow(2d, ZoomLevel) * (double)TileSource.TileSize / 360d;
var transform = new Matrix(1d, 0d, 0d, 1d, -MapOrigin.X, -MapOrigin.Y);
transform.Rotate(-Heading);
transform.Scale(ViewportScale, -ViewportScale);
transform.Rotate(Heading);
transform.Translate(ViewportOrigin.X, ViewportOrigin.Y);
viewportTransform.Matrix = transform;

View file

@ -75,9 +75,6 @@ namespace MapControl
private DoubleAnimation headingAnimation;
private bool internalPropertyChange;
internal Point MapOrigin { get; private set; }
internal Point ViewportOrigin { get; private set; }
public MapBase()
{
Initialize();
@ -272,6 +269,9 @@ namespace MapControl
get { return scaleRotateTransform; }
}
internal Point MapOrigin { get; private set; }
internal Point ViewportOrigin { get; private set; }
/// <summary>
/// Gets the scaling factor from cartesian map coordinates to viewport coordinates.
/// </summary>
@ -337,18 +337,32 @@ namespace MapControl
}
/// <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>
public void TranslateMap(Point translation)
{
if (transformOrigin != null)
{
ResetTransformOrigin();
UpdateTransform();
}
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,25 +373,31 @@ namespace MapControl
/// </summary>
public void TransformMap(Point origin, Point translation, double rotation, double scale)
{
SetTransformOrigin(origin);
ViewportOrigin = new Point(ViewportOrigin.X + translation.X, ViewportOrigin.Y + translation.Y);
if (rotation != 0d)
if (rotation != 0d || scale != 1d)
{
var heading = (((Heading + rotation) % 360d) + 360d) % 360d;
InternalSetValue(HeadingProperty, heading);
InternalSetValue(TargetHeadingProperty, heading);
}
transformOrigin = ViewportPointToLocation(origin);
ViewportOrigin = new Point(origin.X + translation.X, origin.Y + translation.Y);
if (scale != 1d)
if (rotation != 0d)
{
var heading = (((Heading + rotation) % 360d) + 360d) % 360d;
InternalSetValue(HeadingProperty, heading);
InternalSetValue(TargetHeadingProperty, heading);
}
if (scale != 1d)
{
var zoomLevel = Math.Min(Math.Max(ZoomLevel + Math.Log(scale, 2d), MinZoomLevel), MaxZoomLevel);
InternalSetValue(ZoomLevelProperty, zoomLevel);
InternalSetValue(TargetZoomLevelProperty, zoomLevel);
}
UpdateTransform(true);
}
else
{
var zoomLevel = Math.Min(Math.Max(ZoomLevel + Math.Log(scale, 2d), MinZoomLevel), MaxZoomLevel);
InternalSetValue(ZoomLevelProperty, zoomLevel);
InternalSetValue(TargetZoomLevelProperty, zoomLevel);
TranslateMap(translation); // more precise
}
UpdateTransform(true);
}
/// <summary>
@ -567,7 +587,6 @@ namespace MapControl
if (!internalPropertyChange)
{
AdjustCenterProperty(CenterProperty, ref center);
ResetTransformOrigin();
UpdateTransform();
if (centerAnimation == null)
@ -616,8 +635,6 @@ namespace MapControl
InternalSetValue(CenterProperty, TargetCenter);
InternalSetValue(CenterPointProperty, mapTransform.Transform(TargetCenter));
RemoveAnimation(CenterPointProperty); // remove holding animation in WPF
ResetTransformOrigin();
UpdateTransform();
}
}
@ -628,7 +645,6 @@ namespace MapControl
{
centerPoint.X = Location.NormalizeLongitude(centerPoint.X);
InternalSetValue(CenterProperty, mapTransform.Transform(centerPoint));
ResetTransformOrigin();
UpdateTransform();
}
}
@ -720,7 +736,7 @@ namespace MapControl
InternalSetValue(ZoomLevelProperty, TargetZoomLevel);
RemoveAnimation(ZoomLevelProperty); // remove holding animation in WPF
UpdateTransform(true);
Dispatcher.BeginInvoke(() => UpdateTransform(true));
}
}
@ -793,12 +809,11 @@ namespace MapControl
InternalSetValue(HeadingProperty, TargetHeading);
RemoveAnimation(HeadingProperty); // remove holding animation in WPF
UpdateTransform();
}
}
private void UpdateTransform(bool resetTransformOrigin = false)
private void UpdateTransform(bool resetOrigin = false)
{
var center = transformOrigin ?? Center;
@ -812,7 +827,7 @@ namespace MapControl
if (center.Latitude < -mapTransform.MaxLatitude || center.Latitude > mapTransform.MaxLatitude)
{
center.Latitude = Math.Min(Math.Max(center.Latitude, -mapTransform.MaxLatitude), mapTransform.MaxLatitude);
resetTransformOrigin = true;
resetOrigin = true;
}
InternalSetValue(CenterProperty, center);
@ -823,7 +838,7 @@ namespace MapControl
InternalSetValue(CenterPointProperty, mapTransform.Transform(center));
}
if (resetTransformOrigin)
if (resetOrigin)
{
ResetTransformOrigin();
SetViewportTransform(center);

View file

@ -3,6 +3,7 @@
// Licensed under the Microsoft Public License (Ms-PL)
#if NETFX_CORE
using System;
using Windows.Foundation;
#else
using System.Windows;
@ -25,21 +26,21 @@ namespace MapControl
public abstract double MaxLatitude { get; }
/// <summary>
/// Gets the scale factor of the map projection at the specified geographic location
/// relative to the scale at latitude zero.
/// Gets the scale factor of the map projection at the specified
/// geographic location relative to the scale at latitude zero.
/// </summary>
public abstract double RelativeScale(Location location);
/// <summary>
/// Transforms a cartesian coordinate point to a geographic location.
/// </summary>
public abstract Location Transform(Point point);
/// <summary>
/// Transforms a geographic location to a cartesian coordinate point.
/// </summary>
public abstract Point Transform(Location location);
/// <summary>
/// Transforms a cartesian coordinate point to a geographic location.
/// </summary>
public abstract Location Transform(Point point);
/// <summary>
/// Transforms a geographic location to a cartesian coordinate point
/// with minumum distance to the specified reference longitude value.
@ -61,5 +62,22 @@ namespace MapControl
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>
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)
{
@ -46,13 +46,12 @@ namespace MapControl
return double.PositiveInfinity;
}
latitude *= Math.PI / 180d;
return Math.Log(Math.Tan(latitude) + 1d / Math.Cos(latitude)) / Math.PI * 180d;
return Math.Log(Math.Tan((latitude + 90d) * Math.PI / 360d)) / Math.PI * 180d;
}
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

View file

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