Use System.Runtime.Caching.MemoryCache

This commit is contained in:
ClemensF 2012-06-24 23:42:11 +02:00
parent de8e9840b5
commit 38e6c23114
5 changed files with 112 additions and 153 deletions

View file

@ -12,7 +12,8 @@
<AssemblyName>MapControl</AssemblyName> <AssemblyName>MapControl</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<TargetFrameworkProfile>Client</TargetFrameworkProfile> <TargetFrameworkProfile>
</TargetFrameworkProfile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
@ -22,6 +23,7 @@
<DefineConstants>TRACE;DEBUG</DefineConstants> <DefineConstants>TRACE;DEBUG</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<UseVSHostingProcess>false</UseVSHostingProcess>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType> <DebugType>none</DebugType>
@ -31,13 +33,14 @@
</DefineConstants> </DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<UseVSHostingProcess>false</UseVSHostingProcess>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="PresentationCore" /> <Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" /> <Reference Include="PresentationFramework" />
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.configuration" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Runtime.Caching" />
<Reference Include="System.Xaml" /> <Reference Include="System.Xaml" />
<Reference Include="WindowsBase" /> <Reference Include="WindowsBase" />
</ItemGroup> </ItemGroup>

View file

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Runtime.Caching;
using System.Threading; using System.Threading;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
@ -21,18 +22,20 @@ namespace MapControl
public class TileImageLoader : DispatcherObject public class TileImageLoader : DispatcherObject
{ {
public static string TileCacheFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "MapControl Cache"); 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<Tile> pendingTiles = new Queue<Tile>(); private readonly Queue<Tile> pendingTiles = new Queue<Tile>();
private int numDownloads; private int numDownloads;
internal int MaxDownloads; public TileImageLoader(TileLayer tileLayer)
internal string TileLayerName; {
internal TileSource TileSource; this.tileLayer = tileLayer;
}
private bool IsCached private bool IsCached
{ {
get { return !string.IsNullOrEmpty(TileCacheFolder) && !string.IsNullOrEmpty(TileLayerName); } get { return tileLayer.IsCached && !string.IsNullOrEmpty(TileCacheFolder); }
} }
internal void StartDownloadTiles(ICollection<Tile> tiles) internal void StartDownloadTiles(ICollection<Tile> tiles)
@ -51,26 +54,33 @@ namespace MapControl
private void StartDownloadTilesAsync(object newTilesList) private void StartDownloadTilesAsync(object newTilesList)
{ {
List<Tile> newTiles = (List<Tile>)newTilesList; List<Tile> newTiles = (List<Tile>)newTilesList;
List<Tile> expiredTiles = new List<Tile>(newTiles.Count);
lock (pendingTiles) lock (pendingTiles)
{ {
if (IsCached)
{
List<Tile> expiredTiles = new List<Tile>(newTiles.Count);
newTiles.ForEach(tile => newTiles.ForEach(tile =>
{ {
bool cacheExpired; ImageSource image = GetMemoryCachedImage(tile);
ImageSource image = GetCachedImage(tile, out cacheExpired);
if (image == null && IsCached)
{
bool fileCacheExpired;
image = GetFileCachedImage(tile, out fileCacheExpired);
if (image != null)
{
SetMemoryCachedImage(tile, image);
if (fileCacheExpired)
{
expiredTiles.Add(tile); // enqueue later
}
}
}
if (image != null) if (image != null)
{ {
Dispatcher.BeginInvoke((Action)(() => tile.Image = image)); Dispatcher.BeginInvoke((Action)(() => tile.Image = image));
if (cacheExpired)
{
expiredTiles.Add(tile); // enqueue later
}
} }
else else
{ {
@ -79,11 +89,6 @@ namespace MapControl
}); });
expiredTiles.ForEach(tile => pendingTiles.Enqueue(tile)); expiredTiles.ForEach(tile => pendingTiles.Enqueue(tile));
}
else
{
newTiles.ForEach(tile => pendingTiles.Enqueue(tile));
}
DownloadNextTiles(null); DownloadNextTiles(null);
} }
@ -91,10 +96,10 @@ namespace MapControl
private void DownloadNextTiles(object o) private void DownloadNextTiles(object o)
{ {
while (pendingTiles.Count > 0 && numDownloads < MaxDownloads) while (pendingTiles.Count > 0 && numDownloads < tileLayer.MaxDownloads)
{ {
Tile tile = pendingTiles.Dequeue(); 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++; numDownloads++;
ThreadPool.QueueUserWorkItem(DownloadTileAsync, tile); ThreadPool.QueueUserWorkItem(DownloadTileAsync, tile);
@ -108,54 +113,73 @@ namespace MapControl
if (image != null) if (image != null)
{ {
SetMemoryCachedImage(tile, image);
Dispatcher.BeginInvoke((Action)(() => tile.Image = image)); Dispatcher.BeginInvoke((Action)(() => tile.Image = image));
} }
lock (pendingTiles) lock (pendingTiles)
{ {
tile.Uri = null;
numDownloads--; numDownloads--;
DownloadNextTiles(null); 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; ImageSource image = null;
expired = false; expired = false;
try if (File.Exists(path))
{
if (Directory.Exists(tileDir))
{
string tilePath = Directory.GetFiles(tileDir, string.Format("{0}.*", tile.Y)).FirstOrDefault();
if (tilePath != null)
{ {
try try
{ {
using (Stream fileStream = File.OpenRead(tilePath)) using (Stream fileStream = File.OpenRead(path))
{ {
image = BitmapFrame.Create(fileStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); image = BitmapFrame.Create(fileStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
} }
expired = File.GetLastWriteTime(tilePath) + TileCacheExpiryAge <= DateTime.Now; expired = File.GetLastWriteTime(path) + TileCacheExpiryAge <= DateTime.Now;
TraceInformation(expired ? "{0} - File Cache Expired" : "{0} - File Cached", path);
TraceInformation(expired ? "{0} - Cache Expired" : "{0} - Cached", tilePath);
} }
catch (Exception exc) catch (Exception exc)
{ {
TraceWarning("{0} - {1}", tilePath, exc.Message); TraceWarning("{0} - {1}", path, exc.Message);
File.Delete(tilePath); File.Delete(path);
} }
} }
}
}
catch (Exception exc)
{
TraceWarning("{0} - {1}", tileDir, exc.Message);
}
return image; return image;
} }
@ -180,17 +204,14 @@ namespace MapControl
{ {
responseStream.CopyTo(memoryStream); responseStream.CopyTo(memoryStream);
memoryStream.Position = 0; memoryStream.Position = 0;
image = BitmapFrame.Create(memoryStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
BitmapDecoder decoder = BitmapDecoder.Create(memoryStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); if (IsCached)
image = decoder.Frames[0];
string tilePath;
if (IsCached && (tilePath = TilePath(tile, decoder)) != null)
{ {
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.Position = 0;
memoryStream.CopyTo(fileStream); memoryStream.CopyTo(fileStream);
@ -221,43 +242,6 @@ namespace MapControl
return image; 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) private static void TraceWarning(string format, params object[] args)
{ {
System.Diagnostics.Trace.TraceWarning("[{0:00}] {1}", Thread.CurrentThread.ManagedThreadId, string.Format(format, 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) 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));
} }
} }
} }

View file

@ -18,58 +18,32 @@ namespace MapControl
[ContentProperty("TileSource")] [ContentProperty("TileSource")]
public class TileLayer : DrawingVisual public class TileLayer : DrawingVisual
{ {
private readonly TileImageLoader tileImageLoader = new TileImageLoader(); private readonly TileImageLoader tileImageLoader;
private readonly List<Tile> tiles = new List<Tile>(); private readonly List<Tile> tiles = new List<Tile>();
private bool isCached = false;
private string name = string.Empty;
private string description = string.Empty; private string description = string.Empty;
private Int32Rect grid; private Int32Rect grid;
private int zoomLevel; private int zoomLevel;
public TileLayer() public TileLayer()
{ {
tileImageLoader = new TileImageLoader(this);
VisualEdgeMode = EdgeMode.Aliased; VisualEdgeMode = EdgeMode.Aliased;
VisualTransform = new MatrixTransform(); VisualTransform = new MatrixTransform();
Name = string.Empty;
ImageType = "png";
MinZoomLevel = 1; MinZoomLevel = 1;
MaxZoomLevel = 18; MaxZoomLevel = 18;
MaxDownloads = 8; 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 MinZoomLevel { get; set; }
public int MaxZoomLevel { get; set; } public int MaxZoomLevel { get; set; }
public int MaxDownloads { get; set; }
public int MaxDownloads public bool IsCached { get; set; }
{ public bool HasDarkBackground { get; set; }
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 string Description public string Description
{ {
@ -150,7 +124,7 @@ namespace MapControl
tiles.Sort((t1, t2) => t1.ZoomLevel - t2.ZoomLevel); 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() private void RenderTiles()

View file

@ -120,35 +120,30 @@
<ComboBox Name="tileLayerComboBox" ToolTip="Main Tile Layer" Margin="4,0,4,0" DisplayMemberPath="Name" SelectedIndex="0"> <ComboBox Name="tileLayerComboBox" ToolTip="Main Tile Layer" Margin="4,0,4,0" DisplayMemberPath="Name" SelectedIndex="0">
<ComboBox.Items> <ComboBox.Items>
<map:TileLayer Name="OpenStreetMap" Description="© {y} OpenStreetMap Contributors, CC-BY-SA" <map:TileLayer Name="OpenStreetMap" Description="© {y} OpenStreetMap Contributors, CC-BY-SA"
TileSource="http://{c}.tile.openstreetmap.org/{z}/{x}/{y}.png" TileSource="http://{c}.tile.openstreetmap.org/{z}/{x}/{y}.png" IsCached="False"/>
IsCached="False"/>
<map:TileLayer Name="OpenCycleMap" Description="OpenCycleMap - © {y} Andy Allen &amp; OpenStreetMap Contributors, CC-BY-SA" <map:TileLayer Name="OpenCycleMap" Description="OpenCycleMap - © {y} Andy Allen &amp; OpenStreetMap Contributors, CC-BY-SA"
TileSource="http://{c}.tile.opencyclemap.org/cycle/{z}/{x}/{y}.png" TileSource="http://{c}.tile.opencyclemap.org/cycle/{z}/{x}/{y}.png" IsCached="False"/>
IsCached="False"/>
<map:TileLayer Name="OCM Transport" Description="OpenCycleMap Transport - © {y} Andy Allen &amp; OpenStreetMap Contributors, CC-BY-SA" <map:TileLayer Name="OCM Transport" Description="OpenCycleMap Transport - © {y} Andy Allen &amp; OpenStreetMap Contributors, CC-BY-SA"
TileSource="http://{c}.tile2.opencyclemap.org/transport/{z}/{x}/{y}.png" TileSource="http://{c}.tile2.opencyclemap.org/transport/{z}/{x}/{y}.png" IsCached="False"/>
IsCached="False"/>
<map:TileLayer Name="OCM Landscape" Description="OpenCycleMap Landscape - © {y} Andy Allen &amp; OpenStreetMap Contributors, CC-BY-SA" <map:TileLayer Name="OCM Landscape" Description="OpenCycleMap Landscape - © {y} Andy Allen &amp; OpenStreetMap Contributors, CC-BY-SA"
TileSource="http://{c}.tile3.opencyclemap.org/landscape/{z}/{x}/{y}.png" TileSource="http://{c}.tile3.opencyclemap.org/landscape/{z}/{x}/{y}.png" IsCached="False"/>
IsCached="False"/>
<map:TileLayer Name="MapQuest OSM" Description="MapQuest OSM - © {y} MapQuest &amp; OpenStreetMap Contributors" <map:TileLayer Name="MapQuest OSM" Description="MapQuest OSM - © {y} MapQuest &amp; OpenStreetMap Contributors"
TileSource="http://otile{n}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png" TileSource="http://otile{n}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png" IsCached="False"/>
IsCached="False"/>
<!--<map:TileLayer Name="Google Maps" Description="Google Maps - © {y} Google" <!--<map:TileLayer Name="Google Maps" Description="Google Maps - © {y} Google"
TileSource="http://mt{i}.google.com/vt/x={x}&amp;y={y}&amp;z={z}" TileSource="http://mt{i}.google.com/vt/x={x}&amp;y={y}&amp;z={z}"
IsCached="False" MaxZoomLevel="20"/> IsCached="False" MaxZoomLevel="20"/>
<map:TileLayer Name="Google Images" Description="Google Maps - © {y} Google" <map:TileLayer Name="Google Images" Description="Google Maps - © {y} Google"
TileSource="http://khm{i}.google.com/kh/v=113&amp;x={x}&amp;y={y}&amp;z={z}" TileSource="http://khm{i}.google.com/kh/v=113&amp;x={x}&amp;y={y}&amp;z={z}"
IsCached="False" MaxZoomLevel="20" HasDarkBackground="True"/> ImageType="jpg" IsCached="False" MaxZoomLevel="20" HasDarkBackground="True"/>
<map:TileLayer Name="Bing Maps" Description="Bing Maps - © {y} Microsoft Corporation" <map:TileLayer Name="Bing Maps" Description="Bing Maps - © {y} Microsoft Corporation"
TileSource="http://ecn.t{i}.tiles.virtualearth.net/tiles/r{q}.png?g=0&amp;stl=h" TileSource="http://ecn.t{i}.tiles.virtualearth.net/tiles/r{q}.png?g=0&amp;stl=h"
IsCached="False" MaxZoomLevel="20"/> IsCached="False" MaxZoomLevel="20"/>
<map:TileLayer Name="Bing Images" Description="Bing Maps - © {y} Microsoft Corporation" <map:TileLayer Name="Bing Images" Description="Bing Maps - © {y} Microsoft Corporation"
TileSource="http://ecn.t{i}.tiles.virtualearth.net/tiles/a{q}.jpeg?g=0" TileSource="http://ecn.t{i}.tiles.virtualearth.net/tiles/a{q}.jpeg?g=0"
IsCached="False" MaxZoomLevel="20" HasDarkBackground="True"/> ImageType="jpg" IsCached="False" MaxZoomLevel="20" HasDarkBackground="True"/>
<map:TileLayer Name="Bing Hybrid" Description="Bing Maps - © {y} Microsoft Corporation" <map:TileLayer Name="Bing Hybrid" Description="Bing Maps - © {y} Microsoft Corporation"
TileSource="http://ecn.t{i}.tiles.virtualearth.net/tiles/h{q}.jpeg?g=0&amp;stl=h" TileSource="http://ecn.t{i}.tiles.virtualearth.net/tiles/h{q}.jpeg?g=0&amp;stl=h"
IsCached="False" MaxZoomLevel="20" HasDarkBackground="True"/>--> ImageType="jpg" IsCached="False" MaxZoomLevel="20" HasDarkBackground="True"/>-->
</ComboBox.Items> </ComboBox.Items>
</ComboBox> </ComboBox>
</StackPanel> </StackPanel>

View file

@ -11,7 +11,8 @@
<RootNamespace>MapControlTestApp</RootNamespace> <RootNamespace>MapControlTestApp</RootNamespace>
<AssemblyName>MapControlTestApp</AssemblyName> <AssemblyName>MapControlTestApp</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<TargetFrameworkProfile>Client</TargetFrameworkProfile> <TargetFrameworkProfile>
</TargetFrameworkProfile>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
@ -25,6 +26,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<UseVSHostingProcess>false</UseVSHostingProcess>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<PlatformTarget>x86</PlatformTarget> <PlatformTarget>x86</PlatformTarget>
@ -34,6 +36,7 @@
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<UseVSHostingProcess>false</UseVSHostingProcess>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />