From 6938c8251d8d3b2063111f12357483bd08bcbef0 Mon Sep 17 00:00:00 2001 From: ClemensF Date: Mon, 17 Jul 2017 21:31:09 +0200 Subject: [PATCH] Version 3.2. Improved TileImageLoader. --- .../Properties/AssemblyInfo.cs | 4 +- .../Properties/AssemblyInfo.cs | 4 +- .../Properties/AssemblyInfo.cs | 4 +- .../Properties/AssemblyInfo.cs | 4 +- MapControl/BitmapSourceHelper.cs | 53 ++-- MapControl/ImageTileSource.WPF.cs | 28 +- MapControl/MapImageLayer.WPF.cs | 13 +- MapControl/MapImageLayer.cs | 8 +- MapControl/MapTileLayer.cs | 80 ++---- MapControl/MatrixEx.WPF.cs | 2 +- MapControl/Properties/AssemblyInfo.cs | 4 +- MapControl/Tile.Silverlight.WinRT.cs | 31 +-- MapControl/Tile.WPF.cs | 43 +-- MapControl/Tile.cs | 5 +- MapControl/TileImageLoader.Silverlight.cs | 32 ++- MapControl/TileImageLoader.WPF.cs | 247 +++++++----------- MapControl/TileImageLoader.WinRT.cs | 197 +++++++------- MapControl/ViewportChangedEventArgs.cs | 2 +- MapControl/WinRT/Properties/AssemblyInfo.cs | 4 +- SampleApps/Common/MapLayers.cs | 3 +- .../Properties/AssemblyInfo.cs | 4 +- .../Properties/AssemblyInfo.cs | 4 +- SampleApps/UniversalApp/MainPage.xaml.cs | 4 +- .../UniversalApp/Properties/AssemblyInfo.cs | 4 +- .../WpfApplication/Properties/AssemblyInfo.cs | 4 +- 25 files changed, 338 insertions(+), 450 deletions(-) diff --git a/Caching/FileDbCache.WPF/Properties/AssemblyInfo.cs b/Caching/FileDbCache.WPF/Properties/AssemblyInfo.cs index 0a9fe1cc..4545fab0 100644 --- a/Caching/FileDbCache.WPF/Properties/AssemblyInfo.cs +++ b/Caching/FileDbCache.WPF/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("© 2017 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("3.1.0")] -[assembly: AssemblyFileVersion("3.1.0")] +[assembly: AssemblyVersion("3.2.0")] +[assembly: AssemblyFileVersion("3.2.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/Caching/FileDbCache.WinRT/Properties/AssemblyInfo.cs b/Caching/FileDbCache.WinRT/Properties/AssemblyInfo.cs index b4a6df75..6402489d 100644 --- a/Caching/FileDbCache.WinRT/Properties/AssemblyInfo.cs +++ b/Caching/FileDbCache.WinRT/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("© 2017 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("3.1.0")] -[assembly: AssemblyFileVersion("3.1.0")] +[assembly: AssemblyVersion("3.2.0")] +[assembly: AssemblyFileVersion("3.2.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/Caching/ImageFileCache.WPF/Properties/AssemblyInfo.cs b/Caching/ImageFileCache.WPF/Properties/AssemblyInfo.cs index 0b1058e1..d0cdf11b 100644 --- a/Caching/ImageFileCache.WPF/Properties/AssemblyInfo.cs +++ b/Caching/ImageFileCache.WPF/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("© 2017 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("3.1.0")] -[assembly: AssemblyFileVersion("3.1.0")] +[assembly: AssemblyVersion("3.2.0")] +[assembly: AssemblyFileVersion("3.2.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/Caching/ImageFileCache.WinRT/Properties/AssemblyInfo.cs b/Caching/ImageFileCache.WinRT/Properties/AssemblyInfo.cs index cf64836d..0e35675d 100644 --- a/Caching/ImageFileCache.WinRT/Properties/AssemblyInfo.cs +++ b/Caching/ImageFileCache.WinRT/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("© 2017 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("3.1.0")] -[assembly: AssemblyFileVersion("3.1.0")] +[assembly: AssemblyVersion("3.2.0")] +[assembly: AssemblyFileVersion("3.2.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/MapControl/BitmapSourceHelper.cs b/MapControl/BitmapSourceHelper.cs index e43a4b5d..efb32737 100644 --- a/MapControl/BitmapSourceHelper.cs +++ b/MapControl/BitmapSourceHelper.cs @@ -3,7 +3,6 @@ // Licensed under the Microsoft Public License (Ms-PL) using System; -using System.Diagnostics; using System.IO; using System.Net; using System.Windows.Media.Imaging; @@ -11,43 +10,49 @@ using System.Windows.Media.Imaging; namespace MapControl { /// - /// Creates frozen BitmapSources from Stream or Uri. + /// Creates frozen BitmapSources from Stream, file or Uri. /// public static class BitmapSourceHelper { public static BitmapSource FromStream(Stream stream) { - var bitmap = new BitmapImage(); + return BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); + } - bitmap.BeginInit(); - bitmap.CacheOption = BitmapCacheOption.OnLoad; - bitmap.StreamSource = stream; - bitmap.EndInit(); - bitmap.Freeze(); + public static BitmapSource FromFile(string path) + { + if (!File.Exists(path)) + { + return null; + } - return bitmap; + using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read)) + { + return FromStream(fileStream); + } } public static BitmapSource FromUri(Uri uri) { - try + if (!uri.IsAbsoluteUri) { - using (var response = WebRequest.Create(uri).GetResponse()) - using (var responseStream = response.GetResponseStream()) - using (var memoryStream = new MemoryStream()) - { - responseStream.CopyTo(memoryStream); - memoryStream.Seek(0, SeekOrigin.Begin); - - return FromStream(memoryStream); - } - } - catch (Exception ex) - { - Debug.WriteLine(ex.Message); + return FromFile(uri.OriginalString); } - return null; + if (uri.Scheme == "file") + { + return FromFile(uri.LocalPath); + } + + using (var response = WebRequest.Create(uri).GetResponse()) + using (var responseStream = response.GetResponseStream()) + using (var memoryStream = new MemoryStream()) + { + responseStream.CopyTo(memoryStream); + memoryStream.Seek(0, SeekOrigin.Begin); + + return FromStream(memoryStream); + } } } } diff --git a/MapControl/ImageTileSource.WPF.cs b/MapControl/ImageTileSource.WPF.cs index 2030b548..fe0711ba 100644 --- a/MapControl/ImageTileSource.WPF.cs +++ b/MapControl/ImageTileSource.WPF.cs @@ -3,40 +3,22 @@ // Licensed under the Microsoft Public License (Ms-PL) using System.Windows.Media; -using System.Windows.Media.Imaging; namespace MapControl { /// - /// Provides the image of a map tile. ImageTileSource bypasses image downloading - /// and optional caching in TileImageLoader. By overriding the LoadImage method, - /// an application can provide tile images from an arbitrary source. - /// If the IsAsync property is true, LoadImage will be called from a separate, - /// non-UI thread and must therefore return a frozen ImageSource. + /// Provides the image of a map tile. + /// ImageTileSource bypasses image downloading and optional caching in TileImageLoader. + /// By overriding the LoadImage method, an application can provide tile images from an arbitrary source. + /// LoadImage will be called from a non-UI thread and must therefore return a frozen ImageSource. /// public class ImageTileSource : TileSource { - public bool IsAsync { get; set; } - public virtual ImageSource LoadImage(int x, int y, int zoomLevel) { - ImageSource image = null; - var uri = GetUri(x, y, zoomLevel); - if (uri != null) - { - if (IsAsync) - { - image = BitmapSourceHelper.FromUri(uri); - } - else - { - image = new BitmapImage(uri); - } - } - - return image; + return uri != null ? BitmapSourceHelper.FromUri(uri) : null; } } } diff --git a/MapControl/MapImageLayer.WPF.cs b/MapControl/MapImageLayer.WPF.cs index c5f0ca3e..eee4c685 100644 --- a/MapControl/MapImageLayer.WPF.cs +++ b/MapControl/MapImageLayer.WPF.cs @@ -3,6 +3,7 @@ // Licensed under the Microsoft Public License (Ms-PL) using System; +using System.Diagnostics; using System.Threading.Tasks; using System.Windows.Controls; using System.Windows.Media; @@ -16,7 +17,17 @@ namespace MapControl { Task.Run(() => { - var image = BitmapSourceHelper.FromUri(uri); + BitmapSource image = null; + + try + { + image = BitmapSourceHelper.FromUri(uri); + } + catch (Exception ex) + { + Debug.WriteLine("{0}: {1}", uri, ex.Message); + } + Dispatcher.BeginInvoke(new Action(() => UpdateImage(image))); }); } diff --git a/MapControl/MapImageLayer.cs b/MapControl/MapImageLayer.cs index 8f62a62a..c440051e 100644 --- a/MapControl/MapImageLayer.cs +++ b/MapControl/MapImageLayer.cs @@ -123,7 +123,7 @@ namespace MapControl /// /// Relative size of the map image in relation to the current viewport size. - /// Setting a value greater than one will let ImageLayer request images that + /// Setting a value greater than one will let MapImageLayer request images that /// are larger than the viewport, in order to support smooth panning. /// public double RelativeImageSize @@ -151,7 +151,7 @@ namespace MapControl } /// - /// Description of the ImageLayer. + /// Description of the MapImageLayer. /// Used to display copyright information on top of the map. /// public string Description @@ -162,7 +162,7 @@ namespace MapControl /// /// Optional foreground brush. - /// Sets MapBase.Foreground if not null and the ImageLayer is the base map layer. + /// Sets MapBase.Foreground if not null and the MapImageLayer is the base map layer. /// public Brush MapForeground { @@ -172,7 +172,7 @@ namespace MapControl /// /// Optional background brush. - /// Sets MapBase.Background if not null and the ImageLayer is the base map layer. + /// Sets MapBase.Background if not null and the MapImageLayer is the base map layer. /// public Brush MapBackground { diff --git a/MapControl/MapTileLayer.cs b/MapControl/MapTileLayer.cs index 59f98a1b..5ea442c2 100644 --- a/MapControl/MapTileLayer.cs +++ b/MapControl/MapTileLayer.cs @@ -23,8 +23,7 @@ namespace MapControl { public interface ITileImageLoader { - void BeginLoadTiles(MapTileLayer tileLayer, IEnumerable tiles); - void CancelLoadTiles(MapTileLayer tileLayer); + void LoadTiles(MapTileLayer tileLayer); } /// @@ -38,7 +37,7 @@ namespace MapControl public partial class MapTileLayer : Panel, IMapLayer { /// - /// A default TileLayer using OpenStreetMap data. + /// A default MapTileLayer using OpenStreetMap data. /// public static MapTileLayer OpenStreetMapTileLayer { @@ -56,7 +55,7 @@ namespace MapControl public static readonly DependencyProperty TileSourceProperty = DependencyProperty.Register( nameof(TileSource), typeof(TileSource), typeof(MapTileLayer), - new PropertyMetadata(null, (o, e) => ((MapTileLayer)o).UpdateTiles(true))); + new PropertyMetadata(null, (o, e) => ((MapTileLayer)o).ResetTiles())); public static readonly DependencyProperty SourceNameProperty = DependencyProperty.Register( nameof(SourceName), typeof(string), typeof(MapTileLayer), new PropertyMetadata(null)); @@ -84,9 +83,6 @@ namespace MapControl public static readonly DependencyProperty UpdateWhileViewportChangingProperty = DependencyProperty.Register( nameof(UpdateWhileViewportChanging), typeof(bool), typeof(MapTileLayer), new PropertyMetadata(true)); - public static readonly DependencyProperty LoadTilesDescendingProperty = DependencyProperty.Register( - nameof(LoadTilesDescending), typeof(bool), typeof(MapTileLayer), new PropertyMetadata(false)); - public static readonly DependencyProperty MapBackgroundProperty = DependencyProperty.Register( nameof(MapBackground), typeof(Brush), typeof(MapTileLayer), new PropertyMetadata(null)); @@ -138,7 +134,7 @@ namespace MapControl } /// - /// Description of the TileLayer. + /// Description of the MapTileLayer. /// Used to display copyright information on top of the map. /// public string Description @@ -148,7 +144,7 @@ namespace MapControl } /// - /// Adds an offset to the Map's ZoomLevel for a relative scale between the Map and the TileLayer. + /// Adds an offset to the Map's ZoomLevel for a relative scale between the Map and the MapTileLayer. /// public double ZoomLevelOffset { @@ -157,7 +153,7 @@ namespace MapControl } /// - /// Minimum zoom level supported by the TileLayer. + /// Minimum zoom level supported by the MapTileLayer. /// public int MinZoomLevel { @@ -166,7 +162,7 @@ namespace MapControl } /// - /// Maximum zoom level supported by the TileLayer. + /// Maximum zoom level supported by the MapTileLayer. /// public int MaxZoomLevel { @@ -175,7 +171,7 @@ namespace MapControl } /// - /// Maximum number of parallel downloads that may be performed by the TileLayer's ITileImageLoader. + /// Maximum number of parallel downloads that may be performed by the MapTileLayer's ITileImageLoader. /// public int MaxParallelDownloads { @@ -201,19 +197,9 @@ namespace MapControl set { SetValue(UpdateWhileViewportChangingProperty, value); } } - /// - /// Controls the order of zoom levels in which map tiles are loaded. - /// The default is value is false, i.e. tiles are loaded in ascending order. - /// - public bool LoadTilesDescending - { - get { return (bool)GetValue(LoadTilesDescendingProperty); } - set { SetValue(LoadTilesDescendingProperty, value); } - } - /// /// Optional background brush. - /// Sets MapBase.Background if not null and the TileLayer is the base map layer. + /// Sets MapBase.Background if not null and the MapTileLayer is the base map layer. /// public Brush MapBackground { @@ -223,7 +209,7 @@ namespace MapControl /// /// Optional foreground brush. - /// Sets MapBase.Foreground if not null and the TileLayer is the base map layer. + /// Sets MapBase.Foreground if not null and the MapTileLayer is the base map layer. /// public Brush MapForeground { @@ -295,13 +281,13 @@ namespace MapControl { TileGrid = tileGrid; SetRenderTransform(); - UpdateTiles(false); + UpdateTiles(); } } else { TileGrid = null; - UpdateTiles(true); + ResetTiles(); } } @@ -368,38 +354,24 @@ namespace MapControl MatrixEx.TranslateScaleRotateTranslate(tileOrigin, scale, parentMap.Heading, viewCenter); } - private void UpdateTiles(bool clearTiles) + private void ResetTiles() { - if (Tiles.Count > 0) - { - TileImageLoader.CancelLoadTiles(this); - } - - if (clearTiles) - { - Tiles.Clear(); - } + Tiles.Clear(); + UpdateTiles(); + } + private void UpdateTiles() + { SelectTiles(); Children.Clear(); - if (Tiles.Count > 0) + foreach (var tile in Tiles) { - foreach (var tile in Tiles) - { - Children.Add(tile.Image); - } - - var pendingTiles = Tiles.Where(t => t.Pending); - - if (LoadTilesDescending) - { - pendingTiles = pendingTiles.OrderByDescending(t => t.ZoomLevel); // higher zoom levels first - } - - TileImageLoader.BeginLoadTiles(this, pendingTiles); + Children.Add(tile.Image); } + + TileImageLoader.LoadTiles(this); } private void SelectTiles() @@ -409,13 +381,7 @@ namespace MapControl if (parentMap != null && TileGrid != null && TileSource != null) { var maxZoomLevel = Math.Min(TileGrid.ZoomLevel, MaxZoomLevel); - var minZoomLevel = MinZoomLevel; - - if (minZoomLevel < maxZoomLevel && this != parentMap.Children.Cast().FirstOrDefault()) - { - // do not load background tiles if this is not the base layer - minZoomLevel = maxZoomLevel; - } + var minZoomLevel = parentMap.MapLayer == this ? MinZoomLevel : maxZoomLevel; // load background tiles only if this is the base layer for (var z = minZoomLevel; z <= maxZoomLevel; z++) { diff --git a/MapControl/MatrixEx.WPF.cs b/MapControl/MatrixEx.WPF.cs index d78d5c13..8590a55d 100644 --- a/MapControl/MatrixEx.WPF.cs +++ b/MapControl/MatrixEx.WPF.cs @@ -23,7 +23,7 @@ namespace MapControl } /// - /// Used in TileLayer. + /// Used in MapTileLayer. /// public static Matrix TranslateScaleRotateTranslate( Point translation1, double scale, double rotationAngle, Point translation2) diff --git a/MapControl/Properties/AssemblyInfo.cs b/MapControl/Properties/AssemblyInfo.cs index 9a012497..91326449 100644 --- a/MapControl/Properties/AssemblyInfo.cs +++ b/MapControl/Properties/AssemblyInfo.cs @@ -14,8 +14,8 @@ using System.Windows; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("© 2017 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("3.1.0")] -[assembly: AssemblyFileVersion("3.1.0")] +[assembly: AssemblyVersion("3.2.0")] +[assembly: AssemblyFileVersion("3.2.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/MapControl/Tile.Silverlight.WinRT.cs b/MapControl/Tile.Silverlight.WinRT.cs index d4b2cb19..fd0eeca7 100644 --- a/MapControl/Tile.Silverlight.WinRT.cs +++ b/MapControl/Tile.Silverlight.WinRT.cs @@ -25,30 +25,27 @@ namespace MapControl { Pending = false; - if (image != null) + if (fadeIn && FadeDuration > TimeSpan.Zero) { - if (fadeIn && FadeDuration > TimeSpan.Zero) - { - BitmapImage bitmap; + BitmapImage bitmap; - if (isDownloading && (bitmap = image as BitmapImage) != null) - { - bitmap.ImageOpened += BitmapImageOpened; - bitmap.ImageFailed += BitmapImageFailed; - } - else - { - Image.BeginAnimation(UIElement.OpacityProperty, - new DoubleAnimation { From = 0d, To = 1d, Duration = FadeDuration }); - } + if (isDownloading && (bitmap = image as BitmapImage) != null) + { + bitmap.ImageOpened += BitmapImageOpened; + bitmap.ImageFailed += BitmapImageFailed; } else { - Image.Opacity = 1d; + Image.BeginAnimation(UIElement.OpacityProperty, + new DoubleAnimation { From = 0d, To = 1d, Duration = FadeDuration }); } - - Image.Source = image; } + else + { + Image.Opacity = 1d; + } + + Image.Source = image; } private void BitmapImageOpened(object sender, RoutedEventArgs e) diff --git a/MapControl/Tile.WPF.cs b/MapControl/Tile.WPF.cs index 469ebd48..bf14651a 100644 --- a/MapControl/Tile.WPF.cs +++ b/MapControl/Tile.WPF.cs @@ -17,30 +17,39 @@ namespace MapControl { Pending = false; - if (image != null) + if (Image.Dispatcher.CheckAccess()) { - if (fadeIn && FadeDuration > TimeSpan.Zero) - { - var bitmap = image as BitmapSource; + SetImageSource(image, fadeIn); + } + else + { + Image.Dispatcher.BeginInvoke(new Action(() => SetImageSource(image, fadeIn))); + } + } - if (bitmap != null && !bitmap.IsFrozen && bitmap.IsDownloading) - { - bitmap.DownloadCompleted += BitmapDownloadCompleted; - bitmap.DownloadFailed += BitmapDownloadFailed; - } - else - { - Image.BeginAnimation(UIElement.OpacityProperty, - new DoubleAnimation(0d, 1d, FadeDuration)); - } + private void SetImageSource(ImageSource image, bool fadeIn) + { + if (fadeIn && FadeDuration > TimeSpan.Zero) + { + var bitmap = image as BitmapSource; + + if (bitmap != null && !bitmap.IsFrozen && bitmap.IsDownloading) + { + bitmap.DownloadCompleted += BitmapDownloadCompleted; + bitmap.DownloadFailed += BitmapDownloadFailed; } else { - Image.Opacity = 1d; + Image.BeginAnimation(UIElement.OpacityProperty, + new DoubleAnimation(0d, 1d, FadeDuration)); } - - Image.Source = image; } + else + { + Image.Opacity = 1d; + } + + Image.Source = image; } private void BitmapDownloadCompleted(object sender, EventArgs e) diff --git a/MapControl/Tile.cs b/MapControl/Tile.cs index 9c799bcb..d70fc118 100644 --- a/MapControl/Tile.cs +++ b/MapControl/Tile.cs @@ -13,7 +13,7 @@ namespace MapControl { public partial class Tile { - public static TimeSpan FadeDuration { get; set; } = TimeSpan.FromSeconds(0.2); + public static TimeSpan FadeDuration { get; set; } = TimeSpan.FromSeconds(0.1); public readonly int ZoomLevel; public readonly int X; @@ -25,10 +25,9 @@ namespace MapControl ZoomLevel = zoomLevel; X = x; Y = y; - Pending = true; } - public bool Pending { get; private set; } + public bool Pending { get; set; } = true; public int XIndex { diff --git a/MapControl/TileImageLoader.Silverlight.cs b/MapControl/TileImageLoader.Silverlight.cs index 446ac86a..aafc4ec1 100644 --- a/MapControl/TileImageLoader.Silverlight.cs +++ b/MapControl/TileImageLoader.Silverlight.cs @@ -3,7 +3,7 @@ // Licensed under the Microsoft Public License (Ms-PL) using System; -using System.Collections.Generic; +using System.Linq; using System.Diagnostics; using System.Windows.Media; using System.Windows.Media.Imaging; @@ -15,41 +15,39 @@ namespace MapControl /// internal class TileImageLoader : ITileImageLoader { - public void BeginLoadTiles(MapTileLayer tileLayer, IEnumerable tiles) + public void LoadTiles(MapTileLayer tileLayer) { - var imageTileSource = tileLayer.TileSource as ImageTileSource; + var tileSource = tileLayer.TileSource; + var imageTileSource = tileSource as ImageTileSource; - foreach (var tile in tiles) + foreach (var tile in tileLayer.Tiles.Where(t => t.Pending)) { + tile.Pending = false; + try { ImageSource image = null; + Uri uri; if (imageTileSource != null) { image = imageTileSource.LoadImage(tile.XIndex, tile.Y, tile.ZoomLevel); } - else + else if ((uri = tileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel)) != null) { - var uri = tileLayer.TileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel); - - if (uri != null) - { - image = new BitmapImage(uri); - } + image = new BitmapImage(uri); } - tile.SetImage(image); + if (image != null) + { + tile.SetImage(image); + } } catch (Exception ex) { - Debug.WriteLine("Loading tile image failed: {0}", ex.Message); + Debug.WriteLine("{0}/{1}/{2}: {3}", tile.ZoomLevel, tile.XIndex, tile.Y, ex.Message); } } } - - public void CancelLoadTiles(MapTileLayer tileLayer) - { - } } } diff --git a/MapControl/TileImageLoader.WPF.cs b/MapControl/TileImageLoader.WPF.cs index 2fdd6af6..a34e06d0 100644 --- a/MapControl/TileImageLoader.WPF.cs +++ b/MapControl/TileImageLoader.WPF.cs @@ -14,7 +14,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Media; -using System.Windows.Threading; namespace MapControl { @@ -63,195 +62,99 @@ namespace MapControl Cache = MemoryCache.Default; } - private class PendingTile - { - public readonly Tile Tile; - public readonly ImageSource CachedImage; - - public PendingTile(Tile tile, ImageSource cachedImage) - { - Tile = tile; - CachedImage = cachedImage; - } - } - - private readonly ConcurrentQueue pendingTiles = new ConcurrentQueue(); + private readonly ConcurrentStack pendingTiles = new ConcurrentStack(); private int taskCount; - public void BeginLoadTiles(MapTileLayer tileLayer, IEnumerable tiles) + public void LoadTiles(MapTileLayer tileLayer) { - if (tiles.Any()) + pendingTiles.Clear(); + + var tileStack = tileLayer.Tiles.Where(t => t.Pending).Reverse().ToArray(); + + if (tileStack.Length > 0) { - // get current TileLayer property values in UI thread - var dispatcher = tileLayer.Dispatcher; + pendingTiles.PushRange(tileStack); + var tileSource = tileLayer.TileSource; - var imageTileSource = tileSource as ImageTileSource; + var sourceName = tileLayer.SourceName; + var maxDownloads = tileLayer.MaxParallelDownloads; - if (imageTileSource != null && !imageTileSource.IsAsync) // call LoadImage in UI thread with low priority + while (taskCount < Math.Min(pendingTiles.Count, maxDownloads)) { - foreach (var tile in tiles) + Interlocked.Increment(ref taskCount); + + Task.Run(() => { - dispatcher.BeginInvoke(new Action(t => t.SetImage(LoadImage(imageTileSource, t))), DispatcherPriority.Background, tile); - } - } - else - { - var tileList = tiles.ToList(); // evaluate immediately - var sourceName = tileLayer.SourceName; - var maxDownloads = tileLayer.MaxParallelDownloads; + LoadPendingTiles(tileSource, sourceName); - Task.Run(() => GetTiles(tileList, dispatcher, tileSource, sourceName, maxDownloads)); + Interlocked.Decrement(ref taskCount); + }); } } } - public void CancelLoadTiles(MapTileLayer tileLayer) - { - PendingTile pendingTile; - - while (pendingTiles.TryDequeue(out pendingTile)) ; // no Clear method - } - - private void GetTiles(IEnumerable tiles, Dispatcher dispatcher, TileSource tileSource, string sourceName, int maxDownloads) - { - var useCache = Cache != null - && !string.IsNullOrEmpty(sourceName) - && !(tileSource is ImageTileSource) - && !tileSource.UriFormat.StartsWith("file:"); - - foreach (var tile in tiles) - { - ImageSource cachedImage = null; - - if (useCache && GetCachedImage(CacheKey(sourceName, tile), out cachedImage)) - { - dispatcher.BeginInvoke(new Action((t, i) => t.SetImage(i)), tile, cachedImage); - } - else - { - pendingTiles.Enqueue(new PendingTile(tile, cachedImage)); - } - } - - var newTaskCount = Math.Min(pendingTiles.Count, maxDownloads) - taskCount; - - while (newTaskCount-- > 0) - { - Interlocked.Increment(ref taskCount); - - Task.Run(() => LoadPendingTiles(dispatcher, tileSource, sourceName)); - } - } - - private void LoadPendingTiles(Dispatcher dispatcher, TileSource tileSource, string sourceName) + private void LoadPendingTiles(TileSource tileSource, string sourceName) { var imageTileSource = tileSource as ImageTileSource; - PendingTile pendingTile; + Tile tile; - while (pendingTiles.TryDequeue(out pendingTile)) + while (pendingTiles.TryPop(out tile)) { - var tile = pendingTile.Tile; - ImageSource image = null; + tile.Pending = false; - if (imageTileSource != null) - { - image = LoadImage(imageTileSource, tile); - } - else - { - var uri = tileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel); - - if (uri != null) - { - if (!uri.IsAbsoluteUri) - { - image = LoadImage(uri.OriginalString); - } - else if (uri.Scheme == "file") - { - image = LoadImage(uri.LocalPath); - } - else - { - image = DownloadImage(uri, CacheKey(sourceName, tile)) - ?? pendingTile.CachedImage; // use possibly cached image if download failed - } - } - } - - dispatcher.BeginInvoke(new Action((t, i) => t.SetImage(i)), tile, image); - } - - Interlocked.Decrement(ref taskCount); - } - - private static ImageSource LoadImage(ImageTileSource tileSource, Tile tile) - { - ImageSource image = null; - - try - { - image = tileSource.LoadImage(tile.XIndex, tile.Y, tile.ZoomLevel); - } - catch (Exception ex) - { - Debug.WriteLine(ex.Message); - } - - return image; - } - - private static ImageSource LoadImage(string path) - { - ImageSource image = null; - - if (File.Exists(path)) - { try { - using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + ImageSource image = null; + Uri uri; + + if (imageTileSource != null) { - image = BitmapSourceHelper.FromStream(fileStream); + image = imageTileSource.LoadImage(tile.XIndex, tile.Y, tile.ZoomLevel); + } + else if ((uri = tileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel)) != null) + { + image = LoadImage(uri, sourceName, tile.XIndex, tile.Y, tile.ZoomLevel); + } + + if (image != null) + { + tile.SetImage(image); } } catch (Exception ex) { - Debug.WriteLine("{0}: {1}", path, ex.Message); + Debug.WriteLine("{0}/{1}/{2}: {3}", tile.ZoomLevel, tile.XIndex, tile.Y, ex.Message); } } - - return image; } - private static ImageSource DownloadImage(Uri uri, string cacheKey) + private ImageSource LoadImage(Uri uri, string sourceName, int x, int y, int zoomLevel) { ImageSource image = null; try { - var request = WebRequest.CreateHttp(uri); - - if (HttpUserAgent != null) + if (!uri.IsAbsoluteUri) { - request.UserAgent = HttpUserAgent; + image = BitmapSourceHelper.FromFile(uri.OriginalString); } - - using (var response = (HttpWebResponse)request.GetResponse()) + else if (uri.Scheme == "file") { - if (response.Headers["X-VE-Tile-Info"] != "no-tile") // set by Bing Maps - { - using (var responseStream = response.GetResponseStream()) - using (var memoryStream = new MemoryStream()) - { - responseStream.CopyTo(memoryStream); - memoryStream.Seek(0, SeekOrigin.Begin); - image = BitmapSourceHelper.FromStream(memoryStream); + image = BitmapSourceHelper.FromFile(uri.LocalPath); + } + else if (Cache == null || string.IsNullOrEmpty(sourceName)) + { + image = DownloadImage(uri, null); + } + else + { + var cacheKey = string.Format("{0}/{1}/{2}/{3}", sourceName, zoomLevel, x, y); - if (cacheKey != null) - { - SetCachedImage(cacheKey, memoryStream, GetExpiration(response.Headers)); - } - } + if (!GetCachedImage(cacheKey, ref image)) + { + // Either no cached image was found or expiration time has expired. + // If download fails use possibly cached but expired image anyway. + image = DownloadImage(uri, cacheKey); } } } @@ -267,15 +170,41 @@ namespace MapControl return image; } - private static string CacheKey(string sourceName, Tile tile) + private static ImageSource DownloadImage(Uri uri, string cacheKey) { - return string.IsNullOrEmpty(sourceName) ? null : string.Format("{0}/{1}/{2}/{3}", sourceName, tile.ZoomLevel, tile.XIndex, tile.Y); + ImageSource image = null; + var request = WebRequest.CreateHttp(uri); + + if (HttpUserAgent != null) + { + request.UserAgent = HttpUserAgent; + } + + using (var response = (HttpWebResponse)request.GetResponse()) + { + if (response.Headers["X-VE-Tile-Info"] != "no-tile") // set by Bing Maps + { + using (var responseStream = response.GetResponseStream()) + using (var memoryStream = new MemoryStream()) + { + responseStream.CopyTo(memoryStream); + memoryStream.Seek(0, SeekOrigin.Begin); + image = BitmapSourceHelper.FromStream(memoryStream); + + if (cacheKey != null) + { + SetCachedImage(cacheKey, memoryStream, GetExpiration(response.Headers)); + } + } + } + } + + return image; } - private static bool GetCachedImage(string cacheKey, out ImageSource image) + private static bool GetCachedImage(string cacheKey, ref ImageSource image) { - image = null; - + var result = false; var buffer = Cache.Get(cacheKey) as byte[]; if (buffer != null) @@ -294,7 +223,7 @@ namespace MapControl expiration = new DateTime(BitConverter.ToInt64(buffer, buffer.Length - 8), DateTimeKind.Utc); } - return expiration > DateTime.UtcNow; + result = expiration > DateTime.UtcNow; } catch (Exception ex) { @@ -302,7 +231,7 @@ namespace MapControl } } - return false; + return result; } private static void SetCachedImage(string cacheKey, MemoryStream memoryStream, DateTime expiration) diff --git a/MapControl/TileImageLoader.WinRT.cs b/MapControl/TileImageLoader.WinRT.cs index edc2d789..0b755182 100644 --- a/MapControl/TileImageLoader.WinRT.cs +++ b/MapControl/TileImageLoader.WinRT.cs @@ -7,6 +7,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Windows.Storage; @@ -56,130 +57,124 @@ namespace MapControl MinimumCacheExpiration = TimeSpan.FromHours(1); } - private class PendingTile - { - public readonly Tile Tile; - public readonly Uri Uri; - - public PendingTile(Tile tile, Uri uri) - { - Tile = tile; - Uri = uri; - } - } - - private readonly ConcurrentQueue pendingTiles = new ConcurrentQueue(); + private readonly ConcurrentStack pendingTiles = new ConcurrentStack(); private int taskCount; - public void BeginLoadTiles(MapTileLayer tileLayer, IEnumerable tiles) + public void LoadTiles(MapTileLayer tileLayer) { + pendingTiles.Clear(); + var tileSource = tileLayer.TileSource; var imageTileSource = tileSource as ImageTileSource; + var tiles = tileLayer.Tiles.Where(t => t.Pending); if (imageTileSource != null) { - foreach (var tile in tiles) - { - try - { - tile.SetImage(imageTileSource.LoadImage(tile.XIndex, tile.Y, tile.ZoomLevel)); - } - catch (Exception ex) - { - Debug.WriteLine(ex.Message); - } - } + LoadTiles(imageTileSource, tiles); } else { - foreach (var tile in tiles) + var tileStack = tiles.Reverse().ToArray(); + + if (tileStack.Length > 0) { - Uri uri = null; + pendingTiles.PushRange(tileStack); - try - { - uri = tileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel); - } - catch (Exception ex) - { - Debug.WriteLine(ex.Message); - } + var sourceName = tileLayer.SourceName; + var maxDownloads = tileLayer.MaxParallelDownloads; - if (uri == null) + while (taskCount < Math.Min(pendingTiles.Count, maxDownloads)) { - tile.SetImage(null); - } - else - { - pendingTiles.Enqueue(new PendingTile(tile, uri)); + Interlocked.Increment(ref taskCount); - var newTaskCount = Math.Min(pendingTiles.Count, tileLayer.MaxParallelDownloads) - taskCount; - var sourceName = tileLayer.SourceName; - - while (newTaskCount-- > 0) + Task.Run(async () => { - Interlocked.Increment(ref taskCount); + await LoadPendingTiles(tileSource, sourceName); - Task.Run(() => LoadPendingTiles(tileSource, sourceName)); // Task.Run(Func) - } + Interlocked.Decrement(ref taskCount); + }); } } } } - public void CancelLoadTiles(MapTileLayer tileLayer) + private void LoadTiles(ImageTileSource tileSource, IEnumerable tiles) { - PendingTile pendingTile; + foreach (var tile in tiles) + { + tile.Pending = false; - while (pendingTiles.TryDequeue(out pendingTile)) ; // no Clear method + try + { + var image = tileSource.LoadImage(tile.XIndex, tile.Y, tile.ZoomLevel); + + if (image != null) + { + tile.SetImage(image); + } + } + catch (Exception ex) + { + Debug.WriteLine("{0}/{1}/{2}: {3}", tile.ZoomLevel, tile.XIndex, tile.Y, ex.Message); + } + } } private async Task LoadPendingTiles(TileSource tileSource, string sourceName) { - PendingTile pendingTile; + Tile tile; - while (pendingTiles.TryDequeue(out pendingTile)) + while (pendingTiles.TryPop(out tile)) { - var tile = pendingTile.Tile; - var uri = pendingTile.Uri; + tile.Pending = false; - if (Cache == null || sourceName == null) + try { - await DownloadImage(tile, uri, null); - } - else - { - var extension = Path.GetExtension(uri.LocalPath); + var uri = tileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel); - if (string.IsNullOrEmpty(extension) || extension == ".jpeg") + if (uri != null) { - extension = ".jpg"; - } - - var cacheKey = string.Format(@"{0}\{1}\{2}\{3}{4}", sourceName, tile.ZoomLevel, tile.XIndex, tile.Y, extension); - var cacheItem = await Cache.GetAsync(cacheKey); - var loaded = false; - - if (cacheItem == null || cacheItem.Expiration <= DateTime.UtcNow) - { - loaded = await DownloadImage(tile, uri, cacheKey); - } - - if (!loaded && cacheItem != null && cacheItem.Buffer != null) - { - using (var stream = new InMemoryRandomAccessStream()) + if (Cache == null || sourceName == null) { - await stream.WriteAsync(cacheItem.Buffer); - await stream.FlushAsync(); - stream.Seek(0); + await DownloadImage(tile, uri, null); + } + else + { + var extension = Path.GetExtension(uri.LocalPath); - await LoadImageFromStream(tile, stream); + if (string.IsNullOrEmpty(extension) || extension == ".jpeg") + { + extension = ".jpg"; + } + + var cacheKey = string.Format(@"{0}\{1}\{2}\{3}{4}", sourceName, tile.ZoomLevel, tile.XIndex, tile.Y, extension); + var cacheItem = await Cache.GetAsync(cacheKey); + var loaded = false; + + if (cacheItem == null || cacheItem.Expiration <= DateTime.UtcNow) + { + loaded = await DownloadImage(tile, uri, cacheKey); + } + + if (!loaded && cacheItem != null && cacheItem.Buffer != null) + { + using (var stream = new InMemoryRandomAccessStream()) + { + await stream.WriteAsync(cacheItem.Buffer); + await stream.FlushAsync(); + stream.Seek(0); + + await LoadImageFromStream(tile, stream); + } + } } } } + catch (Exception ex) + { + Debug.WriteLine("{0}/{1}/{2}: {3}", tile.ZoomLevel, tile.XIndex, tile.Y, ex.Message); + } } - - Interlocked.Decrement(ref taskCount); } private async Task DownloadImage(Tile tile, Uri uri, string cacheKey) @@ -211,7 +206,6 @@ namespace MapControl if (response.Headers.TryGetValue("X-VE-Tile-Info", out tileInfo) && tileInfo == "no-tile") // set by Bing Maps { - tile.SetImage(null); return true; } @@ -255,28 +249,25 @@ namespace MapControl private async Task LoadImageFromStream(Tile tile, IRandomAccessStream stream) { - var completion = new TaskCompletionSource(); + var tcs = new TaskCompletionSource(); - var action = tile.Image.Dispatcher.RunAsync( - CoreDispatcherPriority.Normal, - async () => + await tile.Image.Dispatcher.RunAsync(CoreDispatcherPriority.Low, async () => + { + try { - try - { - var image = new BitmapImage(); - await image.SetSourceAsync(stream); - tile.SetImage(image, true, false); - completion.SetResult(true); - } - catch (Exception ex) - { - Debug.WriteLine(ex.Message); - tile.SetImage(null); - completion.SetResult(false); - } - }); + var image = new BitmapImage(); + await image.SetSourceAsync(stream); + tile.SetImage(image, true, false); + tcs.SetResult(true); + } + catch (Exception ex) + { + Debug.WriteLine("{0}/{1}/{2}: {3}", tile.ZoomLevel, tile.XIndex, tile.Y, ex.Message); + tcs.SetResult(false); + } + }); - return await completion.Task; + return await tcs.Task; } } } diff --git a/MapControl/ViewportChangedEventArgs.cs b/MapControl/ViewportChangedEventArgs.cs index e19d3984..f1f66e09 100644 --- a/MapControl/ViewportChangedEventArgs.cs +++ b/MapControl/ViewportChangedEventArgs.cs @@ -15,7 +15,7 @@ namespace MapControl } /// - /// Indicates if the map projection has changed, i.e. if a TileLayer or ImageLayer should be + /// Indicates if the map projection has changed, i.e. if a MapTileLayer or MapImageLayer should be /// immediately updated, or MapPath Data in cartesian map coordinates should be recalculated. /// public bool ProjectionChanged { get; } diff --git a/MapControl/WinRT/Properties/AssemblyInfo.cs b/MapControl/WinRT/Properties/AssemblyInfo.cs index e948715b..79d684c1 100644 --- a/MapControl/WinRT/Properties/AssemblyInfo.cs +++ b/MapControl/WinRT/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("© 2017 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("3.1.0")] -[assembly: AssemblyFileVersion("3.1.0")] +[assembly: AssemblyVersion("3.2.0")] +[assembly: AssemblyFileVersion("3.2.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/SampleApps/Common/MapLayers.cs b/SampleApps/Common/MapLayers.cs index 494f60d1..49bf980f 100644 --- a/SampleApps/Common/MapLayers.cs +++ b/SampleApps/Common/MapLayers.cs @@ -29,7 +29,8 @@ namespace ViewModel { SourceName = "OpenStreetMap German", Description = "© [OpenStreetMap Contributors](http://www.openstreetmap.org/copyright)", - TileSource = new TileSource { UriFormat = "http://{c}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png" } + TileSource = new TileSource { UriFormat = "http://{c}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png" }, + MaxZoomLevel = 19 } }, { diff --git a/SampleApps/SilverlightApplication.Web/Properties/AssemblyInfo.cs b/SampleApps/SilverlightApplication.Web/Properties/AssemblyInfo.cs index a06e47a3..995cda98 100644 --- a/SampleApps/SilverlightApplication.Web/Properties/AssemblyInfo.cs +++ b/SampleApps/SilverlightApplication.Web/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("© 2017 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("3.1.0")] -[assembly: AssemblyFileVersion("3.1.0")] +[assembly: AssemblyVersion("3.2.0")] +[assembly: AssemblyFileVersion("3.2.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/SampleApps/SilverlightApplication/Properties/AssemblyInfo.cs b/SampleApps/SilverlightApplication/Properties/AssemblyInfo.cs index 68d0894c..98013e2e 100644 --- a/SampleApps/SilverlightApplication/Properties/AssemblyInfo.cs +++ b/SampleApps/SilverlightApplication/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("© 2017 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("3.1.0")] -[assembly: AssemblyFileVersion("3.1.0")] +[assembly: AssemblyVersion("3.2.0")] +[assembly: AssemblyFileVersion("3.2.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/SampleApps/UniversalApp/MainPage.xaml.cs b/SampleApps/UniversalApp/MainPage.xaml.cs index dd8aac6c..24a6daab 100644 --- a/SampleApps/UniversalApp/MainPage.xaml.cs +++ b/SampleApps/UniversalApp/MainPage.xaml.cs @@ -11,8 +11,8 @@ namespace UniversalApp public MainPage() { - //TileImageLoader.Cache = new MapControl.Caching.ImageFileCache(); - //TileImageLoader.Cache = new MapControl.Caching.FileDbCache(); + //MapControl.TileImageLoader.Cache = new MapControl.Caching.ImageFileCache(); + //MapControl.TileImageLoader.Cache = new MapControl.Caching.FileDbCache(); InitializeComponent(); DataContext = ViewModel; diff --git a/SampleApps/UniversalApp/Properties/AssemblyInfo.cs b/SampleApps/UniversalApp/Properties/AssemblyInfo.cs index cadf7067..f992a7d0 100644 --- a/SampleApps/UniversalApp/Properties/AssemblyInfo.cs +++ b/SampleApps/UniversalApp/Properties/AssemblyInfo.cs @@ -8,7 +8,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyCopyright("© 2017 Clemens Fischer")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("3.1.0")] -[assembly: AssemblyFileVersion("3.1.0")] +[assembly: AssemblyVersion("3.2.0")] +[assembly: AssemblyFileVersion("3.2.0")] [assembly: AssemblyConfiguration("")] [assembly: ComVisible(false)] diff --git a/SampleApps/WpfApplication/Properties/AssemblyInfo.cs b/SampleApps/WpfApplication/Properties/AssemblyInfo.cs index 838701e9..e848ba76 100644 --- a/SampleApps/WpfApplication/Properties/AssemblyInfo.cs +++ b/SampleApps/WpfApplication/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("© 2017 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("3.1.0")] -[assembly: AssemblyFileVersion("3.1.0")] +[assembly: AssemblyVersion("3.2.0")] +[assembly: AssemblyFileVersion("3.2.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)]