From c7cb2efcdb3ee4254e510a7c124d586c15c7f61a Mon Sep 17 00:00:00 2001 From: ClemensF Date: Thu, 26 Mar 2020 19:08:20 +0100 Subject: [PATCH] Version 5.0: Separated map projection and view transform. --- FileDbCache/UWP/Properties/AssemblyInfo.cs | 4 +- FileDbCache/WPF/Properties/AssemblyInfo.cs | 4 +- MBTiles/UWP/Properties/AssemblyInfo.cs | 4 +- MBTiles/WPF/Properties/AssemblyInfo.cs | 4 +- .../Shared/AutoEquirectangularProjection.cs | 16 +- .../Shared/AzimuthalEquidistantProjection.cs | 16 +- MapControl/Shared/AzimuthalProjection.cs | 4 +- .../Shared/EquirectangularProjection.cs | 28 +-- MapControl/Shared/GnomonicProjection.cs | 16 +- MapControl/Shared/MapBase.cs | 76 ++++++-- MapControl/Shared/MapGraticule.cs | 2 +- MapControl/Shared/MapImageLayer.cs | 2 +- MapControl/Shared/MapPanel.cs | 17 +- MapControl/Shared/MapProjection.cs | 165 ++---------------- MapControl/Shared/MapShape.cs | 6 +- MapControl/Shared/MapTileLayer.cs | 62 +++---- MapControl/Shared/OrthographicProjection.cs | 20 +-- MapControl/Shared/StereographicProjection.cs | 16 +- .../Shared/{TileGrid.cs => TileMatrix.cs} | 4 +- MapControl/Shared/ViewTransform.cs | 118 +++++++++++++ MapControl/Shared/WebMercatorProjection.cs | 16 +- MapControl/Shared/WmsImageLayer.cs | 4 +- MapControl/Shared/WmtsTileLayer.cs | 6 +- MapControl/Shared/WmtsTileMatrixLayer.cs | 12 +- MapControl/Shared/WorldMercatorProjection.cs | 16 +- MapControl/UWP/MapBase.UWP.cs | 4 - MapControl/UWP/MapControl.UWP.csproj | 9 +- MapControl/UWP/MapGraticule.UWP.cs | 17 +- MapControl/UWP/Properties/AssemblyInfo.cs | 4 +- MapControl/WPF/MapBase.WPF.cs | 7 - MapControl/WPF/MapControl.WPF.csproj | 2 +- MapControl/WPF/MapGraticule.WPF.cs | 16 +- MapImages/UWP/Properties/AssemblyInfo.cs | 4 +- MapImages/WPF/Properties/AssemblyInfo.cs | 4 +- MapProjections/Shared/GeoApiProjection.cs | 30 ++-- .../Shared/PolarStereographicProjection.cs | 10 +- .../Shared/WebMercatorProjection.cs | 4 +- .../Shared/WorldMercatorProjection.cs | 4 +- MapProjections/UWP/Properties/AssemblyInfo.cs | 4 +- MapProjections/WPF/Properties/AssemblyInfo.cs | 4 +- SQLiteCache/UWP/Properties/AssemblyInfo.cs | 4 +- SQLiteCache/WPF/Properties/AssemblyInfo.cs | 4 +- SampleApps/ProjectionDemo/MainWindow.xaml.cs | 2 +- .../ProjectionDemo/ProjectionDemo.csproj | 2 +- .../UniversalApp/Properties/AssemblyInfo.cs | 4 +- .../WpfApplication/Properties/AssemblyInfo.cs | 4 +- SampleApps/WpfCoreApp/WpfCoreApp.csproj | 2 +- 47 files changed, 401 insertions(+), 382 deletions(-) rename MapControl/Shared/{TileGrid.cs => TileMatrix.cs} (82%) create mode 100644 MapControl/Shared/ViewTransform.cs diff --git a/FileDbCache/UWP/Properties/AssemblyInfo.cs b/FileDbCache/UWP/Properties/AssemblyInfo.cs index fa00c14f..0304a72b 100644 --- a/FileDbCache/UWP/Properties/AssemblyInfo.cs +++ b/FileDbCache/UWP/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("4.17.0")] -[assembly: AssemblyFileVersion("4.17.0")] +[assembly: AssemblyVersion("5.0.0")] +[assembly: AssemblyFileVersion("5.0.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/FileDbCache/WPF/Properties/AssemblyInfo.cs b/FileDbCache/WPF/Properties/AssemblyInfo.cs index a2e41b5d..384d334e 100644 --- a/FileDbCache/WPF/Properties/AssemblyInfo.cs +++ b/FileDbCache/WPF/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("4.17.0")] -[assembly: AssemblyFileVersion("4.17.0")] +[assembly: AssemblyVersion("5.0.0")] +[assembly: AssemblyFileVersion("5.0.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/MBTiles/UWP/Properties/AssemblyInfo.cs b/MBTiles/UWP/Properties/AssemblyInfo.cs index 163ccd7c..caf20f8c 100644 --- a/MBTiles/UWP/Properties/AssemblyInfo.cs +++ b/MBTiles/UWP/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("4.17.0")] -[assembly: AssemblyFileVersion("4.17.0")] +[assembly: AssemblyVersion("5.0.0")] +[assembly: AssemblyFileVersion("5.0.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/MBTiles/WPF/Properties/AssemblyInfo.cs b/MBTiles/WPF/Properties/AssemblyInfo.cs index 01f28b26..26ac782f 100644 --- a/MBTiles/WPF/Properties/AssemblyInfo.cs +++ b/MBTiles/WPF/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("4.17.0")] -[assembly: AssemblyFileVersion("4.17.0")] +[assembly: AssemblyVersion("5.0.0")] +[assembly: AssemblyFileVersion("5.0.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/MapControl/Shared/AutoEquirectangularProjection.cs b/MapControl/Shared/AutoEquirectangularProjection.cs index a333446c..6949e530 100644 --- a/MapControl/Shared/AutoEquirectangularProjection.cs +++ b/MapControl/Shared/AutoEquirectangularProjection.cs @@ -16,22 +16,22 @@ namespace MapControl CrsId = "AUTO2:42004"; } - public override Point LocationToPoint(Location location) + public override Point LocationToMap(Location location) { - var xScale = Wgs84MetersPerDegree * Math.Cos(ProjectionCenter.Latitude * Math.PI / 180d); + var xScale = UnitsPerDegree * Math.Cos(Center.Latitude * Math.PI / 180d); return new Point( - xScale * (location.Longitude - ProjectionCenter.Longitude), - Wgs84MetersPerDegree * location.Latitude); + xScale * (location.Longitude - Center.Longitude), + UnitsPerDegree * location.Latitude); } - public override Location PointToLocation(Point point) + public override Location MapToLocation(Point point) { - var xScale = Wgs84MetersPerDegree * Math.Cos(ProjectionCenter.Latitude * Math.PI / 180d); + var xScale = UnitsPerDegree * Math.Cos(Center.Latitude * Math.PI / 180d); return new Location( - point.Y / Wgs84MetersPerDegree, - point.X / xScale + ProjectionCenter.Longitude); + point.Y / UnitsPerDegree, + point.X / xScale + Center.Longitude); } } } diff --git a/MapControl/Shared/AzimuthalEquidistantProjection.cs b/MapControl/Shared/AzimuthalEquidistantProjection.cs index 4ba2ea68..fd77c207 100644 --- a/MapControl/Shared/AzimuthalEquidistantProjection.cs +++ b/MapControl/Shared/AzimuthalEquidistantProjection.cs @@ -16,35 +16,35 @@ namespace MapControl { // No standard CRS ID - public override Point LocationToPoint(Location location) + public override Point LocationToMap(Location location) { - if (location.Equals(ProjectionCenter)) + if (location.Equals(Center)) { return new Point(); } double azimuth, distance; - GetAzimuthDistance(ProjectionCenter, location, out azimuth, out distance); + GetAzimuthDistance(Center, location, out azimuth, out distance); - var mapDistance = distance * TrueScale * 180d / Math.PI; + var mapDistance = distance * UnitsPerDegree * 180d / Math.PI; return new Point(mapDistance * Math.Sin(azimuth), mapDistance * Math.Cos(azimuth)); } - public override Location PointToLocation(Point point) + public override Location MapToLocation(Point point) { if (point.X == 0d && point.Y == 0d) { - return new Location(ProjectionCenter.Latitude, ProjectionCenter.Longitude); + return new Location(Center.Latitude, Center.Longitude); } var azimuth = Math.Atan2(point.X, point.Y); var mapDistance = Math.Sqrt(point.X * point.X + point.Y * point.Y); - var distance = mapDistance / (TrueScale * 180d / Math.PI); + var distance = mapDistance / (UnitsPerDegree * 180d / Math.PI); - return GetLocation(ProjectionCenter, azimuth, distance); + return GetLocation(Center, azimuth, distance); } } } diff --git a/MapControl/Shared/AzimuthalProjection.cs b/MapControl/Shared/AzimuthalProjection.cs index 021d4bd4..1b77cfe2 100644 --- a/MapControl/Shared/AzimuthalProjection.cs +++ b/MapControl/Shared/AzimuthalProjection.cs @@ -27,7 +27,7 @@ namespace MapControl if (cbbox != null) { - var center = LocationToPoint(cbbox.Center); + var center = LocationToMap(cbbox.Center); return new Rect( center.X - cbbox.Width / 2d, center.Y - cbbox.Height / 2d, @@ -39,7 +39,7 @@ namespace MapControl public override BoundingBox RectToBoundingBox(Rect rect) { - var center = PointToLocation(new Point(rect.X + rect.Width / 2d, rect.Y + rect.Height / 2d)); + var center = MapToLocation(new Point(rect.X + rect.Width / 2d, rect.Y + rect.Height / 2d)); return new CenteredBoundingBox(center, rect.Width, rect.Height); // width and height in meters } diff --git a/MapControl/Shared/EquirectangularProjection.cs b/MapControl/Shared/EquirectangularProjection.cs index 192a24a6..ebdb3c0b 100644 --- a/MapControl/Shared/EquirectangularProjection.cs +++ b/MapControl/Shared/EquirectangularProjection.cs @@ -14,7 +14,7 @@ namespace MapControl { /// /// Equirectangular Projection. - /// Longitude and Latitude values are transformed identically to X and Y. + /// Longitude and Latitude values are transformed linearly to X and Y in meters. /// public class EquirectangularProjection : MapProjection { @@ -23,33 +23,33 @@ namespace MapControl CrsId = "EPSG:4326"; } - public override double TrueScale - { - get { return 1d; } - } - - public override Vector GetMapScale(Location location) + public override Vector GetRelativeScale(Location location) { return new Vector( - ViewportScale / (Wgs84MetersPerDegree * Math.Cos(location.Latitude * Math.PI / 180d)), - ViewportScale / Wgs84MetersPerDegree); + 1d / Math.Cos(location.Latitude * Math.PI / 180d), + 1d); } - public override Point LocationToPoint(Location location) + public override Point LocationToMap(Location location) { - return new Point(location.Longitude, location.Latitude); + return new Point( + location.Longitude * UnitsPerDegree, + location.Latitude * UnitsPerDegree); } - public override Location PointToLocation(Point point) + public override Location MapToLocation(Point point) { - return new Location(point.Y, point.X); + return new Location( + point.Y / UnitsPerDegree, + point.X / UnitsPerDegree); } public override string GetBboxValue(Rect rect) { return string.Format(CultureInfo.InvariantCulture, CrsId != "CRS:84" ? "{1},{0},{3},{2}" : "{0},{1},{2},{3}", - rect.X, rect.Y, (rect.X + rect.Width), (rect.Y + rect.Height)); + rect.X / UnitsPerDegree, rect.Y / UnitsPerDegree, + (rect.X + rect.Width) / UnitsPerDegree, (rect.Y + rect.Height) / UnitsPerDegree); } } } diff --git a/MapControl/Shared/GnomonicProjection.cs b/MapControl/Shared/GnomonicProjection.cs index 52361d5a..be7976cb 100644 --- a/MapControl/Shared/GnomonicProjection.cs +++ b/MapControl/Shared/GnomonicProjection.cs @@ -19,37 +19,37 @@ namespace MapControl CrsId = "AUTO2:97001"; // GeoServer non-standard CRS ID } - public override Point LocationToPoint(Location location) + public override Point LocationToMap(Location location) { - if (location.Equals(ProjectionCenter)) + if (location.Equals(Center)) { return new Point(); } double azimuth, distance; - GetAzimuthDistance(ProjectionCenter, location, out azimuth, out distance); + GetAzimuthDistance(Center, location, out azimuth, out distance); var mapDistance = distance < Math.PI / 2d - ? Math.Tan(distance) * TrueScale * 180d / Math.PI + ? Math.Tan(distance) * UnitsPerDegree * 180d / Math.PI : double.PositiveInfinity; return new Point(mapDistance * Math.Sin(azimuth), mapDistance * Math.Cos(azimuth)); } - public override Location PointToLocation(Point point) + public override Location MapToLocation(Point point) { if (point.X == 0d && point.Y == 0d) { - return new Location(ProjectionCenter.Latitude, ProjectionCenter.Longitude); + return new Location(Center.Latitude, Center.Longitude); } var azimuth = Math.Atan2(point.X, point.Y); var mapDistance = Math.Sqrt(point.X * point.X + point.Y * point.Y); - var distance = Math.Atan(mapDistance / (TrueScale * 180d / Math.PI)); + var distance = Math.Atan(mapDistance / (UnitsPerDegree * 180d / Math.PI)); - return GetLocation(ProjectionCenter, azimuth, distance); + return GetLocation(Center, azimuth, distance); } } } diff --git a/MapControl/Shared/MapBase.cs b/MapControl/Shared/MapBase.cs index 5e664769..ce4d785f 100644 --- a/MapControl/Shared/MapBase.cs +++ b/MapControl/Shared/MapBase.cs @@ -4,6 +4,7 @@ using System; #if WINDOWS_UWP +using Windows.Foundation; using Windows.UI.Xaml; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media.Animation; @@ -22,7 +23,8 @@ namespace MapControl } /// - /// The map control. Displays map content provided by one or more MapTileLayers or MapImageLayers. + /// The map control. Displays map content provided by one or more tile or image layers, + /// i.e. 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. @@ -37,7 +39,7 @@ namespace MapControl public static readonly DependencyProperty MapProjectionProperty = DependencyProperty.Register( nameof(MapProjection), typeof(MapProjection), typeof(MapBase), - new PropertyMetadata(null, (o, e) => ((MapBase)o).MapProjectionPropertyChanged())); + new PropertyMetadata(new WebMercatorProjection(), (o, e) => ((MapBase)o).MapProjectionPropertyChanged())); public static readonly DependencyProperty ProjectionCenterProperty = DependencyProperty.Register( nameof(ProjectionCenter), typeof(Location), typeof(MapBase), @@ -224,6 +226,11 @@ namespace MapControl /// /// Gets the transformation from cartesian map coordinates to viewport coordinates. /// + public ViewTransform ViewTransform { get; } = new ViewTransform(); + + /// + /// Gets the transformation from cartesian map coordinates to viewport coordinates as MatrixTransform. + /// public MatrixTransform ViewportTransform { get; } = new MatrixTransform(); /// @@ -239,14 +246,23 @@ namespace MapControl /// /// Gets the combination of ScaleTransform and RotateTransform /// - public TransformGroup ScaleRotateTransform { get; } = new TransformGroup(); + public TransformGroup ScaleRotateTransform + { + get + { + var transform = new TransformGroup(); + transform.Children.Add(ScaleTransform); + transform.Children.Add(RotateTransform); + return transform; + } + } /// /// Transforms a Location in geographic coordinates to a Point in viewport coordinates. /// public Point LocationToViewportPoint(Location location) { - return MapProjection.LocationToViewportPoint(location); + return ViewTransform.MapToView(MapProjection.LocationToMap(location)); } /// @@ -254,7 +270,25 @@ namespace MapControl /// public Location ViewportPointToLocation(Point point) { - return MapProjection.ViewportPointToLocation(point); + return MapProjection.MapToLocation(ViewTransform.ViewToMap(point)); + } + + /// + /// Transforms a Rect in viewport coordinates to a BoundingBox in geographic coordinates. + /// + public BoundingBox ViewportRectToBoundingBox(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)); + + rect.X = Math.Min(p1.X, Math.Min(p2.X, Math.Min(p3.X, p4.X))); + rect.Y = Math.Min(p1.Y, Math.Min(p2.Y, Math.Min(p3.Y, p4.Y))); + rect.Width = Math.Max(p1.X, Math.Max(p2.X, Math.Max(p3.X, p4.X))) - rect.X; + rect.Height = Math.Max(p1.Y, Math.Max(p2.Y, Math.Max(p3.Y, p4.Y))) - rect.Y; + + return MapProjection.RectToBoundingBox(rect); } /// @@ -263,7 +297,7 @@ namespace MapControl /// public void SetTransformCenter(Point center) { - transformCenter = MapProjection.ViewportPointToLocation(center); + transformCenter = ViewportPointToLocation(center); viewportCenter = center; } @@ -289,7 +323,7 @@ namespace MapControl if (translation.X != 0d || translation.Y != 0d) { - Center = MapProjection.ViewportPointToLocation(viewportCenter - translation); + Center = ViewportPointToLocation(viewportCenter - translation); } } @@ -302,7 +336,7 @@ namespace MapControl { if (rotation != 0d || scale != 1d) { - transformCenter = MapProjection.ViewportPointToLocation(center); + transformCenter = ViewportPointToLocation(center); viewportCenter = center + translation; if (rotation != 0d) @@ -351,10 +385,10 @@ namespace MapControl var rect = MapProjection.BoundingBoxToRect(boundingBox); var center = new Point(rect.X + rect.Width / 2d, rect.Y + rect.Height / 2d); var scale = Math.Min(RenderSize.Width / rect.Width, RenderSize.Height / rect.Height) - * MapProjection.TrueScale * 360d / 256d; + * MapProjection.Wgs84MetersPerDegree * 360d / 256d; TargetZoomLevel = Math.Log(scale, 2d); - TargetCenter = MapProjection.PointToLocation(center); + TargetCenter = MapProjection.MapToLocation(center); TargetHeading = 0d; } @@ -669,13 +703,16 @@ namespace MapControl private void UpdateTransform(bool resetTransformCenter = false, bool projectionChanged = false) { var projection = MapProjection; + var viewportScale = 256d * Math.Pow(2d, ZoomLevel) / (360d * MapProjection.Wgs84MetersPerDegree); var center = transformCenter ?? Center; - projection.SetViewportTransform(ProjectionCenter ?? Center, center, viewportCenter, ZoomLevel, Heading); + projection.Center = ProjectionCenter ?? Center; + + ViewTransform.SetTransform(projection.LocationToMap(center), viewportCenter, viewportScale, Heading); if (transformCenter != null) { - center = projection.ViewportPointToLocation(new Point(RenderSize.Width / 2d, RenderSize.Height / 2d)); + center = ViewportPointToLocation(new Point(RenderSize.Width / 2d, RenderSize.Height / 2d)); center.Longitude = Location.NormalizeLongitude(center.Longitude); if (center.Latitude < -projection.MaxLatitude || center.Latitude > projection.MaxLatitude) @@ -694,17 +731,20 @@ namespace MapControl if (resetTransformCenter) { ResetTransformCenter(); - projection.SetViewportTransform(ProjectionCenter ?? center, center, viewportCenter, ZoomLevel, Heading); + + projection.Center = ProjectionCenter ?? center; + + ViewTransform.SetTransform(projection.LocationToMap(center), viewportCenter, viewportScale, Heading); } } - ViewportTransform.Matrix = projection.ViewportTransform; + ViewportTransform.Matrix = ViewTransform.MapToViewMatrix; - var scale = projection.GetMapScale(center); - ScaleTransform.ScaleX = scale.X; - ScaleTransform.ScaleY = scale.Y; + var scale = projection.GetRelativeScale(center); + ScaleTransform.ScaleX = scale.X * ViewTransform.Scale; + ScaleTransform.ScaleY = scale.Y * ViewTransform.Scale; - RotateTransform.Angle = Heading; + RotateTransform.Angle = ViewTransform.Rotation; OnViewportChanged(new ViewportChangedEventArgs(projectionChanged, Center.Longitude - centerLongitude)); diff --git a/MapControl/Shared/MapGraticule.cs b/MapControl/Shared/MapGraticule.cs index 316aa81a..b06bfef1 100644 --- a/MapControl/Shared/MapGraticule.cs +++ b/MapControl/Shared/MapGraticule.cs @@ -30,7 +30,7 @@ namespace MapControl private double GetLineDistance() { - var pixelPerDegree = ParentMap.MapProjection.ViewportScale * ParentMap.MapProjection.TrueScale; + var pixelPerDegree = ParentMap.ViewTransform.Scale * ParentMap.MapProjection.UnitsPerDegree; var minDistance = MinLineDistance / pixelPerDegree; var scale = 1d; diff --git a/MapControl/Shared/MapImageLayer.cs b/MapControl/Shared/MapImageLayer.cs index 574a3899..a8f92ba7 100644 --- a/MapControl/Shared/MapImageLayer.cs +++ b/MapControl/Shared/MapImageLayer.cs @@ -258,7 +258,7 @@ namespace MapControl var y = (ParentMap.RenderSize.Height - height) / 2d; var rect = new Rect(x, y, width, height); - BoundingBox = ParentMap.MapProjection.ViewportRectToBoundingBox(rect); + BoundingBox = ParentMap.ViewportRectToBoundingBox(rect); if (BoundingBox != null) { diff --git a/MapControl/Shared/MapPanel.cs b/MapControl/Shared/MapPanel.cs index 8941efe3..8b400c4c 100644 --- a/MapControl/Shared/MapPanel.cs +++ b/MapControl/Shared/MapPanel.cs @@ -145,14 +145,13 @@ namespace MapControl private Point ArrangeElement(FrameworkElement element, Location location) { - var projection = parentMap.MapProjection; - var pos = projection.LocationToViewportPoint(location); + var pos = parentMap.LocationToViewportPoint(location); - if (projection.IsNormalCylindrical && + if (parentMap.MapProjection.IsNormalCylindrical && (pos.X < 0d || pos.X > parentMap.RenderSize.Width || pos.Y < 0d || pos.Y > parentMap.RenderSize.Height)) { - pos = projection.LocationToViewportPoint(new Location( + pos = parentMap.LocationToViewportPoint(new Location( location.Latitude, Location.NearestLongitude(location.Longitude, parentMap.Center.Longitude))); } @@ -202,20 +201,20 @@ namespace MapControl var projection = parentMap.MapProjection; var rect = projection.BoundingBoxToRect(boundingBox); var center = new Point(rect.X + rect.Width / 2d, rect.Y + rect.Height / 2d); - var pos = projection.ViewportTransform.Transform(center); + var pos = parentMap.ViewTransform.MapToView(center); if (projection.IsNormalCylindrical && (pos.X < 0d || pos.X > parentMap.RenderSize.Width || pos.Y < 0d || pos.Y > parentMap.RenderSize.Height)) { - var location = projection.PointToLocation(center); + var location = projection.MapToLocation(center); location.Longitude = Location.NearestLongitude(location.Longitude, parentMap.Center.Longitude); - pos = projection.LocationToViewportPoint(location); + pos = parentMap.LocationToViewportPoint(location); } - rect.Width *= projection.ViewportScale; - rect.Height *= projection.ViewportScale; + rect.Width *= parentMap.ViewTransform.Scale; + rect.Height *= parentMap.ViewTransform.Scale; rect.X = pos.X - rect.Width / 2d; rect.Y = pos.Y - rect.Height / 2d; diff --git a/MapControl/Shared/MapProjection.cs b/MapControl/Shared/MapProjection.cs index 125e85e4..f13fb09b 100644 --- a/MapControl/Shared/MapProjection.cs +++ b/MapControl/Shared/MapProjection.cs @@ -6,24 +6,26 @@ using System; using System.Globalization; #if WINDOWS_UWP using Windows.Foundation; -using Windows.UI.Xaml.Media; #else using System.Windows; -using System.Windows.Media; #endif namespace MapControl { /// - /// Defines a map projection between geographic coordinates, cartesian map coordinates and viewport coordinates. + /// Defines a map projection between geographic coordinates and cartesian map coordinates. /// public abstract class MapProjection { public const double Wgs84EquatorialRadius = 6378137d; + public const double Wgs84MetersPerDegree = Wgs84EquatorialRadius * Math.PI / 180d; public const double Wgs84Flattening = 1d / 298.257223563; public static readonly double Wgs84Eccentricity = Math.Sqrt((2d - Wgs84Flattening) * Wgs84Flattening); - public const double Wgs84MetersPerDegree = Wgs84EquatorialRadius * Math.PI / 180d; + /// + /// Gets or sets the projection center. Only relevant for azimuthal projections. + /// + public Location Center { get; set; } = new Location(); /// /// Gets or sets the WMS 1.3.0 CRS identifier. @@ -58,54 +60,28 @@ namespace MapControl /// Gets the scale factor from geographic to cartesian coordinates, on the line of true scale of a /// cylindrical projection (usually the equator), or at the projection center of an azimuthal projection. /// - public virtual double TrueScale + public virtual double UnitsPerDegree { get { return Wgs84MetersPerDegree; } } /// - /// Gets the projection center. Only relevant for azimuthal projections. + /// Gets the relative map scale at the specified Location. /// - public Location ProjectionCenter { get; private set; } = new Location(); - - /// - /// Gets the transform matrix from cartesian map coordinates to viewport coordinates. - /// - public Matrix ViewportTransform { get; private set; } - - /// - /// Gets the transform matrix from viewport coordinates to cartesian map coordinates. - /// - public Matrix InverseViewportTransform { get; private set; } - - /// - /// Gets the rotation angle of the ViewportTransform matrix. - /// - public double ViewportRotation { get; private set; } - - /// - /// Gets the scaling factor from cartesian map coordinates to viewport coordinates - /// at the projection's point of true scale. - /// - public double ViewportScale { get; private set; } - - /// - /// Gets the map scale at the specified Location as viewport coordinate units per meter (px/m). - /// - public virtual Vector GetMapScale(Location location) + public virtual Vector GetRelativeScale(Location location) { - return new Vector(ViewportScale, ViewportScale); + return new Vector(1, 1); } /// /// Transforms a Location in geographic coordinates to a Point in cartesian map coordinates. /// - public abstract Point LocationToPoint(Location location); + public abstract Point LocationToMap(Location location); /// /// Transforms a Point in cartesian map coordinates to a Location in geographic coordinates. /// - public abstract Location PointToLocation(Point point); + public abstract Location MapToLocation(Point point); /// /// Transforms a BoundingBox in geographic coordinates to a Rect in cartesian map coordinates. @@ -113,8 +89,8 @@ namespace MapControl public virtual Rect BoundingBoxToRect(BoundingBox boundingBox) { return new Rect( - LocationToPoint(new Location(boundingBox.South, boundingBox.West)), - LocationToPoint(new Location(boundingBox.North, boundingBox.East))); + LocationToMap(new Location(boundingBox.South, boundingBox.West)), + LocationToMap(new Location(boundingBox.North, boundingBox.East))); } /// @@ -122,53 +98,19 @@ namespace MapControl /// public virtual BoundingBox RectToBoundingBox(Rect rect) { - var sw = PointToLocation(new Point(rect.X, rect.Y)); - var ne = PointToLocation(new Point(rect.X + rect.Width, rect.Y + rect.Height)); + var sw = MapToLocation(new Point(rect.X, rect.Y)); + var ne = MapToLocation(new Point(rect.X + rect.Width, rect.Y + rect.Height)); return new BoundingBox(sw.Latitude, sw.Longitude, ne.Latitude, ne.Longitude); } - /// - /// Transforms a Location in geographic coordinates to a Point in viewport coordinates. - /// - public Point LocationToViewportPoint(Location location) - { - return ViewportTransform.Transform(LocationToPoint(location)); - } - - /// - /// Transforms a Point in viewport coordinates to a Location in geographic coordinates. - /// - public Location ViewportPointToLocation(Point point) - { - return PointToLocation(InverseViewportTransform.Transform(point)); - } - - /// - /// Transforms a Rect in viewport coordinates to a BoundingBox in geographic coordinates. - /// - public BoundingBox ViewportRectToBoundingBox(Rect rect) - { - var p1 = InverseViewportTransform.Transform(new Point(rect.X, rect.Y)); - var p2 = InverseViewportTransform.Transform(new Point(rect.X, rect.Y + rect.Height)); - var p3 = InverseViewportTransform.Transform(new Point(rect.X + rect.Width, rect.Y)); - var p4 = InverseViewportTransform.Transform(new Point(rect.X + rect.Width, rect.Y + rect.Height)); - - rect.X = Math.Min(p1.X, Math.Min(p2.X, Math.Min(p3.X, p4.X))); - rect.Y = Math.Min(p1.Y, Math.Min(p2.Y, Math.Min(p3.Y, p4.Y))); - rect.Width = Math.Max(p1.X, Math.Max(p2.X, Math.Max(p3.X, p4.X))) - rect.X; - rect.Height = Math.Max(p1.Y, Math.Max(p2.Y, Math.Max(p3.Y, p4.Y))) - rect.Y; - - return RectToBoundingBox(rect); - } - /// /// Gets the CRS parameter value for a WMS GetMap request. /// public virtual string GetCrsValue() { return CrsId.StartsWith("AUTO:") || CrsId.StartsWith("AUTO2:") - ? string.Format(CultureInfo.InvariantCulture, "{0},1,{1},{2}", CrsId, ProjectionCenter.Longitude, ProjectionCenter.Latitude) + ? string.Format(CultureInfo.InvariantCulture, "{0},1,{1},{2}", CrsId, Center.Longitude, Center.Latitude) : CrsId; } @@ -180,78 +122,5 @@ namespace MapControl return string.Format(CultureInfo.InvariantCulture, "{0},{1},{2},{3}", rect.X, rect.Y, (rect.X + rect.Width), (rect.Y + rect.Height)); } - - /// - /// Sets ProjectionCenter, ViewportScale, ViewportRotation, ViewportTransform and InverseViewportTransform. - /// - public void SetViewportTransform(Location projectionCenter, Location mapCenter, Point viewportCenter, double zoomLevel, double rotation) - { - ProjectionCenter = projectionCenter; - ViewportScale = 256d * Math.Pow(2d, zoomLevel) / (360d * TrueScale); - ViewportRotation = rotation; - - var center = LocationToPoint(mapCenter); - var matrix = CreateViewportTransform(center, viewportCenter); - - ViewportTransform = matrix; - matrix.Invert(); - InverseViewportTransform = matrix; - } - - private Matrix CreateViewportTransform(Point mapCenter, Point viewportCenter) - { - var matrix = new Matrix(ViewportScale, 0d, 0d, -ViewportScale, -ViewportScale * mapCenter.X, ViewportScale * mapCenter.Y); - - matrix.Rotate(ViewportRotation); - matrix.Translate(viewportCenter.X, viewportCenter.Y); - - return matrix; - } - - internal Matrix CreateTileLayerTransform(double tileGridScale, Point tileGridTopLeft, Point tileGridOrigin) - { - var scale = ViewportScale / tileGridScale; - var matrix = new Matrix(scale, 0d, 0d, scale, 0d, 0d); - - matrix.Rotate(ViewportRotation); - - // tile grid origin in map coordinates - // - var mapOrigin = new Point( - tileGridTopLeft.X + tileGridOrigin.X / tileGridScale, - tileGridTopLeft.Y - tileGridOrigin.Y / tileGridScale); - - // tile grid origin in viewport coordinates - // - var viewOrigin = ViewportTransform.Transform(mapOrigin); - - matrix.Translate(viewOrigin.X, viewOrigin.Y); - - return matrix; - } - - internal Rect GetTileBounds(double tileGridScale, Point tileGridTopLeft, Size viewportSize) - { - var scale = tileGridScale / ViewportScale; - var matrix = new Matrix(scale, 0d, 0d, scale, 0d, 0d); - - matrix.Rotate(-ViewportRotation); - - // viewport origin in map coordinates - // - var origin = InverseViewportTransform.Transform(new Point()); - - // translate origin to tile grid origin in pixels - // - matrix.Translate( - tileGridScale * (origin.X - tileGridTopLeft.X), - tileGridScale * (tileGridTopLeft.Y - origin.Y)); - - // transforms viewport bounds to tile pixel bounds - // - var transform = new MatrixTransform { Matrix = matrix }; - - return transform.TransformBounds(new Rect(0d, 0d, viewportSize.Width, viewportSize.Height)); - } } } diff --git a/MapControl/Shared/MapShape.cs b/MapControl/Shared/MapShape.cs index aaeb9bb3..3ef8fc01 100644 --- a/MapControl/Shared/MapShape.cs +++ b/MapControl/Shared/MapShape.cs @@ -81,9 +81,9 @@ namespace MapControl MapPanel.InitMapElement(this); } - protected Point LocationToPoint(Location location) + protected Point LocationToMap(Location location) { - var point = parentMap.MapProjection.LocationToPoint(location); + var point = parentMap.MapProjection.LocationToMap(location); if (point.Y == double.PositiveInfinity) { @@ -99,7 +99,7 @@ namespace MapControl protected Point LocationToViewportPoint(Location location) { - return parentMap.MapProjection.ViewportTransform.Transform(LocationToPoint(location)); + return parentMap.ViewTransform.MapToView(LocationToMap(location)); } protected double GetLongitudeOffset() diff --git a/MapControl/Shared/MapTileLayer.cs b/MapControl/Shared/MapTileLayer.cs index 8432c8c2..6aa6a7d4 100644 --- a/MapControl/Shared/MapTileLayer.cs +++ b/MapControl/Shared/MapTileLayer.cs @@ -23,10 +23,10 @@ namespace MapControl { public const int TileSize = 256; - public static readonly Point TileGridTopLeft = new Point( + public static readonly Point TileMatrixTopLeft = new Point( -180d * MapProjection.Wgs84MetersPerDegree, 180d * MapProjection.Wgs84MetersPerDegree); - public static double TileGridScale(int zoomLevel) + public static double TileMatrixScale(int zoomLevel) { return (TileSize << zoomLevel) / (360d * MapProjection.Wgs84MetersPerDegree); } @@ -64,7 +64,7 @@ namespace MapControl { } - public TileGrid TileGrid { get; private set; } + public TileMatrix TileMatrix { get; private set; } public IReadOnlyCollection Tiles { get; private set; } = new List(); @@ -88,7 +88,7 @@ namespace MapControl protected override void TileSourcePropertyChanged() { - if (TileGrid != null) + if (TileMatrix != null) { Tiles = new List(); UpdateTiles(); @@ -101,10 +101,10 @@ namespace MapControl if (ParentMap == null || !ParentMap.MapProjection.IsWebMercator) { - TileGrid = null; + TileMatrix = null; UpdateTiles(); } - else if (SetTileGrid()) + else if (SetTileMatrix()) { SetRenderTransform(); UpdateTiles(); @@ -113,22 +113,22 @@ namespace MapControl protected override void SetRenderTransform() { - // tile grid origin in pixels + // tile matrix origin in pixels // - var tileGridOrigin = new Point(TileSize * TileGrid.XMin, TileSize * TileGrid.YMin); + var tileMatrixOrigin = new Point(TileSize * TileMatrix.XMin, TileSize * TileMatrix.YMin); - ((MatrixTransform)RenderTransform).Matrix = ParentMap.MapProjection.CreateTileLayerTransform( - TileGridScale(TileGrid.ZoomLevel), TileGridTopLeft, tileGridOrigin); + ((MatrixTransform)RenderTransform).Matrix = ParentMap.ViewTransform.GetTileLayerTransform( + TileMatrixScale(TileMatrix.ZoomLevel), TileMatrixTopLeft, tileMatrixOrigin); } - private bool SetTileGrid() + private bool SetTileMatrix() { - var tileGridZoomLevel = (int)Math.Floor(ParentMap.ZoomLevel + 0.001); // avoid rounding issues + var tileMatrixZoomLevel = (int)Math.Floor(ParentMap.ZoomLevel + 0.001); // avoid rounding issues // bounds in tile pixels from viewport size // - var tileBounds = ParentMap.MapProjection.GetTileBounds( - TileGridScale(tileGridZoomLevel), TileGridTopLeft, ParentMap.RenderSize); + var tileBounds = ParentMap.ViewTransform.GetTileMatrixBounds( + TileMatrixScale(tileMatrixZoomLevel), TileMatrixTopLeft, ParentMap.RenderSize); // tile column and row index bounds // @@ -137,15 +137,15 @@ namespace MapControl var xMax = (int)Math.Floor((tileBounds.X + tileBounds.Width) / TileSize); var yMax = (int)Math.Floor((tileBounds.Y + tileBounds.Height) / TileSize); - if (TileGrid != null && - TileGrid.ZoomLevel == tileGridZoomLevel && - TileGrid.XMin == xMin && TileGrid.YMin == yMin && - TileGrid.XMax == xMax && TileGrid.YMax == yMax) + if (TileMatrix != null && + TileMatrix.ZoomLevel == tileMatrixZoomLevel && + TileMatrix.XMin == xMin && TileMatrix.YMin == yMin && + TileMatrix.XMax == xMax && TileMatrix.YMax == yMax) { return false; } - TileGrid = new TileGrid(tileGridZoomLevel, xMin, yMin, xMax, yMax); + TileMatrix = new TileMatrix(tileMatrixZoomLevel, xMin, yMin, xMax, yMax); return true; } @@ -154,9 +154,9 @@ namespace MapControl { var newTiles = new List(); - if (ParentMap != null && TileGrid != null && TileSource != null) + if (ParentMap != null && TileMatrix != null && TileSource != null) { - var maxZoomLevel = Math.Min(TileGrid.ZoomLevel, MaxZoomLevel); + var maxZoomLevel = Math.Min(TileMatrix.ZoomLevel, MaxZoomLevel); if (maxZoomLevel >= MinZoomLevel) { @@ -164,16 +164,16 @@ namespace MapControl if (this == ParentMap.MapLayer) // load background tiles { - minZoomLevel = Math.Max(TileGrid.ZoomLevel - MaxBackgroundLevels, MinZoomLevel); + minZoomLevel = Math.Max(TileMatrix.ZoomLevel - MaxBackgroundLevels, MinZoomLevel); } for (var z = minZoomLevel; z <= maxZoomLevel; z++) { - var tileSize = 1 << (TileGrid.ZoomLevel - z); - var x1 = (int)Math.Floor((double)TileGrid.XMin / tileSize); // may be negative - var x2 = TileGrid.XMax / tileSize; - var y1 = Math.Max(TileGrid.YMin / tileSize, 0); - var y2 = Math.Min(TileGrid.YMax / tileSize, (1 << z) - 1); + var tileSize = 1 << (TileMatrix.ZoomLevel - z); + var x1 = (int)Math.Floor((double)TileMatrix.XMin / tileSize); // may be negative + var x2 = TileMatrix.XMax / tileSize; + var y1 = Math.Max(TileMatrix.YMin / tileSize, 0); + var y2 = Math.Min(TileMatrix.YMax / tileSize, (1 << z) - 1); for (var y = y1; y <= y2; y++) { @@ -227,13 +227,13 @@ namespace MapControl protected override Size ArrangeOverride(Size finalSize) { - if (TileGrid != null) + if (TileMatrix != null) { foreach (var tile in Tiles) { - var tileSize = TileSize << (TileGrid.ZoomLevel - tile.ZoomLevel); - var x = tileSize * tile.X - TileSize * TileGrid.XMin; - var y = tileSize * tile.Y - TileSize * TileGrid.YMin; + var tileSize = TileSize << (TileMatrix.ZoomLevel - tile.ZoomLevel); + var x = tileSize * tile.X - TileSize * TileMatrix.XMin; + var y = tileSize * tile.Y - TileSize * TileMatrix.YMin; tile.Image.Width = tileSize; tile.Image.Height = tileSize; diff --git a/MapControl/Shared/OrthographicProjection.cs b/MapControl/Shared/OrthographicProjection.cs index ea389fda..595669d5 100644 --- a/MapControl/Shared/OrthographicProjection.cs +++ b/MapControl/Shared/OrthographicProjection.cs @@ -19,31 +19,31 @@ namespace MapControl CrsId = "AUTO2:42003"; } - public override Point LocationToPoint(Location location) + public override Point LocationToMap(Location location) { - if (location.Equals(ProjectionCenter)) + if (location.Equals(Center)) { return new Point(); } - var lat0 = ProjectionCenter.Latitude * Math.PI / 180d; + var lat0 = Center.Latitude * Math.PI / 180d; var lat = location.Latitude * Math.PI / 180d; - var dLon = (location.Longitude - ProjectionCenter.Longitude) * Math.PI / 180d; - var s = TrueScale * 180d / Math.PI; + var dLon = (location.Longitude - Center.Longitude) * Math.PI / 180d; + var s = UnitsPerDegree * 180d / Math.PI; return new Point( s * Math.Cos(lat) * Math.Sin(dLon), s * (Math.Cos(lat0) * Math.Sin(lat) - Math.Sin(lat0) * Math.Cos(lat) * Math.Cos(dLon))); } - public override Location PointToLocation(Point point) + public override Location MapToLocation(Point point) { if (point.X == 0d && point.Y == 0d) { - return new Location(ProjectionCenter.Latitude, ProjectionCenter.Longitude); + return new Location(Center.Latitude, Center.Longitude); } - var s = TrueScale * 180d / Math.PI; + var s = UnitsPerDegree * 180d / Math.PI; var x = point.X / s; var y = point.Y / s; var r2 = x * x + y * y; @@ -57,13 +57,13 @@ namespace MapControl var sinC = r; var cosC = Math.Sqrt(1 - r2); - var lat0 = ProjectionCenter.Latitude * Math.PI / 180d; + var lat0 = Center.Latitude * Math.PI / 180d; var cosLat0 = Math.Cos(lat0); var sinLat0 = Math.Sin(lat0); return new Location( 180d / Math.PI * Math.Asin(cosC * sinLat0 + y * sinC * cosLat0 / r), - 180d / Math.PI * Math.Atan2(x * sinC, r * cosC * cosLat0 - y * sinC * sinLat0) + ProjectionCenter.Longitude); + 180d / Math.PI * Math.Atan2(x * sinC, r * cosC * cosLat0 - y * sinC * sinLat0) + Center.Longitude); } } } diff --git a/MapControl/Shared/StereographicProjection.cs b/MapControl/Shared/StereographicProjection.cs index 425e5c02..dd442cc6 100644 --- a/MapControl/Shared/StereographicProjection.cs +++ b/MapControl/Shared/StereographicProjection.cs @@ -19,35 +19,35 @@ namespace MapControl CrsId = "AUTO2:97002"; // GeoServer non-standard CRS ID } - public override Point LocationToPoint(Location location) + public override Point LocationToMap(Location location) { - if (location.Equals(ProjectionCenter)) + if (location.Equals(Center)) { return new Point(); } double azimuth, distance; - GetAzimuthDistance(ProjectionCenter, location, out azimuth, out distance); + GetAzimuthDistance(Center, location, out azimuth, out distance); - var mapDistance = Math.Tan(distance / 2d) * 2d * TrueScale * 180d / Math.PI; + var mapDistance = Math.Tan(distance / 2d) * 2d * UnitsPerDegree * 180d / Math.PI; return new Point(mapDistance * Math.Sin(azimuth), mapDistance * Math.Cos(azimuth)); } - public override Location PointToLocation(Point point) + public override Location MapToLocation(Point point) { if (point.X == 0d && point.Y == 0d) { - return new Location(ProjectionCenter.Latitude, ProjectionCenter.Longitude); + return new Location(Center.Latitude, Center.Longitude); } var azimuth = Math.Atan2(point.X, point.Y); var mapDistance = Math.Sqrt(point.X * point.X + point.Y * point.Y); - var distance = 2d * Math.Atan(mapDistance / (2d * TrueScale * 180d / Math.PI)); + var distance = 2d * Math.Atan(mapDistance / (2d * UnitsPerDegree * 180d / Math.PI)); - return GetLocation(ProjectionCenter, azimuth, distance); + return GetLocation(Center, azimuth, distance); } } } diff --git a/MapControl/Shared/TileGrid.cs b/MapControl/Shared/TileMatrix.cs similarity index 82% rename from MapControl/Shared/TileGrid.cs rename to MapControl/Shared/TileMatrix.cs index 825efbb9..ef786815 100644 --- a/MapControl/Shared/TileGrid.cs +++ b/MapControl/Shared/TileMatrix.cs @@ -4,7 +4,7 @@ namespace MapControl { - public class TileGrid + public class TileMatrix { public readonly int ZoomLevel; public readonly int XMin; @@ -12,7 +12,7 @@ namespace MapControl public readonly int XMax; public readonly int YMax; - public TileGrid(int zoomLevel, int xMin, int yMin, int xMax, int yMax) + public TileMatrix(int zoomLevel, int xMin, int yMin, int xMax, int yMax) { ZoomLevel = zoomLevel; XMin = xMin; diff --git a/MapControl/Shared/ViewTransform.cs b/MapControl/Shared/ViewTransform.cs new file mode 100644 index 00000000..dd7ff6cd --- /dev/null +++ b/MapControl/Shared/ViewTransform.cs @@ -0,0 +1,118 @@ +// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control +// © 2020 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +#if WINDOWS_UWP +using Windows.Foundation; +using Windows.UI.Xaml.Media; +#else +using System.Windows; +using System.Windows.Media; +#endif + +namespace MapControl +{ + /// + /// Defines the transformation between cartesian map coordinates and viewport coordinates. + /// + public class ViewTransform + { + /// + /// Gets the transform matrix from cartesian map coordinates to viewport coordinates. + /// + public Matrix MapToViewMatrix { get; private set; } + + /// + /// Gets the transform matrix from viewport coordinates to cartesian map coordinates. + /// + public Matrix ViewToMapMatrix { get; private set; } + + /// + /// Gets the scaling factor from cartesian map coordinates to viewport coordinates. + /// + public double Scale { get; private set; } + + /// + /// Gets the rotation angle of the transform matrix. + /// + public double Rotation { get; private set; } + + /// + /// Transforms a Point from cartesian map coordinates to viewport coordinates. + /// + public Point MapToView(Point point) + { + return MapToViewMatrix.Transform(point); + } + + /// + /// Transforms a Point from viewport coordinates to cartesian map coordinates. + /// + public Point ViewToMap(Point point) + { + return ViewToMapMatrix.Transform(point); + } + + public void SetTransform(Point mapCenter, Point viewportCenter, double scale, double rotation) + { + Scale = scale; + Rotation = rotation; + + var transform = new Matrix(Scale, 0d, 0d, -Scale, -Scale * mapCenter.X, Scale * mapCenter.Y); + + transform.Rotate(Rotation); + transform.Translate(viewportCenter.X, viewportCenter.Y); + + MapToViewMatrix = transform; + + transform.Invert(); + + ViewToMapMatrix = transform; + } + + public Matrix GetTileLayerTransform(double tileMatrixScale, Point tileMatrixTopLeft, Point tileMatrixOrigin) + { + var transformScale = Scale / tileMatrixScale; + var transform = new Matrix(transformScale, 0d, 0d, transformScale, 0d, 0d); + + transform.Rotate(Rotation); + + // tile matrix origin in map coordinates + // + var mapOrigin = new Point( + tileMatrixTopLeft.X + tileMatrixOrigin.X / tileMatrixScale, + tileMatrixTopLeft.Y - tileMatrixOrigin.Y / tileMatrixScale); + + // tile matrix origin in viewport coordinates + // + var viewOrigin = MapToView(mapOrigin); + + transform.Translate(viewOrigin.X, viewOrigin.Y); + + return transform; + } + + public Rect GetTileMatrixBounds(double tileMatrixScale, Point tileMatrixTopLeft, Size viewportSize) + { + var transformScale = tileMatrixScale / Scale; + var transform = new Matrix(transformScale, 0d, 0d, transformScale, 0d, 0d); + + transform.Rotate(-Rotation); + + // viewport origin in map coordinates + // + var origin = ViewToMap(new Point()); + + // translate origin to tile matrix origin in pixels + // + transform.Translate( + tileMatrixScale * (origin.X - tileMatrixTopLeft.X), + tileMatrixScale * (tileMatrixTopLeft.Y - origin.Y)); + + // transform viewport bounds to tile pixel bounds + // + return new MatrixTransform { Matrix = transform } + .TransformBounds(new Rect(0d, 0d, viewportSize.Width, viewportSize.Height)); + } + } +} diff --git a/MapControl/Shared/WebMercatorProjection.cs b/MapControl/Shared/WebMercatorProjection.cs index 68709a41..3fe98c26 100644 --- a/MapControl/Shared/WebMercatorProjection.cs +++ b/MapControl/Shared/WebMercatorProjection.cs @@ -32,25 +32,25 @@ namespace MapControl get { return maxLatitude; } } - public override Vector GetMapScale(Location location) + public override Vector GetRelativeScale(Location location) { var k = 1d / Math.Cos(location.Latitude * Math.PI / 180d); // p.44 (7-3) - return new Vector(ViewportScale * k, ViewportScale * k); + return new Vector(k, k); } - public override Point LocationToPoint(Location location) + public override Point LocationToMap(Location location) { return new Point( - TrueScale * location.Longitude, - TrueScale * LatitudeToY(location.Latitude)); + UnitsPerDegree * location.Longitude, + UnitsPerDegree * LatitudeToY(location.Latitude)); } - public override Location PointToLocation(Point point) + public override Location MapToLocation(Point point) { return new Location( - YToLatitude(point.Y / TrueScale), - point.X / TrueScale); + YToLatitude(point.Y / UnitsPerDegree), + point.X / UnitsPerDegree); } public static double LatitudeToY(double latitude) diff --git a/MapControl/Shared/WmsImageLayer.cs b/MapControl/Shared/WmsImageLayer.cs index 7d9f9d72..8e289304 100644 --- a/MapControl/Shared/WmsImageLayer.cs +++ b/MapControl/Shared/WmsImageLayer.cs @@ -142,8 +142,8 @@ namespace MapControl uri += "&CRS=" + projection.GetCrsValue(); uri += "&BBOX=" + projection.GetBboxValue(rect); - uri += "&WIDTH=" + (int)Math.Round(projection.ViewportScale * rect.Width); - uri += "&HEIGHT=" + (int)Math.Round(projection.ViewportScale * rect.Height); + uri += "&WIDTH=" + (int)Math.Round(ParentMap.ViewTransform.Scale * rect.Width); + uri += "&HEIGHT=" + (int)Math.Round(ParentMap.ViewTransform.Scale * rect.Height); } return uri; diff --git a/MapControl/Shared/WmtsTileLayer.cs b/MapControl/Shared/WmtsTileLayer.cs index 8571ecad..464a05fd 100644 --- a/MapControl/Shared/WmtsTileLayer.cs +++ b/MapControl/Shared/WmtsTileLayer.cs @@ -104,14 +104,14 @@ namespace MapControl { foreach (var layer in ChildLayers) { - layer.SetRenderTransform(ParentMap.MapProjection); + layer.SetRenderTransform(ParentMap.ViewTransform); } } private bool UpdateChildLayers(WmtsTileMatrixSet tileMatrixSet) { var layersChanged = false; - var maxScale = 1.001 * ParentMap.MapProjection.ViewportScale; // avoid rounding issues + var maxScale = 1.001 * ParentMap.ViewTransform.Scale; // avoid rounding issues // show all TileMatrix layers with Scale <= maxScale, at least the first layer // @@ -142,7 +142,7 @@ namespace MapControl layersChanged = true; } - if (layer.SetBounds(ParentMap.MapProjection, ParentMap.RenderSize)) + if (layer.SetBounds(ParentMap.ViewTransform, ParentMap.RenderSize)) { layersChanged = true; } diff --git a/MapControl/Shared/WmtsTileMatrixLayer.cs b/MapControl/Shared/WmtsTileMatrixLayer.cs index c12422c3..bf2c881b 100644 --- a/MapControl/Shared/WmtsTileMatrixLayer.cs +++ b/MapControl/Shared/WmtsTileMatrixLayer.cs @@ -37,21 +37,21 @@ namespace MapControl public IReadOnlyCollection Tiles { get; private set; } = new List(); - public void SetRenderTransform(MapProjection projection) + public void SetRenderTransform(ViewTransform viewTransform) { - // tile grid origin in pixels + // tile matrix origin in pixels // - var tileGridOrigin = new Point(TileMatrix.TileWidth * XMin, TileMatrix.TileHeight * YMin); + var tileMatrixOrigin = new Point(TileMatrix.TileWidth * XMin, TileMatrix.TileHeight * YMin); ((MatrixTransform)RenderTransform).Matrix = - projection.CreateTileLayerTransform(TileMatrix.Scale, TileMatrix.TopLeft, tileGridOrigin); + viewTransform.GetTileLayerTransform(TileMatrix.Scale, TileMatrix.TopLeft, tileMatrixOrigin); } - public bool SetBounds(MapProjection projection, Size viewportSize) + public bool SetBounds(ViewTransform viewTransform, Size viewportSize) { // bounds in tile pixels from viewport size // - var bounds = projection.GetTileBounds(TileMatrix.Scale, TileMatrix.TopLeft, viewportSize); + var bounds = viewTransform.GetTileMatrixBounds(TileMatrix.Scale, TileMatrix.TopLeft, viewportSize); // tile column and row index bounds // diff --git a/MapControl/Shared/WorldMercatorProjection.cs b/MapControl/Shared/WorldMercatorProjection.cs index 75391582..590cd835 100644 --- a/MapControl/Shared/WorldMercatorProjection.cs +++ b/MapControl/Shared/WorldMercatorProjection.cs @@ -30,27 +30,27 @@ namespace MapControl get { return maxLatitude; } } - public override Vector GetMapScale(Location location) + public override Vector GetRelativeScale(Location location) { var lat = location.Latitude * Math.PI / 180d; var eSinLat = Wgs84Eccentricity * Math.Sin(lat); var k = Math.Sqrt(1d - eSinLat * eSinLat) / Math.Cos(lat); // p.44 (7-8) - return new Vector(ViewportScale * k, ViewportScale * k); + return new Vector(k, k); } - public override Point LocationToPoint(Location location) + public override Point LocationToMap(Location location) { return new Point( - TrueScale * location.Longitude, - TrueScale * LatitudeToY(location.Latitude)); + UnitsPerDegree * location.Longitude, + UnitsPerDegree * LatitudeToY(location.Latitude)); } - public override Location PointToLocation(Point point) + public override Location MapToLocation(Point point) { return new Location( - YToLatitude(point.Y / TrueScale), - point.X / TrueScale); + YToLatitude(point.Y / UnitsPerDegree), + point.X / UnitsPerDegree); } public static double LatitudeToY(double latitude) diff --git a/MapControl/UWP/MapBase.UWP.cs b/MapControl/UWP/MapBase.UWP.cs index bad97e0b..b4b10375 100644 --- a/MapControl/UWP/MapBase.UWP.cs +++ b/MapControl/UWP/MapBase.UWP.cs @@ -45,10 +45,6 @@ namespace MapControl public MapBase() { - MapProjection = new WebMercatorProjection(); - ScaleRotateTransform.Children.Add(ScaleTransform); - ScaleRotateTransform.Children.Add(RotateTransform); - // set Background by Style to enable resetting by ClearValue in MapLayerPropertyChanged var style = new Style(typeof(MapBase)); style.Setters.Add(new Setter(BackgroundProperty, new SolidColorBrush(Colors.Transparent))); diff --git a/MapControl/UWP/MapControl.UWP.csproj b/MapControl/UWP/MapControl.UWP.csproj index ed4d6bed..8760dd14 100644 --- a/MapControl/UWP/MapControl.UWP.csproj +++ b/MapControl/UWP/MapControl.UWP.csproj @@ -140,18 +140,21 @@ Tile.cs - - TileGrid.cs - TileImageLoader.cs + + TileMatrix.cs + TileSource.cs ViewportChangedEventArgs.cs + + ViewTransform.cs + WebMercatorProjection.cs diff --git a/MapControl/UWP/MapGraticule.UWP.cs b/MapControl/UWP/MapGraticule.UWP.cs index a2875ca8..0725ea7e 100644 --- a/MapControl/UWP/MapGraticule.UWP.cs +++ b/MapControl/UWP/MapGraticule.UWP.cs @@ -24,7 +24,8 @@ namespace MapControl protected override void OnViewportChanged(ViewportChangedEventArgs e) { - var projection = ParentMap.MapProjection; + var map = ParentMap; + var projection = map.MapProjection; if (projection.IsNormalCylindrical) { @@ -39,7 +40,7 @@ namespace MapControl Children.Add(path); } - var bounds = projection.ViewportRectToBoundingBox(new Rect(0d, 0d, ParentMap.RenderSize.Width, ParentMap.RenderSize.Height)); + var bounds = map.ViewportRectToBoundingBox(new Rect(0d, 0d, map.RenderSize.Width, map.RenderSize.Height)); var lineDistance = GetLineDistance(); var labelStart = new Location( @@ -65,14 +66,14 @@ namespace MapControl { var figure = new PathFigure { - StartPoint = projection.LocationToViewportPoint(new Location(lat, lineStart.Longitude)), + StartPoint = map.LocationToViewportPoint(new Location(lat, lineStart.Longitude)), IsClosed = false, IsFilled = false }; figure.Segments.Add(new LineSegment { - Point = projection.LocationToViewportPoint(new Location(lat, lineEnd.Longitude)) + Point = map.LocationToViewportPoint(new Location(lat, lineEnd.Longitude)) }); geometry.Figures.Add(figure); @@ -82,14 +83,14 @@ namespace MapControl { var figure = new PathFigure { - StartPoint = projection.LocationToViewportPoint(new Location(lineStart.Latitude, lon)), + StartPoint = map.LocationToViewportPoint(new Location(lineStart.Latitude, lon)), IsClosed = false, IsFilled = false }; figure.Segments.Add(new LineSegment { - Point = projection.LocationToViewportPoint(new Location(lineEnd.Latitude, lon)) + Point = map.LocationToViewportPoint(new Location(lineEnd.Latitude, lon)) }); geometry.Figures.Add(figure); @@ -112,7 +113,7 @@ namespace MapControl { var renderTransform = new TransformGroup(); renderTransform.Children.Add(new TranslateTransform()); - renderTransform.Children.Add(ParentMap.RotateTransform); + renderTransform.Children.Add(map.RotateTransform); renderTransform.Children.Add(new TranslateTransform()); label = new TextBlock { RenderTransform = renderTransform }; @@ -153,7 +154,7 @@ namespace MapControl var label = (TextBlock)Children[i]; var location = (Location)label.Tag; var viewportTransform = (TranslateTransform)((TransformGroup)label.RenderTransform).Children[2]; - var viewportPosition = projection.LocationToViewportPoint(location); + var viewportPosition = map.LocationToViewportPoint(location); viewportTransform.X = viewportPosition.X; viewportTransform.Y = viewportPosition.Y; } diff --git a/MapControl/UWP/Properties/AssemblyInfo.cs b/MapControl/UWP/Properties/AssemblyInfo.cs index b54101d4..11355637 100644 --- a/MapControl/UWP/Properties/AssemblyInfo.cs +++ b/MapControl/UWP/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("4.17.0")] -[assembly: AssemblyFileVersion("4.17.0")] +[assembly: AssemblyVersion("5.0.0")] +[assembly: AssemblyFileVersion("5.0.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/MapControl/WPF/MapBase.WPF.cs b/MapControl/WPF/MapBase.WPF.cs index a3438fb3..66754f4c 100644 --- a/MapControl/WPF/MapBase.WPF.cs +++ b/MapControl/WPF/MapBase.WPF.cs @@ -53,13 +53,6 @@ namespace MapControl BackgroundProperty.OverrideMetadata(typeof(MapBase), new FrameworkPropertyMetadata(Brushes.Transparent)); } - public MapBase() - { - MapProjection = new WebMercatorProjection(); - ScaleRotateTransform.Children.Add(ScaleTransform); - ScaleRotateTransform.Children.Add(RotateTransform); - } - protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) { base.OnRenderSizeChanged(sizeInfo); diff --git a/MapControl/WPF/MapControl.WPF.csproj b/MapControl/WPF/MapControl.WPF.csproj index da4b7d47..b46c21e3 100644 --- a/MapControl/WPF/MapControl.WPF.csproj +++ b/MapControl/WPF/MapControl.WPF.csproj @@ -9,7 +9,7 @@ ..\..\MapControl.snk false XAML Map Control - 4.17.0 + 5.0.0 XAML Map Control Library Clemens Fischer Copyright © 2020 Clemens Fischer diff --git a/MapControl/WPF/MapGraticule.WPF.cs b/MapControl/WPF/MapGraticule.WPF.cs index 3dd81524..5200671c 100644 --- a/MapControl/WPF/MapGraticule.WPF.cs +++ b/MapControl/WPF/MapGraticule.WPF.cs @@ -46,7 +46,7 @@ namespace MapControl if (projection.IsNormalCylindrical) { - DrawCylindricalGraticule(drawingContext, projection, lineDistance, labelFormat); + DrawCylindricalGraticule(drawingContext, lineDistance, labelFormat); } else { @@ -54,9 +54,9 @@ namespace MapControl } } - private void DrawCylindricalGraticule(DrawingContext drawingContext, MapProjection projection, double lineDistance, string labelFormat) + private void DrawCylindricalGraticule(DrawingContext drawingContext, double lineDistance, string labelFormat) { - var boundingBox = projection.ViewportRectToBoundingBox(new Rect(ParentMap.RenderSize)); + var boundingBox = ParentMap.ViewportRectToBoundingBox(new Rect(ParentMap.RenderSize)); var latLabelStart = Math.Ceiling(boundingBox.South / lineDistance) * lineDistance; var lonLabelStart = Math.Ceiling(boundingBox.West / lineDistance) * lineDistance; var latLabels = new List