Version 1.8.0: Fixed MapImage, added ImageTileSource to Silverlight and WinRT versions, modified TileImageLoader.

This commit is contained in:
ClemensF 2013-11-12 21:14:53 +01:00
parent 8eedb82a9d
commit 9f4ab0f3e3
29 changed files with 331 additions and 285 deletions

View file

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

View file

@ -16,12 +16,22 @@ namespace Caching
{
/// <summary>
/// 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.
/// </summary>
public class ImageFileCache : ObjectCache
{
private static readonly Tuple<string, byte[]>[] imageFileTypes = new Tuple<string, byte[]>[]
{
new Tuple<string, byte[]>(".png", new byte[] { 0x89, 0x50, 0x4E, 0x47, 0xD, 0xA, 0x1A, 0xA }),
new Tuple<string, byte[]>(".jpg", new byte[] { 0xFF, 0xD8, 0xFF, 0xE0, 0, 0x10, 0x4A, 0x46, 0x49, 0x46, 0 }),
new Tuple<string, byte[]>(".bmp", new byte[] { 0x42, 0x4D }),
new Tuple<string, byte[]>(".gif", new byte[] { 0x47, 0x49, 0x46 }),
new Tuple<string, byte[]>(".tif", new byte[] { 0x49, 0x49, 42, 0 }),
new Tuple<string, byte[]>(".tif", new byte[] { 0x4D, 0x4D, 0, 42 }),
new Tuple<string, byte[]>(".wdp", new byte[] { 0x49, 0x49, 0xBC }),
};
private static readonly FileSystemAccessRule fullControlRule = new FileSystemAccessRule(
new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null),
FileSystemRights.FullControl, AccessControlType.Allow);
@ -169,18 +179,14 @@ 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);
if (extension != null)
{
var path = GetPath(key) + extension;
var path = GetPath(key) + GetFileExtension(buffer);
try
{
@ -200,7 +206,6 @@ namespace Caching
Trace.TraceWarning("ImageFileCache: Writing file {0} failed: {1}", path, ex.Message);
}
}
}
public override void Set(CacheItem item, CacheItemPolicy policy)
{
@ -274,42 +279,24 @@ namespace Caching
return null;
}
private static readonly Tuple<string, byte[]>[] fileTypes = new Tuple<string, byte[]>[]
{
new Tuple<string, byte[]>(".png", new byte[] { 0x89, 0x50, 0x4E, 0x47, 0xD, 0xA, 0x1A, 0xA }),
new Tuple<string, byte[]>(".jpg", new byte[] { 0xFF, 0xD8, 0xFF, 0xE0, 0, 0x10, 0x4A, 0x46, 0x49, 0x46, 0 }),
new Tuple<string, byte[]>(".bmp", new byte[] { 0x42, 0x4D }),
new Tuple<string, byte[]>(".gif", new byte[] { 0x47, 0x49, 0x46 }),
new Tuple<string, byte[]>(".tif", new byte[] { 0x49, 0x49, 42, 0 }),
new Tuple<string, byte[]>(".tif", new byte[] { 0x4D, 0x4D, 0, 42 }),
new Tuple<string, byte[]>(".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)
{
Func<Tuple<string, byte[]>, bool> match =
t =>
var fileType = imageFileTypes.FirstOrDefault(t =>
{
int i = 0;
if (t.Item2.Length + 8 <= buffer.Length)
if (t.Item2.Length <= buffer.Length - 8)
{
while (i < t.Item2.Length && t.Item2[i] == buffer[i + 8])
{
i++;
}
}
return i == t.Item2.Length;
};
});
extension = fileTypes.Where(match).Select(t => t.Item1).FirstOrDefault();
}
return extension;
return fileType != null ? fileType.Item1 : ".bin";
}
}
}

View file

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

View file

@ -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
{
/// <summary>
/// Provides WPF compatibility.
/// </summary>
public static void Freeze(this DependencyObject obj)
{
}
}
}

View file

@ -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; }
}
}
}

View file

@ -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
{
/// <summary>
/// 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.
/// </summary>
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;
}
}
}

View file

@ -70,7 +70,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="AnimationEx.Silverlight.cs" />
<Compile Include="Freezable.cs" />
<Compile Include="ImageTileSource.cs" />
<Compile Include="Int32Rect.cs" />
<Compile Include="Location.cs" />
<Compile Include="LocationCollection.cs" />

View file

@ -53,6 +53,7 @@
<ItemGroup>
<Compile Include="GlyphRunText.cs" />
<Compile Include="ImageTileSource.cs" />
<Compile Include="ImageTileSource.WPF.cs" />
<Compile Include="Location.cs" />
<Compile Include="LocationCollection.cs" />
<Compile Include="LocationCollectionConverter.cs" />
@ -63,6 +64,7 @@
<Compile Include="MapGraticule.cs" />
<Compile Include="MapGraticule.WPF.cs" />
<Compile Include="MapImage.cs" />
<Compile Include="MapImage.WPF.cs" />
<Compile Include="MapImageLayer.cs" />
<Compile Include="MapItem.WPF.cs" />
<Compile Include="MapItemsControl.WPF.cs" />
@ -74,6 +76,7 @@
<Compile Include="MapPolyline.cs" />
<Compile Include="MapPolyline.WPF.cs" />
<Compile Include="MapRectangle.cs" />
<Compile Include="MapRectangle.WPF.cs" />
<Compile Include="MapScale.cs" />
<Compile Include="MapTransform.cs" />
<Compile Include="MercatorTransform.cs" />

View file

@ -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();
}
}
}

View file

@ -15,18 +15,13 @@ namespace MapControl
/// <summary>
/// Fills a rectangular area with an ImageBrush from the Source property.
/// </summary>
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;
}
}

View file

@ -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)
{

View file

@ -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();
}
}
}

View file

@ -16,7 +16,7 @@ namespace MapControl
/// <summary>
/// Fills a rectangular area defined by South, North, West and East with a Brush.
/// </summary>
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()));

View file

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

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -21,7 +21,7 @@ namespace MapControl
Y = y;
}
public bool HasImage { get; private set; }
public bool HasImageSource { get; private set; }
public int XIndex
{

View file

@ -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
{
/// <summary>
/// Loads map tile images by their URIs.
/// Loads map tile images.
/// </summary>
internal class TileImageLoader
{
private readonly TileLayer tileLayer;
internal TileImageLoader(TileLayer tileLayer)
internal void BeginGetTiles(TileLayer tileLayer, IEnumerable<Tile> tiles)
{
this.tileLayer = tileLayer;
}
var imageTileSource = tileLayer.TileSource as ImageTileSource;
internal void StartGetTiles(IEnumerable<Tile> tiles)
{
foreach (var tile in tiles)
{
try
{
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);
if (uri != null)
image = uri != null ? new BitmapImage(uri) : null;
}
tile.SetImageSource(image, tileLayer.AnimateTileOpacity);
}
catch (Exception ex)
{
tile.SetImageSource(new BitmapImage(uri), tileLayer.AnimateTileOpacity);
Debug.WriteLine("Creating tile image failed: {0}", ex.Message);
}
}
}

View file

@ -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
{
/// <summary>
/// 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.
/// </summary>
public class TileImageLoader
{
private readonly TileLayer tileLayer;
private readonly ConcurrentQueue<Tile> pendingTiles = new ConcurrentQueue<Tile>();
private int downloadThreadCount;
/// <summary>
/// Default Name of an ObjectCache instance that is assigned to the Cache property.
/// </summary>
public static readonly string DefaultCacheName = "TileCache";
public const string DefaultCacheName = "TileCache";
/// <summary>
/// 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");
/// <summary>
/// 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.
/// </summary>
public static ObjectCache Cache { get; set; }
/// <summary>
/// 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.
/// </summary>
public static TimeSpan CacheExpiration { get; set; }
/// <summary>
/// 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.
/// </summary>
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<Tile> pendingTiles = new ConcurrentQueue<Tile>();
private int threadCount;
internal void StartGetTiles(IEnumerable<Tile> tiles)
internal void BeginGetTiles(TileLayer tileLayer, IEnumerable<Tile> 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,56 +85,65 @@ namespace MapControl
while (pendingTiles.TryDequeue(out tile)) ; // no Clear method
}
private string GetCacheKey(Tile tile)
private void GetTiles(List<Tile> 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)
if (imageTileSource != null)
{
var tiles = (List<Tile>)tileList;
var imageTileSource = tileLayer.TileSource as ImageTileSource;
if (imageTileSource != null && !imageTileSource.CanLoadAsync)
if (!imageTileSource.CanLoadAsync) // call LoadImage in UI thread
{
foreach (var tile in tiles)
{
tileLayer.Dispatcher.BeginInvoke(
(Action<Tile, ImageTileSource>)((t, ts) => t.SetImageSource(ts.LoadImage(t.XIndex, t.Y, t.ZoomLevel), tileLayer.AnimateTileOpacity)),
dispatcher.BeginInvoke(
(Action<Tile, ImageTileSource>)((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))
{
// no caching here: use default asynchronous downloading and caching done by WPF
foreach (var tile in tiles)
{
dispatcher.BeginInvoke(
(Action<Tile, TileSource>)((t, ts) => t.SetImageSource(CreateImage(ts, t), animateOpacity)),
DispatcherPriority.Background, tile, tileSource);
}
return;
}
var outdatedTiles = new List<Tile>(tiles.Count);
foreach (var tile in tiles)
{
var key = GetCacheKey(tile);
var key = GetCacheKey(sourceName, tile);
var buffer = Cache.Get(key) as byte[];
var image = CreateImage(buffer);
if (image != null)
{
tileLayer.Dispatcher.BeginInvoke(
(Action<Tile, ImageSource>)((t, i) => t.SetImageSource(i, tileLayer.AnimateTileOpacity)),
dispatcher.BeginInvoke(
(Action<Tile, ImageSource>)((t, i) => t.SetImageSource(i, animateOpacity)),
DispatcherPriority.Background, tile, image);
long creationTime = BitConverter.ToInt64(buffer, 0);
if (DateTime.FromBinary(creationTime) + CacheUpdateAge < DateTime.UtcNow)
{
// update outdated cache
outdatedTiles.Add(tile);
outdatedTiles.Add(tile); // update outdated cache
}
}
else
{
pendingTiles.Enqueue(tile);
pendingTiles.Enqueue(tile); // not yet cached
}
}
@ -147,18 +155,17 @@ namespace MapControl
pendingTiles.Enqueue(tile);
}
while (downloadThreadCount < Math.Min(pendingTiles.Count, tileLayer.MaxParallelDownloads))
while (threadCount < Math.Min(pendingTiles.Count, maxDownloads))
{
Interlocked.Increment(ref downloadThreadCount);
Interlocked.Increment(ref threadCount);
ThreadPool.QueueUserWorkItem(LoadTiles);
}
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)
{
image = LoadImage(imageTileSource, tile);
}
else
{
var uri = tileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel);
if (uri != null)
{
if (uri.Scheme == "file")
{
image = CreateImage(uri.AbsolutePath);
}
else
{
buffer = DownloadImage(uri);
image = CreateImage(buffer);
}
}
}
if (image != null || !tile.HasImageSource) // do not set null if tile already has an image (from cache)
{
dispatcher.BeginInvoke(
(Action<Tile, ImageSource>)((t, i) => t.SetImageSource(i, animateOpacity)),
DispatcherPriority.Background, tile, image);
}
if (buffer != null && image != null)
{
Cache.Set(GetCacheKey(sourceName, tile), buffer, new CacheItemPolicy { SlidingExpiration = CacheExpiration });
}
}
Interlocked.Decrement(ref threadCount);
}
private static string GetCacheKey(string sourceName, Tile tile)
{
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 = imageTileSource.LoadImage(tile.XIndex, tile.Y, tile.ZoomLevel);
image = tileSource.LoadImage(tile.XIndex, tile.Y, tile.ZoomLevel);
}
catch (Exception ex)
{
Trace.TraceWarning("Loading tile image failed: {0}", ex.Message);
}
}
else
{
var uri = tileLayer.TileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel);
if (uri != null)
{
if (uri.Scheme == "http")
{
buffer = DownloadImage(uri);
image = CreateImage(buffer);
}
else
{
image = CreateImage(uri);
}
}
return image;
}
if (image != null)
private static ImageSource CreateImage(TileSource tileSource, Tile tile)
{
tileLayer.Dispatcher.BeginInvoke(
(Action<Tile, ImageSource>)((t, i) => t.SetImageSource(i, tileLayer.AnimateTileOpacity)),
DispatcherPriority.Background, tile, image);
if (buffer != null && Cache != null)
{
Cache.Set(GetCacheKey(tile), buffer, new CacheItemPolicy { SlidingExpiration = CacheExpiration });
}
}
}
Interlocked.Decrement(ref downloadThreadCount);
}
private static ImageSource CreateImage(Uri uri)
{
var image = new BitmapImage();
ImageSource image = null;
try
{
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.UriSource = uri;
image.EndInit();
image.Freeze();
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)
{

View file

@ -40,7 +40,7 @@ namespace MapControl
}
private readonly MatrixTransform transform = new MatrixTransform();
private readonly TileImageLoader tileImageLoader;
private readonly TileImageLoader tileImageLoader = new TileImageLoader();
private List<Tile> tiles = new List<Tile>();
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));
}
}

View file

@ -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()).

View file

@ -39,8 +39,8 @@
<Compile Include="..\AnimationEx.WinRT.cs">
<Link>AnimationEx.WinRT.cs</Link>
</Compile>
<Compile Include="..\Freezable.cs">
<Link>Freezable.cs</Link>
<Compile Include="..\ImageTileSource.cs">
<Link>ImageTileSource.cs</Link>
</Compile>
<Compile Include="..\Int32Rect.cs">
<Link>Int32Rect.cs</Link>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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;

View file

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