WMTS tile handling

This commit is contained in:
ClemensFischer 2022-11-22 19:15:34 +01:00
parent 6d359a5a91
commit 0e27e95c6f
11 changed files with 100 additions and 71 deletions

View file

@ -8,18 +8,18 @@ namespace MapControl
{
public class BingMapsTileSource : TileSource
{
public override Uri GetUri(int x, int y, int zoomLevel)
public override Uri GetUri(int column, int row, int zoomLevel)
{
Uri uri = null;
if (UriTemplate != null && Subdomains != null && Subdomains.Length > 0 && zoomLevel > 0)
{
var subdomain = Subdomains[(x + y) % Subdomains.Length];
var subdomain = Subdomains[(column + row) % Subdomains.Length];
var quadkey = new char[zoomLevel];
for (var z = zoomLevel - 1; z >= 0; z--, x /= 2, y /= 2)
for (var z = zoomLevel - 1; z >= 0; z--, column /= 2, row /= 2)
{
quadkey[z] = (char)('0' + 2 * (y % 2) + (x % 2));
quadkey[z] = (char)('0' + 2 * (row % 2) + (column % 2));
}
uri = new Uri(UriTemplate

View file

@ -9,17 +9,17 @@ namespace MapControl
{
public class BoundingBoxTileSource : TileSource
{
public override Uri GetUri(int x, int y, int zoomLevel)
public override Uri GetUri(int column, int row, int zoomLevel)
{
Uri uri = null;
if (UriTemplate != null)
{
var tileSize = 360d / (1 << zoomLevel); // tile width in degrees
var west = MapProjection.Wgs84MeterPerDegree * (x * tileSize - 180d);
var east = MapProjection.Wgs84MeterPerDegree * ((x + 1) * tileSize - 180d);
var south = MapProjection.Wgs84MeterPerDegree * (180d - (y + 1) * tileSize);
var north = MapProjection.Wgs84MeterPerDegree * (180d - y * tileSize);
var west = MapProjection.Wgs84MeterPerDegree * (column * tileSize - 180d);
var east = MapProjection.Wgs84MeterPerDegree * ((column + 1) * tileSize - 180d);
var south = MapProjection.Wgs84MeterPerDegree * (180d - (row + 1) * tileSize);
var north = MapProjection.Wgs84MeterPerDegree * (180d - row * tileSize);
if (UriTemplate.Contains("{bbox}"))
{

View file

@ -3,8 +3,6 @@
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
#if WINUI
using Windows.Foundation;
@ -62,7 +60,7 @@ namespace MapControl
public TileMatrix TileMatrix { get; private set; }
public IReadOnlyCollection<Tile> Tiles { get; private set; } = new List<Tile>();
public TileCollection Tiles { get; private set; } = new TileCollection();
/// <summary>
/// Minimum zoom level supported by the MapTileLayer. Default value is 0.
@ -110,6 +108,8 @@ namespace MapControl
{
foreach (var tile in Tiles)
{
// arrange tiles relative to XMin/YMin
//
var tileSize = TileSize << (TileMatrix.ZoomLevel - tile.ZoomLevel);
var x = tileSize * tile.X - TileSize * TileMatrix.XMin;
var y = tileSize * tile.Y - TileSize * TileMatrix.YMin;
@ -138,7 +138,7 @@ namespace MapControl
{
if (Tiles.Count > 0)
{
Tiles = new List<Tile>(); // clear all
Tiles = new TileCollection(); // clear all
}
update = true;
}
@ -182,7 +182,7 @@ namespace MapControl
//
var bounds = ParentMap.ViewTransform.GetTileMatrixBounds(tileMatrixScale, MapTopLeft, ParentMap.RenderSize);
// tile column and row index bounds
// tile X and Y bounds
//
var xMin = (int)Math.Floor(bounds.X / TileSize);
var yMin = (int)Math.Floor(bounds.Y / TileSize);
@ -204,7 +204,7 @@ namespace MapControl
private void UpdateTiles()
{
var tiles = new List<Tile>();
var tiles = new TileCollection();
if (TileSource != null && TileMatrix != null)
{
@ -218,32 +218,18 @@ namespace MapControl
for (var z = minZoomLevel; z <= maxZoomLevel; z++)
{
var numTiles = 1 << z;
var tileSize = 1 << (TileMatrix.ZoomLevel - z);
var x1 = (int)Math.Floor((double)TileMatrix.XMin / tileSize); // may be negative
var x2 = TileMatrix.XMax / tileSize;
var x2 = TileMatrix.XMax / tileSize; // may be greater than numTiles-1
var y1 = Math.Max(TileMatrix.YMin / tileSize, 0);
var y2 = Math.Min(TileMatrix.YMax / tileSize, (1 << z) - 1);
var y2 = Math.Min(TileMatrix.YMax / tileSize, numTiles - 1);
for (var y = y1; y <= y2; y++)
{
for (var x = x1; x <= x2; x++)
{
var tile = Tiles.FirstOrDefault(t => t.ZoomLevel == z && t.Y == y && t.X == x);
if (tile == null)
{
tile = new Tile(z, x, y);
var equivalentTile = Tiles.FirstOrDefault(
t => t.IsLoaded && t.ZoomLevel == z && t.Y == y && t.XIndex == tile.XIndex);
if (equivalentTile != null)
{
tile.SetImageSource(equivalentTile.Image.Source, false);
}
}
tiles.Add(tile);
tiles.Add(Tiles.GetTile(z, x, y, numTiles));
}
}
}

View file

@ -24,25 +24,19 @@ namespace MapControl
{
public partial class Tile
{
public Tile(int zoomLevel, int x, int y)
public Tile(int zoomLevel, int x, int y, int columnCount)
{
ZoomLevel = zoomLevel;
X = x;
Y = y;
Column = ((x % columnCount) + columnCount) % columnCount;
}
public int ZoomLevel { get; }
public int X { get; }
public int Y { get; }
public int XIndex
{
get
{
var numTiles = 1 << ZoomLevel;
return ((X % numTiles) + numTiles) % numTiles;
}
}
public int Column { get; }
public int Row => Y;
public Image Image { get; } = new Image
{

View file

@ -0,0 +1,35 @@
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
// © 2022 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System.Collections.Generic;
using System.Linq;
namespace MapControl
{
public class TileCollection : List<Tile>
{
/// <summary>
/// Get a matching Tile from a TileCollection or create a new one.
/// </summary>
public Tile GetTile(int zoomLevel, int x, int y, int columnCount)
{
var tile = this.FirstOrDefault(t => t.ZoomLevel == zoomLevel && t.X == x && t.Y == y);
if (tile == null)
{
tile = new Tile(zoomLevel, x, y, columnCount);
var equivalentTile = this.FirstOrDefault(
t => t.IsLoaded && t.ZoomLevel == tile.ZoomLevel && t.Column == tile.Column && t.Row == tile.Row);
if (equivalentTile != null)
{
tile.SetImageSource(equivalentTile.Image.Source, false); // no opacity animation
}
}
return tile;
}
}
}

View file

@ -133,7 +133,7 @@ namespace MapControl
}
catch (Exception ex)
{
Debug.WriteLine($"TileImageLoader: {tile.ZoomLevel}/{tile.XIndex}/{tile.Y}: {ex.Message}");
Debug.WriteLine($"TileImageLoader: {tile.ZoomLevel}/{tile.Column}/{tile.Row}: {ex.Message}");
}
if (Progress != null && !tileQueue.IsCanceled)
@ -149,10 +149,10 @@ namespace MapControl
{
if (string.IsNullOrEmpty(cacheName))
{
return LoadTile(tile, () => tileSource.LoadImageAsync(tile.XIndex, tile.Y, tile.ZoomLevel));
return LoadTile(tile, () => tileSource.LoadImageAsync(tile.Column, tile.Row, tile.ZoomLevel));
}
var uri = tileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel);
var uri = tileSource.GetUri(tile.Column, tile.Row, tile.ZoomLevel);
if (uri != null)
{
@ -164,7 +164,7 @@ namespace MapControl
}
var cacheKey = string.Format(CultureInfo.InvariantCulture,
"{0}/{1}/{2}/{3}{4}", cacheName, tile.ZoomLevel, tile.XIndex, tile.Y, extension);
"{0}/{1}/{2}/{3}{4}", cacheName, tile.ZoomLevel, tile.Column, tile.Row, extension);
return LoadCachedTile(tile, uri, cacheKey);
}

View file

@ -49,20 +49,20 @@ namespace MapControl
/// <summary>
/// Gets the image Uri for the specified tile indices and zoom level.
/// </summary>
public virtual Uri GetUri(int x, int y, int zoomLevel)
public virtual Uri GetUri(int column, int row, int zoomLevel)
{
Uri uri = null;
if (UriTemplate != null && x >= 0 && y >= 0 && zoomLevel >= 0)
if (UriTemplate != null && column >= 0 && row >= 0 && zoomLevel >= 0)
{
var uriString = UriTemplate
.Replace("{x}", x.ToString())
.Replace("{y}", y.ToString())
.Replace("{x}", column.ToString())
.Replace("{y}", row.ToString())
.Replace("{z}", zoomLevel.ToString());
if (Subdomains != null && Subdomains.Length > 0)
{
uriString = uriString.Replace("{s}", Subdomains[(x + y) % Subdomains.Length]);
uriString = uriString.Replace("{s}", Subdomains[(column + row) % Subdomains.Length]);
}
uri = new Uri(uriString, UriKind.RelativeOrAbsolute);
@ -72,11 +72,11 @@ namespace MapControl
}
/// <summary>
/// Loads a tile ImageSource asynchronously from GetUri(x, y, zoomLevel).
/// Loads a tile ImageSource asynchronously from GetUri(column, row, zoomLevel).
/// </summary>
public virtual Task<ImageSource> LoadImageAsync(int x, int y, int zoomLevel)
public virtual Task<ImageSource> LoadImageAsync(int column, int row, int zoomLevel)
{
var uri = GetUri(x, y, zoomLevel);
var uri = GetUri(column, row, zoomLevel);
return uri != null ? ImageLoader.LoadImageAsync(uri) : Task.FromResult((ImageSource)null);
}
@ -84,9 +84,9 @@ namespace MapControl
public class TmsTileSource : TileSource
{
public override Uri GetUri(int x, int y, int zoomLevel)
public override Uri GetUri(int column, int row, int zoomLevel)
{
return base.GetUri(x, (1 << zoomLevel) - 1 - y, zoomLevel);
return base.GetUri(column, (1 << zoomLevel) - 1 - row, zoomLevel);
}
}
}

View file

@ -10,11 +10,14 @@ namespace MapControl
{
public class WmtsTileMatrix
{
// See 07-057r7_Web_Map_Tile_Service_Standard.pdf, section 6.1.a, page 8:
// "standardized rendering pixel size" is 0.28 mm
public WmtsTileMatrix(string identifier, double scaleDenominator, Point topLeft,
int tileWidth, int tileHeight, int matrixWidth, int matrixHeight)
{
Identifier = identifier;
Scale = 1 / (scaleDenominator * 0.00028);
Scale = 1 / (scaleDenominator * 0.00028); // 0.28 mm
TopLeft = topLeft;
TileWidth = tileWidth;
TileHeight = tileHeight;

View file

@ -3,8 +3,6 @@
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Collections.Generic;
using System.Linq;
#if WINUI
using Windows.Foundation;
using Microsoft.UI.Xaml.Controls;
@ -39,7 +37,7 @@ namespace MapControl
public int XMax { get; private set; }
public int YMax { get; private set; }
public IReadOnlyCollection<Tile> Tiles { get; private set; } = new List<Tile>();
public TileCollection Tiles { get; private set; } = new TileCollection();
public void SetRenderTransform(ViewTransform viewTransform)
{
@ -57,16 +55,26 @@ namespace MapControl
//
var bounds = viewTransform.GetTileMatrixBounds(TileMatrix.Scale, TileMatrix.TopLeft, viewSize);
// tile column and row index bounds
// tile X and Y bounds
//
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);
var yMax = (int)Math.Floor((bounds.Y + bounds.Height) / TileMatrix.TileHeight);
xMin = Math.Max(xMin, 0);
// total tile matrix width in meters
//
var totalWidth = TileMatrix.MatrixWidth * TileMatrix.TileWidth / TileMatrix.Scale;
if (Math.Abs(totalWidth - 360d * MapProjection.Wgs84MeterPerDegree) > 1d)
{
// no full longitudinal coverage, restrict x index
//
xMin = Math.Max(xMin, 0);
xMax = Math.Min(Math.Max(xMax, 0), TileMatrix.MatrixWidth - 1);
}
yMin = Math.Max(yMin, 0);
xMax = Math.Min(Math.Max(xMax, 0), TileMatrix.MatrixWidth - 1);
yMax = Math.Min(Math.Max(yMax, 0), TileMatrix.MatrixHeight - 1);
if (XMin == xMin && YMin == yMin && XMax == xMax && YMax == yMax)
@ -84,17 +92,17 @@ namespace MapControl
public void UpdateTiles()
{
var newTiles = new List<Tile>();
var tiles = new TileCollection();
for (var y = YMin; y <= YMax; y++)
{
for (var x = XMin; x <= XMax; x++)
{
newTiles.Add(Tiles.FirstOrDefault(t => t.X == x && t.Y == y) ?? new Tile(ZoomLevel, x, y));
tiles.Add(Tiles.GetTile(ZoomLevel, x, y, TileMatrix.MatrixWidth));
}
}
Tiles = newTiles;
Tiles = tiles;
Children.Clear();

View file

@ -10,19 +10,19 @@ namespace MapControl
{
public WmtsTileMatrixSet TileMatrixSet { get; set; }
public override Uri GetUri(int x, int y, int zoomLevel)
public override Uri GetUri(int column, int row, int zoomLevel)
{
Uri uri = null;
if (UriTemplate != null &&
TileMatrixSet != null && TileMatrixSet.TileMatrixes.Count > zoomLevel &&
x >= 0 && y >= 0 && zoomLevel >= 0)
column >= 0 && row >= 0 && zoomLevel >= 0)
{
uri = new Uri(UriTemplate
.Replace("{TileMatrixSet}", TileMatrixSet.Identifier)
.Replace("{TileMatrix}", TileMatrixSet.TileMatrixes[zoomLevel].Identifier)
.Replace("{TileCol}", x.ToString())
.Replace("{TileRow}", y.ToString()));
.Replace("{TileCol}", column.ToString())
.Replace("{TileRow}", row.ToString()));
}
return uri;

View file

@ -149,6 +149,9 @@
<Compile Include="..\Shared\Tile.cs">
<Link>Tile.cs</Link>
</Compile>
<Compile Include="..\Shared\TileCollection.cs">
<Link>TileCollection.cs</Link>
</Compile>
<Compile Include="..\Shared\TileImageLoader.cs">
<Link>TileImageLoader.cs</Link>
</Compile>