diff --git a/MapControl/Shared/MapImageLayer.cs b/MapControl/Shared/MapImageLayer.cs index 99170f22..28005124 100644 --- a/MapControl/Shared/MapImageLayer.cs +++ b/MapControl/Shared/MapImageLayer.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading.Tasks; #if WINUI using Windows.Foundation; +using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; @@ -68,7 +69,11 @@ namespace MapControl public static readonly DependencyProperty MapForegroundProperty = DependencyProperty.Register( nameof(MapForeground), typeof(Brush), typeof(MapImageLayer), new PropertyMetadata(null)); - private readonly DispatcherTimer updateTimer; +#if WINUI + private readonly DispatcherQueueTimer updateTimer; +#else + private readonly DispatcherTimer updateTimer = new DispatcherTimer(); +#endif private bool updateInProgress; public MapImageLayer() @@ -76,7 +81,10 @@ namespace MapControl Children.Add(new Image { Opacity = 0d, Stretch = Stretch.Fill }); Children.Add(new Image { Opacity = 0d, Stretch = Stretch.Fill }); - updateTimer = new DispatcherTimer { Interval = UpdateInterval }; +#if WINUI + updateTimer = DispatcherQueue.CreateTimer(); +#endif + updateTimer.Interval = UpdateInterval; updateTimer.Tick += async (s, e) => await UpdateImageAsync(); } diff --git a/MapControl/Shared/MapTileLayer.cs b/MapControl/Shared/MapTileLayer.cs index 133f37d0..32ab59fa 100644 --- a/MapControl/Shared/MapTileLayer.cs +++ b/MapControl/Shared/MapTileLayer.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; #if WINUI using Windows.Foundation; using Microsoft.UI.Xaml; @@ -115,7 +116,7 @@ namespace MapControl return finalSize; } - protected override void UpdateTileLayer(bool tileSourceChanged) + protected override Task UpdateTileLayer() { var update = false; @@ -126,7 +127,7 @@ namespace MapControl } else { - if (tileSourceChanged) + if (TileSource != TileImageLoader.TileSource) { Tiles.Clear(); update = true; @@ -141,7 +142,7 @@ namespace MapControl if (update) { - SetTiles(); + UpdateTiles(); Children.Clear(); @@ -150,8 +151,10 @@ namespace MapControl Children.Add(tile.Image); } - LoadTiles(Tiles, SourceName); + return TileImageLoader.LoadTiles(Tiles, TileSource, SourceName); } + + return Task.CompletedTask; } protected override void SetRenderTransform() @@ -196,7 +199,7 @@ namespace MapControl return true; } - private void SetTiles() + private void UpdateTiles() { int maxZoomLevel; diff --git a/MapControl/Shared/MapTileLayerBase.cs b/MapControl/Shared/MapTileLayerBase.cs index c496d15e..51d2ab97 100644 --- a/MapControl/Shared/MapTileLayerBase.cs +++ b/MapControl/Shared/MapTileLayerBase.cs @@ -4,7 +4,9 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; #if WINUI +using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; @@ -23,14 +25,16 @@ namespace MapControl { public interface ITileImageLoader { - void LoadTiles(IEnumerable tiles, TileSource tileSource, string cacheName); + TileSource TileSource { get; } + + Task LoadTiles(IEnumerable tiles, TileSource tileSource, string cacheName); } 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).Update(true))); + new PropertyMetadata(null, async (o, e) => await ((MapTileLayerBase)o).Update())); public static readonly DependencyProperty SourceNameProperty = DependencyProperty.Register( nameof(SourceName), typeof(string), typeof(MapTileLayerBase), new PropertyMetadata(null)); @@ -54,7 +58,11 @@ namespace MapControl public static readonly DependencyProperty MapForegroundProperty = DependencyProperty.Register( nameof(MapForeground), typeof(Brush), typeof(MapTileLayerBase), new PropertyMetadata(null)); - private readonly DispatcherTimer updateTimer; +#if WINUI + private readonly DispatcherQueueTimer updateTimer; +#else + private readonly DispatcherTimer updateTimer = new DispatcherTimer(); +#endif private MapBase parentMap; protected MapTileLayerBase(ITileImageLoader tileImageLoader) @@ -62,8 +70,11 @@ namespace MapControl RenderTransform = new MatrixTransform(); TileImageLoader = tileImageLoader; - updateTimer = new DispatcherTimer { Interval = UpdateInterval }; - updateTimer.Tick += (s, e) => Update(false); +#if WINUI + updateTimer = DispatcherQueue.CreateTimer(); +#endif + updateTimer.Interval = UpdateInterval; + updateTimer.Tick += async (s, e) => await Update(); #if WINUI || WINDOWS_UWP MapPanel.InitMapElement(this); @@ -162,15 +173,15 @@ namespace MapControl parentMap.ViewportChanged += OnViewportChanged; } - Update(false); + updateTimer.Start(); } } - private void OnViewportChanged(object sender, ViewportChangedEventArgs e) + private async void OnViewportChanged(object sender, ViewportChangedEventArgs e) { if (Children.Count == 0 || e.ProjectionChanged || Math.Abs(e.LongitudeOffset) > 180d) { - Update(false); // update immediately when projection has changed or center has moved across 180° longitude + await Update(); // update immediately when projection has changed or center has moved across 180° longitude } else { @@ -185,20 +196,15 @@ namespace MapControl } } - private void Update(bool tileSourceChanged) + private Task Update() { updateTimer.Stop(); - UpdateTileLayer(tileSourceChanged); + return UpdateTileLayer(); } - protected abstract void UpdateTileLayer(bool tileSourceChanged); + protected abstract Task UpdateTileLayer(); protected abstract void SetRenderTransform(); - - protected virtual void LoadTiles(IEnumerable tiles, string cacheName) - { - TileImageLoader.LoadTiles(tiles, TileSource, cacheName); - } } } diff --git a/MapControl/Shared/TileImageLoader.cs b/MapControl/Shared/TileImageLoader.cs index 4a737ddf..09a293a4 100644 --- a/MapControl/Shared/TileImageLoader.cs +++ b/MapControl/Shared/TileImageLoader.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -45,33 +46,28 @@ namespace MapControl /// public static TimeSpan MaxCacheExpiration { get; set; } = TimeSpan.FromDays(10); + public TileSource TileSource { get; private set; } + private readonly ConcurrentQueue tileQueue = new ConcurrentQueue(); - private TileSource tileSource; - private string cacheName; private int taskCount; /// /// Loads all pending tiles from the tiles collection. - /// If source.UriFormat starts with "http" and cache is a non-empty string, + /// If tileSource.UriFormat starts with "http" and tileCacheName is a non-empty string, /// tile images will be cached in the TileImageLoader's Cache (if that is not null). /// - public void LoadTiles(IEnumerable tiles, TileSource source, string cache) + public async Task LoadTiles(IEnumerable tiles, TileSource tileSource, string cacheName) { tileQueue.Clear(); - + TileSource = tileSource; tiles = tiles.Where(tile => tile.Pending); - tileSource = source; - cacheName = null; - - if (tiles.Any() && tileSource != null) + if (tileSource != null && tiles.Any()) { - if (!string.IsNullOrEmpty(cache) && - Cache != null && - tileSource.UriFormat != null && - tileSource.UriFormat.StartsWith("http")) + if (string.IsNullOrEmpty(cacheName) || Cache == null || + tileSource.UriFormat == null || !tileSource.UriFormat.StartsWith("http")) { - cacheName = cache; + cacheName = null; } foreach (var tile in tiles) @@ -79,28 +75,28 @@ namespace MapControl tileQueue.Enqueue(tile); } + var tasks = new List(); + while (taskCount < Math.Min(tileQueue.Count, MaxLoadTasks)) { Interlocked.Increment(ref taskCount); - Task.Run(LoadTilesFromQueueAsync); + tasks.Add(LoadTilesFromQueueAsync(tileSource, cacheName)); } + + await Task.WhenAll(tasks); } } - private async Task LoadTilesFromQueueAsync() + private async Task LoadTilesFromQueueAsync(TileSource tileSource, string cacheName) { - // tileSource or cacheName may change after dequeuing a tile - var source = tileSource; - var cache = cacheName; - - while (tileQueue.TryDequeue(out Tile tile)) + while (tileQueue.TryDequeue(out var tile)) { tile.Pending = false; try { - await LoadTileAsync(tile, source, cache).ConfigureAwait(false); + await LoadTileAsync(tile, tileSource, cacheName).ConfigureAwait(false); } catch (Exception ex) { @@ -132,7 +128,8 @@ namespace MapControl extension = ".jpg"; } - var cacheKey = string.Format("{0}/{1}/{2}/{3}{4}", cacheName, tile.ZoomLevel, tile.XIndex, tile.Y, extension); + var cacheKey = string.Format(CultureInfo.InvariantCulture, + "{0}/{1}/{2}/{3}{4}", cacheName, tile.ZoomLevel, tile.XIndex, tile.Y, extension); return LoadCachedTileAsync(tile, uri, cacheKey); } diff --git a/MapControl/Shared/WmtsTileLayer.cs b/MapControl/Shared/WmtsTileLayer.cs index 1dc4cd74..b004d96e 100644 --- a/MapControl/Shared/WmtsTileLayer.cs +++ b/MapControl/Shared/WmtsTileLayer.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading.Tasks; #if WINUI using Windows.Foundation; using Microsoft.UI.Xaml; @@ -86,19 +87,24 @@ namespace MapControl return finalSize; } - protected override void UpdateTileLayer(bool tileSourceChanged) + protected override Task UpdateTileLayer() { if (ParentMap == null || !TileMatrixSets.TryGetValue(ParentMap.MapProjection.CrsId, out WmtsTileMatrixSet tileMatrixSet)) { Children.Clear(); - UpdateTiles(null); + + return UpdateTiles(null); } - else if (UpdateChildLayers(tileMatrixSet)) + + if (UpdateChildLayers(tileMatrixSet)) { SetRenderTransform(); - UpdateTiles(tileMatrixSet); + + return UpdateTiles(tileMatrixSet); } + + return Task.CompletedTask; } protected override void SetRenderTransform() @@ -154,7 +160,7 @@ namespace MapControl return layersChanged; } - private void UpdateTiles(WmtsTileMatrixSet tileMatrixSet) + private Task UpdateTiles(WmtsTileMatrixSet tileMatrixSet) { var tiles = new List(); var cacheName = SourceName; @@ -180,7 +186,7 @@ namespace MapControl } } - LoadTiles(tiles, cacheName); + return TileImageLoader.LoadTiles(tiles, TileSource, cacheName); } private async void OnLoaded(object sender, RoutedEventArgs e) diff --git a/MapControl/UWP/TileImageLoader.UWP.cs b/MapControl/UWP/TileImageLoader.UWP.cs index 9e1530ef..a1ae698a 100644 --- a/MapControl/UWP/TileImageLoader.UWP.cs +++ b/MapControl/UWP/TileImageLoader.UWP.cs @@ -31,7 +31,7 @@ namespace MapControl } /// - /// An IImageCache implementation used to cache tile images. The default is null. + /// An IImageCache implementation used to cache tile images. /// public static Caching.IImageCache Cache { get; set; } @@ -69,7 +69,7 @@ namespace MapControl { var tcs = new TaskCompletionSource(); - await tile.Image.Dispatcher.RunAsync(CoreDispatcherPriority.Low, async () => + async void callback() { try { @@ -80,7 +80,9 @@ namespace MapControl { tcs.TrySetException(ex); } - }); + } + + await tile.Image.Dispatcher.RunAsync(CoreDispatcherPriority.Low, callback); await tcs.Task.ConfigureAwait(false); } diff --git a/MapControl/WinUI/TileImageLoader.WinUI.cs b/MapControl/WinUI/TileImageLoader.WinUI.cs index d2b60186..d3167daa 100644 --- a/MapControl/WinUI/TileImageLoader.WinUI.cs +++ b/MapControl/WinUI/TileImageLoader.WinUI.cs @@ -2,11 +2,11 @@ // © 2021 Clemens Fischer // Licensed under the Microsoft Public License (Ms-PL) +using Microsoft.UI.Dispatching; +using Microsoft.UI.Xaml.Media; using System; using System.IO; using System.Threading.Tasks; -using Microsoft.UI.Dispatching; -using Microsoft.UI.Xaml.Media; namespace MapControl { @@ -31,7 +31,7 @@ namespace MapControl } /// - /// An IImageCache implementation used to cache tile images. The default is null. + /// An IImageCache implementation used to cache tile images. /// public static Caching.IImageCache Cache { get; set; } @@ -69,7 +69,7 @@ namespace MapControl { var tcs = new TaskCompletionSource(); - tile.Image.DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, async () => + async void callback() { try { @@ -80,7 +80,13 @@ namespace MapControl { tcs.TrySetException(ex); } - }); + } + + if (!tile.Image.DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, callback)) + { + tile.Pending = true; + tcs.TrySetResult(); + } return tcs.Task; }