mirror of
https://github.com/ClemensFischer/XAML-Map-Control.git
synced 2025-12-06 07:12:04 +01:00
Improved TileImageLoader
This commit is contained in:
parent
fb00a11f4b
commit
5fde94acb9
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Tile> tiles, TileSource tileSource, string cacheName);
|
||||
TileSource TileSource { get; }
|
||||
|
||||
Task LoadTiles(IEnumerable<Tile> 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<Tile> tiles, string cacheName)
|
||||
{
|
||||
TileImageLoader.LoadTiles(tiles, TileSource, cacheName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|||
/// </summary>
|
||||
public static TimeSpan MaxCacheExpiration { get; set; } = TimeSpan.FromDays(10);
|
||||
|
||||
public TileSource TileSource { get; private set; }
|
||||
|
||||
private readonly ConcurrentQueue<Tile> tileQueue = new ConcurrentQueue<Tile>();
|
||||
private TileSource tileSource;
|
||||
private string cacheName;
|
||||
private int taskCount;
|
||||
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// </summary>
|
||||
public void LoadTiles(IEnumerable<Tile> tiles, TileSource source, string cache)
|
||||
public async Task LoadTiles(IEnumerable<Tile> tiles, TileSource tileSource, string cacheName)
|
||||
{
|
||||
tileQueue.Clear();
|
||||
|
||||
TileSource = tileSource;
|
||||
tiles = tiles.Where(tile => tile.Pending);
|
||||
|
||||
tileSource = source;
|
||||
if (tileSource != null && tiles.Any())
|
||||
{
|
||||
if (string.IsNullOrEmpty(cacheName) || Cache == null ||
|
||||
tileSource.UriFormat == null || !tileSource.UriFormat.StartsWith("http"))
|
||||
{
|
||||
cacheName = null;
|
||||
|
||||
if (tiles.Any() && tileSource != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(cache) &&
|
||||
Cache != null &&
|
||||
tileSource.UriFormat != null &&
|
||||
tileSource.UriFormat.StartsWith("http"))
|
||||
{
|
||||
cacheName = cache;
|
||||
}
|
||||
|
||||
foreach (var tile in tiles)
|
||||
|
|
@ -79,28 +75,28 @@ namespace MapControl
|
|||
tileQueue.Enqueue(tile);
|
||||
}
|
||||
|
||||
var tasks = new List<Task>();
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Tile>();
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ namespace MapControl
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// An IImageCache implementation used to cache tile images. The default is null.
|
||||
/// An IImageCache implementation used to cache tile images.
|
||||
/// </summary>
|
||||
public static Caching.IImageCache Cache { get; set; }
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ namespace MapControl
|
|||
{
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// An IImageCache implementation used to cache tile images. The default is null.
|
||||
/// An IImageCache implementation used to cache tile images.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue