XAML-Map-Control/MapControl/TileImageLoader.cs

279 lines
9.3 KiB
C#
Raw Normal View History

2012-05-04 12:52:20 +02:00
// WPF MapControl - http://wpfmapcontrol.codeplex.com/
// Copyright © 2012 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
2012-04-25 22:02:53 +02:00
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
namespace MapControl
{
2012-05-04 12:52:20 +02:00
/// <summary>
/// Loads map tiles by their URIs and optionally caches their image files in a folder
/// defined by the static TileCacheFolder property.
/// </summary>
2012-04-25 22:02:53 +02:00
public class TileImageLoader : DispatcherObject
{
2012-05-04 12:52:20 +02:00
public static string TileCacheFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "MapControl Cache");
2012-04-25 22:02:53 +02:00
public static TimeSpan TileCacheExpiryAge = TimeSpan.FromDays(1);
private readonly Queue<Tile> pendingTiles = new Queue<Tile>();
private int numDownloads;
internal int MaxDownloads;
internal string TileLayerName;
internal int TilesPending
{
get { return pendingTiles.Count; }
}
internal void BeginDownloadTiles(ICollection<Tile> tiles)
{
2012-06-12 20:30:05 +02:00
ThreadPool.QueueUserWorkItem(BeginDownloadTilesAsync, new List<Tile>(tiles.Where(t => t.LoadState == TileLoadState.NotLoaded)));
2012-04-25 22:02:53 +02:00
}
internal void EndDownloadTiles()
{
lock (pendingTiles)
{
pendingTiles.Clear();
}
}
2012-06-12 20:30:05 +02:00
private void BeginDownloadTilesAsync(object newTilesList)
2012-04-25 22:02:53 +02:00
{
List<Tile> newTiles = (List<Tile>)newTilesList;
lock (pendingTiles)
{
2012-05-04 12:52:20 +02:00
if (!string.IsNullOrEmpty(TileCacheFolder) &&
2012-04-25 22:02:53 +02:00
!string.IsNullOrEmpty(TileLayerName))
{
List<Tile> expiredTiles = new List<Tile>(newTiles.Count);
newTiles.ForEach(tile =>
{
bool cacheExpired;
ImageSource image = GetCachedImage(tile, out cacheExpired);
if (image != null)
{
2012-06-12 20:30:05 +02:00
tile.LoadState = TileLoadState.Loaded;
2012-04-25 22:02:53 +02:00
Dispatcher.BeginInvoke((Action)(() => tile.Image = image));
if (cacheExpired)
{
expiredTiles.Add(tile); // enqueue later
}
}
else
{
pendingTiles.Enqueue(tile);
}
});
expiredTiles.ForEach(tile => pendingTiles.Enqueue(tile));
}
else
{
newTiles.ForEach(tile => pendingTiles.Enqueue(tile));
}
DownloadNextTiles(null);
}
}
private void DownloadNextTiles(object o)
{
while (pendingTiles.Count > 0 && numDownloads < MaxDownloads)
{
Tile tile = pendingTiles.Dequeue();
tile.LoadState = TileLoadState.Loading;
numDownloads++;
2012-06-12 20:30:05 +02:00
ThreadPool.QueueUserWorkItem(DownloadTileAsync, tile);
2012-04-25 22:02:53 +02:00
}
}
2012-06-12 20:30:05 +02:00
private void DownloadTileAsync(object t)
2012-04-25 22:02:53 +02:00
{
Tile tile = (Tile)t;
ImageSource image = DownloadImage(tile);
2012-06-12 20:30:05 +02:00
if (image != null)
{
tile.LoadState = TileLoadState.Loaded;
Dispatcher.BeginInvoke((Action)(() => tile.Image = image));
}
else
{
tile.LoadState = TileLoadState.NotLoaded;
}
2012-04-25 22:02:53 +02:00
lock (pendingTiles)
{
numDownloads--;
DownloadNextTiles(null);
}
}
private ImageSource GetCachedImage(Tile tile, out bool expired)
{
string tileDir = TileDirectory(tile);
ImageSource image = null;
expired = false;
try
{
if (Directory.Exists(tileDir))
{
string[] tilePath = Directory.GetFiles(tileDir, string.Format("{0}.*", tile.Y));
if (tilePath.Length > 0)
{
try
{
using (Stream fileStream = File.OpenRead(tilePath[0]))
{
image = BitmapFrame.Create(fileStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
}
expired = File.GetLastWriteTime(tilePath[0]) + TileCacheExpiryAge <= DateTime.Now;
TraceInformation(expired ? "{0} - Cache Expired" : "{0} - Cached", tile.Uri);
}
catch (Exception exc)
{
TraceWarning("{0} - {1}", tilePath[0], exc.Message);
File.Delete(tilePath[0]);
}
}
}
}
catch (Exception exc)
{
TraceWarning("{0} - {1}", tileDir, exc.Message);
}
return image;
}
private ImageSource DownloadImage(Tile tile)
{
ImageSource image = null;
try
{
TraceInformation("{0} - Requesting", tile.Uri);
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(tile.Uri);
webRequest.UserAgent = typeof(TileImageLoader).ToString();
webRequest.KeepAlive = true;
using (HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse())
{
using (Stream responseStream = response.GetResponseStream())
{
using (Stream memoryStream = new MemoryStream((int)response.ContentLength))
{
responseStream.CopyTo(memoryStream);
memoryStream.Position = 0;
BitmapDecoder decoder = BitmapDecoder.Create(memoryStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
image = decoder.Frames[0];
string tilePath;
2012-05-04 12:52:20 +02:00
if (!string.IsNullOrEmpty(TileCacheFolder) &&
2012-04-25 22:02:53 +02:00
!string.IsNullOrEmpty(TileLayerName) &&
(tilePath = TilePath(tile, decoder)) != null)
{
Directory.CreateDirectory(Path.GetDirectoryName(tilePath));
using (Stream fileStream = File.OpenWrite(tilePath))
{
memoryStream.Position = 0;
memoryStream.CopyTo(fileStream);
}
}
}
}
}
TraceInformation("{0} - Completed", tile.Uri);
}
catch (WebException exc)
{
if (exc.Status == WebExceptionStatus.ProtocolError)
{
TraceInformation("{0} - {1}", tile.Uri, ((HttpWebResponse)exc.Response).StatusCode);
}
else
{
TraceWarning("{0} - {1}", tile.Uri, exc.Status);
}
}
catch (Exception exc)
{
TraceWarning("{0} - {1}", tile.Uri, exc.Message);
}
return image;
}
private string TileDirectory(Tile tile)
{
2012-05-04 12:52:20 +02:00
return Path.Combine(TileCacheFolder, TileLayerName, tile.ZoomLevel.ToString(), tile.XIndex.ToString());
2012-04-25 22:02:53 +02:00
}
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));
}
private static void TraceInformation(string format, params object[] args)
{
System.Diagnostics.Trace.TraceInformation("[{0:00}] {1}", Thread.CurrentThread.ManagedThreadId, string.Format(format, args));
}
}
}