From b5fe760c83d9df251835512fd9f6b84c5a32fc76 Mon Sep 17 00:00:00 2001 From: ClemensF Date: Sun, 22 Mar 2020 18:33:34 +0100 Subject: [PATCH] Version 4.17.0: Added support for WMTS --- MapControl/Shared/MapBase.cs | 2 +- MapControl/Shared/MapProjection.cs | 73 ++++++++++++++++++++++-- MapControl/Shared/MapTileLayer.cs | 65 ++++++++------------- MapControl/Shared/MatrixFactory.cs | 30 ---------- MapControl/Shared/TileSource.cs | 8 +-- MapControl/Shared/WmtsTileLayer.cs | 11 ++-- MapControl/Shared/WmtsTileMatrixLayer.cs | 48 ++++------------ 7 files changed, 112 insertions(+), 125 deletions(-) delete mode 100644 MapControl/Shared/MatrixFactory.cs diff --git a/MapControl/Shared/MapBase.cs b/MapControl/Shared/MapBase.cs index 15916f96..b37676b1 100644 --- a/MapControl/Shared/MapBase.cs +++ b/MapControl/Shared/MapBase.cs @@ -351,7 +351,7 @@ 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 / MapProjection.TileSize; + * MapProjection.TrueScale * 360d / 256d; TargetZoomLevel = Math.Log(scale, 2d); TargetCenter = MapProjection.PointToLocation(center); diff --git a/MapControl/Shared/MapProjection.cs b/MapControl/Shared/MapProjection.cs index bdcc5c77..33377985 100644 --- a/MapControl/Shared/MapProjection.cs +++ b/MapControl/Shared/MapProjection.cs @@ -6,6 +6,7 @@ using System; using System.Globalization; #if WINDOWS_UWP using Windows.Foundation; +using Windows.UI.Xaml.Media; #else using System.Windows; using System.Windows.Media; @@ -18,8 +19,6 @@ namespace MapControl /// public abstract class MapProjection { - public const int TileSize = 256; - public const double Wgs84EquatorialRadius = 6378137d; public const double Wgs84Flattening = 1d / 298.257223563; public static readonly double Wgs84Eccentricity = Math.Sqrt((2d - Wgs84Flattening) * Wgs84Flattening); @@ -79,6 +78,11 @@ namespace MapControl /// 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. @@ -178,19 +182,76 @@ namespace MapControl } /// - /// Sets ProjectionCenter, ViewportScale, ViewportTransform and InverseViewportTransform. + /// Sets ProjectionCenter, ViewportScale, ViewportRotation, ViewportTransform and InverseViewportTransform. /// - public void SetViewportTransform(Location projectionCenter, Location mapCenter, Point viewportCenter, double zoomLevel, double heading) + public void SetViewportTransform(Location projectionCenter, Location mapCenter, Point viewportCenter, double zoomLevel, double rotation) { ProjectionCenter = projectionCenter; - ViewportScale = Math.Pow(2d, zoomLevel) * TileSize / (360d * TrueScale); + ViewportScale = Math.Pow(2d, zoomLevel) * 256d / (360d * TrueScale); + ViewportRotation = rotation; var center = LocationToPoint(mapCenter); - var matrix = MatrixFactory.Create(center, ViewportScale, -ViewportScale, heading, viewportCenter); + var matrix = CreateViewportTransform(center, viewportCenter); ViewportTransform = matrix; matrix.Invert(); InverseViewportTransform = matrix; } + + internal 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 cordinates + // + var mapOrigin = new Point( + tileGridTopLeft.X + tileGridOrigin.X / tileGridScale, + tileGridTopLeft.Y - tileGridOrigin.Y / tileGridScale); + + // tile grid origin in viewport cordinates + // + 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/MapTileLayer.cs b/MapControl/Shared/MapTileLayer.cs index 8c2246df..54609bec 100644 --- a/MapControl/Shared/MapTileLayer.cs +++ b/MapControl/Shared/MapTileLayer.cs @@ -21,7 +21,14 @@ namespace MapControl /// public class MapTileLayer : MapTileLayerBase { - private const double WebMercatorMapSize = 2 * Math.PI * MapProjection.Wgs84EquatorialRadius; + public const int TileSize = 256; + public const double MapSize = 2 * Math.PI * MapProjection.Wgs84EquatorialRadius; + public static readonly Point TileGridTopLeft = new Point(-MapSize / 2, MapSize / 2); + + public static double TileGridScale(int zoomLevel) + { + return (TileSize << zoomLevel) / MapSize; + } /// /// A default MapTileLayer using OpenStreetMap data. @@ -96,9 +103,9 @@ namespace MapControl { foreach (var tile in Tiles) { - var tileSize = MapProjection.TileSize << (TileGrid.ZoomLevel - tile.ZoomLevel); - var x = tileSize * tile.X - MapProjection.TileSize * TileGrid.XMin; - var y = tileSize * tile.Y - MapProjection.TileSize * TileGrid.YMin; + var tileSize = TileSize << (TileGrid.ZoomLevel - tile.ZoomLevel); + var x = tileSize * tile.X - TileSize * TileGrid.XMin; + var y = tileSize * tile.Y - TileSize * TileGrid.YMin; tile.Image.Width = tileSize; tile.Image.Height = tileSize; @@ -136,53 +143,29 @@ namespace MapControl protected override void SetRenderTransform() { - var tileGridSize = (double)(1 << TileGrid.ZoomLevel); - - // top/left tile grid corner in map coordinates + // tile grid origin in pixels // - var tileGridOrigin = new Point( - WebMercatorMapSize * (TileGrid.XMin / tileGridSize - 0.5), - WebMercatorMapSize * (0.5 - TileGrid.YMin / tileGridSize)); + var tileGridOrigin = new Point(TileSize * TileGrid.XMin, TileSize * TileGrid.YMin); - // top/left tile grid corner in viewport coordinates - // - var viewOrigin = ParentMap.MapProjection.ViewportTransform.Transform(tileGridOrigin); - - // tile pixels per viewport unit, 0.5 .. 2 - // - var tileScale = Math.Pow(2d, ParentMap.ZoomLevel - TileGrid.ZoomLevel); - - ((MatrixTransform)RenderTransform).Matrix = MatrixFactory.Create(tileScale, ParentMap.Heading, viewOrigin); + ((MatrixTransform)RenderTransform).Matrix = ParentMap.MapProjection.CreateTileLayerTransform( + TileGridScale(TileGrid.ZoomLevel), TileGridTopLeft, tileGridOrigin); } private bool SetTileGrid() { - var tileGridZoomLevel = (int)Math.Round(ParentMap.ZoomLevel); - var tileGridSize = (double)(1 << tileGridZoomLevel); + var tileGridZoomLevel = (int)Math.Floor(ParentMap.ZoomLevel + 0.001); // avoid rounding issues - // top/left viewport corner in map coordinates + // bounds in tile pixels from viewport size // - var tileOrigin = ParentMap.MapProjection.InverseViewportTransform.Transform(new Point()); + var tileBounds = ParentMap.MapProjection.GetTileBounds( + TileGridScale(tileGridZoomLevel), TileGridTopLeft, ParentMap.RenderSize); - // top/left viewport corner in tile grid coordinates + // tile column and row index bounds // - var tileGridOrigin = new Point( - tileGridSize * (0.5 + tileOrigin.X / WebMercatorMapSize), - tileGridSize * (0.5 - tileOrigin.Y / WebMercatorMapSize)); - - // transforms viewport bounds to tile grid bounds - // - var transform = new MatrixTransform - { - Matrix = MatrixFactory.Create(1d / MapProjection.TileSize, -ParentMap.Heading, tileGridOrigin) - }; - - var bounds = transform.TransformBounds(new Rect(0d, 0d, ParentMap.RenderSize.Width, ParentMap.RenderSize.Height)); - - var xMin = (int)Math.Floor(bounds.X); - var yMin = (int)Math.Floor(bounds.Y); - var xMax = (int)Math.Floor(bounds.X + bounds.Width); - var yMax = (int)Math.Floor(bounds.Y + bounds.Height); + var xMin = (int)Math.Floor(tileBounds.X / TileSize); + var yMin = (int)Math.Floor(tileBounds.Y / TileSize); + 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 && diff --git a/MapControl/Shared/MatrixFactory.cs b/MapControl/Shared/MatrixFactory.cs deleted file mode 100644 index df837bcb..00000000 --- a/MapControl/Shared/MatrixFactory.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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 System.Windows; -using System.Windows.Media; -#endif - -namespace MapControl -{ - public static class MatrixFactory - { - public static Matrix Create(Point translation1, double scaleX, double scaleY, double rotation, Point translation2) - { - var matrix = new Matrix(scaleX, 0d, 0d, scaleY, -scaleX * translation1.X, -scaleY * translation1.Y); - matrix.Rotate(rotation); - matrix.Translate(translation2.X, translation2.Y); - return matrix; - } - - public static Matrix Create(double scale, double rotation, Point translation) - { - var matrix = new Matrix(scale, 0d, 0d, scale, 0d, 0d); - matrix.Rotate(rotation); - matrix.Translate(translation.X, translation.Y); - return matrix; - } - } -} diff --git a/MapControl/Shared/TileSource.cs b/MapControl/Shared/TileSource.cs index d89be952..c543732d 100644 --- a/MapControl/Shared/TileSource.cs +++ b/MapControl/Shared/TileSource.cs @@ -164,9 +164,7 @@ namespace MapControl .Replace("{W}", west.ToString(CultureInfo.InvariantCulture)) .Replace("{S}", south.ToString(CultureInfo.InvariantCulture)) .Replace("{E}", east.ToString(CultureInfo.InvariantCulture)) - .Replace("{N}", north.ToString(CultureInfo.InvariantCulture)) - .Replace("{X}", MapProjection.TileSize.ToString()) - .Replace("{Y}", MapProjection.TileSize.ToString()); + .Replace("{N}", north.ToString(CultureInfo.InvariantCulture)); } private string GetLatLonBoundingBoxUri(int x, int y, int zoomLevel) @@ -181,9 +179,7 @@ namespace MapControl .Replace("{w}", west.ToString(CultureInfo.InvariantCulture)) .Replace("{s}", south.ToString(CultureInfo.InvariantCulture)) .Replace("{e}", east.ToString(CultureInfo.InvariantCulture)) - .Replace("{n}", north.ToString(CultureInfo.InvariantCulture)) - .Replace("{X}", MapProjection.TileSize.ToString()) - .Replace("{Y}", MapProjection.TileSize.ToString()); + .Replace("{n}", north.ToString(CultureInfo.InvariantCulture)); } } } diff --git a/MapControl/Shared/WmtsTileLayer.cs b/MapControl/Shared/WmtsTileLayer.cs index d69c2abc..5fb98eaa 100644 --- a/MapControl/Shared/WmtsTileLayer.cs +++ b/MapControl/Shared/WmtsTileLayer.cs @@ -97,18 +97,19 @@ namespace MapControl { foreach (var layer in Children.Cast()) { - layer.SetRenderTransform(ParentMap.MapProjection, ParentMap.Heading); + layer.SetRenderTransform(ParentMap.MapProjection); } } private bool UpdateChildLayers(WmtsTileMatrixSet tileMatrixSet) { - bool layersChanged = false; + var layersChanged = false; + var maxScale = 1.001 * ParentMap.MapProjection.ViewportScale; // avoid rounding issues - // show all TileMatrix layers with Scale <= ViewportScale, or at least the first layer + // show all TileMatrix layers with Scale <= maxScale, or at least the first layer // var currentMatrixes = tileMatrixSet.TileMatrixes - .Where((matrix, i) => i == 0 || matrix.Scale <= ParentMap.MapProjection.ViewportScale) + .Where((matrix, i) => i == 0 || matrix.Scale <= maxScale) .ToList(); if (this != ParentMap.MapLayer) // do not load background tiles @@ -136,7 +137,7 @@ namespace MapControl layersChanged = true; } - if (layer.SetBounds(ParentMap.MapProjection, ParentMap.Heading, ParentMap.RenderSize)) + if (layer.SetBounds(ParentMap.MapProjection, ParentMap.RenderSize)) { layersChanged = true; } diff --git a/MapControl/Shared/WmtsTileMatrixLayer.cs b/MapControl/Shared/WmtsTileMatrixLayer.cs index 83dabe1f..2700ad37 100644 --- a/MapControl/Shared/WmtsTileMatrixLayer.cs +++ b/MapControl/Shared/WmtsTileMatrixLayer.cs @@ -36,29 +36,24 @@ namespace MapControl public IReadOnlyCollection Tiles { get; private set; } = new List(); - public bool SetBounds(MapProjection projection, double heading, Size mapSize) + public void SetRenderTransform(MapProjection projection) { - // top/left viewport corner in map coordinates (meters) + // tile grid origin in pixels // - var tileOrigin = projection.InverseViewportTransform.Transform(new Point()); + var tileGridOrigin = new Point(TileMatrix.TileWidth * XMin, TileMatrix.TileHeight * YMin); - // top/left viewport corner in tile matrix coordinates (tile column and row indexes) + ((MatrixTransform)RenderTransform).Matrix = + projection.CreateTileLayerTransform(TileMatrix.Scale, TileMatrix.TopLeft, tileGridOrigin); + } + + public bool SetBounds(MapProjection projection, Size viewportSize) + { + // bounds in tile pixels from viewport size // - var tileMatrixOrigin = new Point( - TileMatrix.Scale * (tileOrigin.X - TileMatrix.TopLeft.X), - TileMatrix.Scale * (TileMatrix.TopLeft.Y - tileOrigin.Y)); + var bounds = projection.GetTileBounds(TileMatrix.Scale, TileMatrix.TopLeft, viewportSize); - // relative layer scale + // tile column and row index bounds // - var scale = TileMatrix.Scale / projection.ViewportScale; - - var transform = new MatrixTransform - { - Matrix = MatrixFactory.Create(scale, -heading, tileMatrixOrigin) - }; - - var bounds = transform.TransformBounds(new Rect(0d, 0d, mapSize.Width, mapSize.Height)); - var xMin = (int)Math.Floor(bounds.X / TileMatrix.TileWidth); var yMin = (int)Math.Floor(bounds.Y / TileMatrix.TileHeight); var xMax = (int)Math.Floor((bounds.X + bounds.Width) / TileMatrix.TileWidth); @@ -82,25 +77,6 @@ namespace MapControl return true; } - public void SetRenderTransform(MapProjection projection, double heading) - { - // XMin/YMin corner in map coordinates (meters) - // - var mapOrigin = new Point( - TileMatrix.TopLeft.X + XMin * TileMatrix.TileWidth / TileMatrix.Scale, - TileMatrix.TopLeft.Y - YMin * TileMatrix.TileHeight / TileMatrix.Scale); - - // XMin/YMin corner in viewport coordinates (pixels) - // - var viewOrigin = projection.ViewportTransform.Transform(mapOrigin); - - // relative layer scale - // - var scale = projection.ViewportScale / TileMatrix.Scale; - - ((MatrixTransform)RenderTransform).Matrix = MatrixFactory.Create(scale, heading, viewOrigin); - } - public void UpdateTiles() { var newTiles = new List();