diff --git a/FileDbCache/UWP/FileDbCache.UWP.csproj b/FileDbCache/UWP/FileDbCache.UWP.csproj index 04b88368..547ab1f9 100644 --- a/FileDbCache/UWP/FileDbCache.UWP.csproj +++ b/FileDbCache/UWP/FileDbCache.UWP.csproj @@ -52,7 +52,7 @@ 7.4.4 - 6.2.9 + 6.2.10 diff --git a/FileDbCache/UWP/Properties/AssemblyInfo.cs b/FileDbCache/UWP/Properties/AssemblyInfo.cs index b0da94d3..fa00c14f 100644 --- a/FileDbCache/UWP/Properties/AssemblyInfo.cs +++ b/FileDbCache/UWP/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("4.16.0")] -[assembly: AssemblyFileVersion("4.16.0")] +[assembly: AssemblyVersion("4.17.0")] +[assembly: AssemblyFileVersion("4.17.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/FileDbCache/WPF/Properties/AssemblyInfo.cs b/FileDbCache/WPF/Properties/AssemblyInfo.cs index 79ea184d..a2e41b5d 100644 --- a/FileDbCache/WPF/Properties/AssemblyInfo.cs +++ b/FileDbCache/WPF/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("4.16.0")] -[assembly: AssemblyFileVersion("4.16.0")] +[assembly: AssemblyVersion("4.17.0")] +[assembly: AssemblyFileVersion("4.17.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/MBTiles/UWP/MBTiles.UWP.csproj b/MBTiles/UWP/MBTiles.UWP.csproj index 18a6c367..d78cb990 100644 --- a/MBTiles/UWP/MBTiles.UWP.csproj +++ b/MBTiles/UWP/MBTiles.UWP.csproj @@ -54,7 +54,7 @@ - 6.2.9 + 6.2.10 1.0.112 diff --git a/MBTiles/UWP/Properties/AssemblyInfo.cs b/MBTiles/UWP/Properties/AssemblyInfo.cs index 07a30da4..163ccd7c 100644 --- a/MBTiles/UWP/Properties/AssemblyInfo.cs +++ b/MBTiles/UWP/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("4.16.0")] -[assembly: AssemblyFileVersion("4.16.0")] +[assembly: AssemblyVersion("4.17.0")] +[assembly: AssemblyFileVersion("4.17.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/MBTiles/WPF/Properties/AssemblyInfo.cs b/MBTiles/WPF/Properties/AssemblyInfo.cs index f8fe7440..01f28b26 100644 --- a/MBTiles/WPF/Properties/AssemblyInfo.cs +++ b/MBTiles/WPF/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("4.16.0")] -[assembly: AssemblyFileVersion("4.16.0")] +[assembly: AssemblyVersion("4.17.0")] +[assembly: AssemblyFileVersion("4.17.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/MapControl/Shared/BingMapsTileLayer.cs b/MapControl/Shared/BingMapsTileLayer.cs index ba45a438..a959fc09 100644 --- a/MapControl/Shared/BingMapsTileLayer.cs +++ b/MapControl/Shared/BingMapsTileLayer.cs @@ -60,9 +60,10 @@ namespace MapControl try { - var stream = await ImageLoader.HttpClient.GetStreamAsync(metadataUri); - - ProcessImageryMetadata(XDocument.Load(stream).Root); + using (var stream = await ImageLoader.HttpClient.GetStreamAsync(metadataUri)) + { + ReadImageryMetadata(XDocument.Load(stream).Root); + } } catch (Exception ex) { @@ -70,7 +71,7 @@ namespace MapControl } } - private void ProcessImageryMetadata(XElement metadataResponse) + private void ReadImageryMetadata(XElement metadataResponse) { var ns = metadataResponse.Name.Namespace; var metadata = metadataResponse.Descendants(ns + "ImageryMetadata").FirstOrDefault(); diff --git a/MapControl/Shared/MapBase.cs b/MapControl/Shared/MapBase.cs index fe9d2b41..15916f96 100644 --- a/MapControl/Shared/MapBase.cs +++ b/MapControl/Shared/MapBase.cs @@ -222,7 +222,7 @@ namespace MapControl } /// - /// Gets the transformation from cartesian map coordinates to viewport coordinates (pixels). + /// Gets the transformation from cartesian map coordinates to viewport coordinates. /// public MatrixTransform ViewportTransform { get; } = new MatrixTransform(); diff --git a/MapControl/Shared/MapProjection.cs b/MapControl/Shared/MapProjection.cs index 524b5408..bdcc5c77 100644 --- a/MapControl/Shared/MapProjection.cs +++ b/MapControl/Shared/MapProjection.cs @@ -65,22 +65,22 @@ namespace MapControl } /// - /// Gets the projection center. Only relevant for azimuthal pprojections. + /// Gets the projection center. Only relevant for azimuthal projections. /// public Location ProjectionCenter { get; private set; } = new Location(); /// - /// Gets the transform matrix from cartesian map coordinates to viewport coordinates (pixels). + /// Gets the transform matrix from cartesian map coordinates to viewport coordinates. /// public Matrix ViewportTransform { get; private set; } /// - /// Gets the transform matrix from viewport coordinates (pixels) to cartesian map coordinates. + /// Gets the transform matrix from viewport coordinates to cartesian map coordinates. /// public Matrix InverseViewportTransform { get; private set; } /// - /// Gets the scaling factor from cartesian map coordinates to viewport coordinates (pixels) + /// Gets the scaling factor from cartesian map coordinates to viewport coordinates /// at the projection's point of true scale. /// public double ViewportScale { get; private set; } @@ -186,26 +186,11 @@ namespace MapControl ViewportScale = Math.Pow(2d, zoomLevel) * TileSize / (360d * TrueScale); var center = LocationToPoint(mapCenter); - var matrix = CreateTransformMatrix(center, ViewportScale, -ViewportScale, heading, viewportCenter); + var matrix = MatrixFactory.Create(center, ViewportScale, -ViewportScale, heading, viewportCenter); ViewportTransform = matrix; matrix.Invert(); InverseViewportTransform = matrix; } - - internal static Matrix CreateTransformMatrix( - Point translation1, double scale, double rotation, Point translation2) - { - return CreateTransformMatrix(translation1, scale, scale, rotation, translation2); - } - - internal static Matrix CreateTransformMatrix( - Point translation1, double scaleX, double scaleY, double rotation, Point translation2) - { - var matrix = new Matrix(scaleX, 0d, 0d, scaleY, -translation1.X * scaleX, -translation1.Y * scaleY); - matrix.Rotate(rotation); - matrix.Translate(translation2.X, translation2.Y); - return matrix; - } } } diff --git a/MapControl/Shared/MapTileLayer.cs b/MapControl/Shared/MapTileLayer.cs index 139e8553..8c2246df 100644 --- a/MapControl/Shared/MapTileLayer.cs +++ b/MapControl/Shared/MapTileLayer.cs @@ -8,27 +8,21 @@ using System.Linq; #if WINDOWS_UWP using Windows.Foundation; using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media; #else using System.Windows; -using System.Windows.Controls; using System.Windows.Media; -using System.Windows.Threading; #endif namespace MapControl { - public interface ITileImageLoader - { - void LoadTilesAsync(IEnumerable tiles, TileSource tileSource, string sourceName); - } - /// /// Fills the map viewport with map tiles from a TileSource. /// - public class MapTileLayer : Panel, IMapLayer + public class MapTileLayer : MapTileLayerBase { + private const double WebMercatorMapSize = 2 * Math.PI * MapProjection.Wgs84EquatorialRadius; + /// /// A default MapTileLayer using OpenStreetMap data. /// @@ -46,103 +40,25 @@ namespace MapControl } } - public static readonly DependencyProperty TileSourceProperty = DependencyProperty.Register( - nameof(TileSource), typeof(TileSource), typeof(MapTileLayer), - new PropertyMetadata(null, (o, e) => ((MapTileLayer)o).TileSourcePropertyChanged())); - - public static readonly DependencyProperty SourceNameProperty = DependencyProperty.Register( - nameof(SourceName), typeof(string), typeof(MapTileLayer), new PropertyMetadata(null)); - - public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register( - nameof(Description), typeof(string), typeof(MapTileLayer), new PropertyMetadata(null)); - - public static readonly DependencyProperty ZoomLevelOffsetProperty = DependencyProperty.Register( - nameof(ZoomLevelOffset), typeof(double), typeof(MapTileLayer), - new PropertyMetadata(0d, (o, e) => ((MapTileLayer)o).UpdateTileGrid())); - public static readonly DependencyProperty MinZoomLevelProperty = DependencyProperty.Register( nameof(MinZoomLevel), typeof(int), typeof(MapTileLayer), new PropertyMetadata(0)); public static readonly DependencyProperty MaxZoomLevelProperty = DependencyProperty.Register( nameof(MaxZoomLevel), typeof(int), typeof(MapTileLayer), new PropertyMetadata(18)); - public static readonly DependencyProperty MaxBackgroundLevelsProperty = DependencyProperty.Register( - nameof(MaxBackgroundLevels), typeof(int), typeof(MapTileLayer), new PropertyMetadata(8)); - - public static readonly DependencyProperty UpdateIntervalProperty = DependencyProperty.Register( - nameof(UpdateInterval), typeof(TimeSpan), typeof(MapTileLayer), - new PropertyMetadata(TimeSpan.FromSeconds(0.2), (o, e) => ((MapTileLayer)o).updateTimer.Interval = (TimeSpan)e.NewValue)); - - public static readonly DependencyProperty UpdateWhileViewportChangingProperty = DependencyProperty.Register( - nameof(UpdateWhileViewportChanging), typeof(bool), typeof(MapTileLayer), new PropertyMetadata(false)); - - public static readonly DependencyProperty MapBackgroundProperty = DependencyProperty.Register( - nameof(MapBackground), typeof(Brush), typeof(MapTileLayer), new PropertyMetadata(null)); - - public static readonly DependencyProperty MapForegroundProperty = DependencyProperty.Register( - nameof(MapForeground), typeof(Brush), typeof(MapTileLayer), new PropertyMetadata(null)); - - private readonly DispatcherTimer updateTimer; - private MapBase parentMap; - public MapTileLayer() : this(new TileImageLoader()) { } public MapTileLayer(ITileImageLoader tileImageLoader) + : base(tileImageLoader) { - IsHitTestVisible = false; - RenderTransform = new MatrixTransform(); - TileImageLoader = tileImageLoader; - - updateTimer = new DispatcherTimer { Interval = UpdateInterval }; - updateTimer.Tick += (s, e) => UpdateTileGrid(); - - MapPanel.InitMapElement(this); } - public ITileImageLoader TileImageLoader { get; } - - public TileGrid TileGrid { get; private set; } - public IReadOnlyCollection Tiles { get; private set; } = new List(); - /// - /// Provides map tile URIs or images. - /// - public TileSource TileSource - { - get { return (TileSource)GetValue(TileSourceProperty); } - set { SetValue(TileSourceProperty, value); } - } - - /// - /// Name of the TileSource. Used as component of a tile cache key. - /// - public string SourceName - { - get { return (string)GetValue(SourceNameProperty); } - set { SetValue(SourceNameProperty, value); } - } - - /// - /// Description of the MapTileLayer. Used to display copyright information on top of the map. - /// - public string Description - { - get { return (string)GetValue(DescriptionProperty); } - set { SetValue(DescriptionProperty, value); } - } - - /// - /// Adds an offset to the Map's ZoomLevel for a relative scale between the Map and the MapTileLayer. - /// - public double ZoomLevelOffset - { - get { return (double)GetValue(ZoomLevelOffsetProperty); } - set { SetValue(ZoomLevelOffsetProperty, value); } - } + public TileGrid TileGrid { get; private set; } /// /// Minimum zoom level supported by the MapTileLayer. Default value is 0. @@ -162,73 +78,6 @@ namespace MapControl set { SetValue(MaxZoomLevelProperty, value); } } - /// - /// Maximum number of background tile levels. Default value is 8. - /// Is only effective in a MapTileLayer that is the MapLayer of its ParentMap. - /// - public int MaxBackgroundLevels - { - get { return (int)GetValue(MaxBackgroundLevelsProperty); } - set { SetValue(MaxBackgroundLevelsProperty, value); } - } - - /// - /// Minimum time interval between tile updates. - /// - public TimeSpan UpdateInterval - { - get { return (TimeSpan)GetValue(UpdateIntervalProperty); } - set { SetValue(UpdateIntervalProperty, value); } - } - - /// - /// Controls if tiles are updated while the viewport is still changing. - /// - public bool UpdateWhileViewportChanging - { - get { return (bool)GetValue(UpdateWhileViewportChangingProperty); } - set { SetValue(UpdateWhileViewportChangingProperty, value); } - } - - /// - /// Optional background brush. Sets MapBase.Background if not null and the MapTileLayer is the base map layer. - /// - public Brush MapBackground - { - get { return (Brush)GetValue(MapBackgroundProperty); } - set { SetValue(MapBackgroundProperty, value); } - } - - /// - /// Optional foreground brush. Sets MapBase.Foreground if not null and the MapTileLayer is the base map layer. - /// - public Brush MapForeground - { - get { return (Brush)GetValue(MapForegroundProperty); } - set { SetValue(MapForegroundProperty, value); } - } - - public MapBase ParentMap - { - get { return parentMap; } - set - { - if (parentMap != null) - { - parentMap.ViewportChanged -= OnViewportChanged; - } - - parentMap = value; - - if (parentMap != null) - { - parentMap.ViewportChanged += OnViewportChanged; - } - - UpdateTileGrid(); - } - } - protected override Size MeasureOverride(Size availableSize) { availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity); @@ -260,29 +109,7 @@ namespace MapControl return finalSize; } - protected virtual void UpdateTileGrid() - { - updateTimer.Stop(); - - if (parentMap != null && parentMap.MapProjection.IsWebMercator) - { - var tileGrid = GetTileGrid(); - - if (!tileGrid.Equals(TileGrid)) - { - TileGrid = tileGrid; - SetRenderTransform(); - UpdateTiles(); - } - } - else - { - TileGrid = null; - UpdateTiles(); - } - } - - private void TileSourcePropertyChanged() + protected override void TileSourcePropertyChanged() { if (TileGrid != null) { @@ -291,68 +118,90 @@ namespace MapControl } } - private void OnViewportChanged(object sender, ViewportChangedEventArgs e) + protected override void UpdateTileLayer() { - if (TileGrid == null || e.ProjectionChanged || Math.Abs(e.LongitudeOffset) > 180d) + UpdateTimer.Stop(); + + if (ParentMap == null || !ParentMap.MapProjection.IsWebMercator) { - UpdateTileGrid(); // update immediately when projection has changed or center has moved across 180° longitude + TileGrid = null; + UpdateTiles(); } - else + else if (SetTileGrid()) { SetRenderTransform(); - - if (updateTimer.IsEnabled && !UpdateWhileViewportChanging) - { - updateTimer.Stop(); // restart - } - - if (!updateTimer.IsEnabled) - { - updateTimer.Start(); - } + UpdateTiles(); } } - private TileGrid GetTileGrid() + protected override void SetRenderTransform() { - var tileZoomLevel = Math.Max(0, (int)Math.Round(parentMap.ZoomLevel + ZoomLevelOffset)); - var tileScale = (double)(1 << tileZoomLevel); - var scale = tileScale / (Math.Pow(2d, parentMap.ZoomLevel) * MapProjection.TileSize); - var tileCenter = new Point(tileScale * (0.5 + parentMap.Center.Longitude / 360d), - tileScale * (0.5 - WebMercatorProjection.LatitudeToY(parentMap.Center.Latitude) / 360d)); - var viewCenter = new Point(parentMap.RenderSize.Width / 2d, parentMap.RenderSize.Height / 2d); + var tileGridSize = (double)(1 << TileGrid.ZoomLevel); - var transform = new MatrixTransform - { - Matrix = MapProjection.CreateTransformMatrix(viewCenter, scale, -parentMap.Heading, tileCenter) - }; + // top/left tile grid corner in map coordinates + // + var tileGridOrigin = new Point( + WebMercatorMapSize * (TileGrid.XMin / tileGridSize - 0.5), + WebMercatorMapSize * (0.5 - TileGrid.YMin / tileGridSize)); - var bounds = transform.TransformBounds(new Rect(0d, 0d, parentMap.RenderSize.Width, parentMap.RenderSize.Height)); + // top/left tile grid corner in viewport coordinates + // + var viewOrigin = ParentMap.MapProjection.ViewportTransform.Transform(tileGridOrigin); - return new TileGrid(tileZoomLevel, - (int)Math.Floor(bounds.X), (int)Math.Floor(bounds.Y), - (int)Math.Floor(bounds.X + bounds.Width), (int)Math.Floor(bounds.Y + bounds.Height)); + // tile pixels per viewport unit, 0.5 .. 2 + // + var tileScale = Math.Pow(2d, ParentMap.ZoomLevel - TileGrid.ZoomLevel); + + ((MatrixTransform)RenderTransform).Matrix = MatrixFactory.Create(tileScale, ParentMap.Heading, viewOrigin); } - private void SetRenderTransform() + private bool SetTileGrid() { - var tileScale = (double)(1 << TileGrid.ZoomLevel); - var scale = Math.Pow(2d, parentMap.ZoomLevel) / tileScale; - var tileCenter = new Point(tileScale * (0.5 + parentMap.Center.Longitude / 360d), - tileScale * (0.5 - WebMercatorProjection.LatitudeToY(parentMap.Center.Latitude) / 360d)); - var tileOrigin = new Point(MapProjection.TileSize * (tileCenter.X - TileGrid.XMin), - MapProjection.TileSize * (tileCenter.Y - TileGrid.YMin)); - var viewCenter = new Point(parentMap.RenderSize.Width / 2d, parentMap.RenderSize.Height / 2d); + var tileGridZoomLevel = (int)Math.Round(ParentMap.ZoomLevel); + var tileGridSize = (double)(1 << tileGridZoomLevel); - ((MatrixTransform)RenderTransform).Matrix = - MapProjection.CreateTransformMatrix(tileOrigin, scale, parentMap.Heading, viewCenter); + // top/left viewport corner in map coordinates + // + var tileOrigin = ParentMap.MapProjection.InverseViewportTransform.Transform(new Point()); + + // top/left viewport corner in tile grid coordinates + // + var tileGridOrigin = new Point( + tileGridSize * (0.5 + tileOrigin.X / WebMercatorMapSize), + tileGridSize * (0.5 - tileOrigin.Y / WebMercatorMapSize)); + + // transforms viewport bounds to tile grid bounds + // + var transform = new MatrixTransform + { + Matrix = MatrixFactory.Create(1d / MapProjection.TileSize, -ParentMap.Heading, tileGridOrigin) + }; + + var bounds = transform.TransformBounds(new Rect(0d, 0d, ParentMap.RenderSize.Width, ParentMap.RenderSize.Height)); + + var xMin = (int)Math.Floor(bounds.X); + var yMin = (int)Math.Floor(bounds.Y); + var xMax = (int)Math.Floor(bounds.X + bounds.Width); + var yMax = (int)Math.Floor(bounds.Y + bounds.Height); + + if (TileGrid != null && + TileGrid.ZoomLevel == tileGridZoomLevel && + TileGrid.XMin == xMin && TileGrid.YMin == yMin && + TileGrid.XMax == xMax && TileGrid.YMax == yMax) + { + return false; + } + + TileGrid = new TileGrid(tileGridZoomLevel, xMin, yMin, xMax, yMax); + + return true; } private void UpdateTiles() { var newTiles = new List(); - if (parentMap != null && TileGrid != null && TileSource != null) + if (ParentMap != null && TileGrid != null && TileSource != null) { var maxZoomLevel = Math.Min(TileGrid.ZoomLevel, MaxZoomLevel); @@ -360,7 +209,7 @@ namespace MapControl { var minZoomLevel = maxZoomLevel; - if (this == parentMap.MapLayer) // load background tiles + if (this == ParentMap.MapLayer) // load background tiles { minZoomLevel = Math.Max(TileGrid.ZoomLevel - MaxBackgroundLevels, MinZoomLevel); } diff --git a/MapControl/Shared/MapTileLayerBase.cs b/MapControl/Shared/MapTileLayerBase.cs new file mode 100644 index 00000000..fa3f19a9 --- /dev/null +++ b/MapControl/Shared/MapTileLayerBase.cs @@ -0,0 +1,198 @@ +// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control +// © 2020 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.Collections.Generic; +#if WINDOWS_UWP +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media; +#else +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Threading; +#endif + +namespace MapControl +{ + public interface ITileImageLoader + { + void LoadTilesAsync(IEnumerable tiles, TileSource tileSource, string sourceName); + } + + public abstract class MapTileLayerBase : Panel, IMapLayer + { + public static readonly DependencyProperty TileSourceProperty = DependencyProperty.Register( + nameof(TileSource), typeof(TileSource), typeof(MapTileLayerBase), + new PropertyMetadata(null, (o, e) => ((MapTileLayerBase)o).TileSourcePropertyChanged())); + + public static readonly DependencyProperty SourceNameProperty = DependencyProperty.Register( + nameof(SourceName), typeof(string), typeof(MapTileLayerBase), new PropertyMetadata(null)); + + public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register( + nameof(Description), typeof(string), typeof(MapTileLayerBase), new PropertyMetadata(null)); + + public static readonly DependencyProperty MaxBackgroundLevelsProperty = DependencyProperty.Register( + nameof(MaxBackgroundLevels), typeof(int), typeof(MapTileLayerBase), new PropertyMetadata(8)); + + public static readonly DependencyProperty UpdateIntervalProperty = DependencyProperty.Register( + nameof(UpdateInterval), typeof(TimeSpan), typeof(MapTileLayerBase), + new PropertyMetadata(TimeSpan.FromSeconds(0.2), (o, e) => ((MapTileLayerBase)o).UpdateTimer.Interval = (TimeSpan)e.NewValue)); + + public static readonly DependencyProperty UpdateWhileViewportChangingProperty = DependencyProperty.Register( + nameof(UpdateWhileViewportChanging), typeof(bool), typeof(MapTileLayerBase), new PropertyMetadata(false)); + + public static readonly DependencyProperty MapBackgroundProperty = DependencyProperty.Register( + nameof(MapBackground), typeof(Brush), typeof(MapTileLayerBase), new PropertyMetadata(null)); + + public static readonly DependencyProperty MapForegroundProperty = DependencyProperty.Register( + nameof(MapForeground), typeof(Brush), typeof(MapTileLayerBase), new PropertyMetadata(null)); + + private MapBase parentMap; + + public MapTileLayerBase() + : this(new TileImageLoader()) + { + } + + public MapTileLayerBase(ITileImageLoader tileImageLoader) + { + IsHitTestVisible = false; + RenderTransform = new MatrixTransform(); + TileImageLoader = tileImageLoader; + + UpdateTimer = new DispatcherTimer { Interval = UpdateInterval }; + UpdateTimer.Tick += (s, e) => UpdateTileLayer(); + + MapPanel.InitMapElement(this); + } + + public ITileImageLoader TileImageLoader { get; } + + public DispatcherTimer UpdateTimer { get; } + + /// + /// Provides map tile URIs or images. + /// + public TileSource TileSource + { + get { return (TileSource)GetValue(TileSourceProperty); } + set { SetValue(TileSourceProperty, value); } + } + + /// + /// Name of the TileSource. Used as component of a tile cache key. + /// + public string SourceName + { + get { return (string)GetValue(SourceNameProperty); } + set { SetValue(SourceNameProperty, value); } + } + + /// + /// Description of the tile layer. Used to display copyright information on top of the map. + /// + public string Description + { + get { return (string)GetValue(DescriptionProperty); } + set { SetValue(DescriptionProperty, value); } + } + + /// + /// Maximum number of background tile levels. Default value is 8. + /// Is only effective in a MapTileLayer that is the MapLayer of its ParentMap. + /// + public int MaxBackgroundLevels + { + get { return (int)GetValue(MaxBackgroundLevelsProperty); } + set { SetValue(MaxBackgroundLevelsProperty, value); } + } + + /// + /// Minimum time interval between tile updates. + /// + public TimeSpan UpdateInterval + { + get { return (TimeSpan)GetValue(UpdateIntervalProperty); } + set { SetValue(UpdateIntervalProperty, value); } + } + + /// + /// Controls if tiles are updated while the viewport is still changing. + /// + public bool UpdateWhileViewportChanging + { + get { return (bool)GetValue(UpdateWhileViewportChangingProperty); } + set { SetValue(UpdateWhileViewportChangingProperty, value); } + } + + /// + /// Optional background brush. Sets MapBase.Background if not null and this layer is the base map layer. + /// + public Brush MapBackground + { + get { return (Brush)GetValue(MapBackgroundProperty); } + set { SetValue(MapBackgroundProperty, value); } + } + + /// + /// Optional foreground brush. Sets MapBase.Foreground if not null and this layer is the base map layer. + /// + public Brush MapForeground + { + get { return (Brush)GetValue(MapForegroundProperty); } + set { SetValue(MapForegroundProperty, value); } + } + + public MapBase ParentMap + { + get { return parentMap; } + set + { + if (parentMap != null) + { + parentMap.ViewportChanged -= OnViewportChanged; + } + + parentMap = value; + + if (parentMap != null) + { + parentMap.ViewportChanged += OnViewportChanged; + } + + UpdateTileLayer(); + } + } + + private void OnViewportChanged(object sender, ViewportChangedEventArgs e) + { + if (Children.Count == 0 || e.ProjectionChanged || Math.Abs(e.LongitudeOffset) > 180d) + { + UpdateTileLayer(); // update immediately when projection has changed or center has moved across 180° longitude + } + else + { + SetRenderTransform(); + + if (UpdateTimer.IsEnabled && !UpdateWhileViewportChanging) + { + UpdateTimer.Stop(); // restart + } + + if (!UpdateTimer.IsEnabled) + { + UpdateTimer.Start(); + } + } + } + + protected abstract void UpdateTileLayer(); + + protected abstract void SetRenderTransform(); + + protected abstract void TileSourcePropertyChanged(); + } +} diff --git a/MapControl/Shared/MatrixFactory.cs b/MapControl/Shared/MatrixFactory.cs new file mode 100644 index 00000000..df837bcb --- /dev/null +++ b/MapControl/Shared/MatrixFactory.cs @@ -0,0 +1,30 @@ +// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control +// © 2020 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +#if !WINDOWS_UWP +using System.Windows; +using System.Windows.Media; +#endif + +namespace MapControl +{ + public static class MatrixFactory + { + public static Matrix Create(Point translation1, double scaleX, double scaleY, double rotation, Point translation2) + { + var matrix = new Matrix(scaleX, 0d, 0d, scaleY, -scaleX * translation1.X, -scaleY * translation1.Y); + matrix.Rotate(rotation); + matrix.Translate(translation2.X, translation2.Y); + return matrix; + } + + public static Matrix Create(double scale, double rotation, Point translation) + { + var matrix = new Matrix(scale, 0d, 0d, scale, 0d, 0d); + matrix.Rotate(rotation); + matrix.Translate(translation.X, translation.Y); + return matrix; + } + } +} diff --git a/MapControl/Shared/TileGrid.cs b/MapControl/Shared/TileGrid.cs index c8c7fd78..825efbb9 100644 --- a/MapControl/Shared/TileGrid.cs +++ b/MapControl/Shared/TileGrid.cs @@ -2,11 +2,9 @@ // © 2020 Clemens Fischer // Licensed under the Microsoft Public License (Ms-PL) -using System; - namespace MapControl { - public class TileGrid : IEquatable + public class TileGrid { public readonly int ZoomLevel; public readonly int XMin; @@ -22,25 +20,5 @@ namespace MapControl XMax = xMax; YMax = yMax; } - - public bool Equals(TileGrid tileGrid) - { - return tileGrid != null - && tileGrid.ZoomLevel == ZoomLevel - && tileGrid.XMin == XMin - && tileGrid.YMin == YMin - && tileGrid.XMax == XMax - && tileGrid.YMax == YMax; - } - - public override bool Equals(object obj) - { - return Equals(obj as TileGrid); - } - - public override int GetHashCode() - { - return ZoomLevel ^ XMin ^ YMin ^ XMax ^ YMax; - } } } diff --git a/MapControl/Shared/WmsImageLayer.cs b/MapControl/Shared/WmsImageLayer.cs index 61c845ea..7d9f9d72 100644 --- a/MapControl/Shared/WmsImageLayer.cs +++ b/MapControl/Shared/WmsImageLayer.cs @@ -74,8 +74,13 @@ namespace MapControl try { - var stream = await ImageLoader.HttpClient.GetStreamAsync(uri); - var capabilities = XDocument.Load(stream).Root; + XElement capabilities; + + using (var stream = await ImageLoader.HttpClient.GetStreamAsync(uri)) + { + capabilities = XDocument.Load(stream).Root; + } + var ns = capabilities.Name.Namespace; layerNames = capabilities diff --git a/MapControl/Shared/WmtsTileLayer.cs b/MapControl/Shared/WmtsTileLayer.cs new file mode 100644 index 00000000..d76fb6e4 --- /dev/null +++ b/MapControl/Shared/WmtsTileLayer.cs @@ -0,0 +1,336 @@ +// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control +// © 2020 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Xml.Linq; +#if WINDOWS_UWP +using Windows.Foundation; +using Windows.UI.Xaml; +#else +using System.Windows; +#endif + +namespace MapControl +{ + public class WmtsTileLayer : MapTileLayerBase + { + public static readonly DependencyProperty CapabilitiesProperty = DependencyProperty.Register( + nameof(Capabilities), typeof(Uri), typeof(WmtsTileLayer), new PropertyMetadata(null)); + + public static readonly DependencyProperty LayerIdentifierProperty = DependencyProperty.Register( + nameof(LayerIdentifier), typeof(string), typeof(WmtsTileLayer), new PropertyMetadata(null)); + + public static readonly DependencyProperty TileMatrixSetProperty = DependencyProperty.Register( + nameof(TileMatrixSet), typeof(WmtsTileMatrixSet), typeof(WmtsTileLayer), + new PropertyMetadata(null, (o, e) => ((WmtsTileLayer)o).UpdateTileLayer())); + + public WmtsTileLayer() + : this(new TileImageLoader()) + { + } + + public WmtsTileLayer(ITileImageLoader tileImageLoader) + : base(tileImageLoader) + { + Loaded += OnLoaded; + } + + public Uri Capabilities + { + get { return (Uri)GetValue(CapabilitiesProperty); } + set { SetValue(CapabilitiesProperty, value); } + } + + public string LayerIdentifier + { + get { return (string)GetValue(LayerIdentifierProperty); } + set { SetValue(LayerIdentifierProperty, value); } + } + + public WmtsTileMatrixSet TileMatrixSet + { + get { return (WmtsTileMatrixSet)GetValue(TileMatrixSetProperty); } + set { SetValue(TileMatrixSetProperty, value); } + } + + protected override Size MeasureOverride(Size availableSize) + { + foreach (var layer in Children.Cast()) + { + layer.Measure(availableSize); + } + + return new Size(); + } + + protected override Size ArrangeOverride(Size finalSize) + { + foreach (var layer in Children.Cast()) + { + layer.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height)); + } + + return finalSize; + } + + protected override void TileSourcePropertyChanged() + { + } + + protected override void UpdateTileLayer() + { + UpdateTimer.Stop(); + + if (ParentMap == null || + TileMatrixSet == null || + ParentMap.MapProjection.CrsId != TileMatrixSet.SupportedCrs) + { + Children.Clear(); + UpdateTiles(); + } + else if (UpdateChildLayers()) + { + SetRenderTransform(); + UpdateTiles(); + } + } + + protected override void SetRenderTransform() + { + foreach (var layer in Children.Cast()) + { + layer.SetRenderTransform(ParentMap.MapProjection, ParentMap.Heading); + } + } + + private bool UpdateChildLayers() + { + bool layersChanged = false; + + if (TileMatrixSet != null) + { + // show all TileMatrix layers with Scale <= ViewportScale, or at least the first layer + // + var currentMatrixes = TileMatrixSet.TileMatrixes + .Where((matrix, i) => i == 0 || matrix.Scale <= ParentMap.MapProjection.ViewportScale) + .ToList(); + + if (this != ParentMap.MapLayer) // do not load background tiles + { + currentMatrixes = currentMatrixes.Skip(currentMatrixes.Count - 1).ToList(); // last element only + } + + var currentLayers = Children.Cast() + .Where(layer => currentMatrixes.Contains(layer.TileMatrix)) + .ToList(); + + Children.Clear(); + + foreach (var tileMatrix in currentMatrixes) + { + var layer = currentLayers.FirstOrDefault(l => l.TileMatrix == tileMatrix); + + if (layer == null) + { + layer = new WmtsTileMatrixLayer(tileMatrix, TileMatrixSet.TileMatrixes.IndexOf(tileMatrix)); + layersChanged = true; + } + + if (layer.SetBounds(ParentMap.MapProjection, ParentMap.Heading, ParentMap.RenderSize)) + { + layersChanged = true; + } + + Children.Add(layer); + } + } + + return layersChanged; + } + + private void UpdateTiles() + { + var tiles = new List(); + + foreach (var layer in Children.Cast()) + { + layer.UpdateTiles(); + + tiles.AddRange(layer.Tiles); + } + + TileImageLoader.LoadTilesAsync(tiles, TileSource, SourceName); + } + + private async void OnLoaded(object sender, RoutedEventArgs e) + { + if (Capabilities != null) + { + try + { + if (Capabilities.IsAbsoluteUri && (Capabilities.Scheme == "http" || Capabilities.Scheme == "https")) + { + using (var stream = await ImageLoader.HttpClient.GetStreamAsync(Capabilities)) + { + ReadCapabilities(XDocument.Load(stream).Root); + } + } + else + { + ReadCapabilities(XDocument.Load(Capabilities.ToString()).Root); + } + } + catch (Exception ex) + { + Debug.WriteLine("WmtsTileLayer: {0}: {1}", Capabilities, ex.Message); + } + } + } + + private void ReadCapabilities(XElement capabilities) + { + var ns = capabilities.Name.Namespace; + var contentsElement = capabilities.Element(ns + "Contents"); + + if (contentsElement == null) + { + throw new ArgumentException("Contents element not found."); + } + + XNamespace ows = "http://www.opengis.net/ows/1.1"; + XElement layerElement; + + if (!string.IsNullOrEmpty(LayerIdentifier)) + { + layerElement = contentsElement.Descendants(ns + "Layer") + .FirstOrDefault(e => e.Element(ows + "Identifier")?.Value == LayerIdentifier); + + if (layerElement == null) + { + throw new ArgumentException("Layer element \"" + LayerIdentifier + "\" not found."); + } + } + else + { + layerElement = capabilities.Descendants(ns + "Layer").FirstOrDefault(); + + if (layerElement == null) + { + throw new ArgumentException("No Layer element found."); + } + + LayerIdentifier = layerElement.Element(ows + "Identifier")?.Value ?? ""; + } + + var tileMatrixSetId = layerElement.Element(ns + "TileMatrixSetLink")?.Element(ns + "TileMatrixSet")?.Value; + + if (tileMatrixSetId == null) + { + throw new ArgumentException("TileMatrixSetLink element not found."); + } + + var urlTemplate = layerElement.Element(ns + "ResourceURL")?.Attribute("template")?.Value; + + if (urlTemplate == null) + { + throw new ArgumentException("ResourceURL element (or template attribute) not found in Layer \"" + LayerIdentifier + "\"."); + } + + var tileMatrixSetElement = capabilities.Descendants(ns + "TileMatrixSet") + .FirstOrDefault(e => e.Element(ows + "Identifier")?.Value == tileMatrixSetId); + + if (tileMatrixSetElement == null) + { + throw new ArgumentException("Linked TileMatrixSet element not found in Layer \"" + LayerIdentifier + "\"."); + } + + var supportedCrs = tileMatrixSetElement.Element(ows + "SupportedCRS")?.Value; + + if (supportedCrs == null) + { + throw new ArgumentException("ows:SupportedCRS element not found in TileMatrixSet \"" + tileMatrixSetId + "\"."); + } + + var tileMatrixes = new List(); + + foreach (var tileMatrix in tileMatrixSetElement.Descendants(ns + "TileMatrix")) + { + var tileMatrixId = tileMatrix.Element(ows + "Identifier")?.Value; + + if (string.IsNullOrEmpty(tileMatrixId)) + { + throw new ArgumentException("ows:Identifier element not found in TileMatrix."); + } + + string[] topLeftCornerStrings; + double scaleDenominator, top, left; + int tileWidth, tileHeight, matrixWidth, matrixHeight; + + var valueString = tileMatrix.Element(ns + "ScaleDenominator")?.Value; + + if (string.IsNullOrEmpty(valueString) || + !double.TryParse(valueString, NumberStyles.Float, CultureInfo.InvariantCulture, out scaleDenominator)) + { + throw new ArgumentException("ScaleDenominator element not found in TileMatrix \"" + tileMatrixId + "\"."); + } + + valueString = tileMatrix.Element(ns + "TopLeftCorner")?.Value; + + if (string.IsNullOrEmpty(valueString) || + (topLeftCornerStrings = valueString.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)).Length < 2 || + !double.TryParse(topLeftCornerStrings[0], NumberStyles.Float, CultureInfo.InvariantCulture, out left) || + !double.TryParse(topLeftCornerStrings[1], NumberStyles.Float, CultureInfo.InvariantCulture, out top)) + { + throw new ArgumentException("TopLeftCorner element not found in TileMatrix \"" + tileMatrixId + "\"."); + } + + valueString = tileMatrix.Element(ns + "TileWidth")?.Value; + + if (string.IsNullOrEmpty(valueString) || !int.TryParse(valueString, out tileWidth)) + { + throw new ArgumentException("TileWidth element not found in TileMatrix \"" + tileMatrixId + "\"."); + } + + valueString = tileMatrix.Element(ns + "TileHeight")?.Value; + + if (string.IsNullOrEmpty(valueString) || !int.TryParse(valueString, out tileHeight)) + { + throw new ArgumentException("TileHeight element not found in TileMatrix \"" + tileMatrixId + "\"."); + } + + valueString = tileMatrix.Element(ns + "MatrixWidth")?.Value; + + if (string.IsNullOrEmpty(valueString) || !int.TryParse(valueString, out matrixWidth)) + { + throw new ArgumentException("MatrixWidth element not found in TileMatrix \"" + tileMatrixId + "\"."); + } + + valueString = tileMatrix.Element(ns + "MatrixHeight")?.Value; + + if (string.IsNullOrEmpty(valueString) || !int.TryParse(valueString, out matrixHeight)) + { + throw new ArgumentException("MatrixHeight element not found in TileMatrix \"" + tileMatrixId + "\"."); + } + + tileMatrixes.Add(new WmtsTileMatrix( + tileMatrixId, scaleDenominator, new Point(left, top), tileWidth, tileHeight, matrixWidth, matrixHeight)); + } + + if (tileMatrixes.Count <= 0) + { + throw new ArgumentException("No TileMatrix elements found in TileMatrixSet \"" + tileMatrixSetId + "\"."); + } + + var tileMatrixSet = new WmtsTileMatrixSet(tileMatrixSetId, supportedCrs, tileMatrixes); + + SourceName = tileMatrixSet.Identifier; + TileSource = new WmtsTileSource(urlTemplate, tileMatrixSet); + TileMatrixSet = tileMatrixSet; // calls UpdateTileLayer() + } + } +} diff --git a/MapControl/Shared/WmtsTileMatrix.cs b/MapControl/Shared/WmtsTileMatrix.cs new file mode 100644 index 00000000..b963eccc --- /dev/null +++ b/MapControl/Shared/WmtsTileMatrix.cs @@ -0,0 +1,33 @@ +// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control +// © 2020 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +#if !WINDOWS_UWP +using System.Windows; +#endif + +namespace MapControl +{ + public class WmtsTileMatrix + { + public WmtsTileMatrix(string identifier, double scaleDenominator, Point topLeft, + int tileWidth, int tileHeight, int matrixWidth, int matrixHeight) + { + Identifier = identifier; + Scale = 1 / (scaleDenominator * 0.00028); + TopLeft = topLeft; + TileWidth = tileWidth; + TileHeight = tileHeight; + MatrixWidth = matrixWidth; + MatrixHeight = matrixHeight; + } + + public string Identifier { get; } + public double Scale { get; } + public Point TopLeft { get; } + public int TileWidth { get; } + public int TileHeight { get; } + public int MatrixWidth { get; } + public int MatrixHeight { get; } + } +} diff --git a/MapControl/Shared/WmtsTileMatrixLayer.cs b/MapControl/Shared/WmtsTileMatrixLayer.cs new file mode 100644 index 00000000..9009678f --- /dev/null +++ b/MapControl/Shared/WmtsTileMatrixLayer.cs @@ -0,0 +1,147 @@ +// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control +// © 2020 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.Collections.Generic; +using System.Linq; +#if WINDOWS_UWP +using Windows.Foundation; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media; +#else +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +#endif + +namespace MapControl +{ + public class WmtsTileMatrixLayer : Panel + { + public WmtsTileMatrixLayer(WmtsTileMatrix tileMatrix, int zoomLevel) + { + IsHitTestVisible = false; + RenderTransform = new MatrixTransform(); + TileMatrix = tileMatrix; + ZoomLevel = zoomLevel; + } + + public WmtsTileMatrix TileMatrix { get; } + public int ZoomLevel { get; } + public int XMin { get; private set; } + public int YMin { get; private set; } + public int XMax { get; private set; } + public int YMax { get; private set; } + + public IReadOnlyCollection Tiles { get; private set; } = new List(); + + public bool SetBounds(MapProjection projection, double heading, Size mapSize) + { + // top/left viewport corner in map coordinates + // + var tileOrigin = projection.InverseViewportTransform.Transform(new Point()); + + // top/left viewport corner in tile matrix coordinates + // + var tileMatrixOrigin = new Point( + TileMatrix.Scale * (tileOrigin.X - TileMatrix.TopLeft.X), + TileMatrix.Scale * (TileMatrix.TopLeft.Y - tileOrigin.Y)); + + var transform = new MatrixTransform + { + Matrix = MatrixFactory.Create(1, -heading, tileMatrixOrigin) + }; + + var bounds = transform.TransformBounds(new Rect(0d, 0d, mapSize.Width, mapSize.Height)); + + 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); + 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) + { + return false; + } + + XMin = xMin; + YMin = yMin; + XMax = xMax; + YMax = yMax; + + return true; + } + + public void SetRenderTransform(MapProjection projection, double heading) + { + // XMin/YMin corner in map and viewport coordinates + // + var mapOrigin = new Point( + TileMatrix.TopLeft.X + XMin * TileMatrix.TileWidth / TileMatrix.Scale, + TileMatrix.TopLeft.Y - YMin * TileMatrix.TileHeight / TileMatrix.Scale); + + var viewOrigin = projection.ViewportTransform.Transform(mapOrigin); + + var tileScale = projection.ViewportScale / TileMatrix.Scale; // relative scale + + ((MatrixTransform)RenderTransform).Matrix = MatrixFactory.Create(tileScale, heading, viewOrigin); + } + + public void UpdateTiles() + { + var newTiles = new List(); + + 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 = newTiles; + + Children.Clear(); + + foreach (var tile in Tiles) + { + Children.Add(tile.Image); + } + } + + protected override Size MeasureOverride(Size availableSize) + { + availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity); + + foreach (var tile in Tiles) + { + tile.Image.Measure(availableSize); + } + + return new Size(); + } + + protected override Size ArrangeOverride(Size finalSize) + { + foreach (var tile in Tiles) + { + // arrange tiles relative to XMin/YMin + // + var x = TileMatrix.TileWidth * (tile.X - XMin); + var y = TileMatrix.TileHeight * (tile.Y - YMin); + + tile.Image.Width = TileMatrix.TileWidth; + tile.Image.Height = TileMatrix.TileHeight; + tile.Image.Arrange(new Rect(x, y, TileMatrix.TileWidth, TileMatrix.TileHeight)); + } + + return finalSize; + } + } +} diff --git a/MapControl/Shared/WmtsTileMatrixSet.cs b/MapControl/Shared/WmtsTileMatrixSet.cs new file mode 100644 index 00000000..7ac271b4 --- /dev/null +++ b/MapControl/Shared/WmtsTileMatrixSet.cs @@ -0,0 +1,39 @@ +// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control +// © 2020 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MapControl +{ + public class WmtsTileMatrixSet + { + public WmtsTileMatrixSet(string identifier, string supportedCrs, IEnumerable tileMatrixes) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException("The parameter identifier must not be null or empty."); + } + + if (string.IsNullOrEmpty(supportedCrs)) + { + throw new ArgumentException("The parameter supportedCrs must not be null or empty."); + } + + if (tileMatrixes == null || tileMatrixes.Count() <= 0) + { + throw new ArgumentException("The parameter tileMatrixes must not be null or an empty collection."); + } + + Identifier = identifier; + SupportedCrs = supportedCrs; + TileMatrixes = tileMatrixes.OrderBy(m => m.Scale).ToList(); + } + + public string Identifier { get; } + public string SupportedCrs { get; } + public IList TileMatrixes { get; } + } +} diff --git a/MapControl/Shared/WmtsTileSource.cs b/MapControl/Shared/WmtsTileSource.cs new file mode 100644 index 00000000..d71993fb --- /dev/null +++ b/MapControl/Shared/WmtsTileSource.cs @@ -0,0 +1,35 @@ +// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control +// © 2020 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.Collections.Generic; + +namespace MapControl +{ + public class WmtsTileSource : TileSource + { + private readonly IList tileMatrixes; + + public WmtsTileSource(string uriFormat, WmtsTileMatrixSet tileMatrixSet) + : base(uriFormat.Replace("{TileMatrixSet}", tileMatrixSet.Identifier)) + { + tileMatrixes = tileMatrixSet.TileMatrixes; + } + + public override Uri GetUri(int x, int y, int zoomLevel) + { + if (zoomLevel < 0 || zoomLevel >= tileMatrixes.Count) + { + return null; + } + + var url = UriFormat + .Replace("{TileMatrix}", tileMatrixes[zoomLevel].Identifier) + .Replace("{TileCol}", x.ToString()) + .Replace("{TileRow}", y.ToString()); + + return new Uri(url); + } + } +} diff --git a/MapControl/UWP/Extensions.UWP.cs b/MapControl/UWP/Extensions.UWP.cs index d41c66f3..198b5ff0 100644 --- a/MapControl/UWP/Extensions.UWP.cs +++ b/MapControl/UWP/Extensions.UWP.cs @@ -3,12 +3,18 @@ // Licensed under the Microsoft Public License (Ms-PL) using Windows.UI.Xaml; +using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media.Animation; namespace MapControl { internal static class Extensions { + public static Point Transform(this GeneralTransform transform, Point point) + { + return transform.TransformPoint(point); + } + public static void BeginAnimation(this DependencyObject obj, DependencyProperty property, Timeline animation) { if (animation != null) diff --git a/MapControl/UWP/MapControl.UWP.csproj b/MapControl/UWP/MapControl.UWP.csproj index 14a0acb6..61765503 100644 --- a/MapControl/UWP/MapControl.UWP.csproj +++ b/MapControl/UWP/MapControl.UWP.csproj @@ -125,6 +125,12 @@ MapTileLayer.cs + + MapTileLayerBase.cs + + + MatrixFactory.cs + OrthographicProjection.cs @@ -155,6 +161,21 @@ WmsImageLayer.cs + + WmtsTileLayer.cs + + + WmtsTileMatrix.cs + + + WmtsTileMatrixLayer.cs + + + WmtsTileMatrixSet.cs + + + WmtsTileSource.cs + WorldMercatorProjection.cs @@ -179,7 +200,7 @@ - 6.2.9 + 6.2.10 diff --git a/MapControl/UWP/Properties/AssemblyInfo.cs b/MapControl/UWP/Properties/AssemblyInfo.cs index 77f138a7..b54101d4 100644 --- a/MapControl/UWP/Properties/AssemblyInfo.cs +++ b/MapControl/UWP/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("4.16.0")] -[assembly: AssemblyFileVersion("4.16.0")] +[assembly: AssemblyVersion("4.17.0")] +[assembly: AssemblyFileVersion("4.17.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/MapControl/WPF/MapControl.WPF.csproj b/MapControl/WPF/MapControl.WPF.csproj index d0ea08ef..da4b7d47 100644 --- a/MapControl/WPF/MapControl.WPF.csproj +++ b/MapControl/WPF/MapControl.WPF.csproj @@ -9,7 +9,7 @@ ..\..\MapControl.snk false XAML Map Control - 4.16.0 + 4.17.0 XAML Map Control Library Clemens Fischer Copyright © 2020 Clemens Fischer @@ -45,5 +45,4 @@ - diff --git a/MapImages/UWP/MapImages.UWP.csproj b/MapImages/UWP/MapImages.UWP.csproj index a9eec841..b99e6e21 100644 --- a/MapImages/UWP/MapImages.UWP.csproj +++ b/MapImages/UWP/MapImages.UWP.csproj @@ -51,7 +51,7 @@ - 6.2.9 + 6.2.10 diff --git a/MapImages/UWP/Properties/AssemblyInfo.cs b/MapImages/UWP/Properties/AssemblyInfo.cs index 723a6963..2d29f820 100644 --- a/MapImages/UWP/Properties/AssemblyInfo.cs +++ b/MapImages/UWP/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("4.16.0")] -[assembly: AssemblyFileVersion("4.16.0")] +[assembly: AssemblyVersion("4.17.0")] +[assembly: AssemblyFileVersion("4.17.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/MapImages/WPF/Properties/AssemblyInfo.cs b/MapImages/WPF/Properties/AssemblyInfo.cs index cd345721..b5e2f65f 100644 --- a/MapImages/WPF/Properties/AssemblyInfo.cs +++ b/MapImages/WPF/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("4.16.0")] -[assembly: AssemblyFileVersion("4.16.0")] +[assembly: AssemblyVersion("4.17.0")] +[assembly: AssemblyFileVersion("4.17.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/MapProjections/UWP/MapProjections.UWP.csproj b/MapProjections/UWP/MapProjections.UWP.csproj index c8882aa3..b1ea5027 100644 --- a/MapProjections/UWP/MapProjections.UWP.csproj +++ b/MapProjections/UWP/MapProjections.UWP.csproj @@ -57,7 +57,7 @@ - 6.2.9 + 6.2.10 1.4.1 diff --git a/MapProjections/UWP/Properties/AssemblyInfo.cs b/MapProjections/UWP/Properties/AssemblyInfo.cs index c23de4ec..74383bad 100644 --- a/MapProjections/UWP/Properties/AssemblyInfo.cs +++ b/MapProjections/UWP/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("4.16.0")] -[assembly: AssemblyFileVersion("4.16.0")] +[assembly: AssemblyVersion("4.17.0")] +[assembly: AssemblyFileVersion("4.17.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/MapProjections/WPF/Properties/AssemblyInfo.cs b/MapProjections/WPF/Properties/AssemblyInfo.cs index 3f037b2e..8599d3a4 100644 --- a/MapProjections/WPF/Properties/AssemblyInfo.cs +++ b/MapProjections/WPF/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("4.16.0")] -[assembly: AssemblyFileVersion("4.16.0")] +[assembly: AssemblyVersion("4.17.0")] +[assembly: AssemblyFileVersion("4.17.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/SQLiteCache/UWP/Properties/AssemblyInfo.cs b/SQLiteCache/UWP/Properties/AssemblyInfo.cs index 6fddfa50..0678a14e 100644 --- a/SQLiteCache/UWP/Properties/AssemblyInfo.cs +++ b/SQLiteCache/UWP/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("4.16.0")] -[assembly: AssemblyFileVersion("4.16.0")] +[assembly: AssemblyVersion("4.17.0")] +[assembly: AssemblyFileVersion("4.17.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/SQLiteCache/UWP/SQLiteCache.UWP.csproj b/SQLiteCache/UWP/SQLiteCache.UWP.csproj index e2787b36..823520f8 100644 --- a/SQLiteCache/UWP/SQLiteCache.UWP.csproj +++ b/SQLiteCache/UWP/SQLiteCache.UWP.csproj @@ -49,7 +49,7 @@ - 6.2.9 + 6.2.10 1.0.112 diff --git a/SQLiteCache/WPF/Properties/AssemblyInfo.cs b/SQLiteCache/WPF/Properties/AssemblyInfo.cs index 3b7a8bd5..45fdccd0 100644 --- a/SQLiteCache/WPF/Properties/AssemblyInfo.cs +++ b/SQLiteCache/WPF/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("4.16.0")] -[assembly: AssemblyFileVersion("4.16.0")] +[assembly: AssemblyVersion("4.17.0")] +[assembly: AssemblyFileVersion("4.17.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/SampleApps/UniversalApp/MainPage.xaml.cs b/SampleApps/UniversalApp/MainPage.xaml.cs index 62ea6081..f214f66c 100644 --- a/SampleApps/UniversalApp/MainPage.xaml.cs +++ b/SampleApps/UniversalApp/MainPage.xaml.cs @@ -1,4 +1,5 @@ using MapControl; +using System; using ViewModel; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; @@ -13,12 +14,29 @@ namespace UniversalApp public MainPage() { ImageLoader.HttpClient.DefaultRequestHeaders.Add("User-Agent", "XAML Map Control Test Application"); - //TileImageLoader.Cache = new MapControl.Caching.ImageFileCache(TileImageLoader.DefaultCacheFolder); + TileImageLoader.Cache = new MapControl.Caching.ImageFileCache(TileImageLoader.DefaultCacheFolder); //TileImageLoader.Cache = new MapControl.Caching.FileDbCache(TileImageLoader.DefaultCacheFolder); //TileImageLoader.Cache = new MapControl.Caching.SQLiteCache(TileImageLoader.DefaultCacheFolder); InitializeComponent(); DataContext = ViewModel; + + Loaded += MainWindow_Loaded; + } + + private async void MainWindow_Loaded(object sender, RoutedEventArgs e) + { + var seachartLayer = new WmsImageLayer + { + ServiceUri = new Uri("https://wms.sevencs.com:9090") + }; + + var layers = await seachartLayer.GetLayerNamesAsync(); + + foreach (var layer in layers) + { + System.Diagnostics.Debug.WriteLine(layer); + } } private void ImageOpacitySliderValueChanged(object sender, RangeBaseValueChangedEventArgs e) diff --git a/SampleApps/UniversalApp/Properties/AssemblyInfo.cs b/SampleApps/UniversalApp/Properties/AssemblyInfo.cs index b54ac6f6..3069403f 100644 --- a/SampleApps/UniversalApp/Properties/AssemblyInfo.cs +++ b/SampleApps/UniversalApp/Properties/AssemblyInfo.cs @@ -8,7 +8,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("4.16.0")] -[assembly: AssemblyFileVersion("4.16.0")] +[assembly: AssemblyVersion("4.17.0")] +[assembly: AssemblyFileVersion("4.17.0")] [assembly: AssemblyConfiguration("")] [assembly: ComVisible(false)] diff --git a/SampleApps/UniversalApp/UniversalApp.csproj b/SampleApps/UniversalApp/UniversalApp.csproj index b37f9668..4a4c296b 100644 --- a/SampleApps/UniversalApp/UniversalApp.csproj +++ b/SampleApps/UniversalApp/UniversalApp.csproj @@ -152,7 +152,7 @@ - 6.2.9 + 6.2.10 diff --git a/SampleApps/WpfApplication/Properties/AssemblyInfo.cs b/SampleApps/WpfApplication/Properties/AssemblyInfo.cs index 27a47018..d5df9058 100644 --- a/SampleApps/WpfApplication/Properties/AssemblyInfo.cs +++ b/SampleApps/WpfApplication/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("4.16.0")] -[assembly: AssemblyFileVersion("4.16.0")] +[assembly: AssemblyVersion("4.17.0")] +[assembly: AssemblyFileVersion("4.17.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/SampleApps/WpfApplication/WpfApplication.csproj b/SampleApps/WpfApplication/WpfApplication.csproj index f5c87cec..70fba3cd 100644 --- a/SampleApps/WpfApplication/WpfApplication.csproj +++ b/SampleApps/WpfApplication/WpfApplication.csproj @@ -94,6 +94,10 @@ {62f1726b-3144-49f4-8bcc-94160a3b2186} MapControl.WPF + + {0109c2f0-ba2c-420f-b2ca-db5b29b1a349} + SQLiteCache.WPF +