mirror of
https://github.com/ClemensFischer/XAML-Map-Control.git
synced 2026-01-05 08:10:15 +01:00
WMTS tile handling
This commit is contained in:
parent
6d359a5a91
commit
0e27e95c6f
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}"))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
35
MapControl/Shared/TileCollection.cs
Normal file
35
MapControl/Shared/TileCollection.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue