TileSources, WMTS

This commit is contained in:
ClemensFischer 2025-10-29 18:59:19 +01:00
parent 605185db6e
commit ce7c33f6e4
7 changed files with 70 additions and 63 deletions

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Text;
namespace MapControl namespace MapControl
{ {
@ -23,9 +24,21 @@ namespace MapControl
var s = south.ToString("F2", CultureInfo.InvariantCulture); var s = south.ToString("F2", CultureInfo.InvariantCulture);
var n = north.ToString("F2", CultureInfo.InvariantCulture); var n = north.ToString("F2", CultureInfo.InvariantCulture);
uri = UriTemplate.Contains("{bbox}") if (UriTemplate.Contains("{bbox}"))
? new Uri(UriTemplate.Replace("{bbox}", $"{w},{s},{e},{n}")) {
: new Uri(UriTemplate.Replace("{west}", w).Replace("{south}", s).Replace("{east}", e).Replace("{north}", n)); uri = new Uri(UriTemplate.Replace("{bbox}", $"{w},{s},{e},{n}"));
}
else
{
var uriBuilder = new StringBuilder(UriTemplate);
uriBuilder.Replace("{west}", w);
uriBuilder.Replace("{south}", s);
uriBuilder.Replace("{east}", e);
uriBuilder.Replace("{north}", n);
uri = new Uri(uriBuilder.ToString());
}
} }
return uri; return uri;

View file

@ -1,5 +1,5 @@
using System; using System;
using System.Threading; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
#if WPF #if WPF
using System.Windows.Media; using System.Windows.Media;
@ -54,17 +54,18 @@ namespace MapControl
if (UriTemplate != null) if (UriTemplate != null)
{ {
var uriString = UriTemplate var uriBuilder = new StringBuilder(UriTemplate);
.Replace("{z}", zoomLevel.ToString())
.Replace("{x}", column.ToString()) uriBuilder.Replace("{z}", zoomLevel.ToString());
.Replace("{y}", row.ToString()); uriBuilder.Replace("{x}", column.ToString());
uriBuilder.Replace("{y}", row.ToString());
if (Subdomains != null && Subdomains.Length > 0) if (Subdomains != null && Subdomains.Length > 0)
{ {
uriString = uriString.Replace("{s}", Subdomains[(column + row) % Subdomains.Length]); uriBuilder.Replace("{s}", Subdomains[(column + row) % Subdomains.Length]);
} }
uri = new Uri(uriString, UriKind.RelativeOrAbsolute); uri = new Uri(uriBuilder.ToString(), UriKind.RelativeOrAbsolute);
} }
return uri; return uri;

View file

@ -23,28 +23,28 @@ namespace MapControl
private static readonly XNamespace xlink = "http://www.w3.org/1999/xlink"; private static readonly XNamespace xlink = "http://www.w3.org/1999/xlink";
public string Layer { get; private set; } public string Layer { get; private set; }
public string UrlTemplate { get; private set; } public string UriTemplate { get; private set; }
public List<WmtsTileMatrixSet> TileMatrixSets { get; private set; } public List<WmtsTileMatrixSet> TileMatrixSets { get; private set; }
public static async Task<WmtsCapabilities> ReadCapabilitiesAsync(Uri uri, string layer) public static async Task<WmtsCapabilities> ReadCapabilitiesAsync(Uri uri, string layer)
{ {
Stream xmlStream; Stream xmlStream;
string defaultUrl = null; string defaultUri = null;
if (!uri.IsAbsoluteUri) if (!uri.IsAbsoluteUri)
{ {
xmlStream = File.OpenRead(uri.OriginalString); xmlStream = File.OpenRead(uri.OriginalString);
} }
else if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)
{
defaultUrl = uri.OriginalString.Split('?')[0];
xmlStream = await ImageLoader.HttpClient.GetStreamAsync(uri);
}
else if (uri.IsFile) else if (uri.IsFile)
{ {
xmlStream = File.OpenRead(uri.LocalPath); xmlStream = File.OpenRead(uri.LocalPath);
} }
else if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)
{
defaultUri = uri.OriginalString.Split('?')[0];
xmlStream = await ImageLoader.HttpClient.GetStreamAsync(uri);
}
else else
{ {
throw new ArgumentException($"Invalid Capabilities URI: {uri}"); throw new ArgumentException($"Invalid Capabilities URI: {uri}");
@ -54,10 +54,10 @@ namespace MapControl
var element = await XDocument.LoadRootElementAsync(stream); var element = await XDocument.LoadRootElementAsync(stream);
return ReadCapabilities(element, layer, defaultUrl); return ReadCapabilities(element, layer, defaultUri);
} }
public static WmtsCapabilities ReadCapabilities(XElement capabilitiesElement, string layer, string defaultUrl) public static WmtsCapabilities ReadCapabilities(XElement capabilitiesElement, string layer, string defaultUri)
{ {
var contentsElement = capabilitiesElement.Element(wmts + "Contents") ?? var contentsElement = capabilitiesElement.Element(wmts + "Contents") ??
throw new ArgumentException("Contents element not found."); throw new ArgumentException("Contents element not found.");
@ -68,23 +68,15 @@ namespace MapControl
{ {
layerElement = contentsElement layerElement = contentsElement
.Elements(wmts + "Layer") .Elements(wmts + "Layer")
.FirstOrDefault(l => l.Element(ows + "Identifier")?.Value == layer); .FirstOrDefault(l => l.Element(ows + "Identifier")?.Value == layer) ??
if (layerElement == null)
{
throw new ArgumentException($"Layer element \"{layer}\" not found."); throw new ArgumentException($"Layer element \"{layer}\" not found.");
}
} }
else else
{ {
layerElement = contentsElement layerElement = contentsElement
.Elements(wmts + "Layer") .Elements(wmts + "Layer")
.FirstOrDefault(); .FirstOrDefault() ??
if (layerElement == null)
{
throw new ArgumentException("No Layer element found."); throw new ArgumentException("No Layer element found.");
}
layer = layerElement.Element(ows + "Identifier")?.Value ?? ""; layer = layerElement.Element(ows + "Identifier")?.Value ?? "";
} }
@ -98,7 +90,7 @@ namespace MapControl
var style = styleElement?.Element(ows + "Identifier")?.Value ?? ""; var style = styleElement?.Element(ows + "Identifier")?.Value ?? "";
var urlTemplate = ReadUrlTemplate(capabilitiesElement, layerElement, layer, style, defaultUrl); var uriTemplate = ReadUriTemplate(capabilitiesElement, layerElement, layer, style, defaultUri);
var tileMatrixSetIds = layerElement var tileMatrixSetIds = layerElement
.Elements(wmts + "TileMatrixSetLink") .Elements(wmts + "TileMatrixSetLink")
@ -120,16 +112,16 @@ namespace MapControl
return new WmtsCapabilities return new WmtsCapabilities
{ {
Layer = layer, Layer = layer,
UrlTemplate = urlTemplate, UriTemplate = uriTemplate,
TileMatrixSets = tileMatrixSets TileMatrixSets = tileMatrixSets
}; };
} }
public static string ReadUrlTemplate(XElement capabilitiesElement, XElement layerElement, string layer, string style, string defaultUrl) public static string ReadUriTemplate(XElement capabilitiesElement, XElement layerElement, string layer, string style, string defaultUri)
{ {
const string formatPng = "image/png"; const string formatPng = "image/png";
const string formatJpg = "image/jpeg"; const string formatJpg = "image/jpeg";
string urlTemplate = null; string uriTemplate = null;
var resourceUrls = layerElement var resourceUrls = layerElement
.Elements(wmts + "ResourceURL") .Elements(wmts + "ResourceURL")
@ -141,15 +133,15 @@ namespace MapControl
if (resourceUrls.Count != 0) if (resourceUrls.Count != 0)
{ {
var urlTemplates = resourceUrls.Contains(formatPng) ? resourceUrls[formatPng] var uriTemplates = resourceUrls.Contains(formatPng) ? resourceUrls[formatPng]
: resourceUrls.Contains(formatJpg) ? resourceUrls[formatJpg] : resourceUrls.Contains(formatJpg) ? resourceUrls[formatJpg]
: resourceUrls.First(); : resourceUrls.First();
urlTemplate = urlTemplates.First().Replace("{Style}", style); uriTemplate = uriTemplates.First().Replace("{Style}", style);
} }
else else
{ {
urlTemplate = capabilitiesElement uriTemplate = capabilitiesElement
.Elements(ows + "OperationsMetadata") .Elements(ows + "OperationsMetadata")
.Elements(ows + "Operation") .Elements(ows + "Operation")
.Where(o => o.Attribute("name")?.Value == "GetTile") .Where(o => o.Attribute("name")?.Value == "GetTile")
@ -163,9 +155,9 @@ namespace MapControl
.Where(h => !string.IsNullOrEmpty(h)) .Where(h => !string.IsNullOrEmpty(h))
.Select(h => h.Split('?')[0]) .Select(h => h.Split('?')[0])
.FirstOrDefault() ?? .FirstOrDefault() ??
defaultUrl; defaultUri;
if (urlTemplate != null) if (uriTemplate != null)
{ {
var formats = layerElement var formats = layerElement
.Elements(wmts + "Format") .Elements(wmts + "Format")
@ -180,7 +172,7 @@ namespace MapControl
format = formatPng; format = formatPng;
} }
urlTemplate += "?Service=WMTS" uriTemplate += "?Service=WMTS"
+ "&Request=GetTile" + "&Request=GetTile"
+ "&Version=1.0.0" + "&Version=1.0.0"
+ "&Layer=" + layer + "&Layer=" + layer
@ -193,12 +185,12 @@ namespace MapControl
} }
} }
if (string.IsNullOrEmpty(urlTemplate)) if (string.IsNullOrEmpty(uriTemplate))
{ {
throw new ArgumentException($"No ResourceURL element in Layer \"{layer}\" and no GetTile KVP Operation Metadata found."); throw new ArgumentException($"No ResourceURL element in Layer \"{layer}\" and no GetTile KVP Operation Metadata found.");
} }
return urlTemplate; return uriTemplate;
} }
public static WmtsTileMatrixSet ReadTileMatrixSet(XElement tileMatrixSetElement) public static WmtsTileMatrixSet ReadTileMatrixSet(XElement tileMatrixSetElement)
@ -301,8 +293,12 @@ namespace MapControl
? new Point(MapProjection.Wgs84MeterPerDegree * top, MapProjection.Wgs84MeterPerDegree * left) ? new Point(MapProjection.Wgs84MeterPerDegree * top, MapProjection.Wgs84MeterPerDegree * left)
: new Point(left, top); : new Point(left, top);
return new WmtsTileMatrix( // See 07-057r7_Web_Map_Tile_Service_Standard.pdf, section 6.1.a, page 8:
identifier, scaleDenominator, topLeft, tileWidth, tileHeight, matrixWidth, matrixHeight); // "standardized rendering pixel size" is 0.28 mm.
//
return new WmtsTileMatrix(identifier,
1d / (scaleDenominator * 0.00028),
topLeft, tileWidth, tileHeight, matrixWidth, matrixHeight);
} }
} }
} }

View file

@ -80,8 +80,6 @@ namespace MapControl
protected IEnumerable<WmtsTileMatrixLayer> ChildLayers => Children.Cast<WmtsTileMatrixLayer>(); protected IEnumerable<WmtsTileMatrixLayer> ChildLayers => Children.Cast<WmtsTileMatrixLayer>();
protected virtual WmtsTileSource CreateTileSource(string uriTemplate) => new() { UriTemplate = uriTemplate };
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
foreach (var layer in ChildLayers) foreach (var layer in ChildLayers)
@ -126,7 +124,10 @@ namespace MapControl
cacheName += "/" + Layer.Replace(':', '_'); cacheName += "/" + Layer.Replace(':', '_');
} }
cacheName += "/" + tileMatrixSet.Identifier.Replace(':', '_'); if (!string.IsNullOrEmpty(tileMatrixSet.Identifier))
{
cacheName += "/" + tileMatrixSet.Identifier.Replace(':', '_');
}
} }
BeginLoadTiles(ChildLayers.SelectMany(layer => layer.Tiles), cacheName); BeginLoadTiles(ChildLayers.SelectMany(layer => layer.Tiles), cacheName);
@ -207,7 +208,7 @@ namespace MapControl
} }
Layer = capabilities.Layer; Layer = capabilities.Layer;
TileSource = CreateTileSource(capabilities.UrlTemplate); TileSource = new WmtsTileSource { UriTemplate = capabilities.UriTemplate };
} }
catch (Exception ex) catch (Exception ex)
{ {

View file

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

View file

@ -8,11 +8,6 @@ namespace MapControl
{ {
public WmtsTileMatrixSet(string identifier, string supportedCrs, IEnumerable<WmtsTileMatrix> tileMatrixes) public WmtsTileMatrixSet(string identifier, string supportedCrs, IEnumerable<WmtsTileMatrix> tileMatrixes)
{ {
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException($"The {nameof(identifier)} argument must not be null or empty.", nameof(identifier));
}
if (string.IsNullOrEmpty(supportedCrs)) if (string.IsNullOrEmpty(supportedCrs))
{ {
throw new ArgumentException($"The {nameof(supportedCrs)} argument must not be null or empty.", nameof(supportedCrs)); throw new ArgumentException($"The {nameof(supportedCrs)} argument must not be null or empty.", nameof(supportedCrs));

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Text;
namespace MapControl namespace MapControl
{ {
@ -14,11 +15,14 @@ namespace MapControl
TileMatrixSet != null && TileMatrixSet != null &&
TileMatrixSet.TileMatrixes.Count > zoomLevel) TileMatrixSet.TileMatrixes.Count > zoomLevel)
{ {
uri = new Uri(UriTemplate var uriBuilder = new StringBuilder(UriTemplate);
.Replace("{TileMatrixSet}", TileMatrixSet.Identifier)
.Replace("{TileMatrix}", TileMatrixSet.TileMatrixes[zoomLevel].Identifier) uriBuilder.Replace("{TileMatrixSet}", TileMatrixSet.Identifier);
.Replace("{TileCol}", column.ToString()) uriBuilder.Replace("{TileMatrix}", TileMatrixSet.TileMatrixes[zoomLevel].Identifier);
.Replace("{TileRow}", row.ToString())); uriBuilder.Replace("{TileCol}", column.ToString());
uriBuilder.Replace("{TileRow}", row.ToString());
uri = new Uri(uriBuilder.ToString());
} }
return uri; return uri;