mirror of
https://github.com/ClemensFischer/XAML-Map-Control.git
synced 2025-12-06 07:12:04 +01:00
Improved tile rendering
This commit is contained in:
parent
f74f4bf404
commit
de8e9840b5
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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" />
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
for (int z = minZoomLevel; z <= maxZoomLevel; z++)
|
||||||
{
|
{
|
||||||
if (t.ZoomLevel > maxZoom || t.ZoomLevel < minZoom)
|
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 y = y1; y <= y2; y++)
|
||||||
{
|
{
|
||||||
return true;
|
for (int x = x1; x <= x2; x++)
|
||||||
}
|
|
||||||
|
|
||||||
Int32Rect tileGrid = GetTileGrid(t.ZoomLevel);
|
|
||||||
return t.X < tileGrid.X || t.X >= tileGrid.X + tileGrid.Width || t.Y < tileGrid.Y || t.Y >= tileGrid.Y + tileGrid.Height;
|
|
||||||
});
|
|
||||||
|
|
||||||
for (int tileZoomLevel = minZoom; tileZoomLevel <= maxZoom; tileZoomLevel++)
|
|
||||||
{
|
|
||||||
Int32Rect tileGrid = GetTileGrid(tileZoomLevel);
|
|
||||||
|
|
||||||
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 == tileZoomLevel && t.X == x && t.Y == y) == null)
|
if (tiles.Find(t => t.ZoomLevel == z && t.X == x && t.Y == y) == null)
|
||||||
{
|
{
|
||||||
Tile tile = new Tile(TileSource, tileZoomLevel, x, y);
|
Tile tile = new Tile(z, 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()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue