Version 4.17.0: Added support for WMTS

This commit is contained in:
ClemensF 2020-03-22 18:33:34 +01:00
parent 7e5c5c0671
commit b5fe760c83
7 changed files with 112 additions and 125 deletions

View file

@ -351,7 +351,7 @@ namespace MapControl
var rect = MapProjection.BoundingBoxToRect(boundingBox); var rect = MapProjection.BoundingBoxToRect(boundingBox);
var center = new Point(rect.X + rect.Width / 2d, rect.Y + rect.Height / 2d); 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) 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); TargetZoomLevel = Math.Log(scale, 2d);
TargetCenter = MapProjection.PointToLocation(center); TargetCenter = MapProjection.PointToLocation(center);

View file

@ -6,6 +6,7 @@ using System;
using System.Globalization; using System.Globalization;
#if WINDOWS_UWP #if WINDOWS_UWP
using Windows.Foundation; using Windows.Foundation;
using Windows.UI.Xaml.Media;
#else #else
using System.Windows; using System.Windows;
using System.Windows.Media; using System.Windows.Media;
@ -18,8 +19,6 @@ namespace MapControl
/// </summary> /// </summary>
public abstract class MapProjection public abstract class MapProjection
{ {
public const int TileSize = 256;
public const double Wgs84EquatorialRadius = 6378137d; public const double Wgs84EquatorialRadius = 6378137d;
public const double Wgs84Flattening = 1d / 298.257223563; public const double Wgs84Flattening = 1d / 298.257223563;
public static readonly double Wgs84Eccentricity = Math.Sqrt((2d - Wgs84Flattening) * Wgs84Flattening); public static readonly double Wgs84Eccentricity = Math.Sqrt((2d - Wgs84Flattening) * Wgs84Flattening);
@ -79,6 +78,11 @@ namespace MapControl
/// </summary> /// </summary>
public Matrix InverseViewportTransform { get; private set; } public Matrix InverseViewportTransform { get; private set; }
/// <summary>
/// Gets the rotation angle of the ViewportTransform matrix.
/// </summary>
public double ViewportRotation { get; private set; }
/// <summary> /// <summary>
/// Gets the scaling factor from cartesian map coordinates to viewport coordinates /// Gets the scaling factor from cartesian map coordinates to viewport coordinates
/// at the projection's point of true scale. /// at the projection's point of true scale.
@ -178,19 +182,76 @@ namespace MapControl
} }
/// <summary> /// <summary>
/// Sets ProjectionCenter, ViewportScale, ViewportTransform and InverseViewportTransform. /// Sets ProjectionCenter, ViewportScale, ViewportRotation, ViewportTransform and InverseViewportTransform.
/// </summary> /// </summary>
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; 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 center = LocationToPoint(mapCenter);
var matrix = MatrixFactory.Create(center, ViewportScale, -ViewportScale, heading, viewportCenter); var matrix = CreateViewportTransform(center, viewportCenter);
ViewportTransform = matrix; ViewportTransform = matrix;
matrix.Invert(); matrix.Invert();
InverseViewportTransform = matrix; 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));
}
} }
} }

View file

@ -21,7 +21,14 @@ namespace MapControl
/// </summary> /// </summary>
public class MapTileLayer : MapTileLayerBase 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;
}
/// <summary> /// <summary>
/// A default MapTileLayer using OpenStreetMap data. /// A default MapTileLayer using OpenStreetMap data.
@ -96,9 +103,9 @@ namespace MapControl
{ {
foreach (var tile in Tiles) foreach (var tile in Tiles)
{ {
var tileSize = MapProjection.TileSize << (TileGrid.ZoomLevel - tile.ZoomLevel); var tileSize = TileSize << (TileGrid.ZoomLevel - tile.ZoomLevel);
var x = tileSize * tile.X - MapProjection.TileSize * TileGrid.XMin; var x = tileSize * tile.X - TileSize * TileGrid.XMin;
var y = tileSize * tile.Y - MapProjection.TileSize * TileGrid.YMin; var y = tileSize * tile.Y - TileSize * TileGrid.YMin;
tile.Image.Width = tileSize; tile.Image.Width = tileSize;
tile.Image.Height = tileSize; tile.Image.Height = tileSize;
@ -136,53 +143,29 @@ namespace MapControl
protected override void SetRenderTransform() protected override void SetRenderTransform()
{ {
var tileGridSize = (double)(1 << TileGrid.ZoomLevel); // tile grid origin in pixels
// top/left tile grid corner in map coordinates
// //
var tileGridOrigin = new Point( var tileGridOrigin = new Point(TileSize * TileGrid.XMin, TileSize * TileGrid.YMin);
WebMercatorMapSize * (TileGrid.XMin / tileGridSize - 0.5),
WebMercatorMapSize * (0.5 - TileGrid.YMin / tileGridSize));
// top/left tile grid corner in viewport coordinates ((MatrixTransform)RenderTransform).Matrix = ParentMap.MapProjection.CreateTileLayerTransform(
// TileGridScale(TileGrid.ZoomLevel), TileGridTopLeft, tileGridOrigin);
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);
} }
private bool SetTileGrid() private bool SetTileGrid()
{ {
var tileGridZoomLevel = (int)Math.Round(ParentMap.ZoomLevel); var tileGridZoomLevel = (int)Math.Floor(ParentMap.ZoomLevel + 0.001); // avoid rounding issues
var tileGridSize = (double)(1 << tileGridZoomLevel);
// 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( var xMin = (int)Math.Floor(tileBounds.X / TileSize);
tileGridSize * (0.5 + tileOrigin.X / WebMercatorMapSize), var yMin = (int)Math.Floor(tileBounds.Y / TileSize);
tileGridSize * (0.5 - tileOrigin.Y / WebMercatorMapSize)); var xMax = (int)Math.Floor((tileBounds.X + tileBounds.Width) / TileSize);
var yMax = (int)Math.Floor((tileBounds.Y + tileBounds.Height) / TileSize);
// 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);
if (TileGrid != null && if (TileGrid != null &&
TileGrid.ZoomLevel == tileGridZoomLevel && TileGrid.ZoomLevel == tileGridZoomLevel &&

View file

@ -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;
}
}
}

View file

@ -164,9 +164,7 @@ namespace MapControl
.Replace("{W}", west.ToString(CultureInfo.InvariantCulture)) .Replace("{W}", west.ToString(CultureInfo.InvariantCulture))
.Replace("{S}", south.ToString(CultureInfo.InvariantCulture)) .Replace("{S}", south.ToString(CultureInfo.InvariantCulture))
.Replace("{E}", east.ToString(CultureInfo.InvariantCulture)) .Replace("{E}", east.ToString(CultureInfo.InvariantCulture))
.Replace("{N}", north.ToString(CultureInfo.InvariantCulture)) .Replace("{N}", north.ToString(CultureInfo.InvariantCulture));
.Replace("{X}", MapProjection.TileSize.ToString())
.Replace("{Y}", MapProjection.TileSize.ToString());
} }
private string GetLatLonBoundingBoxUri(int x, int y, int zoomLevel) private string GetLatLonBoundingBoxUri(int x, int y, int zoomLevel)
@ -181,9 +179,7 @@ namespace MapControl
.Replace("{w}", west.ToString(CultureInfo.InvariantCulture)) .Replace("{w}", west.ToString(CultureInfo.InvariantCulture))
.Replace("{s}", south.ToString(CultureInfo.InvariantCulture)) .Replace("{s}", south.ToString(CultureInfo.InvariantCulture))
.Replace("{e}", east.ToString(CultureInfo.InvariantCulture)) .Replace("{e}", east.ToString(CultureInfo.InvariantCulture))
.Replace("{n}", north.ToString(CultureInfo.InvariantCulture)) .Replace("{n}", north.ToString(CultureInfo.InvariantCulture));
.Replace("{X}", MapProjection.TileSize.ToString())
.Replace("{Y}", MapProjection.TileSize.ToString());
} }
} }
} }

View file

@ -97,18 +97,19 @@ namespace MapControl
{ {
foreach (var layer in Children.Cast<WmtsTileMatrixLayer>()) foreach (var layer in Children.Cast<WmtsTileMatrixLayer>())
{ {
layer.SetRenderTransform(ParentMap.MapProjection, ParentMap.Heading); layer.SetRenderTransform(ParentMap.MapProjection);
} }
} }
private bool UpdateChildLayers(WmtsTileMatrixSet tileMatrixSet) 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 var currentMatrixes = tileMatrixSet.TileMatrixes
.Where((matrix, i) => i == 0 || matrix.Scale <= ParentMap.MapProjection.ViewportScale) .Where((matrix, i) => i == 0 || matrix.Scale <= maxScale)
.ToList(); .ToList();
if (this != ParentMap.MapLayer) // do not load background tiles if (this != ParentMap.MapLayer) // do not load background tiles
@ -136,7 +137,7 @@ namespace MapControl
layersChanged = true; layersChanged = true;
} }
if (layer.SetBounds(ParentMap.MapProjection, ParentMap.Heading, ParentMap.RenderSize)) if (layer.SetBounds(ParentMap.MapProjection, ParentMap.RenderSize))
{ {
layersChanged = true; layersChanged = true;
} }

View file

@ -36,29 +36,24 @@ namespace MapControl
public IReadOnlyCollection<Tile> Tiles { get; private set; } = new List<Tile>(); public IReadOnlyCollection<Tile> Tiles { get; private set; } = new List<Tile>();
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);
var tileMatrixOrigin = new Point( }
TileMatrix.Scale * (tileOrigin.X - TileMatrix.TopLeft.X),
TileMatrix.Scale * (TileMatrix.TopLeft.Y - tileOrigin.Y));
// relative layer scale public bool SetBounds(MapProjection projection, Size viewportSize)
//
var scale = TileMatrix.Scale / projection.ViewportScale;
var transform = new MatrixTransform
{ {
Matrix = MatrixFactory.Create(scale, -heading, tileMatrixOrigin) // bounds in tile pixels from viewport size
}; //
var bounds = projection.GetTileBounds(TileMatrix.Scale, TileMatrix.TopLeft, viewportSize);
var bounds = transform.TransformBounds(new Rect(0d, 0d, mapSize.Width, mapSize.Height));
// tile column and row index bounds
//
var xMin = (int)Math.Floor(bounds.X / TileMatrix.TileWidth); var xMin = (int)Math.Floor(bounds.X / TileMatrix.TileWidth);
var yMin = (int)Math.Floor(bounds.Y / TileMatrix.TileHeight); var yMin = (int)Math.Floor(bounds.Y / TileMatrix.TileHeight);
var xMax = (int)Math.Floor((bounds.X + bounds.Width) / TileMatrix.TileWidth); var xMax = (int)Math.Floor((bounds.X + bounds.Width) / TileMatrix.TileWidth);
@ -82,25 +77,6 @@ namespace MapControl
return true; 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() public void UpdateTiles()
{ {
var newTiles = new List<Tile>(); var newTiles = new List<Tile>();