From ce7c33f6e4fbbcd87ac293bd8e21736af05d1b64 Mon Sep 17 00:00:00 2001 From: ClemensFischer Date: Wed, 29 Oct 2025 18:59:19 +0100 Subject: [PATCH] TileSources, WMTS --- MapControl/Shared/BoundingBoxTileSource.cs | 19 ++++++- MapControl/Shared/TileSource.cs | 15 ++--- MapControl/Shared/WmtsCapabilities.cs | 64 ++++++++++------------ MapControl/Shared/WmtsTileLayer.cs | 9 +-- MapControl/Shared/WmtsTileMatrix.cs | 7 +-- MapControl/Shared/WmtsTileMatrixSet.cs | 5 -- MapControl/Shared/WmtsTileSource.cs | 14 +++-- 7 files changed, 70 insertions(+), 63 deletions(-) diff --git a/MapControl/Shared/BoundingBoxTileSource.cs b/MapControl/Shared/BoundingBoxTileSource.cs index 974a24ae..51ee3ed6 100644 --- a/MapControl/Shared/BoundingBoxTileSource.cs +++ b/MapControl/Shared/BoundingBoxTileSource.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using System.Text; namespace MapControl { @@ -23,9 +24,21 @@ namespace MapControl var s = south.ToString("F2", CultureInfo.InvariantCulture); var n = north.ToString("F2", CultureInfo.InvariantCulture); - uri = 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)); + if (UriTemplate.Contains("{bbox}")) + { + 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; diff --git a/MapControl/Shared/TileSource.cs b/MapControl/Shared/TileSource.cs index d6a27827..cc2b7844 100644 --- a/MapControl/Shared/TileSource.cs +++ b/MapControl/Shared/TileSource.cs @@ -1,5 +1,5 @@ using System; -using System.Threading; +using System.Text; using System.Threading.Tasks; #if WPF using System.Windows.Media; @@ -54,17 +54,18 @@ namespace MapControl if (UriTemplate != null) { - var uriString = UriTemplate - .Replace("{z}", zoomLevel.ToString()) - .Replace("{x}", column.ToString()) - .Replace("{y}", row.ToString()); + var uriBuilder = new StringBuilder(UriTemplate); + + uriBuilder.Replace("{z}", zoomLevel.ToString()); + uriBuilder.Replace("{x}", column.ToString()); + uriBuilder.Replace("{y}", row.ToString()); 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; diff --git a/MapControl/Shared/WmtsCapabilities.cs b/MapControl/Shared/WmtsCapabilities.cs index 2217ee34..4d825239 100644 --- a/MapControl/Shared/WmtsCapabilities.cs +++ b/MapControl/Shared/WmtsCapabilities.cs @@ -23,28 +23,28 @@ namespace MapControl private static readonly XNamespace xlink = "http://www.w3.org/1999/xlink"; public string Layer { get; private set; } - public string UrlTemplate { get; private set; } + public string UriTemplate { get; private set; } public List TileMatrixSets { get; private set; } public static async Task ReadCapabilitiesAsync(Uri uri, string layer) { Stream xmlStream; - string defaultUrl = null; + string defaultUri = null; if (!uri.IsAbsoluteUri) { 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) { 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 { throw new ArgumentException($"Invalid Capabilities URI: {uri}"); @@ -54,10 +54,10 @@ namespace MapControl 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") ?? throw new ArgumentException("Contents element not found."); @@ -68,23 +68,15 @@ namespace MapControl { layerElement = contentsElement .Elements(wmts + "Layer") - .FirstOrDefault(l => l.Element(ows + "Identifier")?.Value == layer); - - if (layerElement == null) - { + .FirstOrDefault(l => l.Element(ows + "Identifier")?.Value == layer) ?? throw new ArgumentException($"Layer element \"{layer}\" not found."); - } } else { layerElement = contentsElement .Elements(wmts + "Layer") - .FirstOrDefault(); - - if (layerElement == null) - { + .FirstOrDefault() ?? throw new ArgumentException("No Layer element found."); - } layer = layerElement.Element(ows + "Identifier")?.Value ?? ""; } @@ -98,7 +90,7 @@ namespace MapControl 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 .Elements(wmts + "TileMatrixSetLink") @@ -120,16 +112,16 @@ namespace MapControl return new WmtsCapabilities { Layer = layer, - UrlTemplate = urlTemplate, + UriTemplate = uriTemplate, 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 formatJpg = "image/jpeg"; - string urlTemplate = null; + string uriTemplate = null; var resourceUrls = layerElement .Elements(wmts + "ResourceURL") @@ -141,15 +133,15 @@ namespace MapControl if (resourceUrls.Count != 0) { - var urlTemplates = resourceUrls.Contains(formatPng) ? resourceUrls[formatPng] + var uriTemplates = resourceUrls.Contains(formatPng) ? resourceUrls[formatPng] : resourceUrls.Contains(formatJpg) ? resourceUrls[formatJpg] : resourceUrls.First(); - urlTemplate = urlTemplates.First().Replace("{Style}", style); + uriTemplate = uriTemplates.First().Replace("{Style}", style); } else { - urlTemplate = capabilitiesElement + uriTemplate = capabilitiesElement .Elements(ows + "OperationsMetadata") .Elements(ows + "Operation") .Where(o => o.Attribute("name")?.Value == "GetTile") @@ -163,9 +155,9 @@ namespace MapControl .Where(h => !string.IsNullOrEmpty(h)) .Select(h => h.Split('?')[0]) .FirstOrDefault() ?? - defaultUrl; + defaultUri; - if (urlTemplate != null) + if (uriTemplate != null) { var formats = layerElement .Elements(wmts + "Format") @@ -180,7 +172,7 @@ namespace MapControl format = formatPng; } - urlTemplate += "?Service=WMTS" + uriTemplate += "?Service=WMTS" + "&Request=GetTile" + "&Version=1.0.0" + "&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."); } - return urlTemplate; + return uriTemplate; } public static WmtsTileMatrixSet ReadTileMatrixSet(XElement tileMatrixSetElement) @@ -301,8 +293,12 @@ namespace MapControl ? new Point(MapProjection.Wgs84MeterPerDegree * top, MapProjection.Wgs84MeterPerDegree * left) : new Point(left, top); - return new WmtsTileMatrix( - identifier, scaleDenominator, topLeft, tileWidth, tileHeight, matrixWidth, matrixHeight); + // See 07-057r7_Web_Map_Tile_Service_Standard.pdf, section 6.1.a, page 8: + // "standardized rendering pixel size" is 0.28 mm. + // + return new WmtsTileMatrix(identifier, + 1d / (scaleDenominator * 0.00028), + topLeft, tileWidth, tileHeight, matrixWidth, matrixHeight); } } } diff --git a/MapControl/Shared/WmtsTileLayer.cs b/MapControl/Shared/WmtsTileLayer.cs index 9a905f36..937cea2c 100644 --- a/MapControl/Shared/WmtsTileLayer.cs +++ b/MapControl/Shared/WmtsTileLayer.cs @@ -80,8 +80,6 @@ namespace MapControl protected IEnumerable ChildLayers => Children.Cast(); - protected virtual WmtsTileSource CreateTileSource(string uriTemplate) => new() { UriTemplate = uriTemplate }; - protected override Size MeasureOverride(Size availableSize) { foreach (var layer in ChildLayers) @@ -126,7 +124,10 @@ namespace MapControl cacheName += "/" + Layer.Replace(':', '_'); } - cacheName += "/" + tileMatrixSet.Identifier.Replace(':', '_'); + if (!string.IsNullOrEmpty(tileMatrixSet.Identifier)) + { + cacheName += "/" + tileMatrixSet.Identifier.Replace(':', '_'); + } } BeginLoadTiles(ChildLayers.SelectMany(layer => layer.Tiles), cacheName); @@ -207,7 +208,7 @@ namespace MapControl } Layer = capabilities.Layer; - TileSource = CreateTileSource(capabilities.UrlTemplate); + TileSource = new WmtsTileSource { UriTemplate = capabilities.UriTemplate }; } catch (Exception ex) { diff --git a/MapControl/Shared/WmtsTileMatrix.cs b/MapControl/Shared/WmtsTileMatrix.cs index b065984b..6baed45f 100644 --- a/MapControl/Shared/WmtsTileMatrix.cs +++ b/MapControl/Shared/WmtsTileMatrix.cs @@ -6,15 +6,12 @@ using Avalonia; 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( - string identifier, double scaleDenominator, Point topLeft, + string identifier, double scale, Point topLeft, int tileWidth, int tileHeight, int matrixWidth, int matrixHeight) { 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 int TileWidth { get; } = tileWidth; public int TileHeight { get; } = tileHeight; diff --git a/MapControl/Shared/WmtsTileMatrixSet.cs b/MapControl/Shared/WmtsTileMatrixSet.cs index 5dd16328..199494a2 100644 --- a/MapControl/Shared/WmtsTileMatrixSet.cs +++ b/MapControl/Shared/WmtsTileMatrixSet.cs @@ -8,11 +8,6 @@ namespace MapControl { public WmtsTileMatrixSet(string identifier, string supportedCrs, IEnumerable tileMatrixes) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException($"The {nameof(identifier)} argument must not be null or empty.", nameof(identifier)); - } - if (string.IsNullOrEmpty(supportedCrs)) { throw new ArgumentException($"The {nameof(supportedCrs)} argument must not be null or empty.", nameof(supportedCrs)); diff --git a/MapControl/Shared/WmtsTileSource.cs b/MapControl/Shared/WmtsTileSource.cs index ff460a09..0bebd836 100644 --- a/MapControl/Shared/WmtsTileSource.cs +++ b/MapControl/Shared/WmtsTileSource.cs @@ -1,4 +1,5 @@ using System; +using System.Text; namespace MapControl { @@ -14,11 +15,14 @@ namespace MapControl TileMatrixSet != null && TileMatrixSet.TileMatrixes.Count > zoomLevel) { - uri = new Uri(UriTemplate - .Replace("{TileMatrixSet}", TileMatrixSet.Identifier) - .Replace("{TileMatrix}", TileMatrixSet.TileMatrixes[zoomLevel].Identifier) - .Replace("{TileCol}", column.ToString()) - .Replace("{TileRow}", row.ToString())); + var uriBuilder = new StringBuilder(UriTemplate); + + uriBuilder.Replace("{TileMatrixSet}", TileMatrixSet.Identifier); + uriBuilder.Replace("{TileMatrix}", TileMatrixSet.TileMatrixes[zoomLevel].Identifier); + uriBuilder.Replace("{TileCol}", column.ToString()); + uriBuilder.Replace("{TileRow}", row.ToString()); + + uri = new Uri(uriBuilder.ToString()); } return uri;