Version 2.1.0:

- TileImageLoader with local file caching in WinRT
- Location implements IEquatable
- Removed Surface sample application
This commit is contained in:
ClemensF 2014-07-09 21:27:28 +02:00
parent 10527c3f0d
commit 4e0253aa70
38 changed files with 493 additions and 393 deletions

View file

@ -38,16 +38,15 @@ namespace MapControl
public static void DrawGlyphRun(this DrawingContext drawingContext, Brush foreground, GlyphRun glyphRun,
Point position, HorizontalAlignment horizontalAlignment, VerticalAlignment verticalAlignment)
{
var bbox = glyphRun.ComputeInkBoundingBox();
var transform = new TranslateTransform(position.X - bbox.X, position.Y - bbox.Y);
var boundingBox = glyphRun.ComputeInkBoundingBox();
switch (horizontalAlignment)
{
case HorizontalAlignment.Center:
transform.X -= bbox.Width / 2d;
position.X -= boundingBox.Width / 2d;
break;
case HorizontalAlignment.Right:
transform.X -= bbox.Width;
position.X -= boundingBox.Width;
break;
default:
break;
@ -56,16 +55,16 @@ namespace MapControl
switch (verticalAlignment)
{
case VerticalAlignment.Center:
transform.Y -= bbox.Height / 2d;
position.Y -= boundingBox.Height / 2d;
break;
case VerticalAlignment.Bottom:
transform.Y -= bbox.Height;
position.Y -= boundingBox.Height;
break;
default:
break;
}
drawingContext.PushTransform(transform);
drawingContext.PushTransform(new TranslateTransform(position.X - boundingBox.X, position.Y - boundingBox.Y));
drawingContext.DrawGlyphRun(foreground, glyphRun);
drawingContext.Pop();
}

View file

@ -0,0 +1,14 @@
// XAML Map Control - http://xamlmapcontrol.codeplex.com/
// Copyright © 2014 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System.Threading.Tasks;
namespace MapControl
{
public interface IObjectCache
{
Task<object> GetAsync(string key);
Task SetAsync(string key, object value);
}
}

View file

@ -0,0 +1,62 @@
// XAML Map Control - http://xamlmapcontrol.codeplex.com/
// Copyright © 2014 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.Storage.Streams;
namespace MapControl
{
public class ImageFileCache : IObjectCache
{
private readonly IStorageFolder rootFolder;
public ImageFileCache()
{
rootFolder = ApplicationData.Current.TemporaryFolder;
}
public ImageFileCache(IStorageFolder folder)
{
rootFolder = folder;
}
public async Task<object> GetAsync(string key)
{
try
{
return await PathIO.ReadBufferAsync(Path.Combine(rootFolder.Path, key));
}
catch
{
return null;
}
}
public async Task SetAsync(string key, object value)
{
try
{
var buffer = (IBuffer)value;
var names = key.Split('\\');
var folder = rootFolder;
for (int i = 0; i < names.Length - 1; i++)
{
folder = await folder.CreateFolderAsync(names[i], CreationCollisionOption.OpenIfExists);
}
var file = await folder.CreateFileAsync(names[names.Length - 1], CreationCollisionOption.ReplaceExisting);
await FileIO.WriteBufferAsync(file, buffer);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
}
}

View file

@ -10,7 +10,7 @@ namespace MapControl
/// <summary>
/// A geographic location with latitude and longitude values in degrees.
/// </summary>
public partial class Location
public partial class Location : IEquatable<Location>
{
private double latitude;
private double longitude;
@ -37,6 +37,23 @@ namespace MapControl
set { longitude = value; }
}
public bool Equals(Location location)
{
return location != null
&& location.latitude == latitude
&& location.longitude == longitude;
}
public override bool Equals(object obj)
{
return Equals(obj as Location);
}
public override int GetHashCode()
{
return latitude.GetHashCode() ^ longitude.GetHashCode();
}
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "{0:F5},{1:F5}", latitude, longitude);

View file

@ -579,7 +579,7 @@ namespace MapControl
{
AdjustCenterProperty(TargetCenterProperty, ref targetCenter);
if (targetCenter.Latitude != Center.Latitude || targetCenter.Longitude != Center.Longitude)
if (!targetCenter.Equals(Center))
{
if (centerAnimation != null)
{

View file

@ -106,7 +106,7 @@
<Compile Include="Tile.Silverlight.WinRT.cs" />
<Compile Include="TileContainer.cs" />
<Compile Include="TileContainer.Silverlight.WinRT.cs" />
<Compile Include="TileImageLoader.Silverlight.WinRT.cs" />
<Compile Include="TileImageLoader.Silverlight.cs" />
<Compile Include="TileLayer.cs" />
<Compile Include="TileLayerCollection.cs" />
<Compile Include="TileSource.cs" />

View file

@ -3,10 +3,10 @@
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Collections.Generic;
using System.Linq;
#if WINDOWS_RUNTIME
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
@ -31,7 +31,7 @@ namespace MapControl
public MapGraticule()
{
IsHitTestVisible = false;
StrokeThickness = 0.5;
Stroke = new SolidColorBrush(Color.FromArgb(127, 0, 0, 0));
path = new Path
{
@ -55,7 +55,6 @@ namespace MapControl
protected override void OnViewportChanged()
{
var geometry = (PathGeometry)path.Data;
var bounds = ParentMap.ViewportTransform.Inverse.TransformBounds(new Rect(new Point(), ParentMap.RenderSize));
var start = ParentMap.MapTransform.Transform(new Point(bounds.X, bounds.Y));
var end = ParentMap.MapTransform.Transform(new Point(bounds.X + bounds.Width, bounds.Y + bounds.Height));
@ -88,28 +87,22 @@ namespace MapControl
graticuleStart = lineStart;
graticuleEnd = lineEnd;
var geometry = (PathGeometry)path.Data;
geometry.Figures.Clear();
geometry.Transform = ParentMap.ViewportTransform;
var latLocations = new List<Location>((int)((end.Latitude - labelStart.Latitude) / spacing) + 1);
for (var lat = labelStart.Latitude; lat <= end.Latitude; lat += spacing)
{
var location = new Location(lat, lineStart.Longitude);
latLocations.Add(location);
var figure = new PathFigure
{
StartPoint = ParentMap.MapTransform.Transform(location),
StartPoint = ParentMap.MapTransform.Transform(new Location(lat, lineStart.Longitude)),
IsClosed = false,
IsFilled = false
};
location.Longitude = lineEnd.Longitude;
figure.Segments.Add(new LineSegment
{
Point = ParentMap.MapTransform.Transform(location),
Point = ParentMap.MapTransform.Transform(new Location(lat, lineEnd.Longitude)),
});
geometry.Figures.Add(figure);
@ -134,14 +127,11 @@ namespace MapControl
var childIndex = 1; // 0 for Path
var format = spacing < 1d ? "{0} {1}°{2:00}'" : "{0} {1}°";
var measureSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
foreach (var location in latLocations)
for (var lat = labelStart.Latitude; lat <= end.Latitude; lat += spacing)
{
for (var lon = labelStart.Longitude; lon <= end.Longitude; lon += spacing)
{
location.Longitude = lon;
TextBlock label;
if (childIndex < Children.Count)
@ -150,9 +140,14 @@ namespace MapControl
}
else
{
var renderTransform = new TransformGroup();
renderTransform.Children.Add(new TranslateTransform());
renderTransform.Children.Add(ParentMap.RotateTransform);
renderTransform.Children.Add(new TranslateTransform());
label = new TextBlock
{
RenderTransform = new TransformGroup()
RenderTransform = renderTransform
};
label.SetBinding(TextBlock.ForegroundProperty, new Binding
@ -175,30 +170,13 @@ namespace MapControl
label.FontStyle = FontStyle;
label.FontStretch = FontStretch;
label.FontWeight = FontWeight;
label.Text = string.Format("{0}\n{1}", CoordinateString(lat, format, "NS"), CoordinateString(Location.NormalizeLongitude(lon), format, "EW"));
label.Tag = new Location(lat, lon);
label.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
label.Text = string.Format("{0}\n{1}",
CoordinateString(location.Latitude, format, "NS"),
CoordinateString(Location.NormalizeLongitude(location.Longitude), format, "EW"));
label.Measure(measureSize);
var transformGroup = (TransformGroup)label.RenderTransform;
if (transformGroup.Children.Count == 0)
{
transformGroup.Children.Add(new TranslateTransform());
transformGroup.Children.Add(ParentMap.RotateTransform);
transformGroup.Children.Add(new TranslateTransform());
}
var translateTransform = (TranslateTransform)transformGroup.Children[0];
var translateTransform = (TranslateTransform)((TransformGroup)label.RenderTransform).Children[0];
translateTransform.X = StrokeThickness / 2d + 2d;
translateTransform.Y = -label.DesiredSize.Height / 2d;
var viewportPosition = ParentMap.LocationToViewportPoint(location);
translateTransform = (TranslateTransform)transformGroup.Children[2];
translateTransform.X = viewportPosition.X;
translateTransform.Y = viewportPosition.Y;
}
}
@ -208,6 +186,18 @@ namespace MapControl
}
}
// don't use MapPanel.Location because labels may be at more than 180° distance from map center
for (int i = 1; i < Children.Count; i++)
{
var label = (TextBlock)Children[i];
var location = (Location)label.Tag;
var viewportTransform = (TranslateTransform)((TransformGroup)label.RenderTransform).Children[2];
var viewportPosition = ParentMap.LocationToViewportPoint(location);
viewportTransform.X = viewportPosition.X;
viewportTransform.Y = viewportPosition.Y;
}
base.OnViewportChanged();
}
}

View file

@ -2,6 +2,7 @@
// Copyright © 2014 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
#if WINDOWS_RUNTIME
using Windows.UI.Xaml;
#else
@ -43,7 +44,7 @@ namespace MapControl
hemisphere = hemispheres[1];
}
var minutes = (int)(value * 60d + 0.5);
var minutes = (int)Math.Round(value * 60d);
return string.Format(format, hemisphere, minutes / 60, (double)(minutes % 60));
}

View file

@ -14,8 +14,8 @@ using System.Windows;
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.0.0")]
[assembly: AssemblyFileVersion("2.0.0")]
[assembly: AssemblyVersion("2.1.0")]
[assembly: AssemblyFileVersion("2.1.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]

View file

@ -13,12 +13,6 @@ namespace MapControl
{
internal partial class TileContainer
{
private Matrix GetViewportTransformMatrix(double scale, double offsetX, double offsetY)
{
return new Matrix(scale, 0d, 0d, -scale, offsetX, offsetY)
.RotateAt(rotation, viewportOrigin.X, viewportOrigin.Y);
}
private Matrix GetTileIndexMatrix(int numTiles)
{
var scale = (double)numTiles / 360d;
@ -29,6 +23,13 @@ namespace MapControl
.Scale(scale, -scale); // map coordinates to tile indices
}
private void UpdateViewportTransform(double scale, double offsetX, double offsetY)
{
ViewportTransform.Matrix =
new Matrix(scale, 0d, 0d, -scale, offsetX, offsetY)
.RotateAt(rotation, viewportOrigin.X, viewportOrigin.Y);
}
/// <summary>
/// Sets a RenderTransform with origin at tileGrid.X and tileGrid.Y to minimize rounding errors.
/// </summary>

View file

@ -9,14 +9,6 @@ namespace MapControl
{
internal partial class TileContainer
{
private Matrix GetViewportTransformMatrix(double scale, double offsetX, double offsetY)
{
var transform = new Matrix(scale, 0d, 0d, -scale, offsetX, offsetY);
transform.RotateAt(rotation, viewportOrigin.X, viewportOrigin.Y);
return transform;
}
private Matrix GetTileIndexMatrix(int numTiles)
{
var scale = (double)numTiles / 360d;
@ -28,6 +20,14 @@ namespace MapControl
return transform;
}
private void UpdateViewportTransform(double scale, double offsetX, double offsetY)
{
var transform = new Matrix(scale, 0d, 0d, -scale, offsetX, offsetY);
transform.RotateAt(rotation, viewportOrigin.X, viewportOrigin.Y);
ViewportTransform.Matrix = transform;
}
/// <summary>
/// Sets a RenderTransform with origin at tileGrid.X and tileGrid.Y to minimize rounding errors.
/// </summary>

View file

@ -22,7 +22,7 @@ namespace MapControl
// relative scaled tile size ranges from 0.75 to 1.5 (192 to 384 pixels)
private static double zoomLevelSwitchDelta = -Math.Log(0.75, 2d);
internal static TimeSpan UpdateInterval = TimeSpan.FromSeconds(0.5);
public static TimeSpan UpdateInterval = TimeSpan.FromSeconds(0.5);
private readonly DispatcherTimer updateTimer;
private Size viewportSize;
@ -102,7 +102,7 @@ namespace MapControl
var transformOffsetX = viewportOrigin.X - mapOrigin.X * scale;
var transformOffsetY = viewportOrigin.Y + mapOrigin.Y * scale;
ViewportTransform.Matrix = GetViewportTransformMatrix(scale, transformOffsetX, transformOffsetY);
UpdateViewportTransform(scale, transformOffsetX, transformOffsetY);
tileLayerOffset.X = transformOffsetX - 180d * scale;
tileLayerOffset.Y = transformOffsetY - 180d * scale;

View file

@ -1,64 +0,0 @@
// XAML Map Control - http://xamlmapcontrol.codeplex.com/
// Copyright © 2014 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Collections.Generic;
using System.Diagnostics;
#if WINDOWS_RUNTIME
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
#else
using System.Windows.Media;
using System.Windows.Media.Imaging;
#endif
namespace MapControl
{
/// <summary>
/// Loads map tile images.
/// </summary>
internal class TileImageLoader
{
internal void BeginGetTiles(TileLayer tileLayer, IEnumerable<Tile> tiles)
{
var imageTileSource = tileLayer.TileSource as ImageTileSource;
if (imageTileSource != null)
{
foreach (var tile in tiles)
{
try
{
var image = imageTileSource.LoadImage(tile.XIndex, tile.Y, tile.ZoomLevel);
tile.SetImageSource(image, tileLayer.AnimateTileOpacity);
}
catch (Exception ex)
{
Debug.WriteLine("Loading tile image failed: {0}", ex.Message);
}
}
}
else
{
foreach (var tile in tiles)
{
try
{
var uri = tileLayer.TileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel);
var image = uri != null ? new BitmapImage(uri) : null;
tile.SetImageSource(image, tileLayer.AnimateTileOpacity);
}
catch (Exception ex)
{
Debug.WriteLine("Creating tile image failed: {0}", ex.Message);
}
}
}
}
public void CancelGetTiles()
{
}
}
}

View file

@ -0,0 +1,55 @@
// XAML Map Control - http://xamlmapcontrol.codeplex.com/
// Copyright © 2014 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace MapControl
{
/// <summary>
/// Loads map tile images.
/// </summary>
internal class TileImageLoader
{
internal void BeginGetTiles(TileLayer tileLayer, IEnumerable<Tile> tiles)
{
var imageTileSource = tileLayer.TileSource as ImageTileSource;
foreach (var tile in tiles)
{
try
{
ImageSource image = null;
if (imageTileSource != null)
{
image = imageTileSource.LoadImage(tile.XIndex, tile.Y, tile.ZoomLevel);
}
else
{
var uri = tileLayer.TileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel);
if (uri != null)
{
image = new BitmapImage(uri);
}
}
tile.SetImageSource(image, tileLayer.AnimateTileOpacity);
}
catch (Exception ex)
{
Debug.WriteLine("Loading tile image failed: {0}", ex.Message);
}
}
}
internal void CancelGetTiles()
{
}
}
}

View file

@ -0,0 +1,196 @@
// XAML Map Control - http://xamlmapcontrol.codeplex.com/
// Copyright © 2014 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Storage.Streams;
using Windows.UI.Core;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
using Windows.Web.Http;
using Windows.Web.Http.Filters;
namespace MapControl
{
/// <summary>
/// Loads map tile images.
/// </summary>
public class TileImageLoader
{
public static IObjectCache Cache { get; set; }
private HttpClient httpClient;
internal void BeginGetTiles(TileLayer tileLayer, IEnumerable<Tile> tiles)
{
var imageTileSource = tileLayer.TileSource as ImageTileSource;
foreach (var tile in tiles)
{
try
{
ImageSource image = null;
if (imageTileSource != null)
{
image = imageTileSource.LoadImage(tile.XIndex, tile.Y, tile.ZoomLevel);
}
else
{
var uri = tileLayer.TileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel);
if (uri != null)
{
if (Cache == null || string.IsNullOrEmpty(tileLayer.SourceName))
{
image = new BitmapImage(uri);
}
else
{
var bitmap = new BitmapImage();
image = bitmap;
Task.Run(async () => await LoadCachedImage(tileLayer, tile, uri, bitmap));
}
}
}
tile.SetImageSource(image, tileLayer.AnimateTileOpacity);
}
catch (Exception ex)
{
Debug.WriteLine("Loading tile image failed: {0}", ex.Message);
}
}
}
internal void CancelGetTiles()
{
}
private async Task LoadCachedImage(TileLayer tileLayer, Tile tile, Uri uri, BitmapImage bitmap)
{
var cacheKey = string.Format(@"{0}\{1}\{2}\{3}{4}",
tileLayer.SourceName, tile.ZoomLevel, tile.XIndex, tile.Y, Path.GetExtension(uri.LocalPath));
var buffer = await Cache.GetAsync(cacheKey) as IBuffer;
if (buffer != null)
{
await LoadImageFromBuffer(buffer, bitmap);
//Debug.WriteLine("Loaded cached image {0}", cacheKey);
}
else
{
DownloadAndCacheImage(uri, bitmap, cacheKey);
}
}
private async Task LoadImageFromBuffer(IBuffer buffer, BitmapImage bitmap)
{
using (var stream = new InMemoryRandomAccessStream())
{
await stream.WriteAsync(buffer);
await stream.FlushAsync();
stream.Seek(0);
await bitmap.Dispatcher.RunAsync(CoreDispatcherPriority.Low, async () =>
{
try
{
await bitmap.SetSourceAsync(stream);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
});
}
}
private void DownloadAndCacheImage(Uri uri, BitmapImage bitmap, string cacheKey)
{
try
{
if (httpClient == null)
{
var filter = new HttpBaseProtocolFilter();
filter.AllowAutoRedirect = false;
filter.CacheControl.ReadBehavior = HttpCacheReadBehavior.Default;
filter.CacheControl.WriteBehavior = HttpCacheWriteBehavior.NoCache;
httpClient = new HttpClient(filter);
}
httpClient.GetAsync(uri).Completed = async (request, status) =>
{
if (status == AsyncStatus.Completed)
{
using (var response = request.GetResults())
{
await LoadImageFromHttpResponse(response, bitmap, cacheKey);
}
}
else
{
Debug.WriteLine("{0}: {1}", uri, request.ErrorCode != null ? request.ErrorCode.Message : status.ToString());
}
};
}
catch (Exception ex)
{
Debug.WriteLine("{0}: {1}", uri, ex.Message);
}
}
private async Task LoadImageFromHttpResponse(HttpResponseMessage response, BitmapImage bitmap, string cacheKey)
{
if (response.IsSuccessStatusCode)
{
var stream = new InMemoryRandomAccessStream();
using (var content = response.Content)
{
await content.WriteToStreamAsync(stream);
}
await stream.FlushAsync();
stream.Seek(0);
await bitmap.Dispatcher.RunAsync(CoreDispatcherPriority.Low, async () =>
{
try
{
await bitmap.SetSourceAsync(stream);
// cache image asynchronously, after successful decoding
var task = Task.Run(async () =>
{
var buffer = new Windows.Storage.Streams.Buffer((uint)stream.Size);
stream.Seek(0);
await stream.ReadAsync(buffer, buffer.Capacity, InputStreamOptions.None);
stream.Dispose();
await Cache.SetAsync(cacheKey, buffer);
});
}
catch (Exception ex)
{
Debug.WriteLine("{0}: {1}", response.RequestMessage.RequestUri, ex.Message);
stream.Dispose();
}
});
}
else
{
Debug.WriteLine("{0}: {1}", response.RequestMessage.RequestUri, response.StatusCode);
}
}
}
}

View file

@ -42,6 +42,9 @@
<Compile Include="..\Extensions.WinRT.cs">
<Link>Extensions.WinRT.cs</Link>
</Compile>
<Compile Include="..\ImageFileCache.WinRT.cs">
<Link>ImageFileCache.WinRT.cs</Link>
</Compile>
<Compile Include="..\ImageTileSource.Silverlight.WinRT.cs">
<Link>ImageTileSource.Silverlight.WinRT.cs</Link>
</Compile>
@ -51,6 +54,9 @@
<Compile Include="..\Int32Rect.cs">
<Link>Int32Rect.cs</Link>
</Compile>
<Compile Include="..\IObjectCache.WinRT.cs">
<Link>IObjectCache.WinRT.cs</Link>
</Compile>
<Compile Include="..\Location.cs">
<Link>Location.cs</Link>
</Compile>
@ -138,8 +144,8 @@
<Compile Include="..\TileContainer.Silverlight.WinRT.cs">
<Link>TileContainer.Silverlight.WinRT.cs</Link>
</Compile>
<Compile Include="..\TileImageLoader.Silverlight.WinRT.cs">
<Link>TileImageLoader.Silverlight.WinRT.cs</Link>
<Compile Include="..\TileImageLoader.WinRT.cs">
<Link>TileImageLoader.WinRT.cs</Link>
</Compile>
<Compile Include="..\TileLayer.cs">
<Link>TileLayer.cs</Link>

View file

@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.0.0")]
[assembly: AssemblyFileVersion("2.0.0")]
[assembly: AssemblyVersion("2.1.0")]
[assembly: AssemblyFileVersion("2.1.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]