From 4a37e94d7726740cbdb792eb3fbc4975997833a7 Mon Sep 17 00:00:00 2001 From: ClemensF Date: Sat, 30 Jan 2016 11:50:53 +0100 Subject: [PATCH] Workaround for rounding errors in MapTransform. --- MapControl/Map.WPF.cs | 4 +- MapControl/MapBase.Silverlight.WinRT.cs | 7 ++- MapControl/MapBase.WPF.cs | 20 ++++++- MapControl/MapBase.cs | 73 ++++++++++++++--------- MapControl/MapTransform.cs | 32 +++++++--- MapControl/MercatorTransform.cs | 7 +-- MapControl/TileLayer.Silverlight.WinRT.cs | 2 +- 7 files changed, 98 insertions(+), 47 deletions(-) diff --git a/MapControl/Map.WPF.cs b/MapControl/Map.WPF.cs index 57c92d46..485698b5 100644 --- a/MapControl/Map.WPF.cs +++ b/MapControl/Map.WPF.cs @@ -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); } } diff --git a/MapControl/MapBase.Silverlight.WinRT.cs b/MapControl/MapBase.Silverlight.WinRT.cs index 80562528..75f119f5 100644 --- a/MapControl/MapBase.Silverlight.WinRT.cs +++ b/MapControl/MapBase.Silverlight.WinRT.cs @@ -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; } } } diff --git a/MapControl/MapBase.WPF.cs b/MapControl/MapBase.WPF.cs index f6f72d9a..7c178000 100644 --- a/MapControl/MapBase.WPF.cs +++ b/MapControl/MapBase.WPF.cs @@ -53,6 +53,24 @@ namespace MapControl typeof(MapBase), new FrameworkPropertyMetadata(Brushes.Transparent)); } + /// + /// Changes the Center property according to the specified translation in viewport coordinates. + /// + public void TranslateMap(Vector translation) + { + TranslateMap((Point)translation); + } + + /// + /// 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. + /// + 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; diff --git a/MapControl/MapBase.cs b/MapControl/MapBase.cs index e19144e8..e54b2956 100644 --- a/MapControl/MapBase.cs +++ b/MapControl/MapBase.cs @@ -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; } + /// /// Gets the scaling factor from cartesian map coordinates to viewport coordinates. /// @@ -337,18 +337,32 @@ namespace MapControl } /// - /// 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. /// 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 /// 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); } /// @@ -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); diff --git a/MapControl/MapTransform.cs b/MapControl/MapTransform.cs index fe5aa3f5..9be6c379 100644 --- a/MapControl/MapTransform.cs +++ b/MapControl/MapTransform.cs @@ -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; } /// - /// 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. /// public abstract double RelativeScale(Location location); - /// - /// Transforms a cartesian coordinate point to a geographic location. - /// - public abstract Location Transform(Point point); - /// /// Transforms a geographic location to a cartesian coordinate point. /// public abstract Point Transform(Location location); + /// + /// Transforms a cartesian coordinate point to a geographic location. + /// + public abstract Location Transform(Point point); + /// /// 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; } + + /// + /// Transforms a geographic location by the specified translation in viewport coordinates. + /// + 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)); + } } + } diff --git a/MapControl/MercatorTransform.cs b/MapControl/MercatorTransform.cs index 002e776a..10f7ddeb 100644 --- a/MapControl/MercatorTransform.cs +++ b/MapControl/MercatorTransform.cs @@ -17,7 +17,7 @@ namespace MapControl /// 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 diff --git a/MapControl/TileLayer.Silverlight.WinRT.cs b/MapControl/TileLayer.Silverlight.WinRT.cs index 98ebfb39..9cef8906 100644 --- a/MapControl/TileLayer.Silverlight.WinRT.cs +++ b/MapControl/TileLayer.Silverlight.WinRT.cs @@ -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); } } }