// 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.Generic; using System.IO; using System.Threading.Tasks; namespace MapControl { /// /// Loads and optionally caches map tile images for a MapTileLayer. /// public partial class TileImageLoader : ITileImageLoader { /// /// Maximum number of parallel tile loading tasks. The default value is 4. /// public static int MaxLoadTasks { get; set; } = 4; /// /// Minimum expiration time for cached tile images. The default value is one hour. /// 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 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, /// the ZoomLevel, XIndex, and Y properties of a Tile, and the image file extension. /// The default value is "{0};{1};{2};{3}{4}". /// public static string CacheKeyFormat { get; set; } = "{0};{1};{2};{3}{4}"; private readonly TileQueue tileQueue = new TileQueue(); /// /// 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(IEnumerable tiles, TileSource tileSource, string sourceName) { tileQueue.Clear(); if (tileSource != null && tileQueue.Enqueue(tiles)) { if (Cache != null && tileSource.UriFormat != null && tileSource.UriFormat.StartsWith("http") && !string.IsNullOrEmpty(sourceName)) { tileQueue.RunDequeueTasks(MaxLoadTasks, tile => LoadCachedTileImageAsync(tile, tileSource, sourceName)); } else { tileQueue.RunDequeueTasks(MaxLoadTasks, tile => LoadTileImageAsync(tile, tileSource)); } } } 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) { var expiration = DefaultCacheExpiration; if (maxAge.HasValue) { expiration = maxAge.Value; if (expiration < MinCacheExpiration) { expiration = MinCacheExpiration; } else if (expiration > MaxCacheExpiration) { expiration = MaxCacheExpiration; } } return DateTime.UtcNow.Add(expiration); } } }