diff --git a/Caching/FileDbCache/Properties/AssemblyInfo.cs b/Caching/FileDbCache/Properties/AssemblyInfo.cs index fcdf257d..c22cc179 100644 --- a/Caching/FileDbCache/Properties/AssemblyInfo.cs +++ b/Caching/FileDbCache/Properties/AssemblyInfo.cs @@ -8,8 +8,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © Clemens Fischer 2012-2013")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("1.7.0")] -[assembly: AssemblyFileVersion("1.7.0")] +[assembly: AssemblyVersion("1.8.0")] +[assembly: AssemblyFileVersion("1.8.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/Caching/ImageFileCache/ImageFileCache.cs b/Caching/ImageFileCache/ImageFileCache.cs index 318a0e11..e27f6b9e 100644 --- a/Caching/ImageFileCache/ImageFileCache.cs +++ b/Caching/ImageFileCache/ImageFileCache.cs @@ -16,12 +16,22 @@ namespace Caching { /// /// ObjectCache implementation based on local image files. - /// The only valid data type for cached values is a byte[], which contains - /// an 8-byte binary UTC time (as created by DateTime.ToBinary), followed - /// by a PNG, JPEG, BMP, GIF, TIFF or WMP image buffer. + /// The only valid data type for cached values is a byte array containing an + /// 8-byte timestamp followed by a PNG, JPEG, BMP, GIF, TIFF or WMP image buffer. /// public class ImageFileCache : ObjectCache { + private static readonly Tuple[] imageFileTypes = new Tuple[] + { + new Tuple(".png", new byte[] { 0x89, 0x50, 0x4E, 0x47, 0xD, 0xA, 0x1A, 0xA }), + new Tuple(".jpg", new byte[] { 0xFF, 0xD8, 0xFF, 0xE0, 0, 0x10, 0x4A, 0x46, 0x49, 0x46, 0 }), + new Tuple(".bmp", new byte[] { 0x42, 0x4D }), + new Tuple(".gif", new byte[] { 0x47, 0x49, 0x46 }), + new Tuple(".tif", new byte[] { 0x49, 0x49, 42, 0 }), + new Tuple(".tif", new byte[] { 0x4D, 0x4D, 0, 42 }), + new Tuple(".wdp", new byte[] { 0x49, 0x49, 0xBC }), + }; + private static readonly FileSystemAccessRule fullControlRule = new FileSystemAccessRule( new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null), FileSystemRights.FullControl, AccessControlType.Allow); @@ -169,36 +179,31 @@ namespace Caching var buffer = value as byte[]; - if (buffer == null) + if (buffer == null || buffer.Length <= 8) { - throw new NotSupportedException("The parameter value must be a byte[]."); + throw new NotSupportedException("The parameter value must be a byte[] containing at least 9 bytes."); } MemoryCache.Default.Set(key, buffer, policy); - var extension = GetFileExtension(buffer); + var path = GetPath(key) + GetFileExtension(buffer); - if (extension != null) + try { - var path = GetPath(key) + extension; + Directory.CreateDirectory(Path.GetDirectoryName(path)); - try + using (var fileStream = new FileStream(path, FileMode.Create)) { - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - using (var fileStream = new FileStream(path, FileMode.Create)) - { - fileStream.Write(buffer, 8, buffer.Length - 8); - } - - var fileSecurity = File.GetAccessControl(path); - fileSecurity.AddAccessRule(fullControlRule); - File.SetAccessControl(path, fileSecurity); - } - catch (Exception ex) - { - Trace.TraceWarning("ImageFileCache: Writing file {0} failed: {1}", path, ex.Message); + fileStream.Write(buffer, 8, buffer.Length - 8); } + + var fileSecurity = File.GetAccessControl(path); + fileSecurity.AddAccessRule(fullControlRule); + File.SetAccessControl(path, fileSecurity); + } + catch (Exception ex) + { + Trace.TraceWarning("ImageFileCache: Writing file {0} failed: {1}", path, ex.Message); } } @@ -274,42 +279,24 @@ namespace Caching return null; } - private static readonly Tuple[] fileTypes = new Tuple[] - { - new Tuple(".png", new byte[] { 0x89, 0x50, 0x4E, 0x47, 0xD, 0xA, 0x1A, 0xA }), - new Tuple(".jpg", new byte[] { 0xFF, 0xD8, 0xFF, 0xE0, 0, 0x10, 0x4A, 0x46, 0x49, 0x46, 0 }), - new Tuple(".bmp", new byte[] { 0x42, 0x4D }), - new Tuple(".gif", new byte[] { 0x47, 0x49, 0x46 }), - new Tuple(".tif", new byte[] { 0x49, 0x49, 42, 0 }), - new Tuple(".tif", new byte[] { 0x4D, 0x4D, 0, 42 }), - new Tuple(".wdp", new byte[] { 0x49, 0x49, 0xBC }), - }; - private static string GetFileExtension(byte[] buffer) { - string extension = null; - var creationTime = DateTime.FromBinary(BitConverter.ToInt64(buffer, 0)); - - if (creationTime.Kind == DateTimeKind.Utc && creationTime <= DateTime.UtcNow) + var fileType = imageFileTypes.FirstOrDefault(t => { - Func, bool> match = - t => + int i = 0; + + if (t.Item2.Length <= buffer.Length - 8) + { + while (i < t.Item2.Length && t.Item2[i] == buffer[i + 8]) { - int i = 0; - if (t.Item2.Length + 8 <= buffer.Length) - { - while (i < t.Item2.Length && t.Item2[i] == buffer[i + 8]) - { - i++; - } - } - return i == t.Item2.Length; - }; + i++; + } + } - extension = fileTypes.Where(match).Select(t => t.Item1).FirstOrDefault(); - } + return i == t.Item2.Length; + }); - return extension; + return fileType != null ? fileType.Item1 : ".bin"; } } } diff --git a/Caching/ImageFileCache/Properties/AssemblyInfo.cs b/Caching/ImageFileCache/Properties/AssemblyInfo.cs index a652f9a1..244d27ac 100644 --- a/Caching/ImageFileCache/Properties/AssemblyInfo.cs +++ b/Caching/ImageFileCache/Properties/AssemblyInfo.cs @@ -8,8 +8,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © Clemens Fischer 2012-2013")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("1.7.0")] -[assembly: AssemblyFileVersion("1.7.0")] +[assembly: AssemblyVersion("1.8.0")] +[assembly: AssemblyFileVersion("1.8.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/MapControl/Freezable.cs b/MapControl/Freezable.cs deleted file mode 100644 index aed1592a..00000000 --- a/MapControl/Freezable.cs +++ /dev/null @@ -1,22 +0,0 @@ -// XAML Map Control - http://xamlmapcontrol.codeplex.com/ -// Copyright © Clemens Fischer 2012-2013 -// Licensed under the Microsoft Public License (Ms-PL) - -#if NETFX_CORE -using Windows.UI.Xaml; -#else -using System.Windows; -#endif - -namespace MapControl -{ - internal static class Freezable - { - /// - /// Provides WPF compatibility. - /// - public static void Freeze(this DependencyObject obj) - { - } - } -} diff --git a/MapControl/ImageTileSource.WPF.cs b/MapControl/ImageTileSource.WPF.cs new file mode 100644 index 00000000..6eb5f869 --- /dev/null +++ b/MapControl/ImageTileSource.WPF.cs @@ -0,0 +1,14 @@ +// XAML Map Control - http://xamlmapcontrol.codeplex.com/ +// Copyright © Clemens Fischer 2012-2013 +// Licensed under the Microsoft Public License (Ms-PL) + +namespace MapControl +{ + public partial class ImageTileSource + { + public virtual bool CanLoadAsync + { + get { return false; } + } + } +} diff --git a/MapControl/ImageTileSource.cs b/MapControl/ImageTileSource.cs index d3d76e5d..b24339d0 100644 --- a/MapControl/ImageTileSource.cs +++ b/MapControl/ImageTileSource.cs @@ -2,28 +2,30 @@ // Copyright © Clemens Fischer 2012-2013 // Licensed under the Microsoft Public License (Ms-PL) +#if NETFX_CORE +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Media.Imaging; +#else using System.Windows.Media; using System.Windows.Media.Imaging; +#endif namespace MapControl { /// - /// Provides the image of a map tile. ImageTileSource bypasses download and - /// cache processing in TileImageLoader. By overriding the LoadImage method, + /// Provides the image of a map tile. ImageTileSource bypasses downloading and + /// optional caching in TileImageLoader. By overriding the LoadImage method, /// an application can provide tile images from an arbitrary source. - /// If the CanLoadAsync property is true, the LoadImage method will be called + /// WPF only: If the CanLoadAsync property is true, LoadImage will be called /// from a separate, non-UI thread and must hence return a frozen ImageSource. /// - public class ImageTileSource : TileSource + public partial class ImageTileSource : TileSource { - public virtual bool CanLoadAsync - { - get { return false; } - } - public virtual ImageSource LoadImage(int x, int y, int zoomLevel) { - return new BitmapImage(GetUri(x, y, zoomLevel)); + var uri = GetUri(x, y, zoomLevel); + + return uri != null ? new BitmapImage(uri) : null; } } } diff --git a/MapControl/MapControl.Silverlight.csproj b/MapControl/MapControl.Silverlight.csproj index a48ec921..b5b8468c 100644 --- a/MapControl/MapControl.Silverlight.csproj +++ b/MapControl/MapControl.Silverlight.csproj @@ -70,7 +70,7 @@ - + diff --git a/MapControl/MapControl.WPF.csproj b/MapControl/MapControl.WPF.csproj index 46a2e8a7..838c8ad3 100644 --- a/MapControl/MapControl.WPF.csproj +++ b/MapControl/MapControl.WPF.csproj @@ -53,6 +53,7 @@ + @@ -63,6 +64,7 @@ + @@ -74,6 +76,7 @@ + diff --git a/MapControl/MapImage.WPF.cs b/MapControl/MapImage.WPF.cs new file mode 100644 index 00000000..72d91f1b --- /dev/null +++ b/MapControl/MapImage.WPF.cs @@ -0,0 +1,14 @@ +// XAML Map Control - http://xamlmapcontrol.codeplex.com/ +// Copyright © Clemens Fischer 2012-2013 +// Licensed under the Microsoft Public License (Ms-PL) + +namespace MapControl +{ + public partial class MapImage + { + static MapImage() + { + imageTransform.Freeze(); + } + } +} diff --git a/MapControl/MapImage.cs b/MapControl/MapImage.cs index 854315fd..18e493f8 100644 --- a/MapControl/MapImage.cs +++ b/MapControl/MapImage.cs @@ -15,18 +15,13 @@ namespace MapControl /// /// Fills a rectangular area with an ImageBrush from the Source property. /// - public class MapImage : MapRectangle + public partial class MapImage : MapRectangle { private static readonly MatrixTransform imageTransform = new MatrixTransform { Matrix = new Matrix(1d, 0d, 0d, -1d, 0d, 1d) }; - static MapImage() - { - imageTransform.Freeze(); - } - public static readonly DependencyProperty SourceProperty = DependencyProperty.Register( "Source", typeof(ImageSource), typeof(MapImage), new PropertyMetadata(null, (o, e) => ((MapImage)o).SourceChanged((ImageSource)e.NewValue))); @@ -45,7 +40,6 @@ namespace MapControl RelativeTransform = imageTransform }; - imageBrush.Freeze(); Fill = imageBrush; } } diff --git a/MapControl/MapImageLayer.cs b/MapControl/MapImageLayer.cs index 570c4203..189d5dab 100644 --- a/MapControl/MapImageLayer.cs +++ b/MapControl/MapImageLayer.cs @@ -119,7 +119,6 @@ namespace MapControl try { - var bitmap = new BitmapImage(); var request = (HttpWebRequest)WebRequest.Create(uri); request.UserAgent = "XAML Map Control"; @@ -128,15 +127,8 @@ namespace MapControl using (var memoryStream = new MemoryStream()) { responseStream.CopyTo(memoryStream); - - bitmap.BeginInit(); - bitmap.CacheOption = BitmapCacheOption.OnLoad; - bitmap.StreamSource = memoryStream; - bitmap.EndInit(); - bitmap.Freeze(); + image = BitmapFrame.Create(memoryStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); } - - image = bitmap; } catch (Exception ex) { diff --git a/MapControl/MapRectangle.WPF.cs b/MapControl/MapRectangle.WPF.cs new file mode 100644 index 00000000..76e47e55 --- /dev/null +++ b/MapControl/MapRectangle.WPF.cs @@ -0,0 +1,14 @@ +// XAML Map Control - http://xamlmapcontrol.codeplex.com/ +// Copyright © Clemens Fischer 2012-2013 +// Licensed under the Microsoft Public License (Ms-PL) + +namespace MapControl +{ + public partial class MapRectangle + { + static MapRectangle() + { + geometryScaleTransform.Freeze(); + } + } +} diff --git a/MapControl/MapRectangle.cs b/MapControl/MapRectangle.cs index 7984f651..9987a00f 100644 --- a/MapControl/MapRectangle.cs +++ b/MapControl/MapRectangle.cs @@ -16,7 +16,7 @@ namespace MapControl /// /// Fills a rectangular area defined by South, North, West and East with a Brush. /// - public class MapRectangle : MapPath + public partial class MapRectangle : MapPath { private const double geometryScale = 1e6; @@ -26,11 +26,6 @@ namespace MapControl ScaleY = 1d / geometryScale }; - static MapRectangle() - { - geometryScaleTransform.Freeze(); - } - public static readonly DependencyProperty SouthProperty = DependencyProperty.Register( "South", typeof(double), typeof(MapRectangle), new PropertyMetadata(double.NaN, (o, e) => ((MapRectangle)o).UpdateData())); diff --git a/MapControl/Properties/AssemblyInfo.cs b/MapControl/Properties/AssemblyInfo.cs index 96bcf68c..39d82727 100644 --- a/MapControl/Properties/AssemblyInfo.cs +++ b/MapControl/Properties/AssemblyInfo.cs @@ -15,8 +15,8 @@ using System.Windows; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © Clemens Fischer 2012-2013")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("1.7.0")] -[assembly: AssemblyFileVersion("1.7.0")] +[assembly: AssemblyVersion("1.8.0")] +[assembly: AssemblyFileVersion("1.8.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/MapControl/Tile.Silverlight.WinRT.cs b/MapControl/Tile.Silverlight.WinRT.cs index aaf9e6e5..a0f35d37 100644 --- a/MapControl/Tile.Silverlight.WinRT.cs +++ b/MapControl/Tile.Silverlight.WinRT.cs @@ -33,12 +33,12 @@ namespace MapControl { if (animateOpacity) { - var bitmapImage = image as BitmapImage; + var bitmap = image as BitmapImage; - if (bitmapImage != null) + if (bitmap != null) { - bitmapImage.ImageOpened += BitmapImageOpened; - bitmapImage.ImageFailed += BitmapImageFailed; + bitmap.ImageOpened += BitmapImageOpened; + bitmap.ImageFailed += BitmapImageFailed; } else { @@ -52,20 +52,26 @@ namespace MapControl } Image.Source = image; - HasImage = true; + HasImageSource = true; } private void BitmapImageOpened(object sender, RoutedEventArgs e) { - ((BitmapImage)sender).ImageOpened -= BitmapImageOpened; - ((BitmapImage)sender).ImageFailed -= BitmapImageFailed; + var bitmap = (BitmapImage)sender; + + bitmap.ImageOpened -= BitmapImageOpened; + bitmap.ImageFailed -= BitmapImageFailed; + Image.BeginAnimation(Image.OpacityProperty, new DoubleAnimation { To = 1d, Duration = AnimationDuration }); } private void BitmapImageFailed(object sender, ExceptionRoutedEventArgs e) { - ((BitmapImage)sender).ImageOpened -= BitmapImageOpened; - ((BitmapImage)sender).ImageFailed -= BitmapImageFailed; + var bitmap = (BitmapImage)sender; + + bitmap.ImageOpened -= BitmapImageOpened; + bitmap.ImageFailed -= BitmapImageFailed; + Image.Source = null; } } diff --git a/MapControl/Tile.WPF.cs b/MapControl/Tile.WPF.cs index 4b711038..8dc1ac2a 100644 --- a/MapControl/Tile.WPF.cs +++ b/MapControl/Tile.WPF.cs @@ -24,12 +24,12 @@ namespace MapControl { if (animateOpacity) { - var bitmapImage = image as BitmapImage; + var bitmap = image as BitmapSource; - if (bitmapImage != null && bitmapImage.IsDownloading) + if (bitmap != null && !bitmap.IsFrozen && bitmap.IsDownloading) { - bitmapImage.DownloadCompleted += BitmapDownloadCompleted; - bitmapImage.DownloadFailed += BitmapDownloadFailed; + bitmap.DownloadCompleted += BitmapDownloadCompleted; + bitmap.DownloadFailed += BitmapDownloadFailed; } else { @@ -43,20 +43,26 @@ namespace MapControl } Brush.ImageSource = image; - HasImage = true; + HasImageSource = true; } private void BitmapDownloadCompleted(object sender, EventArgs e) { - ((BitmapImage)sender).DownloadCompleted -= BitmapDownloadCompleted; - ((BitmapImage)sender).DownloadFailed -= BitmapDownloadFailed; + var bitmap = (BitmapSource)sender; + + bitmap.DownloadCompleted -= BitmapDownloadCompleted; + bitmap.DownloadFailed -= BitmapDownloadFailed; + Brush.BeginAnimation(ImageBrush.OpacityProperty, new DoubleAnimation(1d, AnimationDuration)); } private void BitmapDownloadFailed(object sender, ExceptionEventArgs e) { - ((BitmapImage)sender).DownloadCompleted -= BitmapDownloadCompleted; - ((BitmapImage)sender).DownloadFailed -= BitmapDownloadFailed; + var bitmap = (BitmapSource)sender; + + bitmap.DownloadCompleted -= BitmapDownloadCompleted; + bitmap.DownloadFailed -= BitmapDownloadFailed; + Brush.ImageSource = null; } } diff --git a/MapControl/Tile.cs b/MapControl/Tile.cs index 14b39d6f..6e16e728 100644 --- a/MapControl/Tile.cs +++ b/MapControl/Tile.cs @@ -21,7 +21,7 @@ namespace MapControl Y = y; } - public bool HasImage { get; private set; } + public bool HasImageSource { get; private set; } public int XIndex { diff --git a/MapControl/TileImageLoader.Silverlight.WinRT.cs b/MapControl/TileImageLoader.Silverlight.WinRT.cs index 45958433..eaf50579 100644 --- a/MapControl/TileImageLoader.Silverlight.WinRT.cs +++ b/MapControl/TileImageLoader.Silverlight.WinRT.cs @@ -2,37 +2,50 @@ // Copyright © Clemens Fischer 2012-2013 // Licensed under the Microsoft Public License (Ms-PL) +using System; using System.Collections.Generic; -using System.Linq; +using System.Diagnostics; #if NETFX_CORE +using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media.Imaging; #else +using System.Windows.Media; using System.Windows.Media.Imaging; #endif namespace MapControl { /// - /// Loads map tile images by their URIs. + /// Loads map tile images. /// internal class TileImageLoader { - private readonly TileLayer tileLayer; - - internal TileImageLoader(TileLayer tileLayer) + internal void BeginGetTiles(TileLayer tileLayer, IEnumerable tiles) { - this.tileLayer = tileLayer; - } + var imageTileSource = tileLayer.TileSource as ImageTileSource; - internal void StartGetTiles(IEnumerable tiles) - { foreach (var tile in tiles) { - var uri = tileLayer.TileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel); - - if (uri != null) + try { - tile.SetImageSource(new BitmapImage(uri), tileLayer.AnimateTileOpacity); + ImageSource image; + + if (imageTileSource != null) + { + image = imageTileSource.LoadImage(tile.XIndex, tile.Y, tile.ZoomLevel); + } + else + { + var uri = tileLayer.TileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel); + + image = uri != null ? new BitmapImage(uri) : null; + } + + tile.SetImageSource(image, tileLayer.AnimateTileOpacity); + } + catch (Exception ex) + { + Debug.WriteLine("Creating tile image failed: {0}", ex.Message); } } } diff --git a/MapControl/TileImageLoader.WPF.cs b/MapControl/TileImageLoader.WPF.cs index 47550801..cfac5dec 100644 --- a/MapControl/TileImageLoader.WPF.cs +++ b/MapControl/TileImageLoader.WPF.cs @@ -9,7 +9,6 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Net; -using System.Net.Cache; using System.Runtime.Caching; using System.Threading; using System.Windows.Media; @@ -19,18 +18,14 @@ using System.Windows.Threading; namespace MapControl { /// - /// Loads map tile images by their URIs and optionally caches the images in an ObjectCache. + /// Loads map tile images and optionally caches them in a System.Runtime.Caching.ObjectCache. /// public class TileImageLoader { - private readonly TileLayer tileLayer; - private readonly ConcurrentQueue pendingTiles = new ConcurrentQueue(); - private int downloadThreadCount; - /// /// Default Name of an ObjectCache instance that is assigned to the Cache property. /// - public static readonly string DefaultCacheName = "TileCache"; + public const string DefaultCacheName = "TileCache"; /// /// Default value for the directory where an ObjectCache instance may save cached data. @@ -39,44 +34,48 @@ namespace MapControl Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "MapControl"); /// - /// The ObjectCache used to cache tile images. - /// The default is System.Runtime.Caching.MemoryCache.Default. + /// The ObjectCache used to cache tile images. The default is null. /// public static ObjectCache Cache { get; set; } /// - /// The time interval after which cached images expire. The default value is 30 days. - /// When an image is not retrieved from the cache during this interval it is considered - /// as expired and will be removed from the cache. If an image is retrieved from the - /// cache and the CacheUpdateAge time interval has expired, the image is downloaded - /// again and rewritten to the cache with a new expiration time. + /// The time interval after which cached images expire. The default value is seven days. + /// When an image is not retrieved from the cache during this interval it is considered as expired + /// and will be removed from the cache, provided that the cache implementation supports expiration. + /// If an image is retrieved from the cache and the CacheUpdateAge time interval has expired, + /// the image is downloaded again and rewritten to the cache with a new expiration time. /// public static TimeSpan CacheExpiration { get; set; } /// /// The time interval after which a cached image is updated and rewritten to the cache. - /// The default value is one day. This time interval should be shorter than the value + /// The default value is one day. This time interval should not be greater than the value /// of the CacheExpiration property. /// public static TimeSpan CacheUpdateAge { get; set; } static TileImageLoader() { - Cache = MemoryCache.Default; - CacheExpiration = TimeSpan.FromDays(30d); - CacheUpdateAge = TimeSpan.FromDays(1d); + CacheExpiration = TimeSpan.FromDays(7); + CacheUpdateAge = TimeSpan.FromDays(1); } - internal TileImageLoader(TileLayer tileLayer) - { - this.tileLayer = tileLayer; - } + private readonly ConcurrentQueue pendingTiles = new ConcurrentQueue(); + private int threadCount; - internal void StartGetTiles(IEnumerable tiles) + internal void BeginGetTiles(TileLayer tileLayer, IEnumerable tiles) { - if (tileLayer.TileSource != null && tiles.Any()) + if (tiles.Any()) { - ThreadPool.QueueUserWorkItem(GetTilesAsync, tiles.ToList()); + // get current TileLayer property values in UI thread + var dispatcher = tileLayer.Dispatcher; + var tileSource = tileLayer.TileSource; + var sourceName = tileLayer.SourceName; + var maxDownloads = tileLayer.MaxParallelDownloads; + var animateOpacity = tileLayer.AnimateTileOpacity; + + ThreadPool.QueueUserWorkItem(o => + GetTiles(tiles.ToList(), dispatcher, tileSource, sourceName, maxDownloads, animateOpacity)); } } @@ -86,79 +85,87 @@ namespace MapControl while (pendingTiles.TryDequeue(out tile)) ; // no Clear method } - private string GetCacheKey(Tile tile) + private void GetTiles(List tiles, Dispatcher dispatcher, TileSource tileSource, + string sourceName, int maxDownloads, bool animateOpacity) { - return string.Format("{0}/{1}/{2}/{3}", tileLayer.SourceName, tile.ZoomLevel, tile.XIndex, tile.Y); - } + var imageTileSource = tileSource as ImageTileSource; - private void GetTilesAsync(object tileList) - { - var tiles = (List)tileList; - var imageTileSource = tileLayer.TileSource as ImageTileSource; - - if (imageTileSource != null && !imageTileSource.CanLoadAsync) + if (imageTileSource != null) { - foreach (var tile in tiles) + if (!imageTileSource.CanLoadAsync) // call LoadImage in UI thread { - tileLayer.Dispatcher.BeginInvoke( - (Action)((t, ts) => t.SetImageSource(ts.LoadImage(t.XIndex, t.Y, t.ZoomLevel), tileLayer.AnimateTileOpacity)), - DispatcherPriority.Background, tile, imageTileSource); + foreach (var tile in tiles) + { + dispatcher.BeginInvoke( + (Action)((t, ts) => t.SetImageSource(LoadImage(ts, t), animateOpacity)), + DispatcherPriority.Background, tile, imageTileSource); + } + + return; } } - else + else if (!tileSource.UriFormat.StartsWith("file:")) // always load local image files asynchronously { - if (imageTileSource == null && Cache != null && - !string.IsNullOrWhiteSpace(tileLayer.SourceName) && - !tileLayer.TileSource.UriFormat.StartsWith("file://")) + if (Cache == null || string.IsNullOrWhiteSpace(sourceName)) { - var outdatedTiles = new List(tiles.Count); + // no caching here: use default asynchronous downloading and caching done by WPF foreach (var tile in tiles) { - var key = GetCacheKey(tile); - var buffer = Cache.Get(key) as byte[]; - var image = CreateImage(buffer); - - if (image != null) - { - tileLayer.Dispatcher.BeginInvoke( - (Action)((t, i) => t.SetImageSource(i, tileLayer.AnimateTileOpacity)), - DispatcherPriority.Background, tile, image); - - long creationTime = BitConverter.ToInt64(buffer, 0); - - if (DateTime.FromBinary(creationTime) + CacheUpdateAge < DateTime.UtcNow) - { - // update outdated cache - outdatedTiles.Add(tile); - } - } - else - { - pendingTiles.Enqueue(tile); - } + dispatcher.BeginInvoke( + (Action)((t, ts) => t.SetImageSource(CreateImage(ts, t), animateOpacity)), + DispatcherPriority.Background, tile, tileSource); } - tiles = outdatedTiles; // enqueue outdated tiles at last + return; } + var outdatedTiles = new List(tiles.Count); + foreach (var tile in tiles) { - pendingTiles.Enqueue(tile); + var key = GetCacheKey(sourceName, tile); + var buffer = Cache.Get(key) as byte[]; + var image = CreateImage(buffer); + + if (image != null) + { + dispatcher.BeginInvoke( + (Action)((t, i) => t.SetImageSource(i, animateOpacity)), + DispatcherPriority.Background, tile, image); + + long creationTime = BitConverter.ToInt64(buffer, 0); + + if (DateTime.FromBinary(creationTime) + CacheUpdateAge < DateTime.UtcNow) + { + outdatedTiles.Add(tile); // update outdated cache + } + } + else + { + pendingTiles.Enqueue(tile); // not yet cached + } } - while (downloadThreadCount < Math.Min(pendingTiles.Count, tileLayer.MaxParallelDownloads)) - { - Interlocked.Increment(ref downloadThreadCount); + tiles = outdatedTiles; // enqueue outdated tiles at last + } - ThreadPool.QueueUserWorkItem(LoadTiles); - } + foreach (var tile in tiles) + { + pendingTiles.Enqueue(tile); + } + + while (threadCount < Math.Min(pendingTiles.Count, maxDownloads)) + { + Interlocked.Increment(ref threadCount); + + ThreadPool.QueueUserWorkItem(o => LoadPendingTiles(dispatcher, tileSource, sourceName, animateOpacity)); } } - private void LoadTiles(object o) + private void LoadPendingTiles(Dispatcher dispatcher, TileSource tileSource, string sourceName, bool animateOpacity) { - var imageTileSource = tileLayer.TileSource as ImageTileSource; + var imageTileSource = tileSource as ImageTileSource; Tile tile; while (pendingTiles.TryDequeue(out tile)) @@ -168,65 +175,96 @@ namespace MapControl if (imageTileSource != null) { - try - { - image = imageTileSource.LoadImage(tile.XIndex, tile.Y, tile.ZoomLevel); - } - catch (Exception ex) - { - Trace.TraceWarning("Loading tile image failed: {0}", ex.Message); - } + image = LoadImage(imageTileSource, tile); } else { - var uri = tileLayer.TileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel); + var uri = tileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel); if (uri != null) { - if (uri.Scheme == "http") + if (uri.Scheme == "file") + { + image = CreateImage(uri.AbsolutePath); + } + else { buffer = DownloadImage(uri); image = CreateImage(buffer); } - else - { - image = CreateImage(uri); - } } } - if (image != null) + if (image != null || !tile.HasImageSource) // do not set null if tile already has an image (from cache) { - tileLayer.Dispatcher.BeginInvoke( - (Action)((t, i) => t.SetImageSource(i, tileLayer.AnimateTileOpacity)), + dispatcher.BeginInvoke( + (Action)((t, i) => t.SetImageSource(i, animateOpacity)), DispatcherPriority.Background, tile, image); + } - if (buffer != null && Cache != null) - { - Cache.Set(GetCacheKey(tile), buffer, new CacheItemPolicy { SlidingExpiration = CacheExpiration }); - } + if (buffer != null && image != null) + { + Cache.Set(GetCacheKey(sourceName, tile), buffer, new CacheItemPolicy { SlidingExpiration = CacheExpiration }); } } - Interlocked.Decrement(ref downloadThreadCount); + Interlocked.Decrement(ref threadCount); } - private static ImageSource CreateImage(Uri uri) + private static string GetCacheKey(string sourceName, Tile tile) { - var image = new BitmapImage(); + return string.Format("{0}/{1}/{2}/{3}", sourceName, tile.ZoomLevel, tile.XIndex, tile.Y); + } + + private static ImageSource LoadImage(ImageTileSource tileSource, Tile tile) + { + ImageSource image = null; try { - image.BeginInit(); - image.CacheOption = BitmapCacheOption.OnLoad; - image.UriSource = uri; - image.EndInit(); - image.Freeze(); + image = tileSource.LoadImage(tile.XIndex, tile.Y, tile.ZoomLevel); + } + catch (Exception ex) + { + Trace.TraceWarning("Loading tile image failed: {0}", ex.Message); + } + + return image; + } + + private static ImageSource CreateImage(TileSource tileSource, Tile tile) + { + ImageSource image = null; + + try + { + image = BitmapFrame.Create(tileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel)); } catch (Exception ex) { Trace.TraceWarning("Creating tile image failed: {0}", ex.Message); - image = null; + } + + return image; + } + + private static ImageSource CreateImage(string path) + { + ImageSource image = null; + + if (File.Exists(path)) + { + try + { + using (var stream = new FileStream(path, FileMode.Open)) + { + image = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); + } + } + catch (Exception ex) + { + Trace.TraceWarning("Creating tile image failed: {0}", ex.Message); + } } return image; @@ -234,7 +272,7 @@ namespace MapControl private static ImageSource CreateImage(byte[] buffer) { - BitmapImage image = null; + ImageSource image = null; if (buffer != null && buffer.Length > sizeof(long)) { @@ -242,18 +280,12 @@ namespace MapControl { using (var stream = new MemoryStream(buffer, sizeof(long), buffer.Length - sizeof(long), false)) { - image = new BitmapImage(); - image.BeginInit(); - image.CacheOption = BitmapCacheOption.OnLoad; - image.StreamSource = stream; - image.EndInit(); - image.Freeze(); + image = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); } } catch (Exception ex) { Trace.TraceWarning("Creating tile image failed: {0}", ex.Message); - image = null; } } @@ -269,11 +301,6 @@ namespace MapControl var request = (HttpWebRequest)WebRequest.Create(uri); request.UserAgent = "XAML Map Control"; - if (Cache != null) - { - request.CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore); - } - using (var response = (HttpWebResponse)request.GetResponse()) using (var responseStream = response.GetResponseStream()) { @@ -289,8 +316,6 @@ namespace MapControl buffer = length > 0 ? memoryStream.GetBuffer() : memoryStream.ToArray(); } } - - //Trace.TraceInformation("Downloaded {0}", uri); } catch (WebException ex) { diff --git a/MapControl/TileLayer.cs b/MapControl/TileLayer.cs index e7049644..615c8bac 100644 --- a/MapControl/TileLayer.cs +++ b/MapControl/TileLayer.cs @@ -40,7 +40,7 @@ namespace MapControl } private readonly MatrixTransform transform = new MatrixTransform(); - private readonly TileImageLoader tileImageLoader; + private readonly TileImageLoader tileImageLoader = new TileImageLoader(); private List tiles = new List(); private string description = string.Empty; private Int32Rect grid; @@ -48,7 +48,6 @@ namespace MapControl public TileLayer() { - tileImageLoader = new TileImageLoader(this); MinZoomLevel = 1; MaxZoomLevel = 18; MaxParallelDownloads = 8; @@ -96,7 +95,7 @@ namespace MapControl { SelectTiles(); RenderTiles(); - tileImageLoader.StartGetTiles(tiles.Where(t => !t.HasImage)); + tileImageLoader.BeginGetTiles(this, tiles.Where(t => !t.HasImageSource)); } } diff --git a/MapControl/TileSource.cs b/MapControl/TileSource.cs index 365a6838..be2f4f21 100644 --- a/MapControl/TileSource.cs +++ b/MapControl/TileSource.cs @@ -102,7 +102,7 @@ namespace MapControl private Uri GetOpenStreetMapUri(int x, int y, int zoomLevel) { - var hostIndex = (x + y + zoomLevel) % 3; + var hostIndex = (x + y) % 3; return new Uri(UriFormat. Replace("{c}", "abc".Substring(hostIndex, 1)). @@ -113,7 +113,7 @@ namespace MapControl private Uri GetGoogleMapsUri(int x, int y, int zoomLevel) { - var hostIndex = (x + y + zoomLevel) % 4; + var hostIndex = (x + y) % 4; return new Uri(UriFormat. Replace("{i}", hostIndex.ToString()). @@ -124,7 +124,7 @@ namespace MapControl private Uri GetMapQuestUri(int x, int y, int zoomLevel) { - var hostIndex = (x + y + zoomLevel) % 4 + 1; + var hostIndex = (x + y) % 4 + 1; return new Uri(UriFormat. Replace("{n}", hostIndex.ToString()). diff --git a/MapControl/WinRT/MapControl.WinRT.csproj b/MapControl/WinRT/MapControl.WinRT.csproj index eba43c73..8f42a682 100644 --- a/MapControl/WinRT/MapControl.WinRT.csproj +++ b/MapControl/WinRT/MapControl.WinRT.csproj @@ -39,8 +39,8 @@ AnimationEx.WinRT.cs - - Freezable.cs + + ImageTileSource.cs Int32Rect.cs diff --git a/MapControl/WinRT/Properties/AssemblyInfo.cs b/MapControl/WinRT/Properties/AssemblyInfo.cs index c1ad64c9..e3f48ad2 100644 --- a/MapControl/WinRT/Properties/AssemblyInfo.cs +++ b/MapControl/WinRT/Properties/AssemblyInfo.cs @@ -8,8 +8,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © Clemens Fischer 2012-2013")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("1.7.0")] -[assembly: AssemblyFileVersion("1.7.0")] +[assembly: AssemblyVersion("1.8.0")] +[assembly: AssemblyFileVersion("1.8.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/SampleApps/SilverlightApplication.Web/Properties/AssemblyInfo.cs b/SampleApps/SilverlightApplication.Web/Properties/AssemblyInfo.cs index bb72502c..4d0e3823 100644 --- a/SampleApps/SilverlightApplication.Web/Properties/AssemblyInfo.cs +++ b/SampleApps/SilverlightApplication.Web/Properties/AssemblyInfo.cs @@ -8,8 +8,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © Clemens Fischer 2012-2013")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("1.7.0")] -[assembly: AssemblyFileVersion("1.7.0")] +[assembly: AssemblyVersion("1.8.0")] +[assembly: AssemblyFileVersion("1.8.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/SampleApps/SilverlightApplication/Properties/AssemblyInfo.cs b/SampleApps/SilverlightApplication/Properties/AssemblyInfo.cs index bd39e599..56c8f888 100644 --- a/SampleApps/SilverlightApplication/Properties/AssemblyInfo.cs +++ b/SampleApps/SilverlightApplication/Properties/AssemblyInfo.cs @@ -8,8 +8,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © Clemens Fischer 2012-2013")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("1.7.0")] -[assembly: AssemblyFileVersion("1.7.0")] +[assembly: AssemblyVersion("1.8.0")] +[assembly: AssemblyFileVersion("1.8.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/SampleApps/StoreApplication/Properties/AssemblyInfo.cs b/SampleApps/StoreApplication/Properties/AssemblyInfo.cs index ac38d10c..9f0d38a2 100644 --- a/SampleApps/StoreApplication/Properties/AssemblyInfo.cs +++ b/SampleApps/StoreApplication/Properties/AssemblyInfo.cs @@ -8,8 +8,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © Clemens Fischer 2012-2013")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("1.7.0")] -[assembly: AssemblyFileVersion("1.7.0")] +[assembly: AssemblyVersion("1.8.0")] +[assembly: AssemblyFileVersion("1.8.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/SampleApps/SurfaceApplication/Properties/AssemblyInfo.cs b/SampleApps/SurfaceApplication/Properties/AssemblyInfo.cs index 4939301b..96b900ec 100644 --- a/SampleApps/SurfaceApplication/Properties/AssemblyInfo.cs +++ b/SampleApps/SurfaceApplication/Properties/AssemblyInfo.cs @@ -8,8 +8,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © Clemens Fischer 2012-2013")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("1.7.0")] -[assembly: AssemblyFileVersion("1.7.0")] +[assembly: AssemblyVersion("1.8.0")] +[assembly: AssemblyFileVersion("1.8.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/SampleApps/WpfApplication/MainWindow.xaml.cs b/SampleApps/WpfApplication/MainWindow.xaml.cs index 42b321ea..8d0fb6da 100644 --- a/SampleApps/WpfApplication/MainWindow.xaml.cs +++ b/SampleApps/WpfApplication/MainWindow.xaml.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Runtime.Caching; using System.Windows; using System.Windows.Controls; using System.Windows.Input; @@ -22,6 +23,9 @@ namespace WpfApplication { switch (Properties.Settings.Default.TileCache) { + case "MemoryCache": + TileImageLoader.Cache = MemoryCache.Default; + break; case "FileDbCache": TileImageLoader.Cache = new FileDbCache(TileImageLoader.DefaultCacheName, TileImageLoader.DefaultCacheDirectory); break; diff --git a/SampleApps/WpfApplication/Properties/AssemblyInfo.cs b/SampleApps/WpfApplication/Properties/AssemblyInfo.cs index e0bcf062..216b6127 100644 --- a/SampleApps/WpfApplication/Properties/AssemblyInfo.cs +++ b/SampleApps/WpfApplication/Properties/AssemblyInfo.cs @@ -8,8 +8,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © Clemens Fischer 2012-2013")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("1.7.0")] -[assembly: AssemblyFileVersion("1.7.0")] +[assembly: AssemblyVersion("1.8.0")] +[assembly: AssemblyFileVersion("1.8.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)]