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)]