From c6f7b2d6659c3d13151565812230b47c7057dd91 Mon Sep 17 00:00:00 2001 From: ClemensFischer Date: Sat, 12 Aug 2023 17:36:37 +0200 Subject: [PATCH] Improved TileImageLoader --- MapControl/Shared/MapTileLayer.cs | 6 +- MapControl/Shared/MapTileLayerBase.cs | 27 +++--- MapControl/Shared/Tile.cs | 4 +- MapControl/Shared/TileCollection.cs | 2 +- MapControl/Shared/TileImageLoader.cs | 101 +++++++++------------- MapControl/Shared/TileSource.cs | 1 + MapControl/Shared/WmtsTileLayer.cs | 8 +- MapControl/WPF/TileImageLoader.WPF.cs | 2 +- MapControl/WinUI/TileImageLoader.WinUI.cs | 6 +- 9 files changed, 70 insertions(+), 87 deletions(-) diff --git a/MapControl/Shared/MapTileLayer.cs b/MapControl/Shared/MapTileLayer.cs index 32808993..c0a7da83 100644 --- a/MapControl/Shared/MapTileLayer.cs +++ b/MapControl/Shared/MapTileLayer.cs @@ -123,7 +123,7 @@ namespace MapControl return finalSize; } - protected override Task UpdateTileLayer() + protected override Task UpdateTileLayer(bool tileSourceChanged) { var updateTiles = false; @@ -134,7 +134,7 @@ namespace MapControl } else { - if (TileSource != TileImageLoader.TileSource) + if (tileSourceChanged) { Tiles = new TileCollection(); // clear all updateTiles = true; @@ -152,7 +152,7 @@ namespace MapControl { UpdateTiles(); - return TileImageLoader.LoadTiles(Tiles, TileSource, SourceName); + return LoadTiles(Tiles, SourceName); } return Task.CompletedTask; diff --git a/MapControl/Shared/MapTileLayerBase.cs b/MapControl/Shared/MapTileLayerBase.cs index 9a9fc1ea..29e4529c 100644 --- a/MapControl/Shared/MapTileLayerBase.cs +++ b/MapControl/Shared/MapTileLayerBase.cs @@ -25,18 +25,14 @@ namespace MapControl { public interface ITileImageLoader { - IProgress Progress { get; set; } - - TileSource TileSource { get; } - - Task LoadTiles(IEnumerable tiles, TileSource tileSource, string cacheName); + Task LoadTiles(IEnumerable tiles, TileSource tileSource, string cacheName, IProgress progress); } public abstract class MapTileLayerBase : Panel, IMapLayer { public static readonly DependencyProperty TileSourceProperty = DependencyProperty.Register( nameof(TileSource), typeof(TileSource), typeof(MapTileLayerBase), - new PropertyMetadata(null, async (o, e) => await ((MapTileLayerBase)o).Update())); + new PropertyMetadata(null, async (o, e) => await ((MapTileLayerBase)o).Update(true))); public static readonly DependencyProperty SourceNameProperty = DependencyProperty.Register( nameof(SourceName), typeof(string), typeof(MapTileLayerBase), new PropertyMetadata(null)); @@ -63,6 +59,7 @@ namespace MapControl public static readonly DependencyProperty LoadingProgressProperty = DependencyProperty.Register( nameof(LoadingProgress), typeof(double), typeof(MapTileLayerBase), new PropertyMetadata(1d)); + private readonly IProgress loadingProgress; private readonly DispatcherTimer updateTimer; private MapBase parentMap; @@ -71,10 +68,11 @@ namespace MapControl RenderTransform = new MatrixTransform(); TileImageLoader = tileImageLoader; - TileImageLoader.Progress = new Progress(p => LoadingProgress = p); + + loadingProgress = new Progress(p => LoadingProgress = p); updateTimer = this.CreateTimer(UpdateInterval); - updateTimer.Tick += async (s, e) => await Update(); + updateTimer.Tick += async (s, e) => await Update(false); #if WINUI || UWP MapPanel.InitMapElement(this); @@ -193,20 +191,25 @@ namespace MapControl protected abstract void SetRenderTransform(); - protected abstract Task UpdateTileLayer(); + protected abstract Task UpdateTileLayer(bool tileSourceChanged); - private Task Update() + protected Task LoadTiles(IEnumerable tiles, string cacheName) + { + return TileImageLoader.LoadTiles(tiles, TileSource, cacheName, loadingProgress); + } + + private Task Update(bool tileSourceChanged) { updateTimer.Stop(); - return UpdateTileLayer(); + return UpdateTileLayer(tileSourceChanged); } private async void OnViewportChanged(object sender, ViewportChangedEventArgs e) { if (e.TransformCenterChanged || e.ProjectionChanged || Children.Count == 0) { - await Update(); // update immediately + await Update(false); // update immediately } else { diff --git a/MapControl/Shared/Tile.cs b/MapControl/Shared/Tile.cs index d890481f..b4fbc907 100644 --- a/MapControl/Shared/Tile.cs +++ b/MapControl/Shared/Tile.cs @@ -45,11 +45,11 @@ namespace MapControl IsHitTestVisible = false // avoid touch capture issues }; - public bool IsLoaded { get; set; } + public bool IsPending { get; set; } = true; public void SetImageSource(ImageSource image, bool animateOpacity = true) { - IsLoaded = true; + IsPending = false; Image.Source = image; if (image != null && animateOpacity && MapBase.ImageFadeDuration > TimeSpan.Zero) diff --git a/MapControl/Shared/TileCollection.cs b/MapControl/Shared/TileCollection.cs index fc400c01..bbe28f41 100644 --- a/MapControl/Shared/TileCollection.cs +++ b/MapControl/Shared/TileCollection.cs @@ -21,7 +21,7 @@ namespace MapControl tile = new Tile(zoomLevel, x, y, columnCount); var equivalentTile = this.FirstOrDefault( - t => t.IsLoaded && t.ZoomLevel == tile.ZoomLevel && t.Column == tile.Column && t.Row == tile.Row); + t => t.Image.Source != null && t.ZoomLevel == tile.ZoomLevel && t.Column == tile.Column && t.Row == tile.Row); if (equivalentTile != null) { diff --git a/MapControl/Shared/TileImageLoader.cs b/MapControl/Shared/TileImageLoader.cs index 83aba9ed..6356e85b 100644 --- a/MapControl/Shared/TileImageLoader.cs +++ b/MapControl/Shared/TileImageLoader.cs @@ -22,7 +22,7 @@ namespace MapControl private class TileQueue : ConcurrentStack { public TileQueue(IEnumerable tiles) - : base(tiles.Where(tile => !tile.IsLoaded).Reverse()) + : base(tiles.Where(tile => tile.IsPending).Reverse()) { } @@ -30,14 +30,13 @@ namespace MapControl public bool TryDequeue(out Tile tile) { - tile = null; - if (IsCanceled || !TryPop(out tile)) { + tile = null; return false; } - tile.IsLoaded = true; + tile.IsPending = false; return true; } @@ -65,86 +64,68 @@ namespace MapControl /// public static TimeSpan MaxCacheExpiration { get; set; } = TimeSpan.FromDays(10); - /// - /// Reports tile loading process as double value between 0 and 1. - /// - public IProgress Progress { get; set; } - - /// - /// The current TileSource, passed to the most recent LoadTiles call. - /// - public TileSource TileSource { get; private set; } - - private TileQueue unloadedTiles; - private int progressTotal; - private int progressLoaded; + private TileQueue pendingTiles; /// /// Loads all unloaded tiles from the tiles collection. /// If tileSource.UriFormat starts with "http" and cacheName is a non-empty string, /// tile images will be cached in the TileImageLoader's Cache - if that is not null. /// - public Task LoadTiles(IEnumerable tiles, TileSource tileSource, string cacheName) + public Task LoadTiles(IEnumerable tiles, TileSource tileSource, string cacheName, IProgress progress) { - unloadedTiles?.Cancel(); + pendingTiles?.Cancel(); - TileSource = tileSource; - - if (tileSource != null) + if (tiles != null && tileSource != null) { - unloadedTiles = new TileQueue(tiles); + pendingTiles = new TileQueue(tiles); - var numTasks = Math.Min(unloadedTiles.Count, MaxLoadTasks); + var numTasks = Math.Min(pendingTiles.Count, MaxLoadTasks); if (numTasks > 0) { - if (Progress != null) - { - progressTotal = unloadedTiles.Count; - progressLoaded = 0; - Progress.Report(0d); - } - if (Cache == null || tileSource.UriTemplate == null || !tileSource.UriTemplate.StartsWith("http")) { cacheName = null; // no tile caching } - return Task.WhenAll(Enumerable.Range(0, numTasks).Select( - _ => Task.Run(() => LoadPendingTiles(unloadedTiles, tileSource, cacheName)))); - } - } + var progressLoaded = 0; + var progressTotal = 0; - if (Progress != null && progressLoaded < progressTotal) - { - Progress.Report(1d); + if (progress != null) + { + progressTotal = pendingTiles.Count; + progress.Report(0d); + } + + async Task LoadPendingTiles() + { + while (pendingTiles.TryDequeue(out var tile)) + { + try + { + await LoadTile(tile, tileSource, cacheName).ConfigureAwait(false); + } + catch (Exception ex) + { + Debug.WriteLine($"TileImageLoader: {tile.ZoomLevel}/{tile.Column}/{tile.Row}: {ex.Message}"); + } + + if (progress != null && !pendingTiles.IsCanceled) + { + Interlocked.Increment(ref progressLoaded); + + progress.Report((double)progressLoaded / progressTotal); + } + } + } + + return Task.WhenAll(Enumerable.Range(0, numTasks).Select(_ => Task.Run(LoadPendingTiles))); + } } return Task.CompletedTask; } - private async Task LoadPendingTiles(TileQueue tileQueue, TileSource tileSource, string cacheName) - { - while (tileQueue.TryDequeue(out var tile)) - { - try - { - await LoadTile(tile, tileSource, cacheName).ConfigureAwait(false); - } - catch (Exception ex) - { - Debug.WriteLine($"TileImageLoader: {tile.ZoomLevel}/{tile.Column}/{tile.Row}: {ex.Message}"); - } - - if (Progress != null && !tileQueue.IsCanceled) - { - Interlocked.Increment(ref progressLoaded); - - Progress.Report((double)progressLoaded / progressTotal); - } - } - } - private static Task LoadTile(Tile tile, TileSource tileSource, string cacheName) { if (string.IsNullOrEmpty(cacheName)) diff --git a/MapControl/Shared/TileSource.cs b/MapControl/Shared/TileSource.cs index f082d978..ffc279ba 100644 --- a/MapControl/Shared/TileSource.cs +++ b/MapControl/Shared/TileSource.cs @@ -73,6 +73,7 @@ namespace MapControl /// /// Loads a tile ImageSource asynchronously from GetUri(column, row, zoomLevel). + /// This method is called by a TileImageLoader that does not perform caching. /// public virtual Task LoadImageAsync(int column, int row, int zoomLevel) { diff --git a/MapControl/Shared/WmtsTileLayer.cs b/MapControl/Shared/WmtsTileLayer.cs index da2a5545..a7e851b3 100644 --- a/MapControl/Shared/WmtsTileLayer.cs +++ b/MapControl/Shared/WmtsTileLayer.cs @@ -97,14 +97,16 @@ namespace MapControl return finalSize; } - protected override Task UpdateTileLayer() + protected override Task UpdateTileLayer(bool tileSourceChanged) { + // tileSourceChanged is ignored here because it is always false. + if (ParentMap == null || !TileMatrixSets.TryGetValue(ParentMap.MapProjection.CrsId, out WmtsTileMatrixSet tileMatrixSet)) { Children.Clear(); - return LoadTiles(null); // stop TileImageLoader + return LoadTiles(null, null); // stop TileImageLoader } if (UpdateChildLayers(tileMatrixSet)) @@ -192,7 +194,7 @@ namespace MapControl var tiles = ChildLayers.SelectMany(layer => layer.Tiles); - return TileImageLoader.LoadTiles(tiles, TileSource, cacheName); + return LoadTiles(tiles, cacheName); } private async void OnLoaded(object sender, RoutedEventArgs e) diff --git a/MapControl/WPF/TileImageLoader.WPF.cs b/MapControl/WPF/TileImageLoader.WPF.cs index 2e8bd76f..195ea795 100644 --- a/MapControl/WPF/TileImageLoader.WPF.cs +++ b/MapControl/WPF/TileImageLoader.WPF.cs @@ -46,7 +46,7 @@ namespace MapControl if (buffer != null && buffer.Length > 0) { - await LoadTile(tile, () => ImageLoader.LoadImageAsync(buffer)); + await LoadTile(tile, () => ImageLoader.LoadImageAsync(buffer)).ConfigureAwait(false); } } diff --git a/MapControl/WinUI/TileImageLoader.WinUI.cs b/MapControl/WinUI/TileImageLoader.WinUI.cs index ff14ad13..aa8848d3 100644 --- a/MapControl/WinUI/TileImageLoader.WinUI.cs +++ b/MapControl/WinUI/TileImageLoader.WinUI.cs @@ -75,11 +75,7 @@ namespace MapControl } } - if (!tile.Image.DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, callback)) - { - tile.IsLoaded = false; - tcs.TrySetResult(); - } + tile.Image.DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, callback); return tcs.Task; }