mirror of
https://github.com/ClemensFischer/XAML-Map-Control.git
synced 2026-04-05 06:26:41 +00:00
Version 2.4.0.
- Added ImageFileCache and FileDbCache for WinRT - Improved TileImageLoader - Removed TileContainer, TileLayer can be added as MapBase child - Removed attached property MapPanel.ViewportPosition
This commit is contained in:
parent
f04025067c
commit
3b6545e738
84 changed files with 5504 additions and 1940 deletions
|
|
@ -57,8 +57,7 @@ namespace MapControl
|
|||
var request = (HttpWebRequest)asyncResult.AsyncState;
|
||||
|
||||
using (var response = request.EndGetResponse(asyncResult))
|
||||
using (var responseStream = response.GetResponseStream())
|
||||
using (var xmlReader = XmlReader.Create(responseStream))
|
||||
using (var xmlReader = XmlReader.Create(response.GetResponseStream()))
|
||||
{
|
||||
ReadImageryMetadataResponse(xmlReader);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ namespace MapControl
|
|||
/// Converts text containing hyperlinks in markdown syntax [text](url)
|
||||
/// to a collection of Run and Hyperlink inlines.
|
||||
/// </summary>
|
||||
public static ICollection<Inline> ToInlines(this string text)
|
||||
public static List<Inline> ToInlines(this string text)
|
||||
{
|
||||
var inlines = new List<Inline>();
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ namespace MapControl
|
|||
{
|
||||
public interface IMapElement
|
||||
{
|
||||
MapBase ParentMap { get; }
|
||||
|
||||
void SetParentMap(MapBase parentMap);
|
||||
MapBase ParentMap { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
14
MapControl/ITileImageLoader.cs
Normal file
14
MapControl/ITileImageLoader.cs
Normal 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.Collections.Generic;
|
||||
|
||||
namespace MapControl
|
||||
{
|
||||
public interface ITileImageLoader
|
||||
{
|
||||
void BeginLoadTiles(TileLayer tileLayer, IEnumerable<Tile> tiles);
|
||||
void CancelLoadTiles(TileLayer tileLayer);
|
||||
}
|
||||
}
|
||||
22
MapControl/ImageCache.WinRT.cs
Normal file
22
MapControl/ImageCache.WinRT.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// XAML Map Control - http://xamlmapcontrol.codeplex.com/
|
||||
// Copyright © 2014 Clemens Fischer
|
||||
// Licensed under the Microsoft Public License (Ms-PL)
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace MapControl.Caching
|
||||
{
|
||||
public class ImageCacheItem
|
||||
{
|
||||
public IBuffer Buffer { get; set; }
|
||||
public DateTime Expires { get; set; }
|
||||
}
|
||||
|
||||
public interface IImageCache
|
||||
{
|
||||
Task<ImageCacheItem> GetAsync(string key);
|
||||
Task SetAsync(string key, IBuffer buffer, DateTime expires);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,67 +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.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)
|
||||
{
|
||||
if (folder == null)
|
||||
{
|
||||
throw new ArgumentNullException("The parameter folder must not be null.");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -30,15 +30,16 @@ namespace MapControl
|
|||
{
|
||||
if (IsAsync)
|
||||
{
|
||||
var buffer = new WebClient().DownloadData(uri);
|
||||
var request = HttpWebRequest.CreateHttp(uri);
|
||||
request.UserAgent = TileImageLoader.HttpUserAgent;
|
||||
|
||||
if (buffer != null)
|
||||
using (var response = (HttpWebResponse)request.GetResponse())
|
||||
using (var responseStream = response.GetResponseStream())
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
using (var stream = new MemoryStream(buffer))
|
||||
{
|
||||
image = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
|
||||
image.Freeze();
|
||||
}
|
||||
responseStream.CopyTo(memoryStream);
|
||||
memoryStream.Position = 0;
|
||||
image = BitmapFrame.Create(memoryStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
namespace MapControl
|
||||
{
|
||||
internal struct Int32Rect
|
||||
public struct Int32Rect
|
||||
{
|
||||
public Int32Rect(int x, int y, int width, int height)
|
||||
: this()
|
||||
|
|
|
|||
|
|
@ -2,14 +2,17 @@
|
|||
// Copyright © 2014 Clemens Fischer
|
||||
// Licensed under the Microsoft Public License (Ms-PL)
|
||||
|
||||
using System;
|
||||
#if WINDOWS_RUNTIME
|
||||
using Windows.Foundation;
|
||||
using Windows.UI;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Media;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
#else
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Controls;
|
||||
#endif
|
||||
|
||||
namespace MapControl
|
||||
|
|
@ -45,7 +48,11 @@ namespace MapControl
|
|||
|
||||
partial void Initialize()
|
||||
{
|
||||
Background = new SolidColorBrush(Colors.Transparent);
|
||||
// set Background by Style to enable resetting by ClearValue in RemoveTileLayers
|
||||
var style = new Style(typeof(MapBase));
|
||||
style.Setters.Add(new Setter(Panel.BackgroundProperty, new SolidColorBrush(Colors.Transparent)));
|
||||
Style = style;
|
||||
|
||||
Clip = new RectangleGeometry();
|
||||
|
||||
SizeChanged += OnRenderSizeChanged;
|
||||
|
|
@ -58,11 +65,39 @@ namespace MapControl
|
|||
UpdateTransform();
|
||||
}
|
||||
|
||||
private void SetViewportTransform(Point mapOrigin)
|
||||
{
|
||||
viewportTransform.Matrix = Matrix.Identity
|
||||
.Translate(-mapOrigin.X, -mapOrigin.Y)
|
||||
.Scale(ViewportScale, -ViewportScale)
|
||||
.Rotate(Heading)
|
||||
.Translate(viewportOrigin.X, viewportOrigin.Y);
|
||||
}
|
||||
|
||||
private void SetTileLayerTransform()
|
||||
{
|
||||
var scale = Math.Pow(2d, ZoomLevel - TileZoomLevel);
|
||||
|
||||
tileLayerTransform.Matrix = Matrix.Identity
|
||||
.Translate(TileGrid.X * TileSource.TileSize, TileGrid.Y * TileSource.TileSize)
|
||||
.Scale(scale, scale)
|
||||
.Translate(tileLayerOffset.X, tileLayerOffset.Y)
|
||||
.RotateAt(Heading, viewportOrigin.X, viewportOrigin.Y); ;
|
||||
}
|
||||
|
||||
private void SetTransformMatrixes()
|
||||
{
|
||||
scaleTransform.Matrix = new Matrix(CenterScale, 0d, 0d, CenterScale, 0d, 0d);
|
||||
scaleTransform.Matrix = Matrix.Identity.Scale(CenterScale, CenterScale);
|
||||
rotateTransform.Matrix = Matrix.Identity.Rotate(Heading);
|
||||
scaleRotateTransform.Matrix = scaleTransform.Matrix.Multiply(rotateTransform.Matrix);
|
||||
}
|
||||
|
||||
private Matrix GetTileIndexMatrix(double scale)
|
||||
{
|
||||
return viewportTransform.Matrix
|
||||
.Invert() // view to map coordinates
|
||||
.Translate(180d, -180d)
|
||||
.Scale(scale, -scale); // map coordinates to tile indices
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Copyright © 2014 Clemens Fischer
|
||||
// Licensed under the Microsoft Public License (Ms-PL)
|
||||
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
|
@ -11,7 +12,7 @@ namespace MapControl
|
|||
public partial class MapBase
|
||||
{
|
||||
public static readonly DependencyProperty ForegroundProperty =
|
||||
System.Windows.Controls.Control.ForegroundProperty.AddOwner(typeof(MapBase));
|
||||
Control.ForegroundProperty.AddOwner(typeof(MapBase));
|
||||
|
||||
public static readonly DependencyProperty CenterProperty = DependencyProperty.Register(
|
||||
"Center", typeof(Location), typeof(MapBase), new FrameworkPropertyMetadata(
|
||||
|
|
@ -64,13 +65,50 @@ namespace MapControl
|
|||
UpdateTransform();
|
||||
}
|
||||
|
||||
private void SetViewportTransform(Point mapOrigin)
|
||||
{
|
||||
var transform = Matrix.Identity;
|
||||
transform.Translate(-mapOrigin.X, -mapOrigin.Y);
|
||||
transform.Scale(ViewportScale, -ViewportScale);
|
||||
transform.Rotate(Heading);
|
||||
transform.Translate(viewportOrigin.X, viewportOrigin.Y);
|
||||
|
||||
viewportTransform.Matrix = transform;
|
||||
}
|
||||
|
||||
private void SetTileLayerTransform()
|
||||
{
|
||||
var scale = Math.Pow(2d, ZoomLevel - TileZoomLevel);
|
||||
var transform = Matrix.Identity;
|
||||
transform.Translate(TileGrid.X * TileSource.TileSize, TileGrid.Y * TileSource.TileSize);
|
||||
transform.Scale(scale, scale);
|
||||
transform.Translate(tileLayerOffset.X, tileLayerOffset.Y);
|
||||
transform.RotateAt(Heading, viewportOrigin.X, viewportOrigin.Y);
|
||||
|
||||
tileLayerTransform.Matrix = transform;
|
||||
}
|
||||
|
||||
private void SetTransformMatrixes()
|
||||
{
|
||||
Matrix rotateMatrix = Matrix.Identity;
|
||||
var rotateMatrix = Matrix.Identity;
|
||||
rotateMatrix.Rotate(Heading);
|
||||
rotateTransform.Matrix = rotateMatrix;
|
||||
scaleTransform.Matrix = new Matrix(CenterScale, 0d, 0d, CenterScale, 0d, 0d);
|
||||
scaleRotateTransform.Matrix = scaleTransform.Matrix * rotateMatrix;
|
||||
|
||||
var scaleMatrix = Matrix.Identity;
|
||||
scaleMatrix.Scale(CenterScale, CenterScale);
|
||||
scaleTransform.Matrix = scaleMatrix;
|
||||
|
||||
scaleRotateTransform.Matrix = scaleMatrix * rotateMatrix;
|
||||
}
|
||||
|
||||
private Matrix GetTileIndexMatrix(double scale)
|
||||
{
|
||||
var transform = viewportTransform.Matrix;
|
||||
transform.Invert(); // view to map coordinates
|
||||
transform.Translate(180d, -180d);
|
||||
transform.Scale(scale, -scale); // map coordinates to tile indices
|
||||
|
||||
return transform;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
// Licensed under the Microsoft Public License (Ms-PL)
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
#if WINDOWS_RUNTIME
|
||||
|
|
@ -14,6 +15,7 @@ using Windows.UI.Xaml.Media.Animation;
|
|||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Threading;
|
||||
#endif
|
||||
|
||||
namespace MapControl
|
||||
|
|
@ -28,17 +30,17 @@ namespace MapControl
|
|||
{
|
||||
private const double MaximumZoomLevel = 22d;
|
||||
|
||||
public static readonly DependencyProperty TileLayersProperty = DependencyProperty.Register(
|
||||
"TileLayers", typeof(TileLayerCollection), typeof(MapBase), new PropertyMetadata(null,
|
||||
(o, e) => ((MapBase)o).TileLayersPropertyChanged((TileLayerCollection)e.OldValue, (TileLayerCollection)e.NewValue)));
|
||||
public static TimeSpan TileUpdateInterval = TimeSpan.FromSeconds(0.5);
|
||||
public static TimeSpan AnimationDuration = TimeSpan.FromSeconds(0.3);
|
||||
public static EasingFunctionBase AnimationEasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut };
|
||||
|
||||
public static readonly DependencyProperty TileLayerProperty = DependencyProperty.Register(
|
||||
"TileLayer", typeof(TileLayer), typeof(MapBase), new PropertyMetadata(null,
|
||||
(o, e) => ((MapBase)o).TileLayerPropertyChanged((TileLayer)e.NewValue)));
|
||||
|
||||
public static readonly DependencyProperty TileOpacityProperty = DependencyProperty.Register(
|
||||
"TileOpacity", typeof(double), typeof(MapBase), new PropertyMetadata(1d,
|
||||
(o, e) => ((MapBase)o).tileContainer.Opacity = (double)e.NewValue));
|
||||
public static readonly DependencyProperty TileLayersProperty = DependencyProperty.Register(
|
||||
"TileLayers", typeof(TileLayerCollection), typeof(MapBase), new PropertyMetadata(null,
|
||||
(o, e) => ((MapBase)o).TileLayersPropertyChanged((TileLayerCollection)e.OldValue, (TileLayerCollection)e.NewValue)));
|
||||
|
||||
public static readonly DependencyProperty MinZoomLevelProperty = DependencyProperty.Register(
|
||||
"MinZoomLevel", typeof(double), typeof(MapBase), new PropertyMetadata(1d,
|
||||
|
|
@ -52,29 +54,32 @@ namespace MapControl
|
|||
"CenterPoint", typeof(Point), typeof(MapBase), new PropertyMetadata(new Point(),
|
||||
(o, e) => ((MapBase)o).CenterPointPropertyChanged((Point)e.NewValue)));
|
||||
|
||||
private readonly TileContainer tileContainer = new TileContainer();
|
||||
private readonly PanelBase tileLayerPanel = new PanelBase();
|
||||
private readonly DispatcherTimer tileUpdateTimer = new DispatcherTimer { Interval = TileUpdateInterval };
|
||||
private readonly MapTransform mapTransform = new MercatorTransform();
|
||||
private readonly MatrixTransform viewportTransform = new MatrixTransform();
|
||||
private readonly MatrixTransform tileLayerTransform = new MatrixTransform();
|
||||
private readonly MatrixTransform scaleTransform = new MatrixTransform();
|
||||
private readonly MatrixTransform rotateTransform = new MatrixTransform();
|
||||
private readonly MatrixTransform scaleRotateTransform = new MatrixTransform();
|
||||
|
||||
private Location transformOrigin;
|
||||
private Point viewportOrigin;
|
||||
private Point tileLayerOffset;
|
||||
private PointAnimation centerAnimation;
|
||||
private DoubleAnimation zoomLevelAnimation;
|
||||
private DoubleAnimation headingAnimation;
|
||||
private Brush storedBackground;
|
||||
private Brush storedForeground;
|
||||
private bool internalPropertyChange;
|
||||
|
||||
public MapBase()
|
||||
{
|
||||
SetParentMap();
|
||||
|
||||
Children.Add(tileLayerPanel);
|
||||
TileLayers = new TileLayerCollection();
|
||||
Children.Add(tileContainer);
|
||||
Initialize();
|
||||
|
||||
tileUpdateTimer.Tick += UpdateTiles;
|
||||
Loaded += OnLoaded;
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
partial void Initialize();
|
||||
|
|
@ -85,6 +90,11 @@ namespace MapControl
|
|||
/// </summary>
|
||||
public event EventHandler ViewportChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the TileZoomLevel or TileGrid properties have changed.
|
||||
/// </summary>
|
||||
public event EventHandler TileGridChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the map foreground Brush.
|
||||
/// </summary>
|
||||
|
|
@ -95,16 +105,7 @@ namespace MapControl
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the TileLayers used by this Map.
|
||||
/// </summary>
|
||||
public TileLayerCollection TileLayers
|
||||
{
|
||||
get { return (TileLayerCollection)GetValue(TileLayersProperty); }
|
||||
set { SetValue(TileLayersProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the base TileLayer used by this Map, i.e. TileLayers[0].
|
||||
/// Gets or sets the base TileLayer used by the Map control.
|
||||
/// </summary>
|
||||
public TileLayer TileLayer
|
||||
{
|
||||
|
|
@ -113,12 +114,15 @@ namespace MapControl
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the opacity of the tile layers.
|
||||
/// Gets or sets optional multiple TileLayers that are used simultaneously.
|
||||
/// The first element in the collection is equal to the value of the TileLayer property.
|
||||
/// The additional TileLayers usually have transparent backgrounds and their IsOverlay
|
||||
/// property is set to true.
|
||||
/// </summary>
|
||||
public double TileOpacity
|
||||
public TileLayerCollection TileLayers
|
||||
{
|
||||
get { return (double)GetValue(TileOpacityProperty); }
|
||||
set { SetValue(TileOpacityProperty, value); }
|
||||
get { return (TileLayerCollection)GetValue(TileLayersProperty); }
|
||||
set { SetValue(TileLayersProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -208,7 +212,15 @@ namespace MapControl
|
|||
/// </summary>
|
||||
public Transform ViewportTransform
|
||||
{
|
||||
get { return tileContainer.ViewportTransform; }
|
||||
get { return viewportTransform; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RenderTransform to be used by TileLayers, with origin at TileGrid.X and TileGrid.Y.
|
||||
/// </summary>
|
||||
public Transform TileLayerTransform
|
||||
{
|
||||
get { return tileLayerTransform; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -245,6 +257,16 @@ namespace MapControl
|
|||
/// </summary>
|
||||
public double CenterScale { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the zoom level to be used by TileLayers.
|
||||
/// </summary>
|
||||
public int TileZoomLevel { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tile grid to be used by TileLayers.
|
||||
/// </summary>
|
||||
public Int32Rect TileGrid { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the map scale at the specified location as viewport coordinate units (pixels) per meter.
|
||||
/// </summary>
|
||||
|
|
@ -258,7 +280,7 @@ namespace MapControl
|
|||
/// </summary>
|
||||
public Point LocationToViewportPoint(Location location)
|
||||
{
|
||||
return ViewportTransform.Transform(mapTransform.Transform(location));
|
||||
return viewportTransform.Transform(mapTransform.Transform(location));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -266,7 +288,7 @@ namespace MapControl
|
|||
/// </summary>
|
||||
public Location ViewportPointToLocation(Point point)
|
||||
{
|
||||
return mapTransform.Transform(ViewportTransform.Inverse.Transform(point));
|
||||
return mapTransform.Transform(viewportTransform.Inverse.Transform(point));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -365,105 +387,27 @@ namespace MapControl
|
|||
{
|
||||
if (southWest.Latitude < northEast.Latitude && southWest.Longitude < northEast.Longitude)
|
||||
{
|
||||
var p1 = MapTransform.Transform(southWest);
|
||||
var p2 = MapTransform.Transform(northEast);
|
||||
var p1 = mapTransform.Transform(southWest);
|
||||
var p2 = mapTransform.Transform(northEast);
|
||||
var lonScale = RenderSize.Width / (p2.X - p1.X) * 360d / TileSource.TileSize;
|
||||
var latScale = RenderSize.Height / (p2.Y - p1.Y) * 360d / TileSource.TileSize;
|
||||
var lonZoom = Math.Log(lonScale, 2d);
|
||||
var latZoom = Math.Log(latScale, 2d);
|
||||
|
||||
TargetZoomLevel = Math.Min(lonZoom, latZoom);
|
||||
TargetCenter = MapTransform.Transform(new Point((p1.X + p2.X) / 2d, (p1.Y + p2.Y) / 2d));
|
||||
TargetCenter = mapTransform.Transform(new Point((p1.X + p2.X) / 2d, (p1.Y + p2.Y) / 2d));
|
||||
TargetHeading = 0d;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnViewportChanged()
|
||||
{
|
||||
base.OnViewportChanged();
|
||||
|
||||
if (ViewportChanged != null)
|
||||
{
|
||||
ViewportChanged(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Loaded -= OnLoaded;
|
||||
|
||||
if (TileLayer == null)
|
||||
if (tileLayerPanel.Children.Count == 0 && !Children.OfType<TileLayer>().Any())
|
||||
{
|
||||
TileLayer = TileLayer.Default;
|
||||
}
|
||||
|
||||
UpdateTransform();
|
||||
}
|
||||
|
||||
private void TileLayerCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
tileContainer.AddTileLayers(e.NewStartingIndex, e.NewItems.Cast<TileLayer>());
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
tileContainer.RemoveTileLayers(e.OldStartingIndex, e.OldItems.Count);
|
||||
break;
|
||||
#if !SILVERLIGHT
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
#endif
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
tileContainer.RemoveTileLayers(e.NewStartingIndex, e.OldItems.Count);
|
||||
tileContainer.AddTileLayers(e.NewStartingIndex, e.NewItems.Cast<TileLayer>());
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
tileContainer.ClearTileLayers();
|
||||
if (e.NewItems != null)
|
||||
{
|
||||
tileContainer.AddTileLayers(0, e.NewItems.Cast<TileLayer>());
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
var firstTileLayer = TileLayers.FirstOrDefault();
|
||||
|
||||
if (TileLayer != firstTileLayer)
|
||||
{
|
||||
TileLayer = firstTileLayer;
|
||||
}
|
||||
}
|
||||
|
||||
private void TileLayersPropertyChanged(TileLayerCollection oldTileLayers, TileLayerCollection newTileLayers)
|
||||
{
|
||||
tileContainer.ClearTileLayers();
|
||||
|
||||
if (oldTileLayers != null)
|
||||
{
|
||||
oldTileLayers.CollectionChanged -= TileLayerCollectionChanged;
|
||||
}
|
||||
|
||||
if (newTileLayers != null)
|
||||
{
|
||||
newTileLayers.CollectionChanged += TileLayerCollectionChanged;
|
||||
tileContainer.AddTileLayers(0, newTileLayers);
|
||||
|
||||
var firstTileLayer = TileLayers.FirstOrDefault();
|
||||
|
||||
if (TileLayer != firstTileLayer)
|
||||
{
|
||||
TileLayer = firstTileLayer;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TileLayer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void TileLayerPropertyChanged(TileLayer tileLayer)
|
||||
|
|
@ -484,35 +428,94 @@ namespace MapControl
|
|||
TileLayers[0] = tileLayer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tileLayer != null && tileLayer.Background != null)
|
||||
private void TileLayersPropertyChanged(TileLayerCollection oldTileLayers, TileLayerCollection newTileLayers)
|
||||
{
|
||||
if (oldTileLayers != null)
|
||||
{
|
||||
if (storedBackground == null)
|
||||
oldTileLayers.CollectionChanged -= TileLayerCollectionChanged;
|
||||
RemoveTileLayers(0, oldTileLayers.Count);
|
||||
}
|
||||
|
||||
if (newTileLayers != null)
|
||||
{
|
||||
AddTileLayers(0, newTileLayers);
|
||||
newTileLayers.CollectionChanged += TileLayerCollectionChanged;
|
||||
}
|
||||
|
||||
TileLayer = TileLayers.FirstOrDefault();
|
||||
}
|
||||
|
||||
private void TileLayerCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
AddTileLayers(e.NewStartingIndex, e.NewItems.Cast<TileLayer>());
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
RemoveTileLayers(e.OldStartingIndex, e.OldItems.Count);
|
||||
break;
|
||||
#if !SILVERLIGHT
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
#endif
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
RemoveTileLayers(e.NewStartingIndex, e.OldItems.Count);
|
||||
AddTileLayers(e.NewStartingIndex, e.NewItems.Cast<TileLayer>());
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
if (e.OldItems != null)
|
||||
{
|
||||
RemoveTileLayers(0, e.OldItems.Count);
|
||||
}
|
||||
if (e.NewItems != null)
|
||||
{
|
||||
AddTileLayers(0, e.NewItems.Cast<TileLayer>());
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
TileLayer = TileLayers.FirstOrDefault();
|
||||
}
|
||||
|
||||
private void AddTileLayers(int index, IEnumerable<TileLayer> tileLayers)
|
||||
{
|
||||
foreach (var tileLayer in tileLayers)
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
storedBackground = Background;
|
||||
if (tileLayer.Background != null)
|
||||
{
|
||||
Background = tileLayer.Background;
|
||||
}
|
||||
|
||||
if (tileLayer.Foreground != null)
|
||||
{
|
||||
Foreground = tileLayer.Foreground;
|
||||
}
|
||||
}
|
||||
|
||||
Background = tileLayer.Background;
|
||||
tileLayerPanel.Children.Insert(index++, tileLayer);
|
||||
}
|
||||
else if (storedBackground != null)
|
||||
}
|
||||
|
||||
private void RemoveTileLayers(int index, int count)
|
||||
{
|
||||
while (count-- > 0)
|
||||
{
|
||||
Background = storedBackground;
|
||||
storedBackground = null;
|
||||
tileLayerPanel.Children.RemoveAt(index + count);
|
||||
}
|
||||
|
||||
if (tileLayer != null && tileLayer.Foreground != null)
|
||||
if (index == 0)
|
||||
{
|
||||
if (storedForeground == null)
|
||||
{
|
||||
storedForeground = Foreground;
|
||||
}
|
||||
|
||||
Foreground = tileLayer.Foreground;
|
||||
}
|
||||
else if (storedForeground != null)
|
||||
{
|
||||
Foreground = storedForeground;
|
||||
storedForeground = null;
|
||||
ClearValue(BackgroundProperty);
|
||||
ClearValue(ForegroundProperty);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -552,7 +555,7 @@ namespace MapControl
|
|||
if (centerAnimation == null)
|
||||
{
|
||||
InternalSetValue(TargetCenterProperty, center);
|
||||
InternalSetValue(CenterPointProperty, MapTransform.Transform(center));
|
||||
InternalSetValue(CenterPointProperty, mapTransform.Transform(center));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -573,10 +576,10 @@ namespace MapControl
|
|||
// animate private CenterPoint property by PointAnimation
|
||||
centerAnimation = new PointAnimation
|
||||
{
|
||||
From = MapTransform.Transform(Center),
|
||||
To = MapTransform.Transform(targetCenter, Center.Longitude),
|
||||
Duration = Settings.MapAnimationDuration,
|
||||
EasingFunction = Settings.MapAnimationEasingFunction,
|
||||
From = mapTransform.Transform(Center),
|
||||
To = mapTransform.Transform(targetCenter, Center.Longitude),
|
||||
Duration = AnimationDuration,
|
||||
EasingFunction = AnimationEasingFunction,
|
||||
FillBehavior = FillBehavior.HoldEnd
|
||||
};
|
||||
|
||||
|
|
@ -594,7 +597,7 @@ namespace MapControl
|
|||
centerAnimation = null;
|
||||
|
||||
InternalSetValue(CenterProperty, TargetCenter);
|
||||
InternalSetValue(CenterPointProperty, MapTransform.Transform(TargetCenter));
|
||||
InternalSetValue(CenterPointProperty, mapTransform.Transform(TargetCenter));
|
||||
RemoveAnimation(CenterPointProperty); // remove holding animation in WPF
|
||||
|
||||
ResetTransformOrigin();
|
||||
|
|
@ -607,7 +610,7 @@ namespace MapControl
|
|||
if (!internalPropertyChange)
|
||||
{
|
||||
centerPoint.X = Location.NormalizeLongitude(centerPoint.X);
|
||||
InternalSetValue(CenterProperty, MapTransform.Transform(centerPoint));
|
||||
InternalSetValue(CenterProperty, mapTransform.Transform(centerPoint));
|
||||
ResetTransformOrigin();
|
||||
UpdateTransform();
|
||||
}
|
||||
|
|
@ -680,8 +683,8 @@ namespace MapControl
|
|||
zoomLevelAnimation = new DoubleAnimation
|
||||
{
|
||||
To = targetZoomLevel,
|
||||
Duration = Settings.MapAnimationDuration,
|
||||
EasingFunction = Settings.MapAnimationEasingFunction,
|
||||
Duration = AnimationDuration,
|
||||
EasingFunction = AnimationEasingFunction,
|
||||
FillBehavior = FillBehavior.HoldEnd
|
||||
};
|
||||
|
||||
|
|
@ -755,8 +758,8 @@ namespace MapControl
|
|||
headingAnimation = new DoubleAnimation
|
||||
{
|
||||
By = delta,
|
||||
Duration = Settings.MapAnimationDuration,
|
||||
EasingFunction = Settings.MapAnimationEasingFunction,
|
||||
Duration = AnimationDuration,
|
||||
EasingFunction = AnimationEasingFunction,
|
||||
FillBehavior = FillBehavior.HoldEnd
|
||||
};
|
||||
|
||||
|
|
@ -784,7 +787,12 @@ namespace MapControl
|
|||
{
|
||||
Location center;
|
||||
|
||||
if (transformOrigin != null)
|
||||
if (transformOrigin == null)
|
||||
{
|
||||
center = Center;
|
||||
SetViewportTransform(center);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetViewportTransform(transformOrigin);
|
||||
|
||||
|
|
@ -802,7 +810,7 @@ namespace MapControl
|
|||
if (centerAnimation == null)
|
||||
{
|
||||
InternalSetValue(TargetCenterProperty, center);
|
||||
InternalSetValue(CenterPointProperty, MapTransform.Transform(center));
|
||||
InternalSetValue(CenterPointProperty, mapTransform.Transform(center));
|
||||
}
|
||||
|
||||
if (resetTransformOrigin)
|
||||
|
|
@ -811,11 +819,6 @@ namespace MapControl
|
|||
SetViewportTransform(center);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
center = Center;
|
||||
SetViewportTransform(center);
|
||||
}
|
||||
|
||||
CenterScale = ViewportScale * mapTransform.RelativeScale(center) / TileSource.MetersPerDegree; // Pixels per meter at center latitude
|
||||
|
||||
|
|
@ -823,9 +826,77 @@ namespace MapControl
|
|||
OnViewportChanged();
|
||||
}
|
||||
|
||||
protected override void OnViewportChanged()
|
||||
{
|
||||
base.OnViewportChanged();
|
||||
|
||||
var viewportChanged = ViewportChanged;
|
||||
|
||||
if (viewportChanged != null)
|
||||
{
|
||||
viewportChanged(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetViewportTransform(Location origin)
|
||||
{
|
||||
ViewportScale = tileContainer.SetViewportTransform(ZoomLevel, Heading, mapTransform.Transform(origin), viewportOrigin);
|
||||
var oldMapOriginX = (viewportOrigin.X - tileLayerOffset.X) / ViewportScale - 180d;
|
||||
var mapOrigin = mapTransform.Transform(origin);
|
||||
|
||||
ViewportScale = Math.Pow(2d, ZoomLevel) * TileSource.TileSize / 360d;
|
||||
SetViewportTransform(mapOrigin);
|
||||
|
||||
tileLayerOffset.X = viewportOrigin.X - (180d + mapOrigin.X) * ViewportScale;
|
||||
tileLayerOffset.Y = viewportOrigin.Y - (180d - mapOrigin.Y) * ViewportScale;
|
||||
|
||||
if (Math.Abs(mapOrigin.X - oldMapOriginX) > 180d)
|
||||
{
|
||||
// immediately handle map origin leap when map center moves across 180° longitude
|
||||
UpdateTiles(this, EventArgs.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetTileLayerTransform();
|
||||
tileUpdateTimer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTiles(object sender, object e)
|
||||
{
|
||||
tileUpdateTimer.Stop();
|
||||
|
||||
// relative size of scaled tile ranges from 0.75 to 1.5 (192 to 384 pixels)
|
||||
var zoomLevelSwitchDelta = Math.Log(0.75, 2d);
|
||||
var zoomLevel = (int)Math.Floor(ZoomLevel - zoomLevelSwitchDelta);
|
||||
var transform = GetTileIndexMatrix((double)(1 << zoomLevel) / 360d);
|
||||
|
||||
// tile indices of visible rectangle
|
||||
var p1 = transform.Transform(new Point(0d, 0d));
|
||||
var p2 = transform.Transform(new Point(RenderSize.Width, 0d));
|
||||
var p3 = transform.Transform(new Point(0d, RenderSize.Height));
|
||||
var p4 = transform.Transform(new Point(RenderSize.Width, RenderSize.Height));
|
||||
|
||||
// index ranges of visible tiles
|
||||
var x1 = (int)Math.Floor(Math.Min(p1.X, Math.Min(p2.X, Math.Min(p3.X, p4.X))));
|
||||
var y1 = (int)Math.Floor(Math.Min(p1.Y, Math.Min(p2.Y, Math.Min(p3.Y, p4.Y))));
|
||||
var x2 = (int)Math.Floor(Math.Max(p1.X, Math.Max(p2.X, Math.Max(p3.X, p4.X))));
|
||||
var y2 = (int)Math.Floor(Math.Max(p1.Y, Math.Max(p2.Y, Math.Max(p3.Y, p4.Y))));
|
||||
var grid = new Int32Rect(x1, y1, x2 - x1 + 1, y2 - y1 + 1);
|
||||
|
||||
if (TileZoomLevel != zoomLevel || TileGrid != grid)
|
||||
{
|
||||
TileZoomLevel = zoomLevel;
|
||||
TileGrid = grid;
|
||||
|
||||
SetTileLayerTransform();
|
||||
|
||||
var tileGridChanged = TileGridChanged;
|
||||
|
||||
if (tileGridChanged != null)
|
||||
{
|
||||
tileGridChanged(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>Bin\Debug</OutputPath>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE;SILVERLIGHT;WINDOWS_PHONE</DefineConstants>
|
||||
<NoStdLib>true</NoStdLib>
|
||||
<NoConfig>true</NoConfig>
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>none</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>Bin\Release</OutputPath>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;SILVERLIGHT;WINDOWS_PHONE</DefineConstants>
|
||||
<NoStdLib>true</NoStdLib>
|
||||
<NoConfig>true</NoConfig>
|
||||
|
|
@ -48,6 +48,7 @@
|
|||
<Compile Include="ImageTileSource.Silverlight.WinRT.cs" />
|
||||
<Compile Include="IMapElement.cs" />
|
||||
<Compile Include="Int32Rect.cs" />
|
||||
<Compile Include="ITileImageLoader.cs" />
|
||||
<Compile Include="Location.cs" />
|
||||
<Compile Include="LocationCollection.cs" />
|
||||
<Compile Include="LocationCollectionConverter.cs" />
|
||||
|
|
@ -78,13 +79,11 @@
|
|||
<Compile Include="PanelBase.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Pushpin.Silverlight.WinRT.cs" />
|
||||
<Compile Include="Settings.cs" />
|
||||
<Compile Include="Tile.cs" />
|
||||
<Compile Include="Tile.Silverlight.WinRT.cs" />
|
||||
<Compile Include="TileContainer.cs" />
|
||||
<Compile Include="TileContainer.Silverlight.WinRT.cs" />
|
||||
<Compile Include="TileImageLoader.Silverlight.cs" />
|
||||
<Compile Include="TileLayer.cs" />
|
||||
<Compile Include="TileLayer.Silverlight.WinRT.cs" />
|
||||
<Compile Include="TileLayerCollection.cs" />
|
||||
<Compile Include="TileSource.cs" />
|
||||
<Compile Include="TileSourceConverter.cs" />
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@
|
|||
<Compile Include="ImageTileSource.Silverlight.WinRT.cs" />
|
||||
<Compile Include="IMapElement.cs" />
|
||||
<Compile Include="Int32Rect.cs" />
|
||||
<Compile Include="ITileImageLoader.cs" />
|
||||
<Compile Include="Location.cs" />
|
||||
<Compile Include="LocationCollection.cs" />
|
||||
<Compile Include="LocationCollectionConverter.cs" />
|
||||
|
|
@ -105,13 +106,11 @@
|
|||
<Compile Include="PanelBase.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Pushpin.Silverlight.WinRT.cs" />
|
||||
<Compile Include="Settings.cs" />
|
||||
<Compile Include="Tile.cs" />
|
||||
<Compile Include="Tile.Silverlight.WinRT.cs" />
|
||||
<Compile Include="TileContainer.cs" />
|
||||
<Compile Include="TileContainer.Silverlight.WinRT.cs" />
|
||||
<Compile Include="TileImageLoader.Silverlight.cs" />
|
||||
<Compile Include="TileLayer.cs" />
|
||||
<Compile Include="TileLayer.Silverlight.WinRT.cs" />
|
||||
<Compile Include="TileLayerCollection.cs" />
|
||||
<Compile Include="TileSource.cs" />
|
||||
<Compile Include="TileSourceConverter.cs" />
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
<Compile Include="HyperlinkText.cs" />
|
||||
<Compile Include="ImageTileSource.WPF.cs" />
|
||||
<Compile Include="IMapElement.cs" />
|
||||
<Compile Include="ITileImageLoader.cs" />
|
||||
<Compile Include="Location.cs" />
|
||||
<Compile Include="LocationCollection.cs" />
|
||||
<Compile Include="LocationCollectionConverter.cs" />
|
||||
|
|
@ -77,7 +78,6 @@
|
|||
<Compile Include="MapPanel.cs" />
|
||||
<Compile Include="MapPanel.WPF.cs" />
|
||||
<Compile Include="MapRectangle.WPF.cs" />
|
||||
<Compile Include="PanelBase.cs" />
|
||||
<Compile Include="MapPath.cs" />
|
||||
<Compile Include="MapPath.WPF.cs" />
|
||||
<Compile Include="MapPolyline.cs" />
|
||||
|
|
@ -86,15 +86,14 @@
|
|||
<Compile Include="MapScale.cs" />
|
||||
<Compile Include="MapTransform.cs" />
|
||||
<Compile Include="MercatorTransform.cs" />
|
||||
<Compile Include="PanelBase.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Pushpin.WPF.cs" />
|
||||
<Compile Include="Settings.cs" />
|
||||
<Compile Include="Tile.cs" />
|
||||
<Compile Include="Tile.WPF.cs" />
|
||||
<Compile Include="TileContainer.cs" />
|
||||
<Compile Include="TileContainer.WPF.cs" />
|
||||
<Compile Include="TileImageLoader.WPF.cs" />
|
||||
<Compile Include="TileLayer.cs" />
|
||||
<Compile Include="TileLayer.WPF.cs" />
|
||||
<Compile Include="TileLayerCollection.cs" />
|
||||
<Compile Include="TileSource.cs" />
|
||||
<Compile Include="TileSourceConverter.cs" />
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ namespace MapControl
|
|||
{
|
||||
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(
|
||||
"Source", typeof(ImageSource), typeof(MapImage),
|
||||
new PropertyMetadata(null, (o, e) => ((MapImage)o).SourceChanged((ImageSource)e.NewValue)));
|
||||
new PropertyMetadata(null, (o, e) => ((MapImage)o).SourcePropertyChanged((ImageSource)e.NewValue)));
|
||||
|
||||
public ImageSource Source
|
||||
{
|
||||
|
|
@ -27,7 +27,7 @@ namespace MapControl
|
|||
set { SetValue(SourceProperty, value); }
|
||||
}
|
||||
|
||||
private void SourceChanged(ImageSource image)
|
||||
private void SourcePropertyChanged(ImageSource image)
|
||||
{
|
||||
Fill = new ImageBrush
|
||||
{
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ namespace MapControl
|
|||
Children.Add(new MapImage { Opacity = 0d });
|
||||
Children.Add(new MapImage { Opacity = 0d });
|
||||
|
||||
updateTimer = new DispatcherTimer { Interval = Settings.TileUpdateInterval };
|
||||
updateTimer = new DispatcherTimer { Interval = MapBase.TileUpdateInterval };
|
||||
updateTimer.Tick += (s, e) => UpdateImage();
|
||||
}
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ namespace MapControl
|
|||
/// <summary>
|
||||
/// Relative size of the map images in relation to the current viewport size.
|
||||
/// Setting a value greater than one will let MapImageLayer request images that
|
||||
/// are larger than the viewport, in order to support smooth panning.
|
||||
/// are larger than the viewport, in order to support smooth panning.
|
||||
/// </summary>
|
||||
public double RelativeImageSize
|
||||
{
|
||||
|
|
@ -175,16 +175,16 @@ namespace MapControl
|
|||
if (topImage.Source != null)
|
||||
{
|
||||
topImage.BeginAnimation(UIElement.OpacityProperty,
|
||||
new DoubleAnimation { To = 1d, Duration = Settings.TileAnimationDuration });
|
||||
new DoubleAnimation { To = 1d, Duration = Tile.OpacityAnimationDuration });
|
||||
}
|
||||
|
||||
if (bottomImage.Source != null)
|
||||
{
|
||||
var fadeOutAnimation = new DoubleAnimation { To = 0d, Duration = Settings.TileAnimationDuration };
|
||||
var fadeOutAnimation = new DoubleAnimation { To = 0d, Duration = Tile.OpacityAnimationDuration };
|
||||
|
||||
if (topImage.Source != null)
|
||||
{
|
||||
fadeOutAnimation.BeginTime = Settings.TileAnimationDuration;
|
||||
fadeOutAnimation.BeginTime = Tile.OpacityAnimationDuration;
|
||||
}
|
||||
|
||||
bottomImage.BeginAnimation(UIElement.OpacityProperty, fadeOutAnimation);
|
||||
|
|
|
|||
|
|
@ -3,12 +3,9 @@
|
|||
// Licensed under the Microsoft Public License (Ms-PL)
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace MapControl
|
||||
{
|
||||
|
|
@ -17,10 +14,6 @@ namespace MapControl
|
|||
/// </summary>
|
||||
public class MapItemsControl : ListBox
|
||||
{
|
||||
public static readonly DependencyProperty SelectionGeometryProperty = DependencyProperty.Register(
|
||||
"SelectionGeometry", typeof(Geometry), typeof(MapItemsControl),
|
||||
new PropertyMetadata((o, e) => ((MapItemsControl)o).SelectionGeometryPropertyChanged((Geometry)e.NewValue)));
|
||||
|
||||
static MapItemsControl()
|
||||
{
|
||||
DefaultStyleKeyProperty.OverrideMetadata(
|
||||
|
|
@ -43,58 +36,6 @@ namespace MapControl
|
|||
return item is MapItem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a Geometry that selects all items inside its fill area, i.e.
|
||||
/// where Geometry.FillContains returns true for the item's viewport position.
|
||||
/// </summary>
|
||||
public Geometry SelectionGeometry
|
||||
{
|
||||
get { return (Geometry)GetValue(SelectionGeometryProperty); }
|
||||
set { SetValue(SelectionGeometryProperty, value); }
|
||||
}
|
||||
|
||||
public object GetFirstItemInGeometry(Geometry geometry)
|
||||
{
|
||||
if (geometry == null || geometry.IsEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Items.Cast<object>().FirstOrDefault(i => IsItemInGeometry(i, geometry));
|
||||
}
|
||||
|
||||
public IList<object> GetItemsInGeometry(Geometry geometry)
|
||||
{
|
||||
if (geometry == null || geometry.IsEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Items.Cast<object>().Where(i => IsItemInGeometry(i, geometry)).ToList();
|
||||
}
|
||||
|
||||
private bool IsItemInGeometry(object item, Geometry geometry)
|
||||
{
|
||||
var container = ItemContainerGenerator.ContainerFromItem(item) as UIElement;
|
||||
Point? viewportPosition;
|
||||
|
||||
return container != null &&
|
||||
(viewportPosition = MapPanel.GetViewportPosition(container)).HasValue &&
|
||||
geometry.FillContains(viewportPosition.Value);
|
||||
}
|
||||
|
||||
private void SelectionGeometryPropertyChanged(Geometry geometry)
|
||||
{
|
||||
if (SelectionMode == SelectionMode.Single)
|
||||
{
|
||||
SelectedItem = GetFirstItemInGeometry(geometry);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetSelectedItems(GetItemsInGeometry(geometry));
|
||||
}
|
||||
}
|
||||
|
||||
private void CurrentItemChanging(object sender, CurrentChangingEventArgs e)
|
||||
{
|
||||
var container = ItemContainerGenerator.ContainerFromItem(Items.CurrentItem) as UIElement;
|
||||
|
|
|
|||
|
|
@ -4,11 +4,9 @@
|
|||
|
||||
#if WINDOWS_RUNTIME
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Media;
|
||||
#else
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
#endif
|
||||
|
||||
|
|
@ -21,7 +19,11 @@ namespace MapControl
|
|||
|
||||
public MapPanel()
|
||||
{
|
||||
if (!(this is MapBase))
|
||||
if (this is MapBase)
|
||||
{
|
||||
SetValue(ParentMapProperty, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddParentMapHandlers(this);
|
||||
}
|
||||
|
|
@ -67,10 +69,5 @@ namespace MapControl
|
|||
|
||||
return parentMap;
|
||||
}
|
||||
|
||||
internal void SetParentMap()
|
||||
{
|
||||
SetValue(ParentMapProperty, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,14 +14,17 @@ namespace MapControl
|
|||
|
||||
public static readonly DependencyProperty ParentMapProperty = ParentMapPropertyKey.DependencyProperty;
|
||||
|
||||
public MapPanel()
|
||||
{
|
||||
if (this is MapBase)
|
||||
{
|
||||
SetValue(ParentMapPropertyKey, this);
|
||||
}
|
||||
}
|
||||
|
||||
public static MapBase GetParentMap(UIElement element)
|
||||
{
|
||||
return (MapBase)element.GetValue(ParentMapProperty);
|
||||
}
|
||||
|
||||
internal void SetParentMap()
|
||||
{
|
||||
SetValue(ParentMapPropertyKey, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,9 +24,6 @@ namespace MapControl
|
|||
public static readonly DependencyProperty LocationProperty = DependencyProperty.RegisterAttached(
|
||||
"Location", typeof(Location), typeof(MapPanel), new PropertyMetadata(null, LocationPropertyChanged));
|
||||
|
||||
public static readonly DependencyProperty ViewportPositionProperty = DependencyProperty.RegisterAttached(
|
||||
"ViewportPosition", typeof(Point?), typeof(MapPanel), null);
|
||||
|
||||
public static Location GetLocation(UIElement element)
|
||||
{
|
||||
return (Location)element.GetValue(LocationProperty);
|
||||
|
|
@ -37,21 +34,12 @@ namespace MapControl
|
|||
element.SetValue(LocationProperty, value);
|
||||
}
|
||||
|
||||
public static Point? GetViewportPosition(UIElement element)
|
||||
{
|
||||
return (Point?)element.GetValue(ViewportPositionProperty);
|
||||
}
|
||||
|
||||
private MapBase parentMap;
|
||||
|
||||
public MapBase ParentMap
|
||||
{
|
||||
get { return parentMap; }
|
||||
}
|
||||
|
||||
void IMapElement.SetParentMap(MapBase map)
|
||||
{
|
||||
SetParentMapOverride(map);
|
||||
set { SetParentMapOverride(value); }
|
||||
}
|
||||
|
||||
protected virtual void SetParentMapOverride(MapBase map)
|
||||
|
|
@ -114,7 +102,7 @@ namespace MapControl
|
|||
|
||||
if (mapElement != null)
|
||||
{
|
||||
mapElement.SetParentMap(e.NewValue as MapBase);
|
||||
mapElement.ParentMap = e.NewValue as MapBase;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -149,12 +137,10 @@ namespace MapControl
|
|||
{
|
||||
var mapPosition = parentMap.MapTransform.Transform(location, parentMap.Center.Longitude); // nearest to center longitude
|
||||
viewportPosition = parentMap.ViewportTransform.Transform(mapPosition);
|
||||
element.SetValue(ViewportPositionProperty, viewportPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
viewportPosition = new Point();
|
||||
element.ClearValue(ViewportPositionProperty);
|
||||
}
|
||||
|
||||
var translateTransform = element.RenderTransform as TranslateTransform;
|
||||
|
|
@ -189,7 +175,7 @@ namespace MapControl
|
|||
|
||||
private static void ArrangeElementWithLocation(UIElement element)
|
||||
{
|
||||
var rect = new Rect(0d, 0d, element.DesiredSize.Width, element.DesiredSize.Height);
|
||||
var rect = new Rect(new Point(), element.DesiredSize);
|
||||
var frameworkElement = element as FrameworkElement;
|
||||
|
||||
if (frameworkElement != null)
|
||||
|
|
@ -228,7 +214,7 @@ namespace MapControl
|
|||
|
||||
private static void ArrangeElementWithoutLocation(UIElement element, Size parentSize)
|
||||
{
|
||||
var rect = new Rect(0d, 0d, element.DesiredSize.Width, element.DesiredSize.Height);
|
||||
var rect = new Rect(new Point(), element.DesiredSize);
|
||||
var frameworkElement = element as FrameworkElement;
|
||||
|
||||
if (frameworkElement != null)
|
||||
|
|
|
|||
|
|
@ -20,12 +20,11 @@ namespace MapControl
|
|||
public MapBase ParentMap
|
||||
{
|
||||
get { return parentMap; }
|
||||
}
|
||||
|
||||
void IMapElement.SetParentMap(MapBase map)
|
||||
{
|
||||
parentMap = map;
|
||||
UpdateData();
|
||||
set
|
||||
{
|
||||
parentMap = value;
|
||||
UpdateData();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void UpdateData()
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ using System.Linq;
|
|||
using Windows.UI.Xaml.Media;
|
||||
#else
|
||||
using System.Windows.Media;
|
||||
|
||||
#endif
|
||||
|
||||
namespace MapControl
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ using Windows.Foundation;
|
|||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Media;
|
||||
#else
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
#endif
|
||||
|
|
@ -82,10 +81,9 @@ namespace MapControl
|
|||
!double.IsNaN(West) && !double.IsNaN(East) &&
|
||||
South < North && West < East)
|
||||
{
|
||||
var p1 = ParentMap.MapTransform.Transform(new Location(South, West));
|
||||
var p2 = ParentMap.MapTransform.Transform(new Location(North, East));
|
||||
|
||||
SetRect(new Rect(p1.X, p1.Y, p2.X - p1.X, p2.Y - p1.Y));
|
||||
SetRect(new Rect(
|
||||
ParentMap.MapTransform.Transform(new Location(South, West)),
|
||||
ParentMap.MapTransform.Transform(new Location(North, East))));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ using System.Windows.Controls;
|
|||
namespace MapControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Common base class for MapPanel, TileLayer and TileContainer.
|
||||
/// Common base class for MapPanel and TileLayer.
|
||||
/// </summary>
|
||||
public class PanelBase : Panel
|
||||
{
|
||||
|
|
@ -32,9 +32,9 @@ namespace MapControl
|
|||
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
foreach (UIElement child in Children)
|
||||
foreach (UIElement element in Children)
|
||||
{
|
||||
child.Arrange(new Rect(new Point(), finalSize));
|
||||
element.Arrange(new Rect(new Point(), finalSize));
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ using System.Windows;
|
|||
[assembly: AssemblyCompany("Clemens Fischer")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyVersion("2.3.1")]
|
||||
[assembly: AssemblyFileVersion("2.3.1")]
|
||||
[assembly: AssemblyVersion("2.4.0")]
|
||||
[assembly: AssemblyFileVersion("2.4.0")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
// XAML Map Control - http://xamlmapcontrol.codeplex.com/
|
||||
// Copyright © 2014 Clemens Fischer
|
||||
// Licensed under the Microsoft Public License (Ms-PL)
|
||||
|
||||
using System;
|
||||
#if WINDOWS_RUNTIME
|
||||
using Windows.UI.Xaml.Media.Animation;
|
||||
#else
|
||||
using System.Windows.Media.Animation;
|
||||
#endif
|
||||
|
||||
namespace MapControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores global static properties that control the behaviour of the map control.
|
||||
/// </summary>
|
||||
public static class Settings
|
||||
{
|
||||
public static TimeSpan TileUpdateInterval { get; set; }
|
||||
public static TimeSpan TileAnimationDuration { get; set; }
|
||||
public static TimeSpan MapAnimationDuration { get; set; }
|
||||
public static EasingFunctionBase MapAnimationEasingFunction { get; set; }
|
||||
|
||||
static Settings()
|
||||
{
|
||||
TileUpdateInterval = TimeSpan.FromSeconds(0.5);
|
||||
TileAnimationDuration = TimeSpan.FromSeconds(0.3);
|
||||
MapAnimationDuration = TimeSpan.FromSeconds(0.3);
|
||||
MapAnimationEasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:map="clr-namespace:MapControl">
|
||||
<Style TargetType="map:MapItemsControl">
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
|
@ -20,15 +21,15 @@ namespace MapControl
|
|||
{
|
||||
public partial class Tile
|
||||
{
|
||||
public void SetImageSource(ImageSource image, bool animateOpacity)
|
||||
public void SetImage(ImageSource image, bool animateOpacity = true, bool isDownloading = true)
|
||||
{
|
||||
if (image != null && Image.Source == null)
|
||||
{
|
||||
if (animateOpacity)
|
||||
if (animateOpacity && OpacityAnimationDuration > TimeSpan.Zero)
|
||||
{
|
||||
var bitmap = image as BitmapImage;
|
||||
BitmapImage bitmap;
|
||||
|
||||
if (bitmap != null)
|
||||
if (isDownloading && (bitmap = image as BitmapImage) != null)
|
||||
{
|
||||
bitmap.ImageOpened += BitmapImageOpened;
|
||||
bitmap.ImageFailed += BitmapImageFailed;
|
||||
|
|
@ -36,17 +37,18 @@ namespace MapControl
|
|||
else
|
||||
{
|
||||
Image.BeginAnimation(Image.OpacityProperty,
|
||||
new DoubleAnimation { To = 1d, Duration = Settings.TileAnimationDuration });
|
||||
new DoubleAnimation { From = 0d, To = 1d, Duration = OpacityAnimationDuration });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Image.Opacity = 1d;
|
||||
}
|
||||
|
||||
Image.Source = image;
|
||||
}
|
||||
|
||||
Image.Source = image;
|
||||
HasImageSource = true;
|
||||
Pending = false;
|
||||
}
|
||||
|
||||
private void BitmapImageOpened(object sender, RoutedEventArgs e)
|
||||
|
|
@ -57,7 +59,7 @@ namespace MapControl
|
|||
bitmap.ImageFailed -= BitmapImageFailed;
|
||||
|
||||
Image.BeginAnimation(Image.OpacityProperty,
|
||||
new DoubleAnimation { To = 1d, Duration = Settings.TileAnimationDuration });
|
||||
new DoubleAnimation { From = 0d, To = 1d, Duration = OpacityAnimationDuration });
|
||||
}
|
||||
|
||||
private void BitmapImageFailed(object sender, ExceptionRoutedEventArgs e)
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@ namespace MapControl
|
|||
{
|
||||
public partial class Tile
|
||||
{
|
||||
public void SetImageSource(ImageSource image, bool animateOpacity)
|
||||
public void SetImage(ImageSource image, bool animateOpacity = true)
|
||||
{
|
||||
if (image != null && Image.Source == null)
|
||||
{
|
||||
if (animateOpacity)
|
||||
if (animateOpacity && OpacityAnimationDuration > TimeSpan.Zero)
|
||||
{
|
||||
var bitmap = image as BitmapSource;
|
||||
|
||||
|
|
@ -28,17 +28,18 @@ namespace MapControl
|
|||
else
|
||||
{
|
||||
Image.BeginAnimation(Image.OpacityProperty,
|
||||
new DoubleAnimation(1d, Settings.TileAnimationDuration));
|
||||
new DoubleAnimation(0d, 1d, OpacityAnimationDuration));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Image.Opacity = 1d;
|
||||
}
|
||||
|
||||
Image.Source = image;
|
||||
}
|
||||
|
||||
Image.Source = image;
|
||||
HasImageSource = true;
|
||||
Pending = false;
|
||||
}
|
||||
|
||||
private void BitmapDownloadCompleted(object sender, EventArgs e)
|
||||
|
|
@ -49,7 +50,7 @@ namespace MapControl
|
|||
bitmap.DownloadFailed -= BitmapDownloadFailed;
|
||||
|
||||
Image.BeginAnimation(Image.OpacityProperty,
|
||||
new DoubleAnimation(1d, Settings.TileAnimationDuration));
|
||||
new DoubleAnimation(0d, 1d, OpacityAnimationDuration));
|
||||
}
|
||||
|
||||
private void BitmapDownloadFailed(object sender, ExceptionEventArgs e)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ namespace MapControl
|
|||
{
|
||||
public partial class Tile
|
||||
{
|
||||
public static TimeSpan OpacityAnimationDuration = TimeSpan.FromSeconds(0.3);
|
||||
|
||||
public readonly int ZoomLevel;
|
||||
public readonly int X;
|
||||
public readonly int Y;
|
||||
|
|
@ -23,9 +25,10 @@ namespace MapControl
|
|||
ZoomLevel = zoomLevel;
|
||||
X = x;
|
||||
Y = y;
|
||||
Pending = true;
|
||||
}
|
||||
|
||||
public bool HasImageSource { get; private set; }
|
||||
public bool Pending { get; private set; }
|
||||
|
||||
public int XIndex
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,49 +0,0 @@
|
|||
// XAML Map Control - http://xamlmapcontrol.codeplex.com/
|
||||
// Copyright © 2014 Clemens Fischer
|
||||
// Licensed under the Microsoft Public License (Ms-PL)
|
||||
|
||||
using System;
|
||||
#if WINDOWS_RUNTIME
|
||||
using Windows.Foundation;
|
||||
using Windows.UI.Xaml.Media;
|
||||
#else
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
#endif
|
||||
|
||||
namespace MapControl
|
||||
{
|
||||
internal partial class TileContainer
|
||||
{
|
||||
private Matrix GetTileIndexMatrix(double scale)
|
||||
{
|
||||
return ViewportTransform.Matrix
|
||||
.Invert() // view to map coordinates
|
||||
.Translate(180d, -180d)
|
||||
.Scale(scale, -scale); // map coordinates to tile indices
|
||||
}
|
||||
|
||||
private void UpdateViewportTransform(double scale, Point mapOrigin)
|
||||
{
|
||||
ViewportTransform.Matrix = Matrix.Identity
|
||||
.Translate(-mapOrigin.X, -mapOrigin.Y)
|
||||
.Scale(scale, -scale)
|
||||
.Rotate(rotation)
|
||||
.Translate(viewportOrigin.X, viewportOrigin.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a RenderTransform with origin at tileGrid.X and tileGrid.Y to minimize rounding errors.
|
||||
/// </summary>
|
||||
private void UpdateRenderTransform()
|
||||
{
|
||||
var scale = Math.Pow(2d, zoomLevel - tileZoomLevel);
|
||||
|
||||
((MatrixTransform)RenderTransform).Matrix = Matrix.Identity
|
||||
.Translate(tileGrid.X * TileSource.TileSize, tileGrid.Y * TileSource.TileSize)
|
||||
.Scale(scale, scale)
|
||||
.Translate(tileLayerOffset.X, tileLayerOffset.Y)
|
||||
.RotateAt(rotation, viewportOrigin.X, viewportOrigin.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,49 +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.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace MapControl
|
||||
{
|
||||
internal partial class TileContainer
|
||||
{
|
||||
private Matrix GetTileIndexMatrix(double scale)
|
||||
{
|
||||
var transform = ViewportTransform.Matrix;
|
||||
transform.Invert(); // view to map coordinates
|
||||
transform.Translate(180d, -180d);
|
||||
transform.Scale(scale, -scale); // map coordinates to tile indices
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
||||
private void UpdateViewportTransform(double scale, Point mapOrigin)
|
||||
{
|
||||
var transform = Matrix.Identity;
|
||||
transform.Translate(-mapOrigin.X, -mapOrigin.Y);
|
||||
transform.Scale(scale, -scale);
|
||||
transform.Rotate(rotation);
|
||||
transform.Translate(viewportOrigin.X, viewportOrigin.Y);
|
||||
|
||||
ViewportTransform.Matrix = transform;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a RenderTransform with origin at tileGrid.X and tileGrid.Y to minimize rounding errors.
|
||||
/// </summary>
|
||||
private void UpdateRenderTransform()
|
||||
{
|
||||
var scale = Math.Pow(2d, zoomLevel - tileZoomLevel);
|
||||
var transform = Matrix.Identity;
|
||||
transform.Translate(tileGrid.X * TileSource.TileSize, tileGrid.Y * TileSource.TileSize);
|
||||
transform.Scale(scale, scale);
|
||||
transform.Translate(tileLayerOffset.X, tileLayerOffset.Y);
|
||||
transform.RotateAt(rotation, viewportOrigin.X, viewportOrigin.Y);
|
||||
|
||||
((MatrixTransform)RenderTransform).Matrix = transform;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,151 +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.Linq;
|
||||
#if WINDOWS_RUNTIME
|
||||
using Windows.Foundation;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Media;
|
||||
#else
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
#endif
|
||||
|
||||
namespace MapControl
|
||||
{
|
||||
internal partial class TileContainer : PanelBase
|
||||
{
|
||||
// relative size of scaled tile ranges from 0.75 to 1.5 (192 to 384 pixels)
|
||||
private static double zoomLevelSwitchDelta = -Math.Log(0.75, 2d);
|
||||
|
||||
private readonly DispatcherTimer updateTimer;
|
||||
private Point viewportOrigin;
|
||||
private Point tileLayerOffset;
|
||||
private double rotation;
|
||||
private double zoomLevel;
|
||||
private int tileZoomLevel;
|
||||
private Int32Rect tileGrid;
|
||||
|
||||
public readonly MatrixTransform ViewportTransform = new MatrixTransform();
|
||||
|
||||
public TileContainer()
|
||||
{
|
||||
RenderTransform = new MatrixTransform();
|
||||
updateTimer = new DispatcherTimer { Interval = Settings.TileUpdateInterval };
|
||||
updateTimer.Tick += UpdateTiles;
|
||||
}
|
||||
|
||||
public IEnumerable<TileLayer> TileLayers
|
||||
{
|
||||
get { return Children.Cast<TileLayer>(); }
|
||||
}
|
||||
|
||||
public void AddTileLayers(int index, IEnumerable<TileLayer> tileLayers)
|
||||
{
|
||||
foreach (var tileLayer in tileLayers)
|
||||
{
|
||||
if (index < Children.Count)
|
||||
{
|
||||
Children.Insert(index, tileLayer);
|
||||
}
|
||||
else
|
||||
{
|
||||
Children.Add(tileLayer);
|
||||
}
|
||||
|
||||
index++;
|
||||
tileLayer.UpdateTiles(tileZoomLevel, tileGrid);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveTileLayers(int index, int count)
|
||||
{
|
||||
while (count-- > 0)
|
||||
{
|
||||
((TileLayer)Children[index]).ClearTiles();
|
||||
Children.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearTileLayers()
|
||||
{
|
||||
foreach (TileLayer tileLayer in Children)
|
||||
{
|
||||
tileLayer.ClearTiles();
|
||||
}
|
||||
|
||||
Children.Clear();
|
||||
}
|
||||
|
||||
public double SetViewportTransform(double mapZoomLevel, double mapRotation, Point mapOrigin, Point viewOrigin)
|
||||
{
|
||||
var scale = Math.Pow(2d, zoomLevel) * TileSource.TileSize / 360d;
|
||||
var oldMapOriginX = (viewportOrigin.X - tileLayerOffset.X) / scale - 180d;
|
||||
|
||||
if (zoomLevel != mapZoomLevel)
|
||||
{
|
||||
zoomLevel = mapZoomLevel;
|
||||
scale = Math.Pow(2d, zoomLevel) * TileSource.TileSize / 360d;
|
||||
}
|
||||
|
||||
rotation = mapRotation;
|
||||
viewportOrigin = viewOrigin;
|
||||
tileLayerOffset.X = viewportOrigin.X - (180d + mapOrigin.X) * scale;
|
||||
tileLayerOffset.Y = viewportOrigin.Y - (180d - mapOrigin.Y) * scale;
|
||||
|
||||
UpdateViewportTransform(scale, mapOrigin);
|
||||
UpdateRenderTransform();
|
||||
|
||||
if (Math.Abs(mapOrigin.X - oldMapOriginX) > 180d)
|
||||
{
|
||||
// immediately handle map origin leap when map center moves across 180° longitude
|
||||
UpdateTiles(this, EventArgs.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
updateTimer.Start();
|
||||
}
|
||||
|
||||
return scale;
|
||||
}
|
||||
|
||||
private void UpdateTiles(object sender, object e)
|
||||
{
|
||||
updateTimer.Stop();
|
||||
|
||||
var zoom = (int)Math.Floor(zoomLevel + zoomLevelSwitchDelta);
|
||||
var scale = (double)(1 << zoom) / 360d;
|
||||
var transform = GetTileIndexMatrix(scale);
|
||||
|
||||
// tile indices of visible rectangle
|
||||
var p1 = transform.Transform(new Point(0d, 0d));
|
||||
var p2 = transform.Transform(new Point(RenderSize.Width, 0d));
|
||||
var p3 = transform.Transform(new Point(0d, RenderSize.Height));
|
||||
var p4 = transform.Transform(new Point(RenderSize.Width, RenderSize.Height));
|
||||
|
||||
// index ranges of visible tiles
|
||||
var x1 = (int)Math.Floor(Math.Min(p1.X, Math.Min(p2.X, Math.Min(p3.X, p4.X))));
|
||||
var y1 = (int)Math.Floor(Math.Min(p1.Y, Math.Min(p2.Y, Math.Min(p3.Y, p4.Y))));
|
||||
var x2 = (int)Math.Floor(Math.Max(p1.X, Math.Max(p2.X, Math.Max(p3.X, p4.X))));
|
||||
var y2 = (int)Math.Floor(Math.Max(p1.Y, Math.Max(p2.Y, Math.Max(p3.Y, p4.Y))));
|
||||
var grid = new Int32Rect(x1, y1, x2 - x1 + 1, y2 - y1 + 1);
|
||||
|
||||
if (tileZoomLevel != zoom || tileGrid != grid)
|
||||
{
|
||||
tileZoomLevel = zoom;
|
||||
tileGrid = grid;
|
||||
|
||||
UpdateRenderTransform();
|
||||
|
||||
foreach (TileLayer tileLayer in Children)
|
||||
{
|
||||
tileLayer.UpdateTiles(tileZoomLevel, tileGrid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -39,7 +39,7 @@ namespace MapControl
|
|||
}
|
||||
}
|
||||
|
||||
tile.SetImageSource(image, tileLayer.AnimateTileOpacity);
|
||||
tile.SetImage(image);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ using System.Linq;
|
|||
using System.Net;
|
||||
using System.Runtime.Caching;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Threading;
|
||||
|
|
@ -24,53 +25,62 @@ namespace MapControl
|
|||
public class TileImageLoader : ITileImageLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// Default Name of an ObjectCache instance that is assigned to the Cache property.
|
||||
/// Default name of an ObjectCache instance that is assigned to the Cache property.
|
||||
/// </summary>
|
||||
public const string DefaultCacheName = "TileCache";
|
||||
|
||||
/// <summary>
|
||||
/// Default value for the directory where an ObjectCache instance may save cached data.
|
||||
/// Default folder path where an ObjectCache instance may save cached data.
|
||||
/// </summary>
|
||||
public static readonly string DefaultCacheDirectory =
|
||||
public static readonly string DefaultCacheFolder =
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "MapControl");
|
||||
|
||||
/// <summary>
|
||||
/// Default expiration time span for cached images. Used when no expiration date
|
||||
/// was transmitted on download. The default value is seven days.
|
||||
/// Default expiration time for cached tile images. Used when no expiration time
|
||||
/// was transmitted on download. The default and recommended minimum value is seven days.
|
||||
/// See OpenStreetMap tile usage policy: http://wiki.openstreetmap.org/wiki/Tile_usage_policy
|
||||
/// </summary>
|
||||
public static TimeSpan DefaultCacheExpiration { get; set; }
|
||||
public static TimeSpan DefaultCacheExpiration = TimeSpan.FromDays(7);
|
||||
|
||||
/// <summary>
|
||||
/// The ObjectCache used to cache tile images. The default is MemoryCache.Default.
|
||||
/// </summary>
|
||||
public static ObjectCache Cache { get; set; }
|
||||
public static ObjectCache Cache = MemoryCache.Default;
|
||||
|
||||
static TileImageLoader()
|
||||
/// <summary>
|
||||
/// Optional value to be used for the HttpWebRequest.UserAgent property. The default is null.
|
||||
/// </summary>
|
||||
public static string HttpUserAgent;
|
||||
|
||||
private class PendingTile
|
||||
{
|
||||
DefaultCacheExpiration = TimeSpan.FromDays(7);
|
||||
Cache = MemoryCache.Default;
|
||||
public readonly Tile Tile;
|
||||
public readonly ImageSource CachedImage;
|
||||
|
||||
public PendingTile(Tile tile, ImageSource cachedImage = null)
|
||||
{
|
||||
Tile = tile;
|
||||
CachedImage = cachedImage;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ConcurrentQueue<Tile> pendingTiles = new ConcurrentQueue<Tile>();
|
||||
private int threadCount;
|
||||
private readonly ConcurrentQueue<PendingTile> pendingTiles = new ConcurrentQueue<PendingTile>();
|
||||
private int taskCount;
|
||||
|
||||
public void BeginLoadTiles(TileLayer tileLayer, IEnumerable<Tile> tiles)
|
||||
{
|
||||
if (tiles.Any())
|
||||
{
|
||||
// get current TileLayer property values in UI thread
|
||||
var dispatcher = tileLayer.Dispatcher;
|
||||
var tileSource = tileLayer.TileSource;
|
||||
var imageTileSource = tileSource as ImageTileSource;
|
||||
var animateOpacity = tileLayer.AnimateTileOpacity;
|
||||
var dispatcher = tileLayer.Dispatcher;
|
||||
|
||||
if (imageTileSource != null && !imageTileSource.IsAsync) // call LoadImage in UI thread
|
||||
if (imageTileSource != null && !imageTileSource.IsAsync) // call LoadImage in UI thread with low priority
|
||||
{
|
||||
var setImageAction = new Action<Tile>(t => t.SetImageSource(LoadImage(imageTileSource, t), animateOpacity));
|
||||
|
||||
foreach (var tile in tiles)
|
||||
{
|
||||
dispatcher.BeginInvoke(setImageAction, DispatcherPriority.Background, tile); // with low priority
|
||||
dispatcher.BeginInvoke(new Action<Tile>(t => t.SetImage(LoadImage(imageTileSource, t))), DispatcherPriority.Background, tile);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -79,70 +89,65 @@ namespace MapControl
|
|||
var sourceName = tileLayer.SourceName;
|
||||
var maxDownloads = tileLayer.MaxParallelDownloads;
|
||||
|
||||
ThreadPool.QueueUserWorkItem(o =>
|
||||
GetTiles(tileList, dispatcher, tileSource, sourceName, animateOpacity, maxDownloads));
|
||||
Task.Run(() => GetTiles(tileList, dispatcher, tileSource, sourceName, maxDownloads));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CancelLoadTiles(TileLayer tileLayer)
|
||||
{
|
||||
Tile tile;
|
||||
while (pendingTiles.TryDequeue(out tile)) ; // no Clear method
|
||||
PendingTile pendingTile;
|
||||
|
||||
while (pendingTiles.TryDequeue(out pendingTile)) ; // no Clear method
|
||||
}
|
||||
|
||||
private void GetTiles(List<Tile> tiles, Dispatcher dispatcher, TileSource tileSource, string sourceName, bool animateOpacity, int maxDownloads)
|
||||
private void GetTiles(List<Tile> tiles, Dispatcher dispatcher, TileSource tileSource, string sourceName, int maxDownloads)
|
||||
{
|
||||
if (Cache != null && !string.IsNullOrWhiteSpace(sourceName) &&
|
||||
!(tileSource is ImageTileSource) && !tileSource.UriFormat.StartsWith("file:"))
|
||||
if (Cache != null &&
|
||||
!string.IsNullOrWhiteSpace(sourceName) &&
|
||||
!(tileSource is ImageTileSource) &&
|
||||
!tileSource.UriFormat.StartsWith("file:"))
|
||||
{
|
||||
var setImageAction = new Action<Tile, ImageSource>((t, i) => t.SetImageSource(i, animateOpacity));
|
||||
var outdatedTiles = new List<Tile>(tiles.Count);
|
||||
|
||||
foreach (var tile in tiles)
|
||||
{
|
||||
var buffer = Cache.Get(TileCache.Key(sourceName, tile)) as byte[];
|
||||
var image = CreateImage(buffer);
|
||||
BitmapSource image;
|
||||
|
||||
if (image == null)
|
||||
if (GetCachedImage(GetCacheKey(sourceName, tile), out image))
|
||||
{
|
||||
pendingTiles.Enqueue(tile); // not yet cached
|
||||
}
|
||||
else if (TileCache.IsExpired(buffer))
|
||||
{
|
||||
dispatcher.Invoke(setImageAction, tile, image); // synchronously before enqueuing
|
||||
outdatedTiles.Add(tile); // update outdated cache
|
||||
dispatcher.BeginInvoke(new Action<Tile, ImageSource>((t, i) => t.SetImage(i)), tile, image);
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatcher.BeginInvoke(setImageAction, tile, image);
|
||||
pendingTiles.Enqueue(new PendingTile(tile, image));
|
||||
}
|
||||
}
|
||||
|
||||
tiles = outdatedTiles; // enqueue outdated tiles after current tiles
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var tile in tiles)
|
||||
{
|
||||
pendingTiles.Enqueue(new PendingTile(tile));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var tile in tiles)
|
||||
{
|
||||
pendingTiles.Enqueue(tile);
|
||||
}
|
||||
var newTaskCount = Math.Min(pendingTiles.Count, maxDownloads) - taskCount;
|
||||
|
||||
while (threadCount < Math.Min(pendingTiles.Count, maxDownloads))
|
||||
while (newTaskCount-- > 0)
|
||||
{
|
||||
Interlocked.Increment(ref threadCount);
|
||||
Interlocked.Increment(ref taskCount);
|
||||
|
||||
ThreadPool.QueueUserWorkItem(o => LoadPendingTiles(dispatcher, tileSource, sourceName, animateOpacity));
|
||||
Task.Run(() => LoadPendingTiles(dispatcher, tileSource, sourceName));
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadPendingTiles(Dispatcher dispatcher, TileSource tileSource, string sourceName, bool animateOpacity)
|
||||
private void LoadPendingTiles(Dispatcher dispatcher, TileSource tileSource, string sourceName)
|
||||
{
|
||||
var setImageAction = new Action<Tile, ImageSource>((t, i) => t.SetImageSource(i, animateOpacity));
|
||||
var imageTileSource = tileSource as ImageTileSource;
|
||||
Tile tile;
|
||||
PendingTile pendingTile;
|
||||
|
||||
while (pendingTiles.TryDequeue(out tile))
|
||||
while (pendingTiles.TryDequeue(out pendingTile))
|
||||
{
|
||||
var tile = pendingTile.Tile;
|
||||
ImageSource image = null;
|
||||
|
||||
if (imageTileSource != null)
|
||||
|
|
@ -161,29 +166,33 @@ namespace MapControl
|
|||
}
|
||||
else
|
||||
{
|
||||
DateTime expirationTime;
|
||||
var buffer = DownloadImage(uri, out expirationTime);
|
||||
HttpStatusCode statusCode;
|
||||
|
||||
image = CreateImage(buffer);
|
||||
image = DownloadImage(uri, GetCacheKey(sourceName, tile), out statusCode);
|
||||
|
||||
if (image != null &&
|
||||
Cache != null &&
|
||||
!string.IsNullOrWhiteSpace(sourceName) &&
|
||||
expirationTime > DateTime.UtcNow)
|
||||
if (statusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
Cache.Set(TileCache.Key(sourceName, tile), buffer, new CacheItemPolicy { AbsoluteExpiration = expirationTime });
|
||||
tileSource.IgnoreTile(tile.XIndex, tile.Y, tile.ZoomLevel); // do not request again
|
||||
}
|
||||
else if (image == null) // download failed, use cached image if available
|
||||
{
|
||||
image = pendingTile.CachedImage;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (image != null || !tile.HasImageSource) // set null image if tile does not yet have an ImageSource
|
||||
if (image != null)
|
||||
{
|
||||
dispatcher.BeginInvoke(setImageAction, tile, image);
|
||||
dispatcher.BeginInvoke(new Action<Tile, ImageSource>((t, i) => t.SetImage(i)), tile, image);
|
||||
}
|
||||
else
|
||||
{
|
||||
tile.SetImage(null);
|
||||
}
|
||||
}
|
||||
|
||||
Interlocked.Decrement(ref threadCount);
|
||||
Interlocked.Decrement(ref taskCount);
|
||||
}
|
||||
|
||||
private static ImageSource LoadImage(ImageTileSource tileSource, Tile tile)
|
||||
|
|
@ -196,7 +205,7 @@ namespace MapControl
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceWarning("Loading tile image failed: {0}", ex.Message);
|
||||
Debug.WriteLine("Loading tile image failed: {0}", (object)ex.Message);
|
||||
}
|
||||
|
||||
return image;
|
||||
|
|
@ -210,122 +219,138 @@ namespace MapControl
|
|||
{
|
||||
try
|
||||
{
|
||||
using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
image = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
|
||||
image = BitmapFrame.Create(fileStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceWarning("Creating tile image failed: {0}", ex.Message);
|
||||
Debug.WriteLine("Creating tile image failed: {0}", (object)ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
private static ImageSource CreateImage(byte[] buffer)
|
||||
private static ImageSource DownloadImage(Uri uri, string cacheKey, out HttpStatusCode statusCode)
|
||||
{
|
||||
ImageSource image = null;
|
||||
|
||||
if (buffer != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var stream = TileCache.ImageStream(buffer))
|
||||
{
|
||||
image = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceWarning("Creating tile image failed: {0}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
private static byte[] DownloadImage(Uri uri, out DateTime expirationTime)
|
||||
{
|
||||
expirationTime = DateTime.UtcNow + DefaultCacheExpiration;
|
||||
|
||||
byte[] buffer = null;
|
||||
BitmapSource image = null;
|
||||
statusCode = HttpStatusCode.Unused;
|
||||
|
||||
try
|
||||
{
|
||||
var request = HttpWebRequest.CreateHttp(uri);
|
||||
request.UserAgent = HttpUserAgent;
|
||||
|
||||
using (var response = (HttpWebResponse)request.GetResponse())
|
||||
using (var responseStream = response.GetResponseStream())
|
||||
{
|
||||
var expiresHeader = response.Headers["Expires"];
|
||||
DateTime expires;
|
||||
statusCode = response.StatusCode;
|
||||
|
||||
if (expiresHeader != null &&
|
||||
DateTime.TryParse(expiresHeader, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out expires) &&
|
||||
expirationTime > expires)
|
||||
using (var responseStream = response.GetResponseStream())
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
expirationTime = expires;
|
||||
responseStream.CopyTo(memoryStream);
|
||||
memoryStream.Position = 0;
|
||||
image = BitmapFrame.Create(memoryStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
|
||||
}
|
||||
|
||||
buffer = TileCache.CreateBuffer(responseStream, (int)response.ContentLength, expirationTime);
|
||||
if (cacheKey != null)
|
||||
{
|
||||
SetCachedImage(cacheKey, image, GetExpiration(response.Headers));
|
||||
}
|
||||
}
|
||||
|
||||
//Trace.TraceInformation("Downloaded {0}, expires {1}", uri, expirationTime);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
if (ex.Status == WebExceptionStatus.ProtocolError)
|
||||
var response = ex.Response as HttpWebResponse;
|
||||
if (response != null)
|
||||
{
|
||||
var statusCode = ((HttpWebResponse)ex.Response).StatusCode;
|
||||
if (statusCode != HttpStatusCode.NotFound)
|
||||
{
|
||||
Trace.TraceWarning("Downloading {0} failed: {1}", uri, ex.Message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace.TraceWarning("Downloading {0} failed with {1}: {2}", uri, ex.Status, ex.Message);
|
||||
statusCode = response.StatusCode;
|
||||
}
|
||||
|
||||
Debug.WriteLine("Downloading {0} failed: {1}: {2}", uri, ex.Status, ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceWarning("Downloading {0} failed: {1}", uri, ex.Message);
|
||||
Debug.WriteLine("Downloading {0} failed: {1}", uri, ex.Message);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
return image;
|
||||
}
|
||||
|
||||
private static class TileCache
|
||||
private static string GetCacheKey(string sourceName, Tile tile)
|
||||
{
|
||||
private const int imageBufferOffset = sizeof(Int64);
|
||||
|
||||
public static string Key(string sourceName, Tile tile)
|
||||
if (Cache == null || string.IsNullOrWhiteSpace(sourceName))
|
||||
{
|
||||
return string.Format("{0}/{1}/{2}/{3}", sourceName, tile.ZoomLevel, tile.XIndex, tile.Y);
|
||||
return null;
|
||||
}
|
||||
|
||||
public static MemoryStream ImageStream(byte[] cacheBuffer)
|
||||
return string.Format("{0}/{1}/{2}/{3}", sourceName, tile.ZoomLevel, tile.XIndex, tile.Y);
|
||||
}
|
||||
|
||||
private static bool GetCachedImage(string cacheKey, out BitmapSource image)
|
||||
{
|
||||
image = Cache.Get(cacheKey) as BitmapSource;
|
||||
|
||||
if (image == null)
|
||||
{
|
||||
return new MemoryStream(cacheBuffer, imageBufferOffset, cacheBuffer.Length - imageBufferOffset, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsExpired(byte[] cacheBuffer)
|
||||
{
|
||||
return DateTime.FromBinary(BitConverter.ToInt64(cacheBuffer, 0)) < DateTime.UtcNow;
|
||||
}
|
||||
var metadata = (BitmapMetadata)image.Metadata;
|
||||
DateTime expiration;
|
||||
|
||||
public static byte[] CreateBuffer(Stream imageStream, int length, DateTime expirationTime)
|
||||
// get cache expiration date from BitmapMetadata.DateTaken, must be parsed with CurrentCulture
|
||||
return metadata == null
|
||||
|| metadata.DateTaken == null
|
||||
|| !DateTime.TryParse(metadata.DateTaken, CultureInfo.CurrentCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out expiration)
|
||||
|| expiration > DateTime.UtcNow;
|
||||
}
|
||||
|
||||
private static void SetCachedImage(string cacheKey, BitmapSource image, DateTime expiration)
|
||||
{
|
||||
var bitmap = BitmapFrame.Create(image);
|
||||
var metadata = (BitmapMetadata)bitmap.Metadata;
|
||||
|
||||
// store cache expiration date in BitmapMetadata.DateTaken
|
||||
metadata.DateTaken = expiration.ToString(CultureInfo.InvariantCulture);
|
||||
metadata.Freeze();
|
||||
bitmap.Freeze();
|
||||
|
||||
Cache.Set(cacheKey, bitmap, new CacheItemPolicy { AbsoluteExpiration = expiration });
|
||||
|
||||
//Debug.WriteLine("Cached {0}, Expires {1}", cacheKey, expiration);
|
||||
}
|
||||
|
||||
private static DateTime GetExpiration(WebHeaderCollection headers)
|
||||
{
|
||||
var cacheControl = headers["Cache-Control"];
|
||||
int maxAge;
|
||||
DateTime expiration;
|
||||
|
||||
if (cacheControl != null &&
|
||||
cacheControl.StartsWith("max-age=") &&
|
||||
int.TryParse(cacheControl.Substring(8), out maxAge))
|
||||
{
|
||||
using (var memoryStream = length > 0 ? new MemoryStream(length + imageBufferOffset) : new MemoryStream())
|
||||
maxAge = Math.Min(maxAge, (int)DefaultCacheExpiration.TotalSeconds);
|
||||
|
||||
expiration = DateTime.UtcNow.AddSeconds(maxAge);
|
||||
}
|
||||
else
|
||||
{
|
||||
var expires = headers["Expires"];
|
||||
var maxExpiration = DateTime.UtcNow.Add(DefaultCacheExpiration);
|
||||
|
||||
if (expires == null ||
|
||||
!DateTime.TryParse(expires, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out expiration) ||
|
||||
expiration > maxExpiration)
|
||||
{
|
||||
memoryStream.Write(BitConverter.GetBytes(expirationTime.ToBinary()), 0, imageBufferOffset);
|
||||
imageStream.CopyTo(memoryStream);
|
||||
|
||||
return length > 0 ? memoryStream.GetBuffer() : memoryStream.ToArray();
|
||||
expiration = maxExpiration;
|
||||
}
|
||||
}
|
||||
|
||||
return expiration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,15 @@
|
|||
// Licensed under the Microsoft Public License (Ms-PL)
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Foundation;
|
||||
using Windows.Storage;
|
||||
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;
|
||||
|
|
@ -18,142 +19,180 @@ using Windows.Web.Http.Filters;
|
|||
namespace MapControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads map tile images.
|
||||
/// Loads map tile images and optionally caches them in a IImageCache.
|
||||
/// </summary>
|
||||
public class TileImageLoader : ITileImageLoader
|
||||
{
|
||||
public static IObjectCache Cache { get; set; }
|
||||
/// <summary>
|
||||
/// Default name of an IImageCache instance that is assigned to the Cache property.
|
||||
/// </summary>
|
||||
public const string DefaultCacheName = "TileCache";
|
||||
|
||||
private HttpClient httpClient;
|
||||
/// <summary>
|
||||
/// Default StorageFolder where an IImageCache instance may save cached data.
|
||||
/// </summary>
|
||||
public static readonly StorageFolder DefaultCacheFolder = ApplicationData.Current.TemporaryFolder;
|
||||
|
||||
/// <summary>
|
||||
/// Default expiration time for cached tile images. Used when no expiration time
|
||||
/// was transmitted on download. The default and recommended minimum value is seven days.
|
||||
/// See OpenStreetMap tile usage policy: http://wiki.openstreetmap.org/wiki/Tile_usage_policy
|
||||
/// </summary>
|
||||
public static TimeSpan DefaultCacheExpiration = TimeSpan.FromDays(7);
|
||||
|
||||
/// <summary>
|
||||
/// The IImageCache implementation used to cache tile images. The default is null.
|
||||
/// </summary>
|
||||
public static Caching.IImageCache Cache;
|
||||
|
||||
private class PendingTile
|
||||
{
|
||||
public readonly Tile Tile;
|
||||
public readonly Uri Uri;
|
||||
public readonly BitmapSource Image;
|
||||
|
||||
public PendingTile(Tile tile, Uri uri)
|
||||
{
|
||||
Tile = tile;
|
||||
Uri = uri;
|
||||
Image = new BitmapImage();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ConcurrentQueue<PendingTile> pendingTiles = new ConcurrentQueue<PendingTile>();
|
||||
private int taskCount;
|
||||
|
||||
public void BeginLoadTiles(TileLayer tileLayer, IEnumerable<Tile> tiles)
|
||||
{
|
||||
var imageTileSource = tileLayer.TileSource as ImageTileSource;
|
||||
var tileSource = tileLayer.TileSource;
|
||||
var imageTileSource = tileSource as ImageTileSource;
|
||||
var sourceName = tileLayer.SourceName;
|
||||
var useCache = Cache != null && !string.IsNullOrEmpty(sourceName);
|
||||
|
||||
foreach (var tile in tiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
ImageSource image = null;
|
||||
|
||||
if (imageTileSource != null)
|
||||
{
|
||||
image = imageTileSource.LoadImage(tile.XIndex, tile.Y, tile.ZoomLevel);
|
||||
tile.SetImage(imageTileSource.LoadImage(tile.XIndex, tile.Y, tile.ZoomLevel));
|
||||
}
|
||||
else
|
||||
{
|
||||
var uri = tileLayer.TileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel);
|
||||
var uri = tileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel);
|
||||
|
||||
if (uri != null)
|
||||
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.SetImage(null);
|
||||
}
|
||||
else if (!useCache)
|
||||
{
|
||||
tile.SetImage(new BitmapImage(uri));
|
||||
}
|
||||
else
|
||||
{
|
||||
pendingTiles.Enqueue(new PendingTile(tile, uri));
|
||||
}
|
||||
}
|
||||
|
||||
tile.SetImageSource(image, tileLayer.AnimateTileOpacity);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine("Loading tile image failed: {0}", ex.Message);
|
||||
}
|
||||
|
||||
var newTaskCount = Math.Min(pendingTiles.Count, tileLayer.MaxParallelDownloads) - taskCount;
|
||||
|
||||
while (newTaskCount-- > 0)
|
||||
{
|
||||
Interlocked.Increment(ref taskCount);
|
||||
|
||||
Task.Run(async () => await LoadPendingTiles(tileSource, sourceName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CancelLoadTiles(TileLayer tileLayer)
|
||||
{
|
||||
PendingTile pendingTile;
|
||||
|
||||
while (pendingTiles.TryDequeue(out pendingTile)) ; // no Clear method
|
||||
}
|
||||
|
||||
private async Task LoadCachedImage(TileLayer tileLayer, Tile tile, Uri uri, BitmapImage bitmap)
|
||||
private async Task LoadPendingTiles(TileSource tileSource, string sourceName)
|
||||
{
|
||||
var cacheKey = string.Format(@"{0}\{1}\{2}\{3}{4}",
|
||||
tileLayer.SourceName, tile.ZoomLevel, tile.XIndex, tile.Y, Path.GetExtension(uri.LocalPath));
|
||||
PendingTile pendingTile;
|
||||
|
||||
var buffer = await Cache.GetAsync(cacheKey) as IBuffer;
|
||||
|
||||
if (buffer != null)
|
||||
while (pendingTiles.TryDequeue(out pendingTile))
|
||||
{
|
||||
await LoadImageFromBuffer(buffer, bitmap);
|
||||
//Debug.WriteLine("Loaded cached image {0}", cacheKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
DownloadAndCacheImage(uri, bitmap, cacheKey);
|
||||
}
|
||||
}
|
||||
var tile = pendingTile.Tile;
|
||||
var image = pendingTile.Image;
|
||||
var uri = pendingTile.Uri;
|
||||
var extension = Path.GetExtension(uri.LocalPath);
|
||||
|
||||
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 () =>
|
||||
if (string.IsNullOrEmpty(extension) || extension == ".jpeg")
|
||||
{
|
||||
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);
|
||||
extension = ".jpg";
|
||||
}
|
||||
|
||||
httpClient.GetAsync(uri).Completed = async (request, status) =>
|
||||
var cacheKey = string.Format(@"{0}\{1}\{2}\{3}{4}", sourceName, tile.ZoomLevel, tile.XIndex, tile.Y, extension);
|
||||
var cacheItem = await Cache.GetAsync(cacheKey);
|
||||
var cachedBuffer = cacheItem != null ? cacheItem.Buffer : null;
|
||||
|
||||
if (cachedBuffer != null && cacheItem.Expires > DateTime.UtcNow)
|
||||
{
|
||||
if (status == AsyncStatus.Completed)
|
||||
await LoadImageFromBuffer(tile, image, cachedBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
var statusCode = await DownloadImage(tile, image, uri, cacheKey, cachedBuffer);
|
||||
|
||||
if (statusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
using (var response = request.GetResults())
|
||||
{
|
||||
await LoadImageFromHttpResponse(response, bitmap, cacheKey);
|
||||
}
|
||||
tileSource.IgnoreTile(tile.XIndex, tile.Y, tile.ZoomLevel); // do not request again
|
||||
}
|
||||
else
|
||||
}
|
||||
}
|
||||
|
||||
Interlocked.Decrement(ref taskCount);
|
||||
}
|
||||
|
||||
private async Task<HttpStatusCode> DownloadImage(Tile tile, BitmapSource image, Uri uri, string cacheKey, IBuffer cachedBuffer)
|
||||
{
|
||||
HttpStatusCode result = HttpStatusCode.None;
|
||||
|
||||
try
|
||||
{
|
||||
using (var httpClient = new HttpClient(new HttpBaseProtocolFilter { AllowAutoRedirect = false }))
|
||||
using (var response = await httpClient.GetAsync(uri))
|
||||
{
|
||||
result = response.StatusCode;
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
Debug.WriteLine("{0}: {1}", uri, request.ErrorCode != null ? request.ErrorCode.Message : status.ToString());
|
||||
await LoadImageFromHttpResponse(response, tile, image, cacheKey);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
Debug.WriteLine("{0}: ({1}) {2}", uri, (int)response.StatusCode, response.ReasonPhrase);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine("{0}: {1}", uri, ex.Message);
|
||||
}
|
||||
|
||||
if (cachedBuffer != null)
|
||||
{
|
||||
await LoadImageFromBuffer(tile, image, cachedBuffer);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task LoadImageFromHttpResponse(HttpResponseMessage response, BitmapImage bitmap, string cacheKey)
|
||||
private async Task LoadImageFromHttpResponse(HttpResponseMessage response, Tile tile, BitmapSource image, string cacheKey)
|
||||
{
|
||||
if (response.IsSuccessStatusCode)
|
||||
using (var stream = new InMemoryRandomAccessStream())
|
||||
{
|
||||
var stream = new InMemoryRandomAccessStream();
|
||||
|
||||
using (var content = response.Content)
|
||||
{
|
||||
await content.WriteToStreamAsync(stream);
|
||||
|
|
@ -162,35 +201,65 @@ namespace MapControl
|
|||
await stream.FlushAsync();
|
||||
stream.Seek(0);
|
||||
|
||||
await bitmap.Dispatcher.RunAsync(CoreDispatcherPriority.Low, async () =>
|
||||
var loaded = await LoadImageFromStream(tile, image, stream);
|
||||
|
||||
if (loaded && cacheKey != null)
|
||||
{
|
||||
var buffer = new Windows.Storage.Streams.Buffer((uint)stream.Size);
|
||||
|
||||
stream.Seek(0);
|
||||
await stream.ReadAsync(buffer, buffer.Capacity, InputStreamOptions.None);
|
||||
|
||||
var maxAge = DefaultCacheExpiration;
|
||||
|
||||
if (response.Headers.CacheControl.MaxAge.HasValue &&
|
||||
response.Headers.CacheControl.MaxAge.Value < maxAge)
|
||||
{
|
||||
maxAge = response.Headers.CacheControl.MaxAge.Value;
|
||||
}
|
||||
|
||||
await Cache.SetAsync(cacheKey, buffer, DateTime.UtcNow.Add(maxAge));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> LoadImageFromBuffer(Tile tile, BitmapSource image, IBuffer buffer)
|
||||
{
|
||||
using (var stream = new InMemoryRandomAccessStream())
|
||||
{
|
||||
await stream.WriteAsync(buffer);
|
||||
await stream.FlushAsync();
|
||||
stream.Seek(0);
|
||||
|
||||
return await LoadImageFromStream(tile, image, stream);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> LoadImageFromStream(Tile tile, BitmapSource image, IRandomAccessStream stream)
|
||||
{
|
||||
var completion = new TaskCompletionSource<bool>();
|
||||
|
||||
var action = image.Dispatcher.RunAsync(
|
||||
CoreDispatcherPriority.Normal,
|
||||
async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await bitmap.SetSourceAsync(stream);
|
||||
await image.SetSourceAsync(stream);
|
||||
tile.SetImage(image, true, false);
|
||||
|
||||
// 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);
|
||||
});
|
||||
completion.SetResult(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine("{0}: {1}", response.RequestMessage.RequestUri, ex.Message);
|
||||
stream.Dispose();
|
||||
Debug.WriteLine(ex.Message);
|
||||
tile.SetImage(null);
|
||||
|
||||
completion.SetResult(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine("{0}: {1}", response.RequestMessage.RequestUri, response.StatusCode);
|
||||
}
|
||||
|
||||
return await completion.Task;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@
|
|||
// Copyright © 2014 Clemens Fischer
|
||||
// Licensed under the Microsoft Public License (Ms-PL)
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MapControl
|
||||
{
|
||||
public interface IObjectCache
|
||||
public partial class TileLayer
|
||||
{
|
||||
Task<object> GetAsync(string key);
|
||||
Task SetAsync(string key, object value);
|
||||
partial void Initialize()
|
||||
{
|
||||
IsHitTestVisible = false;
|
||||
MapPanel.AddParentMapHandlers(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
MapControl/TileLayer.WPF.cs
Normal file
17
MapControl/TileLayer.WPF.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// XAML Map Control - http://xamlmapcontrol.codeplex.com/
|
||||
// Copyright © 2014 Clemens Fischer
|
||||
// Licensed under the Microsoft Public License (Ms-PL)
|
||||
|
||||
using System.Windows;
|
||||
|
||||
namespace MapControl
|
||||
{
|
||||
public partial class TileLayer
|
||||
{
|
||||
static TileLayer()
|
||||
{
|
||||
UIElement.IsHitTestVisibleProperty.OverrideMetadata(
|
||||
typeof(TileLayer), new FrameworkPropertyMetadata(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
#if WINDOWS_RUNTIME
|
||||
using Windows.Foundation;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Documents;
|
||||
using Windows.UI.Xaml.Markup;
|
||||
using Windows.UI.Xaml.Media;
|
||||
|
|
@ -19,12 +20,6 @@ using System.Windows.Media;
|
|||
|
||||
namespace MapControl
|
||||
{
|
||||
public interface ITileImageLoader
|
||||
{
|
||||
void BeginLoadTiles(TileLayer tileLayer, IEnumerable<Tile> tiles);
|
||||
void CancelLoadTiles(TileLayer tileLayer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills a rectangular area with map tiles from a TileSource.
|
||||
/// </summary>
|
||||
|
|
@ -33,7 +28,7 @@ namespace MapControl
|
|||
#else
|
||||
[ContentProperty("TileSource")]
|
||||
#endif
|
||||
public class TileLayer : PanelBase
|
||||
public partial class TileLayer : PanelBase, IMapElement
|
||||
{
|
||||
public static TileLayer Default
|
||||
{
|
||||
|
|
@ -42,17 +37,40 @@ namespace MapControl
|
|||
return new TileLayer
|
||||
{
|
||||
SourceName = "OpenStreetMap",
|
||||
Description="© [OpenStreetMap Contributors](http://www.openstreetmap.org/copyright)",
|
||||
Description = "© [OpenStreetMap Contributors](http://www.openstreetmap.org/copyright)",
|
||||
TileSource = new TileSource { UriFormat = "http://{c}.tile.openstreetmap.org/{z}/{x}/{y}.png" }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TileSourceProperty = DependencyProperty.Register(
|
||||
"TileSource", typeof(TileSource), typeof(TileLayer),
|
||||
new PropertyMetadata(null, (o, e) => ((TileLayer)o).UpdateTiles()));
|
||||
|
||||
public static readonly DependencyProperty SourceNameProperty = DependencyProperty.Register(
|
||||
"SourceName", typeof(string), typeof(TileLayer), new PropertyMetadata(null));
|
||||
|
||||
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register(
|
||||
"Description", typeof(string), typeof(TileLayer), new PropertyMetadata(null));
|
||||
|
||||
public static readonly DependencyProperty MinZoomLevelProperty = DependencyProperty.Register(
|
||||
"MinZoomLevel", typeof(int), typeof(TileLayer), new PropertyMetadata(0));
|
||||
|
||||
public static readonly DependencyProperty MaxZoomLevelProperty = DependencyProperty.Register(
|
||||
"MaxZoomLevel", typeof(int), typeof(TileLayer), new PropertyMetadata(18));
|
||||
|
||||
public static readonly DependencyProperty MaxParallelDownloadsProperty = DependencyProperty.Register(
|
||||
"MaxParallelDownloads", typeof(int), typeof(TileLayer), new PropertyMetadata(4));
|
||||
|
||||
public static readonly DependencyProperty ForegroundProperty = DependencyProperty.Register(
|
||||
"Foreground", typeof(Brush), typeof(TileLayer), new PropertyMetadata(null));
|
||||
|
||||
public static readonly new DependencyProperty BackgroundProperty = DependencyProperty.Register(
|
||||
"Background", typeof(Brush), typeof(TileLayer), new PropertyMetadata(null));
|
||||
|
||||
private readonly ITileImageLoader tileImageLoader;
|
||||
private TileSource tileSource;
|
||||
private List<Tile> tiles = new List<Tile>();
|
||||
private int zoomLevel;
|
||||
private Int32Rect grid;
|
||||
private MapBase parentMap;
|
||||
|
||||
public TileLayer()
|
||||
: this(new TileImageLoader())
|
||||
|
|
@ -62,123 +80,190 @@ namespace MapControl
|
|||
public TileLayer(ITileImageLoader tileImageLoader)
|
||||
{
|
||||
this.tileImageLoader = tileImageLoader;
|
||||
MinZoomLevel = 0;
|
||||
MaxZoomLevel = 18;
|
||||
MaxParallelDownloads = 4;
|
||||
LoadLowerZoomLevels = true;
|
||||
AnimateTileOpacity = true;
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public string SourceName { get; set; }
|
||||
public string Description { get; set; }
|
||||
public int MinZoomLevel { get; set; }
|
||||
public int MaxZoomLevel { get; set; }
|
||||
public int MaxParallelDownloads { get; set; }
|
||||
public bool LoadLowerZoomLevels { get; set; }
|
||||
public bool AnimateTileOpacity { get; set; }
|
||||
public Brush Foreground { get; set; }
|
||||
partial void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// Controls how map tiles are loaded.
|
||||
/// </summary>
|
||||
public TileSource TileSource
|
||||
{
|
||||
get { return (TileSource)GetValue(TileSourceProperty); }
|
||||
set { SetValue(TileSourceProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Name of the TileSource. Used as key in a TileLayerCollection and to name an optional tile cache.
|
||||
/// </summary>
|
||||
public string SourceName
|
||||
{
|
||||
get { return (string)GetValue(SourceNameProperty); }
|
||||
set { SetValue(SourceNameProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Description of the TileLayer.
|
||||
/// </summary>
|
||||
public string Description
|
||||
{
|
||||
get { return (string)GetValue(DescriptionProperty); }
|
||||
set { SetValue(DescriptionProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Minimum zoom level supported by the TileLayer.
|
||||
/// </summary>
|
||||
public int MinZoomLevel
|
||||
{
|
||||
get { return (int)GetValue(MinZoomLevelProperty); }
|
||||
set { SetValue(MinZoomLevelProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maximum zoom level supported by the TileLayer.
|
||||
/// </summary>
|
||||
public int MaxZoomLevel
|
||||
{
|
||||
get { return (int)GetValue(MaxZoomLevelProperty); }
|
||||
set { SetValue(MaxZoomLevelProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of parallel downloads that may be performed by the TileLayer's ITileImageLoader.
|
||||
/// </summary>
|
||||
public int MaxParallelDownloads
|
||||
{
|
||||
get { return (int)GetValue(MaxParallelDownloadsProperty); }
|
||||
set { SetValue(MaxParallelDownloadsProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets MapBase.Foreground, if not null.
|
||||
/// </summary>
|
||||
public Brush Foreground
|
||||
{
|
||||
get { return (Brush)GetValue(ForegroundProperty); }
|
||||
set { SetValue(ForegroundProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets MapBase.Background, if not null.
|
||||
/// New property prevents filling of RenderTransformed TileLayer with Panel.Background.
|
||||
/// </summary>
|
||||
public new Brush Background
|
||||
{
|
||||
get { return (Brush)GetValue(BackgroundProperty); }
|
||||
set { SetValue(BackgroundProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In case the Description text contains copyright links in markdown syntax [text](url),
|
||||
/// the DescriptionInlines property may be used to create a collection of Run and Hyperlink
|
||||
/// inlines to be displayed in e.g. a TextBlock or a Silverlight RichTextBlock.
|
||||
/// </summary>
|
||||
public ICollection<Inline> DescriptionInlines
|
||||
public List<Inline> DescriptionInlines
|
||||
{
|
||||
get { return Description.ToInlines(); }
|
||||
}
|
||||
|
||||
public TileSource TileSource
|
||||
public MapBase ParentMap
|
||||
{
|
||||
get { return tileSource; }
|
||||
get { return parentMap; }
|
||||
set
|
||||
{
|
||||
tileSource = value;
|
||||
|
||||
if (grid.Width > 0 && grid.Height > 0)
|
||||
if (parentMap != null)
|
||||
{
|
||||
ClearTiles();
|
||||
UpdateTiles();
|
||||
parentMap.TileGridChanged -= UpdateTiles;
|
||||
ClearValue(RenderTransformProperty);
|
||||
}
|
||||
|
||||
parentMap = value;
|
||||
|
||||
if (parentMap != null)
|
||||
{
|
||||
parentMap.TileGridChanged += UpdateTiles;
|
||||
RenderTransform = parentMap.TileLayerTransform;
|
||||
}
|
||||
|
||||
UpdateTiles();
|
||||
}
|
||||
}
|
||||
|
||||
internal void ClearTiles()
|
||||
protected virtual void UpdateTiles()
|
||||
{
|
||||
tileImageLoader.CancelLoadTiles(this);
|
||||
tiles.Clear();
|
||||
Children.Clear();
|
||||
}
|
||||
|
||||
internal void UpdateTiles(int zoomLevel, Int32Rect grid)
|
||||
{
|
||||
this.zoomLevel = zoomLevel;
|
||||
this.grid = grid;
|
||||
|
||||
UpdateTiles();
|
||||
}
|
||||
|
||||
private void UpdateTiles()
|
||||
{
|
||||
if (tileSource != null)
|
||||
if (tiles.Count > 0)
|
||||
{
|
||||
tileImageLoader.CancelLoadTiles(this);
|
||||
}
|
||||
|
||||
SelectTiles();
|
||||
Children.Clear();
|
||||
SelectTiles();
|
||||
Children.Clear();
|
||||
|
||||
if (tiles.Count > 0)
|
||||
{
|
||||
foreach (var tile in tiles)
|
||||
{
|
||||
Children.Add(tile.Image);
|
||||
}
|
||||
|
||||
tileImageLoader.BeginLoadTiles(this, tiles.Where(t => !t.HasImageSource));
|
||||
tileImageLoader.BeginLoadTiles(this, tiles.Where(t => t.Pending));
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTiles(object sender, EventArgs e)
|
||||
{
|
||||
UpdateTiles();
|
||||
}
|
||||
|
||||
private void SelectTiles()
|
||||
{
|
||||
var maxZoomLevel = Math.Min(zoomLevel, MaxZoomLevel);
|
||||
var minZoomLevel = maxZoomLevel;
|
||||
|
||||
if (LoadLowerZoomLevels &&
|
||||
Parent is TileContainer &&
|
||||
((TileContainer)Parent).TileLayers.FirstOrDefault() == this)
|
||||
{
|
||||
minZoomLevel = MinZoomLevel;
|
||||
}
|
||||
|
||||
var newTiles = new List<Tile>();
|
||||
|
||||
for (var z = minZoomLevel; z <= maxZoomLevel; z++)
|
||||
if (parentMap != null && TileSource != null)
|
||||
{
|
||||
var tileSize = 1 << (zoomLevel - z);
|
||||
var x1 = (int)Math.Floor((double)grid.X / tileSize); // may be negative
|
||||
var x2 = (grid.X + grid.Width - 1) / tileSize;
|
||||
var y1 = Math.Max(grid.Y / tileSize, 0);
|
||||
var y2 = Math.Min((grid.Y + grid.Height - 1) / tileSize, (1 << z) - 1);
|
||||
var grid = parentMap.TileGrid;
|
||||
var zoomLevel = parentMap.TileZoomLevel;
|
||||
var maxZoomLevel = Math.Min(zoomLevel, MaxZoomLevel);
|
||||
var minZoomLevel = MinZoomLevel;
|
||||
|
||||
for (var y = y1; y <= y2; y++)
|
||||
if (parentMap.TileLayers.FirstOrDefault() != this)
|
||||
{
|
||||
for (var x = x1; x <= x2; x++)
|
||||
// do not load background tiles if this is not the base layer
|
||||
minZoomLevel = Math.Max(maxZoomLevel, minZoomLevel);
|
||||
}
|
||||
|
||||
for (var z = minZoomLevel; z <= maxZoomLevel; z++)
|
||||
{
|
||||
var tileSize = 1 << (zoomLevel - z);
|
||||
var x1 = (int)Math.Floor((double)grid.X / tileSize); // may be negative
|
||||
var x2 = (grid.X + grid.Width - 1) / tileSize;
|
||||
var y1 = Math.Max(grid.Y / tileSize, 0);
|
||||
var y2 = Math.Min((grid.Y + grid.Height - 1) / tileSize, (1 << z) - 1);
|
||||
|
||||
for (var y = y1; y <= y2; y++)
|
||||
{
|
||||
var tile = tiles.FirstOrDefault(t => t.ZoomLevel == z && t.X == x && t.Y == y);
|
||||
|
||||
if (tile == null)
|
||||
for (var x = x1; x <= x2; x++)
|
||||
{
|
||||
tile = new Tile(z, x, y);
|
||||
var tile = tiles.FirstOrDefault(t => t.ZoomLevel == z && t.X == x && t.Y == y);
|
||||
|
||||
var equivalentTile = tiles.FirstOrDefault(
|
||||
t => t.Image.Source != null && t.ZoomLevel == z && t.XIndex == tile.XIndex && t.Y == y);
|
||||
|
||||
if (equivalentTile != null)
|
||||
if (tile == null)
|
||||
{
|
||||
// do not animate to avoid flicker when crossing date line
|
||||
tile.SetImageSource(equivalentTile.Image.Source, false);
|
||||
}
|
||||
}
|
||||
tile = new Tile(z, x, y);
|
||||
|
||||
newTiles.Add(tile);
|
||||
var equivalentTile = tiles.FirstOrDefault(
|
||||
t => t.Image.Source != null && t.ZoomLevel == z && t.XIndex == tile.XIndex && t.Y == y);
|
||||
|
||||
if (equivalentTile != null)
|
||||
{
|
||||
// do not animate to avoid flicker when crossing 180°
|
||||
tile.SetImage(equivalentTile.Image.Source, false);
|
||||
}
|
||||
}
|
||||
|
||||
newTiles.Add(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -188,12 +273,18 @@ namespace MapControl
|
|||
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
foreach (var tile in tiles)
|
||||
if (parentMap != null)
|
||||
{
|
||||
var tileSize = (double)(256 << (zoomLevel - tile.ZoomLevel));
|
||||
tile.Image.Width = tileSize;
|
||||
tile.Image.Height = tileSize;
|
||||
tile.Image.Arrange(new Rect(tileSize * tile.X - 256 * grid.X, tileSize * tile.Y - 256 * grid.Y, tileSize, tileSize));
|
||||
foreach (var tile in tiles)
|
||||
{
|
||||
var tileSize = (double)(256 << (parentMap.TileZoomLevel - tile.ZoomLevel));
|
||||
var x = tileSize * tile.X - 256 * parentMap.TileGrid.X;
|
||||
var y = tileSize * tile.Y - 256 * parentMap.TileGrid.Y;
|
||||
|
||||
tile.Image.Width = tileSize;
|
||||
tile.Image.Height = tileSize;
|
||||
tile.Image.Arrange(new Rect(x, y, tileSize, tileSize));
|
||||
}
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
// Licensed under the Microsoft Public License (Ms-PL)
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
#if WINDOWS_RUNTIME
|
||||
using Windows.Foundation;
|
||||
|
|
@ -20,6 +21,8 @@ namespace MapControl
|
|||
public const int TileSize = 256;
|
||||
public const double MetersPerDegree = 6378137d * Math.PI / 180d; // WGS 84 semi major axis
|
||||
|
||||
protected readonly HashSet<long> IgnoredTiles = new HashSet<long>();
|
||||
|
||||
private Func<int, int, int, Uri> getUri;
|
||||
private string uriFormat = string.Empty;
|
||||
|
||||
|
|
@ -31,7 +34,7 @@ namespace MapControl
|
|||
{
|
||||
this.uriFormat = uriFormat;
|
||||
}
|
||||
|
||||
|
||||
public string UriFormat
|
||||
{
|
||||
get { return uriFormat; }
|
||||
|
|
@ -84,7 +87,38 @@ namespace MapControl
|
|||
|
||||
public virtual Uri GetUri(int x, int y, int zoomLevel)
|
||||
{
|
||||
return getUri != null ? getUri(x, y, zoomLevel) : null;
|
||||
if (getUri == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (IgnoredTiles.Count > 0)
|
||||
{
|
||||
lock (IgnoredTiles)
|
||||
{
|
||||
if (IgnoredTiles.Contains(GetHashCode(x, y, zoomLevel)))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return getUri(x, y, zoomLevel);
|
||||
}
|
||||
|
||||
public void IgnoreTile(int x, int y, int zoomLevel)
|
||||
{
|
||||
lock (IgnoredTiles)
|
||||
{
|
||||
IgnoredTiles.Add(GetHashCode(x, y, zoomLevel));
|
||||
}
|
||||
}
|
||||
|
||||
protected static long GetHashCode(int x, int y, int zoomLevel)
|
||||
{
|
||||
return (long)(x & 0xFFFFFF)
|
||||
+ ((long)(y & 0xFFFFFF) << 24)
|
||||
+ ((long)(zoomLevel & 0xFF) << 48);
|
||||
}
|
||||
|
||||
private Uri GetBasicUri(int x, int y, int zoomLevel)
|
||||
|
|
|
|||
|
|
@ -48,8 +48,8 @@
|
|||
<Compile Include="..\HyperlinkText.cs">
|
||||
<Link>HyperlinkText.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\ImageFileCache.WinRT.cs">
|
||||
<Link>ImageFileCache.WinRT.cs</Link>
|
||||
<Compile Include="..\ImageCache.WinRT.cs">
|
||||
<Link>ImageCache.WinRT.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\ImageTileSource.Silverlight.WinRT.cs">
|
||||
<Link>ImageTileSource.Silverlight.WinRT.cs</Link>
|
||||
|
|
@ -60,8 +60,8 @@
|
|||
<Compile Include="..\Int32Rect.cs">
|
||||
<Link>Int32Rect.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\IObjectCache.WinRT.cs">
|
||||
<Link>IObjectCache.WinRT.cs</Link>
|
||||
<Compile Include="..\ITileImageLoader.cs">
|
||||
<Link>ITileImageLoader.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Location.cs">
|
||||
<Link>Location.cs</Link>
|
||||
|
|
@ -144,27 +144,21 @@
|
|||
<Compile Include="..\Pushpin.Silverlight.WinRT.cs">
|
||||
<Link>Pushpin.Silverlight.WinRT.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Settings.cs">
|
||||
<Link>Settings.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Tile.cs">
|
||||
<Link>Tile.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Tile.Silverlight.WinRT.cs">
|
||||
<Link>Tile.Silverlight.WinRT.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\TileContainer.cs">
|
||||
<Link>TileContainer.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\TileContainer.Silverlight.WinRT.cs">
|
||||
<Link>TileContainer.Silverlight.WinRT.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\TileImageLoader.WinRT.cs">
|
||||
<Link>TileImageLoader.WinRT.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\TileLayer.cs">
|
||||
<Link>TileLayer.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\TileLayer.Silverlight.WinRT.cs">
|
||||
<Link>TileLayer.Silverlight.WinRT.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\TileLayerCollection.cs">
|
||||
<Link>TileLayerCollection.cs</Link>
|
||||
</Compile>
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyCompany("Clemens Fischer")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyVersion("2.3.1")]
|
||||
[assembly: AssemblyFileVersion("2.3.1")]
|
||||
[assembly: AssemblyVersion("2.4.0")]
|
||||
[assembly: AssemblyFileVersion("2.4.0")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue