mirror of
https://github.com/ClemensFischer/XAML-Map-Control.git
synced 2026-04-05 14:37:01 +00:00
Simplified TileImageLoader and TileSource, added ImageTileSource, fixed FileDbCache
This commit is contained in:
parent
300c22a2e7
commit
2fd3f5f8f6
12 changed files with 280 additions and 195 deletions
|
|
@ -115,7 +115,7 @@ namespace MapControl
|
|||
MainTileLayer = new TileLayer
|
||||
{
|
||||
Description = "© {y} OpenStreetMap Contributors, CC-BY-SA",
|
||||
TileSource = new OpenStreetMapTileSource { UriFormat = "http://{c}.tile.openstreetmap.org/{z}/{x}/{y}.png" }
|
||||
TileSource = new TileSource("http://{c}.tile.openstreetmap.org/{z}/{x}/{y}.png")
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
using System;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace MapControl
|
||||
{
|
||||
|
|
@ -42,11 +43,35 @@ namespace MapControl
|
|||
{
|
||||
if (Brush.ImageSource == null)
|
||||
{
|
||||
Brush.BeginAnimation(ImageBrush.OpacityProperty, opacityAnimation);
|
||||
BitmapImage bitmap = value as BitmapImage;
|
||||
|
||||
if (bitmap != null && bitmap.IsDownloading)
|
||||
{
|
||||
bitmap.DownloadCompleted += BitmapDownloadCompleted;
|
||||
bitmap.DownloadFailed += BitmapDownloadFailed;
|
||||
}
|
||||
else
|
||||
{
|
||||
Brush.BeginAnimation(ImageBrush.OpacityProperty, opacityAnimation);
|
||||
}
|
||||
}
|
||||
|
||||
Brush.ImageSource = value;
|
||||
}
|
||||
}
|
||||
|
||||
private void BitmapDownloadCompleted(object sender, EventArgs e)
|
||||
{
|
||||
((BitmapImage)sender).DownloadCompleted -= BitmapDownloadCompleted;
|
||||
((BitmapImage)sender).DownloadFailed -= BitmapDownloadFailed;
|
||||
Brush.BeginAnimation(ImageBrush.OpacityProperty, opacityAnimation);
|
||||
}
|
||||
|
||||
private void BitmapDownloadFailed(object sender, ExceptionEventArgs e)
|
||||
{
|
||||
((BitmapImage)sender).DownloadCompleted -= BitmapDownloadCompleted;
|
||||
((BitmapImage)sender).DownloadFailed -= BitmapDownloadFailed;
|
||||
Brush.ImageSource = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ namespace MapControl
|
|||
private readonly TileLayer tileLayer;
|
||||
private readonly Queue<Tile> pendingTiles = new Queue<Tile>();
|
||||
private readonly HashSet<HttpWebRequest> currentRequests = new HashSet<HttpWebRequest>();
|
||||
private int numDownloads;
|
||||
|
||||
/// <summary>
|
||||
/// The ObjectCache used to cache tile images.
|
||||
|
|
@ -115,12 +114,12 @@ namespace MapControl
|
|||
this.tileLayer = tileLayer;
|
||||
}
|
||||
|
||||
internal void BeginDownloadTiles(ICollection<Tile> tiles)
|
||||
internal void BeginGetTiles(ICollection<Tile> tiles)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(BeginDownloadTilesAsync, new List<Tile>(tiles.Where(t => t.Image == null && t.Uri == null)));
|
||||
ThreadPool.QueueUserWorkItem(BeginGetTilesAsync, new List<Tile>(tiles.Where(t => t.Image == null && t.Uri == null)));
|
||||
}
|
||||
|
||||
internal void CancelDownloadTiles()
|
||||
internal void CancelGetTiles()
|
||||
{
|
||||
lock (pendingTiles)
|
||||
{
|
||||
|
|
@ -136,77 +135,90 @@ namespace MapControl
|
|||
}
|
||||
}
|
||||
|
||||
private void BeginDownloadTilesAsync(object newTilesList)
|
||||
private void BeginGetTilesAsync(object newTilesList)
|
||||
{
|
||||
List<Tile> newTiles = (List<Tile>)newTilesList;
|
||||
ImageTileSource imageTileSource = tileLayer.TileSource as ImageTileSource;
|
||||
|
||||
lock (pendingTiles)
|
||||
if (imageTileSource != null)
|
||||
{
|
||||
if (Cache == null)
|
||||
newTiles.ForEach(tile =>
|
||||
{
|
||||
newTiles.ForEach(tile => pendingTiles.Enqueue(tile));
|
||||
}
|
||||
else
|
||||
Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)(() => tile.Image = imageTileSource.GetImage(tile.XIndex, tile.Y, tile.ZoomLevel)));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
lock (pendingTiles)
|
||||
{
|
||||
List<Tile> outdatedTiles = new List<Tile>(newTiles.Count);
|
||||
|
||||
newTiles.ForEach(tile =>
|
||||
if (Cache == null)
|
||||
{
|
||||
string key = CacheKey(tile);
|
||||
CachedImage cachedImage = Cache.Get(key) as CachedImage;
|
||||
newTiles.ForEach(tile => pendingTiles.Enqueue(tile));
|
||||
}
|
||||
else
|
||||
{
|
||||
List<Tile> outdatedTiles = new List<Tile>(newTiles.Count);
|
||||
|
||||
if (cachedImage == null)
|
||||
newTiles.ForEach(tile =>
|
||||
{
|
||||
pendingTiles.Enqueue(tile);
|
||||
}
|
||||
else if (!CreateTileImage(tile, cachedImage.ImageBuffer))
|
||||
{
|
||||
// got corrupted buffer from cache
|
||||
Cache.Remove(key);
|
||||
pendingTiles.Enqueue(tile);
|
||||
}
|
||||
else if (cachedImage.CreationTime + CacheUpdateAge < DateTime.UtcNow)
|
||||
{
|
||||
// update cached image
|
||||
outdatedTiles.Add(tile);
|
||||
}
|
||||
});
|
||||
string key = CacheKey(tile);
|
||||
CachedImage cachedImage = Cache.Get(key) as CachedImage;
|
||||
|
||||
outdatedTiles.ForEach(tile => pendingTiles.Enqueue(tile));
|
||||
if (cachedImage == null)
|
||||
{
|
||||
pendingTiles.Enqueue(tile);
|
||||
}
|
||||
else if (!CreateTileImage(tile, cachedImage.ImageBuffer))
|
||||
{
|
||||
// got corrupted buffer from cache
|
||||
Cache.Remove(key);
|
||||
pendingTiles.Enqueue(tile);
|
||||
}
|
||||
else if (cachedImage.CreationTime + CacheUpdateAge < DateTime.UtcNow)
|
||||
{
|
||||
// update cached image
|
||||
outdatedTiles.Add(tile);
|
||||
}
|
||||
});
|
||||
|
||||
outdatedTiles.ForEach(tile => pendingTiles.Enqueue(tile));
|
||||
}
|
||||
|
||||
int numDownloads = Math.Min(pendingTiles.Count, tileLayer.MaxParallelDownloads);
|
||||
|
||||
while (--numDownloads >= 0)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(DownloadTiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DownloadTiles(object o)
|
||||
{
|
||||
while (pendingTiles.Count > 0)
|
||||
{
|
||||
Tile tile;
|
||||
|
||||
lock (pendingTiles)
|
||||
{
|
||||
if (pendingTiles.Count == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
tile = pendingTiles.Dequeue();
|
||||
}
|
||||
|
||||
DownloadNextTiles(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void DownloadNextTiles(object o)
|
||||
{
|
||||
while (pendingTiles.Count > 0 && numDownloads < tileLayer.MaxDownloads)
|
||||
{
|
||||
Tile tile = pendingTiles.Dequeue();
|
||||
tile.Uri = tileLayer.TileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel);
|
||||
numDownloads++;
|
||||
byte[] imageBuffer = DownloadImage(tile);
|
||||
|
||||
ThreadPool.QueueUserWorkItem(DownloadTileAsync, tile);
|
||||
}
|
||||
}
|
||||
|
||||
private void DownloadTileAsync(object t)
|
||||
{
|
||||
Tile tile = (Tile)t;
|
||||
byte[] imageBuffer = DownloadImage(tile);
|
||||
|
||||
if (imageBuffer != null &&
|
||||
CreateTileImage(tile, imageBuffer) &&
|
||||
Cache != null)
|
||||
{
|
||||
Cache.Set(CacheKey(tile), new CachedImage(imageBuffer), new CacheItemPolicy { SlidingExpiration = CacheExpiration });
|
||||
}
|
||||
|
||||
lock (pendingTiles)
|
||||
{
|
||||
numDownloads--;
|
||||
DownloadNextTiles(null);
|
||||
if (imageBuffer != null &&
|
||||
CreateTileImage(tile, imageBuffer) &&
|
||||
Cache != null)
|
||||
{
|
||||
Cache.Set(CacheKey(tile), new CachedImage(imageBuffer), new CacheItemPolicy { SlidingExpiration = CacheExpiration });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -256,7 +268,7 @@ namespace MapControl
|
|||
{
|
||||
TraceInformation("{0} - {1}", tile.Uri, ((HttpWebResponse)ex.Response).StatusCode);
|
||||
}
|
||||
else if (ex.Status == WebExceptionStatus.RequestCanceled) // by HttpWebRequest.Abort in CancelDownloadTiles
|
||||
else if (ex.Status == WebExceptionStatus.RequestCanceled) // by HttpWebRequest.Abort in CancelGetTiles
|
||||
{
|
||||
TraceInformation("{0} - {1}", tile.Uri, ex.Status);
|
||||
}
|
||||
|
|
@ -285,10 +297,10 @@ namespace MapControl
|
|||
|
||||
private bool CreateTileImage(Tile tile, byte[] buffer)
|
||||
{
|
||||
BitmapImage bitmap = new BitmapImage();
|
||||
|
||||
try
|
||||
{
|
||||
BitmapImage bitmap = new BitmapImage();
|
||||
|
||||
using (Stream stream = new MemoryStream(buffer))
|
||||
{
|
||||
bitmap.BeginInit();
|
||||
|
|
@ -297,8 +309,6 @@ namespace MapControl
|
|||
bitmap.EndInit();
|
||||
bitmap.Freeze();
|
||||
}
|
||||
|
||||
Dispatcher.BeginInvoke((Action)(() => tile.Image = bitmap));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -306,6 +316,7 @@ namespace MapControl
|
|||
return false;
|
||||
}
|
||||
|
||||
Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)(() => tile.Image = bitmap));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,14 +30,14 @@ namespace MapControl
|
|||
Name = string.Empty;
|
||||
MinZoomLevel = 1;
|
||||
MaxZoomLevel = 18;
|
||||
MaxDownloads = 8;
|
||||
MaxParallelDownloads = 8;
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
public TileSource TileSource { get; set; }
|
||||
public int MinZoomLevel { get; set; }
|
||||
public int MaxZoomLevel { get; set; }
|
||||
public int MaxDownloads { get; set; }
|
||||
public int MaxParallelDownloads { get; set; }
|
||||
public bool HasDarkBackground { get; set; }
|
||||
|
||||
public string Description
|
||||
|
|
@ -57,21 +57,21 @@ namespace MapControl
|
|||
this.grid = grid;
|
||||
this.zoomLevel = zoomLevel;
|
||||
|
||||
tileImageLoader.CancelDownloadTiles();
|
||||
tileImageLoader.CancelGetTiles();
|
||||
|
||||
if (VisualParent != null && TileSource != null)
|
||||
{
|
||||
SelectTiles();
|
||||
RenderTiles();
|
||||
|
||||
tileImageLoader.BeginDownloadTiles(tiles);
|
||||
tileImageLoader.BeginGetTiles(tiles);
|
||||
}
|
||||
}
|
||||
|
||||
internal void ClearTiles()
|
||||
{
|
||||
tiles.Clear();
|
||||
tileImageLoader.CancelDownloadTiles();
|
||||
tileImageLoader.CancelGetTiles();
|
||||
}
|
||||
|
||||
private void SelectTiles()
|
||||
|
|
|
|||
|
|
@ -7,48 +7,100 @@ using System.ComponentModel;
|
|||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace MapControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the URI of a map tile.
|
||||
/// Provides the URI of a map tile.
|
||||
/// </summary>
|
||||
[TypeConverter(typeof(TileSourceTypeConverter))]
|
||||
public class TileSource
|
||||
{
|
||||
public string UriFormat { get; set; }
|
||||
private Func<int, int, int, Uri> getUri;
|
||||
private string uriFormat = string.Empty;
|
||||
private int hostIndex = -1;
|
||||
|
||||
public TileSource()
|
||||
{
|
||||
}
|
||||
|
||||
public TileSource(string uriFormat)
|
||||
{
|
||||
UriFormat = uriFormat;
|
||||
}
|
||||
|
||||
public string UriFormat
|
||||
{
|
||||
get { return uriFormat; }
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
throw new ArgumentException("The value of the UriFormat proprty must not be null or empty or white-space only.");
|
||||
}
|
||||
|
||||
if (value.Contains("{x}") && value.Contains("{y}") && value.Contains("{z}"))
|
||||
{
|
||||
if (value.Contains("{c}"))
|
||||
{
|
||||
getUri = GetOpenStreetMapUri;
|
||||
}
|
||||
else if (value.Contains("{i}"))
|
||||
{
|
||||
getUri = GetGoogleMapsUri;
|
||||
}
|
||||
else if (value.Contains("{n}"))
|
||||
{
|
||||
getUri = GetMapQuestUri;
|
||||
}
|
||||
else
|
||||
{
|
||||
getUri = GetDefaultUri;
|
||||
}
|
||||
}
|
||||
else if (value.Contains("{q}")) // {i} is optional
|
||||
{
|
||||
getUri = GetQuadKeyUri;
|
||||
}
|
||||
else if (value.Contains("{w}") && value.Contains("{s}") && value.Contains("{e}") && value.Contains("{n}"))
|
||||
{
|
||||
getUri = GetBoundingBoxUri;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("The specified UriFormat is not supported.");
|
||||
}
|
||||
|
||||
uriFormat = value;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual Uri GetUri(int x, int y, int zoomLevel)
|
||||
{
|
||||
return new Uri(UriFormat.
|
||||
Replace("{x}", x.ToString()).
|
||||
Replace("{y}", y.ToString()).
|
||||
Replace("{z}", zoomLevel.ToString()));
|
||||
return getUri != null ? getUri(x, y, zoomLevel) : null;
|
||||
}
|
||||
}
|
||||
|
||||
public class OpenStreetMapTileSource : TileSource
|
||||
{
|
||||
private static string[] hostChars = { "a", "b", "c" };
|
||||
private int hostChar = -1;
|
||||
|
||||
public override Uri GetUri(int x, int y, int zoomLevel)
|
||||
private Uri GetDefaultUri(int x, int y, int zoomLevel)
|
||||
{
|
||||
hostChar = (hostChar + 1) % 3;
|
||||
|
||||
return new Uri(UriFormat.
|
||||
Replace("{c}", hostChars[hostChar]).
|
||||
Replace("{x}", x.ToString()).
|
||||
Replace("{y}", y.ToString()).
|
||||
Replace("{z}", zoomLevel.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
public class GoogleMapsTileSource : TileSource
|
||||
{
|
||||
private int hostIndex = -1;
|
||||
private Uri GetOpenStreetMapUri(int x, int y, int zoomLevel)
|
||||
{
|
||||
hostIndex = (hostIndex + 1) % 3;
|
||||
|
||||
public override Uri GetUri(int x, int y, int zoomLevel)
|
||||
return new Uri(UriFormat.
|
||||
Replace("{c}", "abc".Substring(hostIndex, 1)).
|
||||
Replace("{x}", x.ToString()).
|
||||
Replace("{y}", y.ToString()).
|
||||
Replace("{z}", zoomLevel.ToString()));
|
||||
}
|
||||
|
||||
private Uri GetGoogleMapsUri(int x, int y, int zoomLevel)
|
||||
{
|
||||
hostIndex = (hostIndex + 1) % 4;
|
||||
|
||||
|
|
@ -58,27 +110,19 @@ namespace MapControl
|
|||
Replace("{y}", y.ToString()).
|
||||
Replace("{z}", zoomLevel.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
public class MapQuestTileSource : TileSource
|
||||
{
|
||||
private int hostNumber;
|
||||
|
||||
public override Uri GetUri(int x, int y, int zoomLevel)
|
||||
private Uri GetMapQuestUri(int x, int y, int zoomLevel)
|
||||
{
|
||||
hostNumber = (hostNumber % 4) + 1;
|
||||
hostIndex = (hostIndex % 4) + 1;
|
||||
|
||||
return new Uri(UriFormat.
|
||||
Replace("{n}", hostNumber.ToString()).
|
||||
Replace("{n}", hostIndex.ToString()).
|
||||
Replace("{x}", x.ToString()).
|
||||
Replace("{y}", y.ToString()).
|
||||
Replace("{z}", zoomLevel.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
public class QuadKeyTileSource : TileSource
|
||||
{
|
||||
public override Uri GetUri(int x, int y, int zoomLevel)
|
||||
private Uri GetQuadKeyUri(int x, int y, int zoomLevel)
|
||||
{
|
||||
StringBuilder key = new StringBuilder { Length = zoomLevel };
|
||||
|
||||
|
|
@ -91,11 +135,8 @@ namespace MapControl
|
|||
Replace("{i}", key.ToString(key.Length - 1, 1)).
|
||||
Replace("{q}", key.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
public class BoundingBoxTileSource : TileSource
|
||||
{
|
||||
public override Uri GetUri(int x, int y, int zoomLevel)
|
||||
private Uri GetBoundingBoxUri(int x, int y, int zoomLevel)
|
||||
{
|
||||
MercatorTransform t = new MercatorTransform();
|
||||
double n = 1 << zoomLevel;
|
||||
|
|
@ -114,57 +155,28 @@ namespace MapControl
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the image of a map tile. ImageTileSource bypasses downloading
|
||||
/// and caching of tile images that is performed by TileImageLoader.
|
||||
/// </summary>
|
||||
public abstract class ImageTileSource : TileSource
|
||||
{
|
||||
public abstract ImageSource GetImage(int x, int y, int zoomLevel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts from string to TileSource.
|
||||
/// </summary>
|
||||
public class TileSourceTypeConverter : TypeConverter
|
||||
{
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
{
|
||||
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
|
||||
return sourceType == typeof(string);
|
||||
}
|
||||
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
string uriFormat = value as string;
|
||||
|
||||
if (uriFormat != null)
|
||||
{
|
||||
TileSource tileSource = null;
|
||||
|
||||
if (uriFormat.Contains("{x}") && uriFormat.Contains("{y}") && uriFormat.Contains("{z}"))
|
||||
{
|
||||
if (uriFormat.Contains("{c}"))
|
||||
{
|
||||
tileSource = new OpenStreetMapTileSource();
|
||||
}
|
||||
else if (uriFormat.Contains("{i}"))
|
||||
{
|
||||
tileSource = new GoogleMapsTileSource();
|
||||
}
|
||||
else if (uriFormat.Contains("{n}"))
|
||||
{
|
||||
tileSource = new MapQuestTileSource();
|
||||
}
|
||||
else
|
||||
{
|
||||
tileSource = new TileSource();
|
||||
}
|
||||
}
|
||||
else if (uriFormat.Contains("{q}"))
|
||||
{
|
||||
tileSource = new QuadKeyTileSource();
|
||||
}
|
||||
else if (uriFormat.Contains("{w}") && uriFormat.Contains("{s}") && uriFormat.Contains("{e}") && uriFormat.Contains("{n}"))
|
||||
{
|
||||
tileSource = new BoundingBoxTileSource();
|
||||
}
|
||||
|
||||
if (tileSource != null)
|
||||
{
|
||||
tileSource.UriFormat = uriFormat;
|
||||
return tileSource;
|
||||
}
|
||||
}
|
||||
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
return new TileSource(value as string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue