Simplified caching in TileImageLoader, FileDbCache deletes expired items on creation, TileLayers collection moved to Window resources in SampleApplication.

This commit is contained in:
ClemensF 2012-08-13 19:16:59 +02:00
parent fbeb01fca3
commit ae4fb7881a
7 changed files with 126 additions and 153 deletions

View file

@ -56,14 +56,14 @@ namespace Caching
public FileDbCache(string name, string directory)
{
if (string.IsNullOrEmpty(name))
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException("The parameter name must not be null or empty.");
throw new ArgumentException("The parameter name must not be null or empty or only white-space.");
}
if (string.IsNullOrEmpty(directory))
if (string.IsNullOrWhiteSpace(directory))
{
throw new ArgumentException("The parameter directory must not be null or empty.");
throw new ArgumentException("The parameter directory must not be null or empty or only white-space.");
}
this.name = name;
@ -78,6 +78,14 @@ namespace Caching
{
fileDb.Open(path, false);
Trace.TraceInformation("FileDbCache: Opened database with {0} cached items in {1}", fileDb.NumRecords, path);
fileDb.DeleteRecords(new FilterExpression(expiresField, DateTime.UtcNow, EqualityEnum.LessThan));
if (fileDb.NumDeleted > 0)
{
Trace.TraceInformation("FileDbCache: Deleted {0} expired items", fileDb.NumDeleted);
fileDb.Clean();
}
}
catch
{
@ -372,7 +380,10 @@ namespace Caching
{
try
{
fileDb.Flush();
if (fileDb.IsOpen)
{
fileDb.Flush();
}
}
catch (Exception ex)
{
@ -384,7 +395,10 @@ namespace Caching
{
try
{
fileDb.Clean();
if (fileDb.IsOpen)
{
fileDb.Clean();
}
}
catch (Exception ex)
{
@ -396,16 +410,6 @@ namespace Caching
{
if (fileDb.IsOpen)
{
try
{
fileDb.DeleteRecords(new FilterExpression(expiresField, DateTime.UtcNow, EqualityEnum.LessThanOrEqual));
Trace.TraceInformation("FileDbCache: Deleted {0} expired items", fileDb.NumDeleted);
fileDb.Clean();
}
catch
{
}
fileDb.Close();
}
}

View file

@ -13,7 +13,7 @@ using System.Windows.Media.Animation;
namespace MapControl
{
/// <summary>
/// The main map control. Draws map content provided by the TileLayers or the MainTileLayer property.
/// The main map control. Draws map content provided by the TileLayers or the BaseTileLayer property.
/// The visible map area is defined by the Center and ZoomLevel properties. The map can be rotated
/// by an angle that is given by the Heading property.
/// Map is a MapPanel and hence can contain map overlays like other MapPanels or MapItemsControls.
@ -46,10 +46,10 @@ namespace MapControl
(o, e) => ((Map)o).TileLayersPropertyChanged((TileLayerCollection)e.OldValue, (TileLayerCollection)e.NewValue),
(o, v) => ((Map)o).CoerceTileLayersProperty((TileLayerCollection)v)));
public static readonly DependencyProperty MainTileLayerProperty = DependencyProperty.Register(
"MainTileLayer", typeof(TileLayer), typeof(Map), new FrameworkPropertyMetadata(
(o, e) => ((Map)o).MainTileLayerPropertyChanged((TileLayer)e.NewValue),
(o, v) => ((Map)o).CoerceMainTileLayerProperty((TileLayer)v)));
public static readonly DependencyProperty BaseTileLayerProperty = DependencyProperty.Register(
"BaseTileLayer", typeof(TileLayer), typeof(Map), new FrameworkPropertyMetadata(
(o, e) => ((Map)o).BaseTileLayerPropertyChanged((TileLayer)e.NewValue),
(o, v) => ((Map)o).CoerceBaseTileLayerProperty((TileLayer)v)));
public static readonly DependencyProperty TileOpacityProperty = DependencyProperty.Register(
"TileOpacity", typeof(double), typeof(Map), new FrameworkPropertyMetadata(1d,
@ -112,9 +112,9 @@ namespace MapControl
Loaded += (o, e) =>
{
if (MainTileLayer == null)
if (BaseTileLayer == null)
{
MainTileLayer = new TileLayer
BaseTileLayer = new TileLayer
{
Name = "OpenStreetMap",
Description = "© {y} OpenStreetMap Contributors, CC-BY-SA",
@ -202,12 +202,12 @@ namespace MapControl
}
/// <summary>
/// Gets or sets the main TileLayer used by this Map, i.e. TileLayers[0].
/// Gets or sets the base TileLayer used by this Map, i.e. TileLayers[0].
/// </summary>
public TileLayer MainTileLayer
public TileLayer BaseTileLayer
{
get { return (TileLayer)GetValue(MainTileLayerProperty); }
set { SetValue(MainTileLayerProperty, value); }
get { return (TileLayer)GetValue(BaseTileLayerProperty); }
set { SetValue(BaseTileLayerProperty, value); }
}
/// <summary>
@ -491,7 +491,7 @@ namespace MapControl
break;
}
UpdateMainTileLayer();
UpdateBaseTileLayer();
}
private void TileLayersPropertyChanged(TileLayerCollection oldTileLayers, TileLayerCollection newTileLayers)
@ -509,7 +509,7 @@ namespace MapControl
tileContainer.AddTileLayers(0, newTileLayers);
}
UpdateMainTileLayer();
UpdateBaseTileLayer();
}
private TileLayerCollection CoerceTileLayersProperty(TileLayerCollection tileLayers)
@ -522,21 +522,21 @@ namespace MapControl
return tileLayers;
}
private void MainTileLayerPropertyChanged(TileLayer mainTileLayer)
private void BaseTileLayerPropertyChanged(TileLayer baseTileLayer)
{
if (mainTileLayer != null)
if (baseTileLayer != null)
{
if (TileLayers.Count == 0)
{
TileLayers.Add(mainTileLayer);
TileLayers.Add(baseTileLayer);
}
else if (TileLayers[0] != mainTileLayer)
else if (TileLayers[0] != baseTileLayer)
{
TileLayers[0] = mainTileLayer;
TileLayers[0] = baseTileLayer;
}
}
if (mainTileLayer != null && mainTileLayer.HasDarkBackground)
if (baseTileLayer != null && baseTileLayer.HasDarkBackground)
{
if (DarkForeground != null)
{
@ -562,23 +562,23 @@ namespace MapControl
}
}
private TileLayer CoerceMainTileLayerProperty(TileLayer mainTileLayer)
private TileLayer CoerceBaseTileLayerProperty(TileLayer baseTileLayer)
{
if (mainTileLayer == null && TileLayers.Count > 0)
if (baseTileLayer == null && TileLayers.Count > 0)
{
mainTileLayer = TileLayers[0];
baseTileLayer = TileLayers[0];
}
return mainTileLayer;
return baseTileLayer;
}
private void UpdateMainTileLayer()
private void UpdateBaseTileLayer()
{
TileLayer mainTileLayer = TileLayers.FirstOrDefault();
TileLayer baseTileLayer = TileLayers.FirstOrDefault();
if (MainTileLayer != mainTileLayer)
if (BaseTileLayer != baseTileLayer)
{
MainTileLayer = mainTileLayer;
BaseTileLayer = baseTileLayer;
}
}

View file

@ -113,6 +113,20 @@ namespace MapControl
IsSelected = !IsSelected;
}
private void IsSelectedChanged(bool isSelected)
{
if (isSelected)
{
VisualStateManager.GoToState(this, "Selected", true);
RaiseEvent(new RoutedEventArgs(SelectedEvent));
}
else
{
VisualStateManager.GoToState(this, "Unselected", true);
RaiseEvent(new RoutedEventArgs(UnselectedEvent));
}
}
private void CommonStateChanged()
{
if (!IsEnabled)
@ -128,19 +142,5 @@ namespace MapControl
VisualStateManager.GoToState(this, "Normal", true);
}
}
private void IsSelectedChanged(bool isSelected)
{
if (isSelected)
{
VisualStateManager.GoToState(this, "Selected", true);
RaiseEvent(new RoutedEventArgs(SelectedEvent));
}
else
{
VisualStateManager.GoToState(this, "Unselected", true);
RaiseEvent(new RoutedEventArgs(UnselectedEvent));
}
}
}
}

View file

@ -23,18 +23,6 @@ namespace MapControl
/// </summary>
public class TileImageLoader : DispatcherObject
{
[Serializable]
private class CachedImage
{
public readonly DateTime CreationTime = DateTime.UtcNow;
public readonly byte[] ImageBuffer;
public CachedImage(byte[] imageBuffer)
{
ImageBuffer = imageBuffer;
}
}
private readonly TileLayer tileLayer;
private readonly ConcurrentQueue<Tile> pendingTiles = new ConcurrentQueue<Tile>();
@ -151,19 +139,19 @@ namespace MapControl
newTiles.ForEach(tile =>
{
string key = CacheKey(tile);
CachedImage cachedImage = Cache.Get(key) as CachedImage;
byte[] imageBuffer = Cache.Get(key) as byte[];
if (cachedImage == null)
if (imageBuffer == null)
{
pendingTiles.Enqueue(tile);
}
else if (!CreateTileImage(tile, cachedImage.ImageBuffer))
else if (!CreateTileImage(tile, imageBuffer))
{
// got corrupted buffer from cache
Cache.Remove(key);
pendingTiles.Enqueue(tile);
}
else if (cachedImage.CreationTime + CacheUpdateAge < DateTime.UtcNow)
else if (GetCreationTime(imageBuffer) + CacheUpdateAge < DateTime.UtcNow)
{
// update cached image
outdatedTiles.Add(tile);
@ -190,18 +178,16 @@ namespace MapControl
tile.Uri = tileLayer.TileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel);
byte[] imageBuffer = DownloadImage(tile);
if (imageBuffer != null &&
CreateTileImage(tile, imageBuffer) &&
Cache != null)
if (imageBuffer != null && CreateTileImage(tile, imageBuffer) && Cache != null)
{
Cache.Set(CacheKey(tile), new CachedImage(imageBuffer), new CacheItemPolicy { SlidingExpiration = CacheExpiration });
Cache.Set(CacheKey(tile), imageBuffer, new CacheItemPolicy { SlidingExpiration = CacheExpiration });
}
}
}
private string CacheKey(Tile tile)
{
return string.Format("{0}-{1}-{2}-{3}", tileLayer.Name, tile.ZoomLevel, tile.XIndex, tile.Y);
return string.Format("{0}/{1}/{2}/{3}", tileLayer.Name, tile.ZoomLevel, tile.XIndex, tile.Y);
}
private byte[] DownloadImage(Tile tile)
@ -222,9 +208,9 @@ namespace MapControl
{
if (response.ContentLength > 0)
{
using (MemoryStream memoryStream = new MemoryStream((int)response.ContentLength))
using (MemoryStream memoryStream = new MemoryStream((int)response.ContentLength + 8))
{
responseStream.CopyTo(memoryStream);
CopyWithCreationTime(responseStream, memoryStream);
buffer = memoryStream.GetBuffer();
}
}
@ -232,7 +218,7 @@ namespace MapControl
{
using (MemoryStream memoryStream = new MemoryStream())
{
responseStream.CopyTo(memoryStream);
CopyWithCreationTime(responseStream, memoryStream);
buffer = memoryStream.ToArray();
}
}
@ -265,7 +251,7 @@ namespace MapControl
try
{
using (Stream stream = new MemoryStream(buffer))
using (Stream stream = new MemoryStream(buffer, 0, buffer.Length - 8, false))
{
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
@ -284,6 +270,17 @@ namespace MapControl
return true;
}
private static DateTime GetCreationTime(byte[] imageBuffer)
{
return new DateTime(BitConverter.ToInt64(imageBuffer, imageBuffer.Length - 8));
}
private static void CopyWithCreationTime(Stream source, Stream target)
{
source.CopyTo(target);
target.Write(BitConverter.GetBytes(DateTime.UtcNow.Ticks), 0, 8);
}
private static void TraceWarning(string format, params object[] args)
{
Trace.TraceWarning("[{0:00}] {1}", Thread.CurrentThread.ManagedThreadId, string.Format(format, args));

View file

@ -5,8 +5,43 @@
xmlns:local="clr-namespace:SampleApplication"
Title="MainWindow" Height="600" Width="800">
<Window.Resources>
<map:TileLayer x:Key="SeamarksTileLayer"
Name="Seamarks" Description="© {y} OpenSeaMap Contributors, CC-BY-SA"
<map:TileLayerCollection x:Key="TileLayers">
<map:TileLayer Name="OpenStreetMap" Description="© {y} OpenStreetMap Contributors, CC-BY-SA"
TileSource="http://{c}.tile.openstreetmap.org/{z}/{x}/{y}.png"/>
<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"/>
<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"/>
<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"/>
<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"/>
<!-- Note: The providers of the below TileLayers do not allow access to their
map content without using their APIs (i.e. Google Maps API or Bing Maps API).
Hence the declarations below are for demonstration purpose only. -->
<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}" MaxZoomLevel="20"/>
<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}" MaxZoomLevel="20" HasDarkBackground="True"/>
<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" MaxZoomLevel="20"/>
<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" MaxZoomLevel="20" HasDarkBackground="True"/>
<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" MaxZoomLevel="20" HasDarkBackground="True"/>
<!-- The TileLayer below uses an ImageTileSource, which bypasses caching of map tile images -->
<map:TileLayer Name="OSM Uncached" Description="© {y} OpenStreetMap Contributors, CC-BY-SA">
<map:TileLayer.TileSource>
<map:ImageTileSource UriFormat="http://{c}.tile.openstreetmap.org/{z}/{x}/{y}.png"/>
</map:TileLayer.TileSource>
</map:TileLayer>
</map:TileLayerCollection>
<CollectionViewSource x:Key="TileLayersView" Source="{StaticResource TileLayers}"/>
<map:TileLayer x:Key="SeamarksTileLayer" Name="Seamarks" Description="© {y} OpenSeaMap Contributors, CC-BY-SA"
TileSource="http://tiles.openseamap.org/seamark/{z}/{x}/{y}.png" MinZoomLevel="10" MaxZoomLevel="18"/>
<local:SampleItemCollection x:Key="Polylines"/>
<local:SampleItemCollection x:Key="Points"/>
@ -90,7 +125,7 @@
<map:Map Name="map" IsManipulationEnabled="True" Margin="2" FontSize="10"
LightForeground="Black" LightBackground="White" DarkForeground="White" DarkBackground="#FF3F3F3F"
Center="53.5,8.2" ZoomLevel="11"
MainTileLayer="{Binding SelectedItem, ElementName=tileLayerComboBox}"
BaseTileLayer="{Binding Source={StaticResource TileLayersView}, Path=CurrentItem}"
ManipulationInertiaStarting="MapManipulationInertiaStarting"
MouseMove="MapMouseMove" MouseLeave="MapMouseLeave">
<map:MapGraticule Opacity="0.6"/>
@ -105,7 +140,7 @@
<map:Pushpin Location="53.5,8.2" Background="Yellow" Foreground="Blue" Content="N 53° 30' E 8° 12'"
Visibility="{Binding (map:MapPanel.ViewportPosition).IsInside, Converter={StaticResource BooleanToVisibilityConverter}, RelativeSource={RelativeSource Self}}"/>
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="2,0,0,0" FontSize="10"
Text="{Binding MainTileLayer.Description, ElementName=map}"/>
Text="{Binding BaseTileLayer.Description, ElementName=map}"/>
</map:Map>
<Grid Grid.Row="1" Margin="2">
<Grid.ColumnDefinitions>
@ -119,43 +154,8 @@
<Slider Name="headingSlider" ToolTip="Heading" Margin="4,0,4,0" Width="100" Minimum="0" Maximum="360" SmallChange="10" LargeChange="45"
Value="{Binding TargetHeading, ElementName=map}"/>
<CheckBox ToolTip="Map Overlay" Margin="4,0,4,0" VerticalAlignment="Center" Content="Seamarks" Click="SeamarksClick"/>
<ComboBox Name="tileLayerComboBox" ToolTip="Main Tile Layer" Margin="4,0,0,0" DisplayMemberPath="Name" SelectedIndex="0">
<ComboBox.Items>
<map:TileLayer Name="OpenStreetMap" Description="© {y} OpenStreetMap Contributors, CC-BY-SA"
TileSource="http://{c}.tile.openstreetmap.org/{z}/{x}/{y}.png"/>
<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"/>
<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"/>
<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"/>
<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"/>
<!-- Note: The providers of the below TileLayers do not allow access to their
map content without using their APIs (i.e. Google Maps API or Bing Maps API).
Hence the declarations below are for demonstration purpose only. -->
<!--<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}" MaxZoomLevel="20"/>
<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}" MaxZoomLevel="20" HasDarkBackground="True"/>
<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" MaxZoomLevel="20"/>
<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" MaxZoomLevel="20" HasDarkBackground="True"/>
<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" MaxZoomLevel="20" HasDarkBackground="True"/>-->
<!-- The TileLayer below uses an ImageTileSource, which bypasses caching of map tile images -->
<map:TileLayer Name="OSM Uncached" Description="© {y} OpenStreetMap Contributors, CC-BY-SA">
<map:TileLayer.TileSource>
<map:ImageTileSource UriFormat="http://{c}.tile.openstreetmap.org/{z}/{x}/{y}.png"/>
</map:TileLayer.TileSource>
</map:TileLayer>
</ComboBox.Items>
</ComboBox>
<ComboBox ToolTip="Main Tile Layer" Margin="4,0,0,0" DisplayMemberPath="Name"
ItemsSource="{Binding Source={StaticResource TileLayersView}}"/>
</StackPanel>
</Grid>
</Grid>

View file

@ -104,14 +104,14 @@ namespace SampleApplication
});
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(0.05);
timer.Interval = TimeSpan.FromSeconds(0.1);
timer.Tick += MovePoint;
timer.Start();
}
private void MovePoint(object sender, EventArgs e)
{
movingPoint.Location = new Location(movingPoint.Location.Latitude + 0.0005, movingPoint.Location.Longitude + 0.001);
movingPoint.Location = new Location(movingPoint.Location.Latitude + 0.001, movingPoint.Location.Longitude + 0.002);
if (movingPoint.Location.Latitude > 54d)
{

View file

@ -1,28 +0,0 @@
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
using MapControl;
namespace MapControlTestApp
{
class MapBackgroundConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
MapBackground mapBackground = (MapBackground)value;
if (parameter as string == "Foreground")
{
return mapBackground == MapBackground.Light ? Brushes.Black : Brushes.White;
}
return mapBackground == MapBackground.Light ? Brushes.White : Brushes.Black;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}