2017-06-25 23:05:48 +02:00
|
|
|
|
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
|
2018-02-09 17:43:47 +01:00
|
|
|
|
// © 2018 Clemens Fischer
|
2012-05-04 12:52:20 +02:00
|
|
|
|
// Licensed under the Microsoft Public License (Ms-PL)
|
|
|
|
|
|
|
|
|
|
|
|
using System;
|
2017-10-08 17:35:07 +02:00
|
|
|
|
using System.Diagnostics;
|
2012-04-25 22:02:53 +02:00
|
|
|
|
using System.Globalization;
|
2017-10-08 17:35:07 +02:00
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
#if WINDOWS_UWP
|
|
|
|
|
|
using Windows.UI.Xaml.Media;
|
|
|
|
|
|
#else
|
|
|
|
|
|
using System.Windows.Media;
|
|
|
|
|
|
#endif
|
2012-04-25 22:02:53 +02:00
|
|
|
|
|
|
|
|
|
|
namespace MapControl
|
|
|
|
|
|
{
|
2012-05-04 12:52:20 +02:00
|
|
|
|
/// <summary>
|
2017-08-04 21:38:58 +02:00
|
|
|
|
/// Provides the download Uri or ImageSource of map tiles.
|
2012-05-04 12:52:20 +02:00
|
|
|
|
/// </summary>
|
2012-11-22 21:42:29 +01:00
|
|
|
|
public partial class TileSource
|
2012-04-25 22:02:53 +02:00
|
|
|
|
{
|
2017-10-09 19:17:04 +02:00
|
|
|
|
private Func<int, int, int, string> getUri;
|
|
|
|
|
|
private string uriFormat;
|
|
|
|
|
|
private int subdomainIndex = -1;
|
2012-07-20 21:57:29 +02:00
|
|
|
|
|
|
|
|
|
|
public TileSource()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-10-19 21:50:23 +02:00
|
|
|
|
protected TileSource(string uriFormat)
|
2012-07-20 21:57:29 +02:00
|
|
|
|
{
|
2014-10-19 21:50:23 +02:00
|
|
|
|
this.uriFormat = uriFormat;
|
2012-07-20 21:57:29 +02:00
|
|
|
|
}
|
2014-11-19 21:11:14 +01:00
|
|
|
|
|
2017-10-09 19:17:04 +02:00
|
|
|
|
public string[] Subdomains { get; set; }
|
|
|
|
|
|
|
2017-08-04 21:38:58 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets or sets the format string to produce tile Uris.
|
|
|
|
|
|
/// </summary>
|
2012-07-20 21:57:29 +02:00
|
|
|
|
public string UriFormat
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return uriFormat; }
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
2015-03-15 10:11:19 +01:00
|
|
|
|
if (string.IsNullOrEmpty(value))
|
2012-07-20 21:57:29 +02:00
|
|
|
|
{
|
2015-03-15 10:11:19 +01:00
|
|
|
|
throw new ArgumentException("The value of the UriFormat property must not be null or empty.");
|
2012-07-20 21:57:29 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2013-01-28 23:49:22 +01:00
|
|
|
|
uriFormat = value;
|
|
|
|
|
|
|
2017-10-09 19:17:04 +02:00
|
|
|
|
if (uriFormat.Contains("{x}") && uriFormat.Contains("{z}"))
|
2012-07-20 21:57:29 +02:00
|
|
|
|
{
|
2017-10-09 19:17:04 +02:00
|
|
|
|
if (uriFormat.Contains("{y}"))
|
2013-01-29 17:55:53 +01:00
|
|
|
|
{
|
2017-10-09 19:17:04 +02:00
|
|
|
|
getUri = GetDefaultUri;
|
2013-01-29 17:55:53 +01:00
|
|
|
|
}
|
2017-10-09 19:17:04 +02:00
|
|
|
|
else if (uriFormat.Contains("{v}"))
|
2013-05-07 18:12:25 +02:00
|
|
|
|
{
|
2017-10-09 19:17:04 +02:00
|
|
|
|
getUri = GetTmsUri;
|
2013-05-07 18:12:25 +02:00
|
|
|
|
}
|
2012-07-20 21:57:29 +02:00
|
|
|
|
}
|
2013-01-29 17:55:53 +01:00
|
|
|
|
else if (uriFormat.Contains("{q}")) // {i} is optional
|
2012-07-20 21:57:29 +02:00
|
|
|
|
{
|
2013-01-29 17:55:53 +01:00
|
|
|
|
getUri = GetQuadKeyUri;
|
2012-07-20 21:57:29 +02:00
|
|
|
|
}
|
2013-05-07 18:12:25 +02:00
|
|
|
|
else if (uriFormat.Contains("{W}") && uriFormat.Contains("{S}") && uriFormat.Contains("{E}") && uriFormat.Contains("{N}"))
|
2012-07-20 21:57:29 +02:00
|
|
|
|
{
|
2013-01-29 17:55:53 +01:00
|
|
|
|
getUri = GetBoundingBoxUri;
|
2012-07-20 21:57:29 +02:00
|
|
|
|
}
|
2013-05-07 18:12:25 +02:00
|
|
|
|
else if (uriFormat.Contains("{w}") && uriFormat.Contains("{s}") && uriFormat.Contains("{e}") && uriFormat.Contains("{n}"))
|
|
|
|
|
|
{
|
|
|
|
|
|
getUri = GetLatLonBoundingBoxUri;
|
|
|
|
|
|
}
|
2017-10-09 19:17:04 +02:00
|
|
|
|
|
|
|
|
|
|
if (Subdomains == null && uriFormat.Contains("{c}"))
|
|
|
|
|
|
{
|
|
|
|
|
|
Subdomains = new string[] { "a", "b", "c" };
|
|
|
|
|
|
}
|
2012-07-20 21:57:29 +02:00
|
|
|
|
}
|
2013-01-29 17:55:53 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-08-04 21:38:58 +02:00
|
|
|
|
/// <summary>
|
2017-10-09 19:17:04 +02:00
|
|
|
|
/// Gets the image Uri for the specified tile indices and zoom level.
|
2017-08-04 21:38:58 +02:00
|
|
|
|
/// </summary>
|
2013-01-29 17:55:53 +01:00
|
|
|
|
public virtual Uri GetUri(int x, int y, int zoomLevel)
|
|
|
|
|
|
{
|
2017-10-09 19:17:04 +02:00
|
|
|
|
if (getUri == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var uri = getUri(x, y, zoomLevel);
|
|
|
|
|
|
|
|
|
|
|
|
if (Subdomains != null && Subdomains.Length > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
subdomainIndex = (subdomainIndex + 1) % Subdomains.Length;
|
|
|
|
|
|
|
|
|
|
|
|
uri = uri.Replace("{c}", Subdomains[subdomainIndex]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return new Uri(uri, UriKind.RelativeOrAbsolute);
|
2012-07-20 21:57:29 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-10-08 17:35:07 +02:00
|
|
|
|
/// <summary>
|
2017-10-09 19:17:04 +02:00
|
|
|
|
/// Loads a tile ImageSource asynchronously from GetUri(x, y, zoomLevel).
|
2017-10-08 17:35:07 +02:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
public virtual async Task<ImageSource> LoadImageAsync(int x, int y, int zoomLevel)
|
|
|
|
|
|
{
|
|
|
|
|
|
ImageSource imageSource = null;
|
|
|
|
|
|
|
|
|
|
|
|
var uri = GetUri(x, y, zoomLevel);
|
|
|
|
|
|
|
|
|
|
|
|
if (uri != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2017-10-27 17:15:18 +02:00
|
|
|
|
imageSource = await ImageLoader.LoadImageAsync(uri, true);
|
2017-10-08 17:35:07 +02:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.WriteLine("TileSource: {0}: {1}", uri, ex.Message);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return imageSource;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-10-09 19:17:04 +02:00
|
|
|
|
private string GetDefaultUri(int x, int y, int zoomLevel)
|
2012-04-25 22:02:53 +02:00
|
|
|
|
{
|
2017-10-09 19:17:04 +02:00
|
|
|
|
return uriFormat
|
2015-08-09 20:04:44 +02:00
|
|
|
|
.Replace("{x}", x.ToString())
|
|
|
|
|
|
.Replace("{y}", y.ToString())
|
2017-10-09 19:17:04 +02:00
|
|
|
|
.Replace("{z}", zoomLevel.ToString());
|
2012-04-25 22:02:53 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-10-09 19:17:04 +02:00
|
|
|
|
private string GetTmsUri(int x, int y, int zoomLevel)
|
2013-04-21 23:56:08 +02:00
|
|
|
|
{
|
|
|
|
|
|
y = (1 << zoomLevel) - 1 - y;
|
|
|
|
|
|
|
2017-10-09 19:17:04 +02:00
|
|
|
|
return uriFormat
|
2015-08-09 20:04:44 +02:00
|
|
|
|
.Replace("{x}", x.ToString())
|
|
|
|
|
|
.Replace("{v}", y.ToString())
|
2017-10-09 19:17:04 +02:00
|
|
|
|
.Replace("{z}", zoomLevel.ToString());
|
2013-04-21 23:56:08 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-10-09 19:17:04 +02:00
|
|
|
|
private string GetQuadKeyUri(int x, int y, int zoomLevel)
|
2012-04-25 22:02:53 +02:00
|
|
|
|
{
|
2013-11-17 16:52:03 +01:00
|
|
|
|
if (zoomLevel < 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-10-19 21:50:23 +02:00
|
|
|
|
var quadkey = new char[zoomLevel];
|
2012-04-25 22:02:53 +02:00
|
|
|
|
|
2012-11-22 21:42:29 +01:00
|
|
|
|
for (var z = zoomLevel - 1; z >= 0; z--, x /= 2, y /= 2)
|
2012-04-25 22:02:53 +02:00
|
|
|
|
{
|
2014-10-19 21:50:23 +02:00
|
|
|
|
quadkey[z] = (char)('0' + 2 * (y % 2) + (x % 2));
|
2012-04-25 22:02:53 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-10-09 19:17:04 +02:00
|
|
|
|
return uriFormat
|
2015-11-28 21:09:25 +01:00
|
|
|
|
.Replace("{i}", new string(quadkey, zoomLevel - 1, 1))
|
2017-10-09 19:17:04 +02:00
|
|
|
|
.Replace("{q}", new string(quadkey));
|
2012-04-25 22:02:53 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-10-09 19:17:04 +02:00
|
|
|
|
private string GetBoundingBoxUri(int x, int y, int zoomLevel)
|
2012-04-25 22:02:53 +02:00
|
|
|
|
{
|
2017-02-05 18:35:30 +01:00
|
|
|
|
var tileSize = 360d / (1 << zoomLevel); // tile width in degrees
|
2017-06-25 23:05:48 +02:00
|
|
|
|
var west = MapProjection.MetersPerDegree * (x * tileSize - 180d);
|
|
|
|
|
|
var east = MapProjection.MetersPerDegree * ((x + 1) * tileSize - 180d);
|
|
|
|
|
|
var south = MapProjection.MetersPerDegree * (180d - (y + 1) * tileSize);
|
|
|
|
|
|
var north = MapProjection.MetersPerDegree * (180d - y * tileSize);
|
2015-08-09 20:04:44 +02:00
|
|
|
|
|
2017-10-09 19:17:04 +02:00
|
|
|
|
return uriFormat
|
2015-08-09 20:04:44 +02:00
|
|
|
|
.Replace("{W}", west.ToString(CultureInfo.InvariantCulture))
|
|
|
|
|
|
.Replace("{S}", south.ToString(CultureInfo.InvariantCulture))
|
|
|
|
|
|
.Replace("{E}", east.ToString(CultureInfo.InvariantCulture))
|
|
|
|
|
|
.Replace("{N}", north.ToString(CultureInfo.InvariantCulture))
|
2017-08-04 21:38:58 +02:00
|
|
|
|
.Replace("{X}", MapProjection.TileSize.ToString())
|
2017-10-09 19:17:04 +02:00
|
|
|
|
.Replace("{Y}", MapProjection.TileSize.ToString());
|
2013-05-07 18:12:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-10-09 19:17:04 +02:00
|
|
|
|
private string GetLatLonBoundingBoxUri(int x, int y, int zoomLevel)
|
2013-05-07 18:12:25 +02:00
|
|
|
|
{
|
2017-02-05 18:35:30 +01:00
|
|
|
|
var tileSize = 360d / (1 << zoomLevel); // tile width in degrees
|
|
|
|
|
|
var west = x * tileSize - 180d;
|
|
|
|
|
|
var east = (x + 1) * tileSize - 180d;
|
2017-06-25 23:05:48 +02:00
|
|
|
|
var south = WebMercatorProjection.YToLatitude(180d - (y + 1) * tileSize);
|
|
|
|
|
|
var north = WebMercatorProjection.YToLatitude(180d - y * tileSize);
|
2015-08-09 20:04:44 +02:00
|
|
|
|
|
2017-10-09 19:17:04 +02:00
|
|
|
|
return uriFormat
|
2015-08-09 20:04:44 +02:00
|
|
|
|
.Replace("{w}", west.ToString(CultureInfo.InvariantCulture))
|
|
|
|
|
|
.Replace("{s}", south.ToString(CultureInfo.InvariantCulture))
|
|
|
|
|
|
.Replace("{e}", east.ToString(CultureInfo.InvariantCulture))
|
|
|
|
|
|
.Replace("{n}", north.ToString(CultureInfo.InvariantCulture))
|
2017-08-04 21:38:58 +02:00
|
|
|
|
.Replace("{X}", MapProjection.TileSize.ToString())
|
2017-10-09 19:17:04 +02:00
|
|
|
|
.Replace("{Y}", MapProjection.TileSize.ToString());
|
2012-04-25 22:02:53 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|