diff --git a/MapControl/Shared/MapTileLayer.cs b/MapControl/Shared/MapTileLayer.cs index 5a177c7d..574d8aa7 100644 --- a/MapControl/Shared/MapTileLayer.cs +++ b/MapControl/Shared/MapTileLayer.cs @@ -21,7 +21,7 @@ namespace MapControl { public interface ITileImageLoader { - void LoadTilesAsync(MapTileLayer tileLayer); + void LoadTilesAsync(IEnumerable tiles, TileSource tileSource, string sourceName); } /// @@ -391,7 +391,7 @@ namespace MapControl Children.Add(tile.Image); } - TileImageLoader.LoadTilesAsync(this); + TileImageLoader.LoadTilesAsync(Tiles, TileSource, SourceName); } } } diff --git a/MapControl/Shared/Tile.cs b/MapControl/Shared/Tile.cs index 911c3b41..b691c140 100644 --- a/MapControl/Shared/Tile.cs +++ b/MapControl/Shared/Tile.cs @@ -44,6 +44,11 @@ namespace MapControl } } + public override string ToString() + { + return string.Format("{0}/{1}/{2}", ZoomLevel, XIndex, Y); + } + private void FadeIn() { Image.BeginAnimation(UIElement.OpacityProperty, new DoubleAnimation { From = 0d, To = 1d, Duration = FadeDuration, FillBehavior = FillBehavior.Stop }); diff --git a/MapControl/Shared/TileImageLoader.cs b/MapControl/Shared/TileImageLoader.cs index 889cb86b..88333012 100644 --- a/MapControl/Shared/TileImageLoader.cs +++ b/MapControl/Shared/TileImageLoader.cs @@ -3,11 +3,8 @@ // Licensed under the Microsoft Public License (Ms-PL) using System; -using System.Collections.Concurrent; -using System.Diagnostics; +using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Threading; using System.Threading.Tasks; namespace MapControl @@ -45,84 +42,49 @@ namespace MapControl /// public static string CacheKeyFormat { get; set; } = "{0};{1};{2};{3}{4}"; - private readonly ConcurrentStack pendingTiles = new ConcurrentStack(); - private int taskCount; + private readonly TileQueue pendingTiles = new TileQueue(); /// - /// Loads all pending tiles from the Tiles collection of a MapTileLayer by running up to MaxLoadTasks parallel Tasks. - /// If the TileSource's SourceName is non-empty and its UriFormat starts with "http", tile images are cached in the - /// TileImageLoader's Cache. + /// Loads all pending tiles from the tiles collection in up to MaxLoadTasks parallel Tasks. + /// If the UriFormat of the TileSource starts with "http" and the sourceName string is non-empty, + /// tile images are cached in the TileImageLoader's Cache. /// - public void LoadTilesAsync(MapTileLayer tileLayer) + public void LoadTilesAsync(IEnumerable tiles, TileSource tileSource, string sourceName) { pendingTiles.Clear(); - var tileSource = tileLayer.TileSource; - var sourceName = tileLayer.SourceName; - var tiles = tileLayer.Tiles.Where(t => t.Pending); - - if (tileSource != null && tiles.Any()) + if (tileSource != null && pendingTiles.Enqueue(tiles)) { - if (Cache == null || tileSource.UriFormat == null || !tileSource.UriFormat.StartsWith("http")) + if (Cache != null && + tileSource.UriFormat != null && + tileSource.UriFormat.StartsWith("http") && + !string.IsNullOrEmpty(sourceName)) { - sourceName = null; // do not use cache + pendingTiles.RunDequeueTasks(MaxLoadTasks, tile => LoadTileImageAsync(tile, tileSource, sourceName)); } - - pendingTiles.PushRange(tiles.Reverse().ToArray()); - - var newTasks = Math.Min(pendingTiles.Count, MaxLoadTasks) - taskCount; - - while (--newTasks >= 0) + else { - Interlocked.Increment(ref taskCount); - - Task.Run(async () => // do not await - { - Tile tile; - - while (pendingTiles.TryPop(out tile)) - { - tile.Pending = false; - - try - { - await LoadTileImageAsync(tile, tileSource, sourceName); - } - catch (Exception ex) - { - Debug.WriteLine("TileImageLoader: {0}/{1}/{2}: {3}", tile.ZoomLevel, tile.XIndex, tile.Y, ex.Message); - } - } - - Interlocked.Decrement(ref taskCount); - }); + pendingTiles.RunDequeueTasks(MaxLoadTasks, tile => LoadTileImageAsync(tile, tileSource)); } } } private async Task LoadTileImageAsync(Tile tile, TileSource tileSource, string sourceName) { - if (string.IsNullOrEmpty(sourceName)) - { - await LoadTileImageAsync(tile, tileSource); - } - else - { - var uri = tileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel); + var uri = tileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel); - if (uri != null) + if (uri != null) + { + var extension = Path.GetExtension(uri.LocalPath); + + if (string.IsNullOrEmpty(extension) || extension == ".jpeg") { - var extension = Path.GetExtension(uri.LocalPath); - - if (string.IsNullOrEmpty(extension) || extension == ".jpeg") - { - extension = ".jpg"; - } - - var cacheKey = string.Format(CacheKeyFormat, sourceName, tile.ZoomLevel, tile.XIndex, tile.Y, extension); - - await LoadTileImageAsync(tile, uri, cacheKey); + extension = ".jpg"; } + + var cacheKey = string.Format(CacheKeyFormat, sourceName, tile.ZoomLevel, tile.XIndex, tile.Y, extension); + + await LoadTileImageAsync(tile, uri, cacheKey); } } diff --git a/MapControl/Shared/TileQueue.cs b/MapControl/Shared/TileQueue.cs new file mode 100644 index 00000000..ef62c9cf --- /dev/null +++ b/MapControl/Shared/TileQueue.cs @@ -0,0 +1,75 @@ +// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control +// © 2019 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MapControl +{ + public class TileQueue : ConcurrentStack + { + private int taskCount; + + public bool Enqueue(IEnumerable tiles) + { + tiles = tiles.Where(tile => tile.Pending); + + if (tiles.Any()) + { + PushRange(tiles.Reverse().ToArray()); + return true; + } + + return false; + } + + public bool TryDequeue(out Tile tile) + { + var success = TryPop(out tile); + + if (success) + { + tile.Pending = false; + } + + return success; + } + + public void RunDequeueTasks(int maxTasks, Func tileFunc) + { + var newTasks = Math.Min(Count, maxTasks) - taskCount; + + while (--newTasks >= 0) + { + Interlocked.Increment(ref taskCount); + + Task.Run(() => DequeueTiles(tileFunc)); + } + } + + private async Task DequeueTiles(Func tileFunc) + { + Tile tile; + + while (TryDequeue(out tile)) + { + try + { + await tileFunc(tile); + } + catch (Exception ex) + { + Debug.WriteLine("TileQueue: {0}: {1}", tile, ex.Message); + } + } + + Interlocked.Decrement(ref taskCount); + } + } +} diff --git a/MapControl/UWP/MapControl.UWP.csproj b/MapControl/UWP/MapControl.UWP.csproj index 867cbb36..cfaaa23f 100644 --- a/MapControl/UWP/MapControl.UWP.csproj +++ b/MapControl/UWP/MapControl.UWP.csproj @@ -139,6 +139,9 @@ TileImageLoader.cs + + TileQueue.cs + TileSource.cs diff --git a/MapControl/WPF/MapControl.WPF.csproj b/MapControl/WPF/MapControl.WPF.csproj index 5c16c06d..33cf9fe5 100644 --- a/MapControl/WPF/MapControl.WPF.csproj +++ b/MapControl/WPF/MapControl.WPF.csproj @@ -164,6 +164,9 @@ TileImageLoader.cs + + TileQueue.cs + TileSource.cs diff --git a/MapControl/WPF/TileImageLoader.WPF.cs b/MapControl/WPF/TileImageLoader.WPF.cs index d3f2e976..ad83997d 100644 --- a/MapControl/WPF/TileImageLoader.WPF.cs +++ b/MapControl/WPF/TileImageLoader.WPF.cs @@ -8,7 +8,6 @@ using System.Runtime.Caching; using System.Text; using System.Threading.Tasks; using System.Windows.Media; -using System.Windows.Threading; namespace MapControl {