Improved tile rendering

This commit is contained in:
ClemensF 2012-06-18 20:12:35 +02:00
parent f74f4bf404
commit de8e9840b5
6 changed files with 56 additions and 89 deletions

View file

@ -567,22 +567,18 @@ namespace MapControl
From = Center, From = Center,
To = targetCenter, To = targetCenter,
Duration = TimeSpan.FromSeconds(0.5), Duration = TimeSpan.FromSeconds(0.5),
FillBehavior = FillBehavior.Stop,
EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut } EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut }
}; };
centerAnimation.Completed += CenterAnimationCompleted; centerAnimation.Completed += CenterAnimationCompleted;
updateTransform = false;
Center = targetCenter;
updateTransform = true;
BeginAnimation(CenterProperty, centerAnimation); BeginAnimation(CenterProperty, centerAnimation);
} }
} }
private void CenterAnimationCompleted(object sender, EventArgs eventArgs) private void CenterAnimationCompleted(object sender, EventArgs eventArgs)
{ {
Center = TargetCenter;
BeginAnimation(CenterProperty, null);
centerAnimation = null; centerAnimation = null;
} }
@ -620,22 +616,18 @@ namespace MapControl
From = ZoomLevel, From = ZoomLevel,
To = targetZoomLevel, To = targetZoomLevel,
Duration = TimeSpan.FromSeconds(0.5), Duration = TimeSpan.FromSeconds(0.5),
FillBehavior = FillBehavior.Stop,
EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut } EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut }
}; };
zoomLevelAnimation.Completed += ZoomLevelAnimationCompleted; zoomLevelAnimation.Completed += ZoomLevelAnimationCompleted;
updateTransform = false;
ZoomLevel = targetZoomLevel;
updateTransform = true;
BeginAnimation(ZoomLevelProperty, zoomLevelAnimation); BeginAnimation(ZoomLevelProperty, zoomLevelAnimation);
} }
} }
private void ZoomLevelAnimationCompleted(object sender, EventArgs eventArgs) private void ZoomLevelAnimationCompleted(object sender, EventArgs eventArgs)
{ {
ZoomLevel = TargetZoomLevel;
BeginAnimation(ZoomLevelProperty, null);
zoomLevelAnimation = null; zoomLevelAnimation = null;
ResetTransformOrigin(); ResetTransformOrigin();
} }
@ -683,22 +675,18 @@ namespace MapControl
From = Heading, From = Heading,
By = delta, By = delta,
Duration = TimeSpan.FromSeconds(0.5), Duration = TimeSpan.FromSeconds(0.5),
FillBehavior = FillBehavior.Stop,
EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut } EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut }
}; };
headingAnimation.Completed += HeadingAnimationCompleted; headingAnimation.Completed += HeadingAnimationCompleted;
updateTransform = false;
Heading = targetHeading;
updateTransform = true;
BeginAnimation(HeadingProperty, headingAnimation); BeginAnimation(HeadingProperty, headingAnimation);
} }
} }
private void HeadingAnimationCompleted(object sender, EventArgs eventArgs) private void HeadingAnimationCompleted(object sender, EventArgs eventArgs)
{ {
Heading = TargetHeading;
BeginAnimation(HeadingProperty, null);
headingAnimation = null; headingAnimation = null;
} }

View file

@ -54,7 +54,6 @@
<Compile Include="MapPolygon.cs" /> <Compile Include="MapPolygon.cs" />
<Compile Include="MapPolyline.cs" /> <Compile Include="MapPolyline.cs" />
<Compile Include="Pushpin.cs" /> <Compile Include="Pushpin.cs" />
<Compile Include="TileImageLoader.cs" />
<Compile Include="MapTransform.cs" /> <Compile Include="MapTransform.cs" />
<Compile Include="Map.cs" /> <Compile Include="Map.cs" />
<Compile Include="MapInput.cs" /> <Compile Include="MapInput.cs" />
@ -62,6 +61,7 @@
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Tile.cs" /> <Compile Include="Tile.cs" />
<Compile Include="TileContainer.cs" /> <Compile Include="TileContainer.cs" />
<Compile Include="TileImageLoader.cs" />
<Compile Include="TileLayer.cs" /> <Compile Include="TileLayer.cs" />
<Compile Include="TileLayerCollection.cs" /> <Compile Include="TileLayerCollection.cs" />
<Compile Include="TileSource.cs" /> <Compile Include="TileSource.cs" />

View file

@ -10,7 +10,7 @@ namespace MapControl
{ {
public partial class Map public partial class Map
{ {
private double mouseWheelZoom = 0.25; private double mouseWheelZoom = 1d;
private Point? mousePosition; private Point? mousePosition;
public double MouseWheelZoom public double MouseWheelZoom

View file

@ -9,8 +9,6 @@ using System.Windows.Media.Animation;
namespace MapControl namespace MapControl
{ {
public enum TileLoadState { NotLoaded, Loading, Loaded };
internal class Tile internal class Tile
{ {
private static readonly DoubleAnimation opacityAnimation = new DoubleAnimation(0d, 1d, TimeSpan.FromSeconds(0.5), FillBehavior.Stop); private static readonly DoubleAnimation opacityAnimation = new DoubleAnimation(0d, 1d, TimeSpan.FromSeconds(0.5), FillBehavior.Stop);
@ -18,18 +16,16 @@ namespace MapControl
public readonly int ZoomLevel; public readonly int ZoomLevel;
public readonly int X; public readonly int X;
public readonly int Y; public readonly int Y;
public readonly Uri Uri;
public readonly ImageBrush Brush = new ImageBrush(); public readonly ImageBrush Brush = new ImageBrush();
public Tile(TileSource tileSource, int zoomLevel, int x, int y) public Tile(int zoomLevel, int x, int y)
{ {
ZoomLevel = zoomLevel; ZoomLevel = zoomLevel;
X = x; X = x;
Y = y; Y = y;
Uri = tileSource.GetUri(XIndex, Y, ZoomLevel);
} }
public TileLoadState LoadState { get; set; } public Uri Uri { get; set; }
public int XIndex public int XIndex
{ {

View file

@ -28,18 +28,19 @@ namespace MapControl
internal int MaxDownloads; internal int MaxDownloads;
internal string TileLayerName; internal string TileLayerName;
internal TileSource TileSource;
internal int TilesPending private bool IsCached
{ {
get { return pendingTiles.Count; } get { return !string.IsNullOrEmpty(TileCacheFolder) && !string.IsNullOrEmpty(TileLayerName); }
} }
internal void BeginDownloadTiles(ICollection<Tile> tiles) internal void StartDownloadTiles(ICollection<Tile> tiles)
{ {
ThreadPool.QueueUserWorkItem(BeginDownloadTilesAsync, new List<Tile>(tiles.Where(t => t.LoadState == TileLoadState.NotLoaded))); ThreadPool.QueueUserWorkItem(StartDownloadTilesAsync, new List<Tile>(tiles.Where(t => t.Image == null && t.Uri == null)));
} }
internal void EndDownloadTiles() internal void StopDownloadTiles()
{ {
lock (pendingTiles) lock (pendingTiles)
{ {
@ -47,14 +48,13 @@ namespace MapControl
} }
} }
private void BeginDownloadTilesAsync(object newTilesList) private void StartDownloadTilesAsync(object newTilesList)
{ {
List<Tile> newTiles = (List<Tile>)newTilesList; List<Tile> newTiles = (List<Tile>)newTilesList;
lock (pendingTiles) lock (pendingTiles)
{ {
if (!string.IsNullOrEmpty(TileCacheFolder) && if (IsCached)
!string.IsNullOrEmpty(TileLayerName))
{ {
List<Tile> expiredTiles = new List<Tile>(newTiles.Count); List<Tile> expiredTiles = new List<Tile>(newTiles.Count);
@ -65,7 +65,6 @@ namespace MapControl
if (image != null) if (image != null)
{ {
tile.LoadState = TileLoadState.Loaded;
Dispatcher.BeginInvoke((Action)(() => tile.Image = image)); Dispatcher.BeginInvoke((Action)(() => tile.Image = image));
if (cacheExpired) if (cacheExpired)
@ -95,7 +94,7 @@ namespace MapControl
while (pendingTiles.Count > 0 && numDownloads < MaxDownloads) while (pendingTiles.Count > 0 && numDownloads < MaxDownloads)
{ {
Tile tile = pendingTiles.Dequeue(); Tile tile = pendingTiles.Dequeue();
tile.LoadState = TileLoadState.Loading; tile.Uri = TileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel);
numDownloads++; numDownloads++;
ThreadPool.QueueUserWorkItem(DownloadTileAsync, tile); ThreadPool.QueueUserWorkItem(DownloadTileAsync, tile);
@ -109,16 +108,12 @@ namespace MapControl
if (image != null) if (image != null)
{ {
tile.LoadState = TileLoadState.Loaded;
Dispatcher.BeginInvoke((Action)(() => tile.Image = image)); Dispatcher.BeginInvoke((Action)(() => tile.Image = image));
} }
else
{
tile.LoadState = TileLoadState.NotLoaded;
}
lock (pendingTiles) lock (pendingTiles)
{ {
tile.Uri = null;
numDownloads--; numDownloads--;
DownloadNextTiles(null); DownloadNextTiles(null);
} }
@ -134,25 +129,25 @@ namespace MapControl
{ {
if (Directory.Exists(tileDir)) if (Directory.Exists(tileDir))
{ {
string[] tilePath = Directory.GetFiles(tileDir, string.Format("{0}.*", tile.Y)); string tilePath = Directory.GetFiles(tileDir, string.Format("{0}.*", tile.Y)).FirstOrDefault();
if (tilePath.Length > 0) if (tilePath != null)
{ {
try try
{ {
using (Stream fileStream = File.OpenRead(tilePath[0])) using (Stream fileStream = File.OpenRead(tilePath))
{ {
image = BitmapFrame.Create(fileStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); image = BitmapFrame.Create(fileStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
} }
expired = File.GetLastWriteTime(tilePath[0]) + TileCacheExpiryAge <= DateTime.Now; expired = File.GetLastWriteTime(tilePath) + TileCacheExpiryAge <= DateTime.Now;
TraceInformation(expired ? "{0} - Cache Expired" : "{0} - Cached", tile.Uri); TraceInformation(expired ? "{0} - Cache Expired" : "{0} - Cached", tilePath);
} }
catch (Exception exc) catch (Exception exc)
{ {
TraceWarning("{0} - {1}", tilePath[0], exc.Message); TraceWarning("{0} - {1}", tilePath, exc.Message);
File.Delete(tilePath[0]); File.Delete(tilePath);
} }
} }
} }
@ -191,9 +186,7 @@ namespace MapControl
string tilePath; string tilePath;
if (!string.IsNullOrEmpty(TileCacheFolder) && if (IsCached && (tilePath = TilePath(tile, decoder)) != null)
!string.IsNullOrEmpty(TileLayerName) &&
(tilePath = TilePath(tile, decoder)) != null)
{ {
Directory.CreateDirectory(Path.GetDirectoryName(tilePath)); Directory.CreateDirectory(Path.GetDirectoryName(tilePath));

View file

@ -35,7 +35,6 @@ namespace MapControl
MaxDownloads = 8; MaxDownloads = 8;
} }
public TileSource TileSource { get; set; }
public bool HasDarkBackground { get; set; } public bool HasDarkBackground { get; set; }
public int MinZoomLevel { get; set; } public int MinZoomLevel { get; set; }
public int MaxZoomLevel { get; set; } public int MaxZoomLevel { get; set; }
@ -46,6 +45,12 @@ namespace MapControl
set { tileImageLoader.MaxDownloads = value; } set { tileImageLoader.MaxDownloads = value; }
} }
public TileSource TileSource
{
get { return tileImageLoader.TileSource; }
set { tileImageLoader.TileSource = value; }
}
public bool IsCached public bool IsCached
{ {
get { return isCached; } get { return isCached; }
@ -83,68 +88,51 @@ namespace MapControl
this.grid = grid; this.grid = grid;
this.zoomLevel = zoomLevel; this.zoomLevel = zoomLevel;
tileImageLoader.EndDownloadTiles(); tileImageLoader.StopDownloadTiles();
if (VisualParent != null && TileSource != null) if (VisualParent != null && TileSource != null)
{ {
SelectTiles(); SelectTiles();
RenderTiles(); RenderTiles();
tileImageLoader.BeginDownloadTiles(tiles); tileImageLoader.StartDownloadTiles(tiles);
} }
} }
public void ClearTiles() public void ClearTiles()
{ {
tiles.Clear(); tiles.Clear();
tileImageLoader.EndDownloadTiles(); tileImageLoader.StopDownloadTiles();
}
private Int32Rect GetTileGrid(int tileZoomLevel)
{
int tileSize = 1 << (zoomLevel - tileZoomLevel);
int max = (1 << tileZoomLevel) - 1;
int x1 = grid.X / tileSize - 1;
int x2 = (grid.X + grid.Width - 1) / tileSize + 1;
int y1 = Math.Max(0, grid.Y / tileSize - 1);
int y2 = Math.Min(max, (grid.Y + grid.Height - 1) / tileSize + 1);
return new Int32Rect(x1, y1, x2 - x1 + 1, y2 - y1 + 1);
} }
private void SelectTiles() private void SelectTiles()
{ {
TileContainer tileContainer = VisualParent as TileContainer; TileContainer tileContainer = VisualParent as TileContainer;
int maxZoom = Math.Min(zoomLevel, MaxZoomLevel); int maxZoomLevel = Math.Min(zoomLevel, MaxZoomLevel);
int minZoom = maxZoom; int minZoomLevel = maxZoomLevel;
if (tileContainer != null && tileContainer.TileLayers.IndexOf(this) == 0) if (tileContainer != null && tileContainer.TileLayers.IndexOf(this) == 0)
{ {
minZoom = MinZoomLevel; minZoomLevel = MinZoomLevel;
} }
tiles.RemoveAll(t => tiles.RemoveAll(t => t.ZoomLevel < minZoomLevel || t.ZoomLevel > maxZoomLevel);
{
if (t.ZoomLevel > maxZoom || t.ZoomLevel < minZoom)
{
return true;
}
Int32Rect tileGrid = GetTileGrid(t.ZoomLevel); for (int z = minZoomLevel; z <= maxZoomLevel; z++)
return t.X < tileGrid.X || t.X >= tileGrid.X + tileGrid.Width || t.Y < tileGrid.Y || t.Y >= tileGrid.Y + tileGrid.Height; {
}); int tileSize = 1 << (zoomLevel - z);
int x1 = grid.X / tileSize;
int x2 = (grid.X + grid.Width - 1) / tileSize;
int y1 = Math.Max(0, grid.Y / tileSize);
int y2 = Math.Min((1 << z) - 1, (grid.Y + grid.Height - 1) / tileSize);
for (int tileZoomLevel = minZoom; tileZoomLevel <= maxZoom; tileZoomLevel++) for (int y = y1; y <= y2; y++)
{ {
Int32Rect tileGrid = GetTileGrid(tileZoomLevel); for (int x = x1; x <= x2; x++)
for (int y = tileGrid.Y; y < tileGrid.Y + tileGrid.Height; y++)
{ {
for (int x = tileGrid.X; x < tileGrid.X + tileGrid.Width; x++) if (tiles.Find(t => t.ZoomLevel == z && t.X == x && t.Y == y) == null)
{ {
if (tiles.Find(t => t.ZoomLevel == tileZoomLevel && t.X == x && t.Y == y) == null) Tile tile = new Tile(z, x, y);
{
Tile tile = new Tile(TileSource, tileZoomLevel, x, y);
Tile equivalent = tiles.Find(t => t.Image != null && t.ZoomLevel == tile.ZoomLevel && t.XIndex == tile.XIndex && t.Y == tile.Y); Tile equivalent = tiles.Find(t => t.Image != null && t.ZoomLevel == tile.ZoomLevel && t.XIndex == tile.XIndex && t.Y == tile.Y);
if (equivalent != null) if (equivalent != null)
@ -156,11 +144,13 @@ namespace MapControl
} }
} }
} }
tiles.RemoveAll(t => t.ZoomLevel == z && (t.X < x1 || t.X > x2 || t.Y < y1 || t.Y > y2));
} }
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()