diff --git a/MapControl/Shared/MapTileLayer.cs b/MapControl/Shared/MapTileLayer.cs
index 676cb21b..69d5f917 100644
--- a/MapControl/Shared/MapTileLayer.cs
+++ b/MapControl/Shared/MapTileLayer.cs
@@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Threading.Tasks;
#if WINDOWS_UWP
using Windows.Foundation;
using Windows.UI.Xaml;
@@ -22,7 +21,7 @@ namespace MapControl
{
public interface ITileImageLoader
{
- Task LoadTilesAsync(MapTileLayer tileLayer);
+ void LoadTilesAsync(MapTileLayer tileLayer);
}
///
@@ -93,7 +92,6 @@ namespace MapControl
IsHitTestVisible = false;
RenderTransform = new MatrixTransform();
TileImageLoader = tileImageLoader;
- Tiles = new List();
updateTimer = new DispatcherTimer { Interval = UpdateInterval };
updateTimer.Tick += (s, e) => UpdateTileGrid();
@@ -102,9 +100,11 @@ namespace MapControl
}
public ITileImageLoader TileImageLoader { get; private set; }
- public ICollection Tiles { get; private set; }
+
public TileGrid TileGrid { get; private set; }
+ public IReadOnlyCollection Tiles { get; private set; } = new List();
+
///
/// Provides map tile URIs or images.
///
@@ -273,7 +273,7 @@ namespace MapControl
{
if (TileGrid != null)
{
- Tiles.Clear();
+ Tiles = new List();
UpdateTiles();
}
}
@@ -344,9 +344,7 @@ namespace MapControl
var maxZoomLevel = Math.Min(TileGrid.ZoomLevel, MaxZoomLevel);
var minZoomLevel = MinZoomLevel;
- if (minZoomLevel < maxZoomLevel &&
- parentMap.MapLayer != this &&
- parentMap.Children.Cast().FirstOrDefault() != this)
+ if (minZoomLevel < maxZoomLevel && parentMap.MapLayer != this)
{
minZoomLevel = maxZoomLevel; // do not load lower level tiles if this is note a "base" layer
}
@@ -393,7 +391,7 @@ namespace MapControl
Children.Add(tile.Image);
}
- var task = TileImageLoader.LoadTilesAsync(this);
+ TileImageLoader.LoadTilesAsync(this);
}
}
}
diff --git a/MapControl/Shared/PolygonCollection.cs b/MapControl/Shared/PolygonCollection.cs
index 084433f2..3ed971f9 100644
--- a/MapControl/Shared/PolygonCollection.cs
+++ b/MapControl/Shared/PolygonCollection.cs
@@ -18,6 +18,12 @@ namespace MapControl
///
public class PolygonCollection : ObservableCollection>, IWeakEventListener
{
+ public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
+ {
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender));
+ return true;
+ }
+
protected override void InsertItem(int index, IEnumerable polygon)
{
var observablePolygon = polygon as INotifyCollectionChanged;
@@ -63,12 +69,5 @@ namespace MapControl
base.ClearItems();
}
-
- bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
- {
- OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender));
-
- return true;
- }
}
}
diff --git a/MapControl/Shared/TileImageLoader.cs b/MapControl/Shared/TileImageLoader.cs
index 30df2cd2..46f2c181 100644
--- a/MapControl/Shared/TileImageLoader.cs
+++ b/MapControl/Shared/TileImageLoader.cs
@@ -18,20 +18,25 @@ namespace MapControl
public partial class TileImageLoader : ITileImageLoader
{
///
- /// Default expiration time for cached tile images. Used when no expiration time
- /// was transmitted on download. The default value is one day.
+ /// Maximum number of parallel tile loading tasks. The default value is 4.
///
- public static TimeSpan DefaultCacheExpiration { get; set; } = TimeSpan.FromDays(1);
+ public static int MaxLoadTasks { get; set; } = 4;
///
/// Minimum expiration time for cached tile images. The default value is one hour.
///
- public static TimeSpan MinimumCacheExpiration { get; set; } = TimeSpan.FromHours(1);
+ public static TimeSpan MinCacheExpiration { get; set; } = TimeSpan.FromHours(1);
///
/// Maximum expiration time for cached tile images. The default value is one week.
///
- public static TimeSpan MaximumCacheExpiration { get; set; } = TimeSpan.FromDays(7);
+ public static TimeSpan MaxCacheExpiration { get; set; } = TimeSpan.FromDays(7);
+
+ ///
+ /// Default expiration time for cached tile images. Used when no expiration time
+ /// was transmitted on download. The default value is one day.
+ ///
+ public static TimeSpan DefaultCacheExpiration { get; set; } = TimeSpan.FromDays(1);
///
/// Format string for creating cache keys from the SourceName property of a TileSource,
@@ -43,7 +48,12 @@ namespace MapControl
private readonly ConcurrentStack pendingTiles = new ConcurrentStack();
private int taskCount;
- public async Task LoadTilesAsync(MapTileLayer tileLayer)
+ ///
+ /// 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.
+ ///
+ public void LoadTilesAsync(MapTileLayer tileLayer)
{
pendingTiles.Clear();
@@ -53,55 +63,34 @@ namespace MapControl
if (tileSource != null && tiles.Any())
{
- if (Cache == null || string.IsNullOrEmpty(sourceName) ||
- tileSource.UriFormat == null || !tileSource.UriFormat.StartsWith("http"))
- {
- // no caching, load tile images directly
+ pendingTiles.PushRange(tiles.Reverse().ToArray());
- foreach (var tile in tiles)
- {
- await LoadTileImageAsync(tileSource, tile);
- }
+ Func loadFunc;
+
+ if (Cache != null && !string.IsNullOrEmpty(sourceName) &&
+ tileSource.UriFormat != null && tileSource.UriFormat.StartsWith("http"))
+ {
+ loadFunc = tile => LoadCachedTileImageAsync(tile, tileSource, sourceName);
}
else
{
- pendingTiles.PushRange(tiles.Reverse().ToArray());
-
- while (taskCount < Math.Min(pendingTiles.Count, DefaultConnectionLimit))
- {
- Interlocked.Increment(ref taskCount);
-
- var task = Task.Run(async () => // do not await
- {
- await LoadPendingTilesAsync(tileSource, sourceName); // run multiple times in parallel
-
- Interlocked.Decrement(ref taskCount);
- });
- }
+ loadFunc = tile => LoadTileImageAsync(tile, tileSource);
}
- }
- }
- private async Task LoadTileImageAsync(TileSource tileSource, Tile tile)
- {
- tile.Pending = false;
+ var maxTasks = Math.Min(pendingTiles.Count, MaxLoadTasks);
- try
- {
- var imageSource = await tileSource.LoadImageAsync(tile.XIndex, tile.Y, tile.ZoomLevel);
-
- if (imageSource != null)
+ while (taskCount < maxTasks)
{
- tile.SetImage(imageSource);
+ Interlocked.Increment(ref taskCount);
+
+ var task = Task.Run(() => LoadTilesAsync(loadFunc)); // do not await
}
- }
- catch (Exception ex)
- {
- Debug.WriteLine("TileImageLoader: {0}/{1}/{2}: {3}", tile.ZoomLevel, tile.XIndex, tile.Y, ex.Message);
+
+ //Debug.WriteLine("{0}: {1} tasks", Environment.CurrentManagedThreadId, taskCount);
}
}
- private async Task LoadPendingTilesAsync(TileSource tileSource, string sourceName)
+ private async Task LoadTilesAsync(Func loadTileImageFunc)
{
Tile tile;
@@ -111,27 +100,35 @@ namespace MapControl
try
{
- var uri = tileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel);
-
- if (uri != null)
- {
- 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);
- }
+ await loadTileImageFunc(tile);
}
catch (Exception ex)
{
Debug.WriteLine("TileImageLoader: {0}/{1}/{2}: {3}", tile.ZoomLevel, tile.XIndex, tile.Y, ex.Message);
}
}
+
+ Interlocked.Decrement(ref taskCount);
+ //Debug.WriteLine("{0}: {1} tasks", Environment.CurrentManagedThreadId, taskCount);
+ }
+
+ private async Task LoadCachedTileImageAsync(Tile tile, TileSource tileSource, string sourceName)
+ {
+ var uri = tileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel);
+
+ if (uri != null)
+ {
+ 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 LoadCachedTileImageAsync(tile, uri, cacheKey);
+ }
}
private static DateTime GetExpiration(TimeSpan? maxAge)
@@ -142,13 +139,13 @@ namespace MapControl
{
expiration = maxAge.Value;
- if (expiration < MinimumCacheExpiration)
+ if (expiration < MinCacheExpiration)
{
- expiration = MinimumCacheExpiration;
+ expiration = MinCacheExpiration;
}
- else if (expiration > MaximumCacheExpiration)
+ else if (expiration > MaxCacheExpiration)
{
- expiration = MaximumCacheExpiration;
+ expiration = MaxCacheExpiration;
}
}
diff --git a/MapControl/UWP/TileImageLoader.UWP.cs b/MapControl/UWP/TileImageLoader.UWP.cs
index bbed6327..94527f46 100644
--- a/MapControl/UWP/TileImageLoader.UWP.cs
+++ b/MapControl/UWP/TileImageLoader.UWP.cs
@@ -26,12 +26,7 @@ namespace MapControl
///
public static Caching.IImageCache Cache { get; set; }
- ///
- /// Gets or sets the maximum number of concurrent connections. The default value is 2.
- ///
- public static int DefaultConnectionLimit { get; set; } = 2;
-
- private async Task LoadTileImageAsync(Tile tile, Uri uri, string cacheKey)
+ private async Task LoadCachedTileImageAsync(Tile tile, Uri uri, string cacheKey)
{
var cacheItem = await Cache.GetAsync(cacheKey);
var cacheBuffer = cacheItem?.Buffer;
@@ -46,7 +41,7 @@ namespace MapControl
if (result.Item1 != null) // tile image available
{
- await SetTileImageAsync(tile, result.Item1); // show before caching
+ await LoadTileImageAsync(tile, result.Item1);
await Cache.SetAsync(cacheKey, result.Item1, GetExpiration(result.Item2));
}
}
@@ -54,21 +49,20 @@ namespace MapControl
if (cacheBuffer != null)
{
- await SetTileImageAsync(tile, cacheBuffer);
+ await LoadTileImageAsync(tile, cacheBuffer);
}
}
- private async Task SetTileImageAsync(Tile tile, IBuffer buffer)
+ private async Task LoadTileImageAsync(Tile tile, IBuffer buffer)
{
var tcs = new TaskCompletionSource