diff --git a/MapControl/MapControl.csproj b/MapControl/MapControl.csproj index d8a3df6c..dce34dd9 100644 --- a/MapControl/MapControl.csproj +++ b/MapControl/MapControl.csproj @@ -12,7 +12,8 @@ MapControl v4.0 512 - Client + + true @@ -22,6 +23,7 @@ TRACE;DEBUG prompt 4 + false none @@ -31,13 +33,14 @@ prompt 4 + false - + diff --git a/MapControl/TileImageLoader.cs b/MapControl/TileImageLoader.cs index 433074d4..f30958f2 100644 --- a/MapControl/TileImageLoader.cs +++ b/MapControl/TileImageLoader.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; +using System.Runtime.Caching; using System.Threading; using System.Windows.Media; using System.Windows.Media.Imaging; @@ -21,18 +22,20 @@ namespace MapControl public class TileImageLoader : DispatcherObject { public static string TileCacheFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "MapControl Cache"); - public static TimeSpan TileCacheExpiryAge = TimeSpan.FromDays(1); + public static TimeSpan TileCacheExpiryAge = TimeSpan.FromDays(1d); + private readonly TileLayer tileLayer; private readonly Queue pendingTiles = new Queue(); private int numDownloads; - internal int MaxDownloads; - internal string TileLayerName; - internal TileSource TileSource; + public TileImageLoader(TileLayer tileLayer) + { + this.tileLayer = tileLayer; + } private bool IsCached { - get { return !string.IsNullOrEmpty(TileCacheFolder) && !string.IsNullOrEmpty(TileLayerName); } + get { return tileLayer.IsCached && !string.IsNullOrEmpty(TileCacheFolder); } } internal void StartDownloadTiles(ICollection tiles) @@ -51,39 +54,41 @@ namespace MapControl private void StartDownloadTilesAsync(object newTilesList) { List newTiles = (List)newTilesList; + List expiredTiles = new List(newTiles.Count); lock (pendingTiles) { - if (IsCached) + newTiles.ForEach(tile => { - List expiredTiles = new List(newTiles.Count); + ImageSource image = GetMemoryCachedImage(tile); - newTiles.ForEach(tile => + if (image == null && IsCached) { - bool cacheExpired; - ImageSource image = GetCachedImage(tile, out cacheExpired); + bool fileCacheExpired; + image = GetFileCachedImage(tile, out fileCacheExpired); if (image != null) { - Dispatcher.BeginInvoke((Action)(() => tile.Image = image)); + SetMemoryCachedImage(tile, image); - if (cacheExpired) + if (fileCacheExpired) { expiredTiles.Add(tile); // enqueue later } } - else - { - pendingTiles.Enqueue(tile); - } - }); + } - expiredTiles.ForEach(tile => pendingTiles.Enqueue(tile)); - } - else - { - newTiles.ForEach(tile => pendingTiles.Enqueue(tile)); - } + if (image != null) + { + Dispatcher.BeginInvoke((Action)(() => tile.Image = image)); + } + else + { + pendingTiles.Enqueue(tile); + } + }); + + expiredTiles.ForEach(tile => pendingTiles.Enqueue(tile)); DownloadNextTiles(null); } @@ -91,10 +96,10 @@ namespace MapControl private void DownloadNextTiles(object o) { - while (pendingTiles.Count > 0 && numDownloads < MaxDownloads) + while (pendingTiles.Count > 0 && numDownloads < tileLayer.MaxDownloads) { Tile tile = pendingTiles.Dequeue(); - tile.Uri = TileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel); + tile.Uri = tileLayer.TileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel); numDownloads++; ThreadPool.QueueUserWorkItem(DownloadTileAsync, tile); @@ -108,53 +113,72 @@ namespace MapControl if (image != null) { + SetMemoryCachedImage(tile, image); + Dispatcher.BeginInvoke((Action)(() => tile.Image = image)); } lock (pendingTiles) { - tile.Uri = null; numDownloads--; DownloadNextTiles(null); } } - private ImageSource GetCachedImage(Tile tile, out bool expired) + private string MemoryCacheKey(Tile tile) { - string tileDir = TileDirectory(tile); + return string.Format("{0}/{1}/{2}/{3}", tileLayer.Name, tile.ZoomLevel, tile.XIndex, tile.Y); + } + + private string CacheFilePath(Tile tile) + { + return string.Format("{0}.{1}", + Path.Combine(TileCacheFolder, tileLayer.Name, tile.ZoomLevel.ToString(), tile.XIndex.ToString(), tile.Y.ToString()), + tileLayer.ImageType); + } + + private ImageSource GetMemoryCachedImage(Tile tile) + { + string key = MemoryCacheKey(tile); + ImageSource image = MemoryCache.Default.Get(key) as ImageSource; + + if (image != null) + { + TraceInformation("{0} - Memory Cached", key); + } + + return image; + } + + private void SetMemoryCachedImage(Tile tile, ImageSource image) + { + MemoryCache.Default.Set(MemoryCacheKey(tile), image, + new CacheItemPolicy { SlidingExpiration = TimeSpan.FromMinutes(10d) }); + } + + private ImageSource GetFileCachedImage(Tile tile, out bool expired) + { + string path = CacheFilePath(tile); ImageSource image = null; expired = false; - try + if (File.Exists(path)) { - if (Directory.Exists(tileDir)) + try { - string tilePath = Directory.GetFiles(tileDir, string.Format("{0}.*", tile.Y)).FirstOrDefault(); - - if (tilePath != null) + using (Stream fileStream = File.OpenRead(path)) { - try - { - using (Stream fileStream = File.OpenRead(tilePath)) - { - image = BitmapFrame.Create(fileStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); - } - - expired = File.GetLastWriteTime(tilePath) + TileCacheExpiryAge <= DateTime.Now; - - TraceInformation(expired ? "{0} - Cache Expired" : "{0} - Cached", tilePath); - } - catch (Exception exc) - { - TraceWarning("{0} - {1}", tilePath, exc.Message); - File.Delete(tilePath); - } + image = BitmapFrame.Create(fileStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); } + + expired = File.GetLastWriteTime(path) + TileCacheExpiryAge <= DateTime.Now; + TraceInformation(expired ? "{0} - File Cache Expired" : "{0} - File Cached", path); + } + catch (Exception exc) + { + TraceWarning("{0} - {1}", path, exc.Message); + File.Delete(path); } - } - catch (Exception exc) - { - TraceWarning("{0} - {1}", tileDir, exc.Message); } return image; @@ -180,17 +204,14 @@ namespace MapControl { responseStream.CopyTo(memoryStream); memoryStream.Position = 0; + image = BitmapFrame.Create(memoryStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); - BitmapDecoder decoder = BitmapDecoder.Create(memoryStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); - image = decoder.Frames[0]; - - string tilePath; - - if (IsCached && (tilePath = TilePath(tile, decoder)) != null) + if (IsCached) { - Directory.CreateDirectory(Path.GetDirectoryName(tilePath)); + string path = CacheFilePath(tile); + Directory.CreateDirectory(Path.GetDirectoryName(path)); - using (Stream fileStream = File.OpenWrite(tilePath)) + using (Stream fileStream = File.OpenWrite(path)) { memoryStream.Position = 0; memoryStream.CopyTo(fileStream); @@ -221,43 +242,6 @@ namespace MapControl return image; } - private string TileDirectory(Tile tile) - { - return Path.Combine(TileCacheFolder, TileLayerName, tile.ZoomLevel.ToString(), tile.XIndex.ToString()); - } - - private string TilePath(Tile tile, BitmapDecoder decoder) - { - string extension; - - if (decoder is PngBitmapDecoder) - { - extension = "png"; - } - else if (decoder is JpegBitmapDecoder) - { - extension = "jpg"; - } - else if (decoder is BmpBitmapDecoder) - { - extension = "bmp"; - } - else if (decoder is GifBitmapDecoder) - { - extension = "gif"; - } - else if (decoder is TiffBitmapDecoder) - { - extension = "tif"; - } - else - { - return null; - } - - return Path.Combine(TileDirectory(tile), string.Format("{0}.{1}", tile.Y, extension)); - } - private static void TraceWarning(string format, params object[] args) { System.Diagnostics.Trace.TraceWarning("[{0:00}] {1}", Thread.CurrentThread.ManagedThreadId, string.Format(format, args)); @@ -265,7 +249,7 @@ namespace MapControl private static void TraceInformation(string format, params object[] args) { - System.Diagnostics.Trace.TraceInformation("[{0:00}] {1}", Thread.CurrentThread.ManagedThreadId, string.Format(format, args)); + //System.Diagnostics.Trace.TraceInformation("[{0:00}] {1}", Thread.CurrentThread.ManagedThreadId, string.Format(format, args)); } } } diff --git a/MapControl/TileLayer.cs b/MapControl/TileLayer.cs index 69a58ec2..aefa105a 100644 --- a/MapControl/TileLayer.cs +++ b/MapControl/TileLayer.cs @@ -18,58 +18,32 @@ namespace MapControl [ContentProperty("TileSource")] public class TileLayer : DrawingVisual { - private readonly TileImageLoader tileImageLoader = new TileImageLoader(); + private readonly TileImageLoader tileImageLoader; private readonly List tiles = new List(); - private bool isCached = false; - private string name = string.Empty; private string description = string.Empty; private Int32Rect grid; private int zoomLevel; public TileLayer() { + tileImageLoader = new TileImageLoader(this); VisualEdgeMode = EdgeMode.Aliased; VisualTransform = new MatrixTransform(); + Name = string.Empty; + ImageType = "png"; MinZoomLevel = 1; MaxZoomLevel = 18; MaxDownloads = 8; } - public bool HasDarkBackground { get; set; } + public string Name { get; set; } + public string ImageType { get; set; } + public TileSource TileSource { get; set; } public int MinZoomLevel { get; set; } public int MaxZoomLevel { get; set; } - - public int MaxDownloads - { - get { return tileImageLoader.MaxDownloads; } - set { tileImageLoader.MaxDownloads = value; } - } - - public TileSource TileSource - { - get { return tileImageLoader.TileSource; } - set { tileImageLoader.TileSource = value; } - } - - public bool IsCached - { - get { return isCached; } - set - { - isCached = value; - tileImageLoader.TileLayerName = isCached ? name : null; - } - } - - public string Name - { - get { return name; } - set - { - name = value; - tileImageLoader.TileLayerName = isCached ? name : null; - } - } + public int MaxDownloads { get; set; } + public bool IsCached { get; set; } + public bool HasDarkBackground { get; set; } public string Description { @@ -150,7 +124,7 @@ namespace MapControl tiles.Sort((t1, t2) => t1.ZoomLevel - t2.ZoomLevel); - System.Diagnostics.Trace.TraceInformation("{0} Tiles: {1}", tiles.Count, string.Join(", ", tiles.Select(t => t.ZoomLevel.ToString()))); + //System.Diagnostics.Trace.TraceInformation("{0} Tiles: {1}", tiles.Count, string.Join(", ", tiles.Select(t => t.ZoomLevel.ToString()))); } private void RenderTiles() @@ -170,4 +144,4 @@ namespace MapControl } } } -} \ No newline at end of file +} diff --git a/TestApplication/MainWindow.xaml b/TestApplication/MainWindow.xaml index 2d1078b1..3a63fdd8 100644 --- a/TestApplication/MainWindow.xaml +++ b/TestApplication/MainWindow.xaml @@ -120,35 +120,30 @@ + TileSource="http://{c}.tile.openstreetmap.org/{z}/{x}/{y}.png" IsCached="False"/> + TileSource="http://{c}.tile.opencyclemap.org/cycle/{z}/{x}/{y}.png" IsCached="False"/> + TileSource="http://{c}.tile2.opencyclemap.org/transport/{z}/{x}/{y}.png" IsCached="False"/> + TileSource="http://{c}.tile3.opencyclemap.org/landscape/{z}/{x}/{y}.png" IsCached="False"/> + TileSource="http://otile{n}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png" IsCached="False"/> + ImageType="jpg" IsCached="False" MaxZoomLevel="20" HasDarkBackground="True"/>--> diff --git a/TestApplication/TestApplication.csproj b/TestApplication/TestApplication.csproj index 07180684..56948b58 100644 --- a/TestApplication/TestApplication.csproj +++ b/TestApplication/TestApplication.csproj @@ -11,7 +11,8 @@ MapControlTestApp MapControlTestApp v4.0 - Client + + 512 {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 4 @@ -25,6 +26,7 @@ DEBUG;TRACE prompt 4 + false x86 @@ -34,6 +36,7 @@ TRACE prompt 4 + false