diff --git a/MapControl/Shared/MapBase.cs b/MapControl/Shared/MapBase.cs
index 821888b1..fa744e42 100644
--- a/MapControl/Shared/MapBase.cs
+++ b/MapControl/Shared/MapBase.cs
@@ -76,18 +76,6 @@ namespace MapControl
private double centerLongitude;
private bool internalPropertyChange;
- public MapBase()
- {
- Initialize();
-
- MapProjection = new WebMercatorProjection();
- ScaleRotateTransform.Children.Add(ScaleTransform);
- ScaleRotateTransform.Children.Add(RotateTransform);
- }
-
- partial void Initialize(); // Windows Runtime and Silverlight only
- partial void RemoveAnimation(DependencyProperty property); // WPF only
-
///
/// Raised when the current viewport has changed.
///
@@ -504,7 +492,8 @@ namespace MapControl
targetCenter.Latitude,
Location.NearestLongitude(targetCenter.Longitude, Center.Longitude))),
Duration = AnimationDuration,
- EasingFunction = AnimationEasingFunction
+ EasingFunction = AnimationEasingFunction,
+ FillBehavior = AnimationFillBehavior
};
centerAnimation.Completed += CenterAnimationCompleted;
@@ -522,7 +511,6 @@ namespace MapControl
InternalSetValue(CenterProperty, TargetCenter);
InternalSetValue(CenterPointProperty, MapProjection.LocationToPoint(TargetCenter));
- RemoveAnimation(CenterPointProperty); // remove holding animation in WPF
UpdateTransform();
}
}
@@ -607,7 +595,8 @@ namespace MapControl
{
To = targetZoomLevel,
Duration = AnimationDuration,
- EasingFunction = AnimationEasingFunction
+ EasingFunction = AnimationEasingFunction,
+ FillBehavior = AnimationFillBehavior
};
zoomLevelAnimation.Completed += ZoomLevelAnimationCompleted;
@@ -624,8 +613,6 @@ namespace MapControl
zoomLevelAnimation = null;
InternalSetValue(ZoomLevelProperty, TargetZoomLevel);
- RemoveAnimation(ZoomLevelProperty); // remove holding animation in WPF
-
UpdateTransform(true);
}
}
@@ -681,7 +668,8 @@ namespace MapControl
{
By = delta,
Duration = AnimationDuration,
- EasingFunction = AnimationEasingFunction
+ EasingFunction = AnimationEasingFunction,
+ FillBehavior = AnimationFillBehavior
};
headingAnimation.Completed += HeadingAnimationCompleted;
@@ -698,7 +686,6 @@ namespace MapControl
headingAnimation = null;
InternalSetValue(HeadingProperty, TargetHeading);
- RemoveAnimation(HeadingProperty); // remove holding animation in WPF
UpdateTransform();
}
}
diff --git a/MapControl/UWP/MapItemsControl.UWP.cs b/MapControl/Shared/MapItemsControl.cs
similarity index 59%
rename from MapControl/UWP/MapItemsControl.UWP.cs
rename to MapControl/Shared/MapItemsControl.cs
index a01a9d25..12f49643 100644
--- a/MapControl/UWP/MapItemsControl.UWP.cs
+++ b/MapControl/Shared/MapItemsControl.cs
@@ -2,20 +2,39 @@
// © 2017 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
+#if WINDOWS_UWP
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
+#else
+using System.Windows;
+using System.Windows.Controls;
+#endif
namespace MapControl
{
///
- /// Manages a collection of selectable items on a Map. Uses MapItem as item container class.
+ /// Container class for an item in a MapItemsControl.
+ ///
+ public class MapItem : ListBoxItem
+ {
+ public MapItem()
+ {
+ DefaultStyleKey = typeof(MapItem);
+
+ MapPanel.InitMapElement(this);
+ }
+ }
+
+ ///
+ /// Manages a collection of selectable items on a Map.
///
public class MapItemsControl : ListBox
{
public MapItemsControl()
{
DefaultStyleKey = typeof(MapItemsControl);
- MapPanel.AddParentMapHandlers(this);
+
+ MapPanel.InitMapElement(this);
}
protected override DependencyObject GetContainerForItemOverride()
diff --git a/MapControl/Shared/MapPanel.cs b/MapControl/Shared/MapPanel.cs
index 5a5889d5..0e608d8b 100644
--- a/MapControl/Shared/MapPanel.cs
+++ b/MapControl/Shared/MapPanel.cs
@@ -16,6 +16,10 @@ using System.Windows.Media;
namespace MapControl
{
+ ///
+ /// Optional interface to hold the value of the MapPanel.ParentMap attached property.
+ /// May be used to get notified when the property value changes.
+ ///
public interface IMapElement
{
MapBase ParentMap { get; set; }
@@ -35,6 +39,13 @@ namespace MapControl
public static readonly DependencyProperty BoundingBoxProperty = DependencyProperty.RegisterAttached(
"BoundingBox", typeof(BoundingBox), typeof(MapPanel), new PropertyMetadata(null, BoundingBoxPropertyChanged));
+ private MapBase parentMap;
+
+ public MapPanel()
+ {
+ InitMapElement(this);
+ }
+
public static Location GetLocation(UIElement element)
{
return (Location)element.GetValue(LocationProperty);
@@ -55,12 +66,10 @@ namespace MapControl
element.SetValue(BoundingBoxProperty, value);
}
- private MapBase parentMap;
-
public MapBase ParentMap
{
get { return parentMap; }
- set { SetParentMapOverride(value); }
+ set { SetParentMap(value); }
}
protected override Size MeasureOverride(Size availableSize)
@@ -101,7 +110,7 @@ namespace MapControl
return finalSize;
}
- protected virtual void SetParentMapOverride(MapBase map)
+ protected virtual void SetParentMap(MapBase map)
{
if (parentMap != null && parentMap != this)
{
diff --git a/MapControl/Shared/MapPath.cs b/MapControl/Shared/MapPath.cs
index 4dbf244a..d36a0bc3 100644
--- a/MapControl/Shared/MapPath.cs
+++ b/MapControl/Shared/MapPath.cs
@@ -24,6 +24,11 @@ namespace MapControl
nameof(Location), typeof(Location), typeof(MapPath),
new PropertyMetadata(null, (o, e) => ((MapPath)o).LocationPropertyChanged()));
+ public MapPath()
+ {
+ MapPanel.InitMapElement(this);
+ }
+
public Location Location
{
get { return (Location)GetValue(LocationProperty); }
diff --git a/MapControl/Shared/MapTileLayer.cs b/MapControl/Shared/MapTileLayer.cs
index ccf3e691..8343d4ea 100644
--- a/MapControl/Shared/MapTileLayer.cs
+++ b/MapControl/Shared/MapTileLayer.cs
@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Threading.Tasks;
#if WINDOWS_UWP
using Windows.Foundation;
using Windows.UI.Xaml;
@@ -21,13 +22,13 @@ namespace MapControl
{
public interface ITileImageLoader
{
- void LoadTiles(MapTileLayer tileLayer);
+ Task LoadTilesAsync(MapTileLayer tileLayer);
}
///
/// Fills the map viewport with map tiles from a TileSource.
///
- public partial class MapTileLayer : Panel, IMapLayer
+ public class MapTileLayer : Panel, IMapLayer
{
///
/// A default MapTileLayer using OpenStreetMap data.
@@ -66,9 +67,6 @@ namespace MapControl
public static readonly DependencyProperty MaxZoomLevelProperty = DependencyProperty.Register(
nameof(MaxZoomLevel), typeof(int), typeof(MapTileLayer), new PropertyMetadata(18));
- public static readonly DependencyProperty MaxParallelDownloadsProperty = DependencyProperty.Register(
- nameof(MaxParallelDownloads), typeof(int), typeof(MapTileLayer), new PropertyMetadata(4));
-
public static readonly DependencyProperty UpdateIntervalProperty = DependencyProperty.Register(
nameof(UpdateInterval), typeof(TimeSpan), typeof(MapTileLayer),
new PropertyMetadata(TimeSpan.FromSeconds(0.2), (o, e) => ((MapTileLayer)o).updateTimer.Interval = (TimeSpan)e.NewValue));
@@ -92,17 +90,16 @@ namespace MapControl
public MapTileLayer(ITileImageLoader tileImageLoader)
{
- Initialize();
-
+ IsHitTestVisible = false;
RenderTransform = new MatrixTransform();
TileImageLoader = tileImageLoader;
Tiles = new List();
updateTimer = new DispatcherTimer { Interval = UpdateInterval };
updateTimer.Tick += (s, e) => UpdateTileGrid();
- }
- partial void Initialize(); // Windows Runtime and Silverlight only
+ MapPanel.InitMapElement(this);
+ }
public ITileImageLoader TileImageLoader { get; private set; }
public ICollection Tiles { get; private set; }
@@ -162,15 +159,6 @@ namespace MapControl
set { SetValue(MaxZoomLevelProperty, value); }
}
- ///
- /// Maximum number of parallel downloads that may be performed by the MapTileLayer's ITileImageLoader.
- ///
- public int MaxParallelDownloads
- {
- get { return (int)GetValue(MaxParallelDownloadsProperty); }
- set { SetValue(MaxParallelDownloadsProperty, value); }
- }
-
///
/// Minimum time interval between tile updates.
///
@@ -400,7 +388,7 @@ namespace MapControl
Children.Add(tile.Image);
}
- TileImageLoader.LoadTiles(this);
+ var task = TileImageLoader.LoadTilesAsync(this);
}
}
}
diff --git a/MapControl/UWP/Pushpin.UWP.cs b/MapControl/Shared/Pushpin.cs
similarity index 53%
rename from MapControl/UWP/Pushpin.UWP.cs
rename to MapControl/Shared/Pushpin.cs
index 2577f4be..97bb14e9 100644
--- a/MapControl/UWP/Pushpin.UWP.cs
+++ b/MapControl/Shared/Pushpin.cs
@@ -2,25 +2,24 @@
// © 2017 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
+#if WINDOWS_UWP
using Windows.UI.Xaml.Controls;
+#else
+using System.Windows.Controls;
+#endif
namespace MapControl
{
///
- /// Displays a pushpin at a geographic location provided by the MapPanel.Location attached property.
+ /// Pushpin at a geographic location specified by the MapPanel.Location attached property.
///
public class Pushpin : ContentControl
{
public Pushpin()
{
DefaultStyleKey = typeof(Pushpin);
- MapPanel.AddParentMapHandlers(this);
- }
- public Location Location
- {
- get { return (Location)GetValue(MapPanel.LocationProperty); }
- set { SetValue(MapPanel.LocationProperty, value); }
+ MapPanel.InitMapElement(this);
}
}
}
diff --git a/MapControl/Shared/TileImageLoader.cs b/MapControl/Shared/TileImageLoader.cs
index 51891c95..986ca740 100644
--- a/MapControl/Shared/TileImageLoader.cs
+++ b/MapControl/Shared/TileImageLoader.cs
@@ -7,6 +7,7 @@ using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.Linq;
+using System.Net;
using System.Threading;
using System.Threading.Tasks;
#if WINDOWS_UWP
@@ -22,11 +23,6 @@ namespace MapControl
///
public partial class TileImageLoader : ITileImageLoader
{
- ///
- /// The HttpClient instance used when image data is downloaded from a web resource.
- ///
- public static HttpClient HttpClient { get; set; } = new HttpClient();
-
///
/// Default expiration time for cached tile images. Used when no expiration time
/// was transmitted on download. The default value is one day.
@@ -50,13 +46,10 @@ namespace MapControl
///
public static string CacheKeyFormat { get; set; } = "{0};{1};{2};{3}{4}";
- private const string bingMapsTileInfo = "X-VE-Tile-Info";
- private const string bingMapsNoTile = "no-tile";
-
private readonly ConcurrentStack pendingTiles = new ConcurrentStack();
private int taskCount;
- public void LoadTiles(MapTileLayer tileLayer)
+ public async Task LoadTilesAsync(MapTileLayer tileLayer)
{
pendingTiles.Clear();
@@ -69,18 +62,18 @@ namespace MapControl
if (Cache == null || string.IsNullOrEmpty(sourceName) ||
tileSource.UriFormat == null || !tileSource.UriFormat.StartsWith("http"))
{
- // no caching, load tile images in UI thread
+ // no caching, load tile images directly
foreach (var tile in tiles)
{
- LoadTileImage(tileSource, tile);
+ await LoadTileImageAsync(tileSource, tile);
}
}
else
{
pendingTiles.PushRange(tiles.Reverse().ToArray());
- while (taskCount < Math.Min(pendingTiles.Count, tileLayer.MaxParallelDownloads))
+ while (taskCount < Math.Min(pendingTiles.Count, ServicePointManager.DefaultConnectionLimit))
{
Interlocked.Increment(ref taskCount);
@@ -95,13 +88,13 @@ namespace MapControl
}
}
- private void LoadTileImage(TileSource tileSource, Tile tile)
+ private async Task LoadTileImageAsync(TileSource tileSource, Tile tile)
{
tile.Pending = false;
try
{
- var imageSource = tileSource.LoadImage(tile.XIndex, tile.Y, tile.ZoomLevel);
+ var imageSource = await tileSource.LoadImageAsync(tile.XIndex, tile.Y, tile.ZoomLevel);
if (imageSource != null)
{
diff --git a/MapControl/Shared/TileSource.cs b/MapControl/Shared/TileSource.cs
index cf268663..aea491c4 100644
--- a/MapControl/Shared/TileSource.cs
+++ b/MapControl/Shared/TileSource.cs
@@ -4,13 +4,6 @@
using System;
using System.Globalization;
-#if WINDOWS_UWP
-using Windows.UI.Xaml.Media;
-using Windows.UI.Xaml.Media.Imaging;
-#else
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-#endif
namespace MapControl
{
@@ -92,17 +85,6 @@ namespace MapControl
return getUri?.Invoke(x, y, zoomLevel);
}
- ///
- /// Gets the map tile ImageSource without caching in TileImageLoader.Cache.
- /// By overriding LoadImage an application can provide arbitrary tile images.
- ///
- public virtual ImageSource LoadImage(int x, int y, int zoomLevel)
- {
- var uri = GetUri(x, y, zoomLevel);
-
- return uri != null ? new BitmapImage(uri) : null;
- }
-
private Uri GetBasicUri(int x, int y, int zoomLevel)
{
return new Uri(uriFormat
diff --git a/MapControl/UWP/MapBase.UWP.cs b/MapControl/UWP/MapBase.UWP.cs
index fe16760c..9dacb391 100644
--- a/MapControl/UWP/MapBase.UWP.cs
+++ b/MapControl/UWP/MapBase.UWP.cs
@@ -6,11 +6,14 @@ using Windows.Foundation;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Media.Animation;
namespace MapControl
{
public partial class MapBase
{
+ private const FillBehavior AnimationFillBehavior = FillBehavior.HoldEnd;
+
public static readonly DependencyProperty ForegroundProperty = DependencyProperty.Register(
nameof(Foreground), typeof(Brush), typeof(MapBase),
new PropertyMetadata(new SolidColorBrush(Colors.Black)));
@@ -39,25 +42,24 @@ namespace MapControl
nameof(TargetHeading), typeof(double), typeof(MapBase),
new PropertyMetadata(0d, (o, e) => ((MapBase)o).TargetHeadingPropertyChanged((double)e.NewValue)));
- partial void Initialize()
+ public MapBase()
{
+ MapProjection = new WebMercatorProjection();
+ ScaleRotateTransform.Children.Add(ScaleTransform);
+ ScaleRotateTransform.Children.Add(RotateTransform);
+
// set Background by Style to enable resetting by ClearValue in MapLayerPropertyChanged
var style = new Style(typeof(MapBase));
style.Setters.Add(new Setter(BackgroundProperty, new SolidColorBrush(Colors.Transparent)));
Style = style;
- var clip = new RectangleGeometry();
- Clip = clip;
+ Clip = new RectangleGeometry();
SizeChanged += (s, e) =>
{
- if (clip.Rect.Width != e.NewSize.Width || clip.Rect.Height != e.NewSize.Height)
- {
- clip.Rect = new Rect(0d, 0d, e.NewSize.Width, e.NewSize.Height);
-
- ResetTransformCenter();
- UpdateTransform();
- }
+ Clip.Rect = new Rect(0d, 0d, e.NewSize.Width, e.NewSize.Height);
+ ResetTransformCenter();
+ UpdateTransform();
};
}
}
diff --git a/MapControl/UWP/MapControl.UWP.csproj b/MapControl/UWP/MapControl.UWP.csproj
index ad8d5913..a6a0b399 100644
--- a/MapControl/UWP/MapControl.UWP.csproj
+++ b/MapControl/UWP/MapControl.UWP.csproj
@@ -27,6 +27,7 @@
prompt
4
true
+ CS1998
AnyCPU
@@ -88,6 +89,9 @@
MapImageLayer.cs
+
+ MapItemsControl.cs
+
MapOverlay.cs
@@ -112,6 +116,9 @@
OrthographicProjection.cs
+
+ Pushpin.cs
+
StereographicProjection.cs
@@ -143,18 +150,15 @@
-
-
-
-
+
diff --git a/MapControl/UWP/MapItem.UWP.cs b/MapControl/UWP/MapItem.UWP.cs
deleted file mode 100644
index 82189b56..00000000
--- a/MapControl/UWP/MapItem.UWP.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
-// © 2017 Clemens Fischer
-// Licensed under the Microsoft Public License (Ms-PL)
-
-using Windows.UI.Xaml.Controls;
-
-namespace MapControl
-{
- ///
- /// Container class for an item in a MapItemsControl.
- ///
- public class MapItem : ListBoxItem
- {
- public MapItem()
- {
- DefaultStyleKey = typeof(MapItem);
- MapPanel.AddParentMapHandlers(this);
- }
-
- public Location Location
- {
- get { return (Location)GetValue(MapPanel.LocationProperty); }
- set { SetValue(MapPanel.LocationProperty, value); }
- }
- }
-}
diff --git a/MapControl/UWP/MapOverlay.UWP.cs b/MapControl/UWP/MapOverlay.UWP.cs
index c6d18f93..04aa4b5f 100644
--- a/MapControl/UWP/MapOverlay.UWP.cs
+++ b/MapControl/UWP/MapOverlay.UWP.cs
@@ -56,7 +56,7 @@ namespace MapControl
public static readonly DependencyProperty StrokeMiterLimitProperty = DependencyProperty.Register(
nameof(StrokeMiterLimit), typeof(double), typeof(MapOverlay), new PropertyMetadata(1d));
- protected override void SetParentMapOverride(MapBase parentMap)
+ protected override void SetParentMap(MapBase parentMap)
{
if (GetBindingExpression(ForegroundProperty) != null)
{
@@ -89,7 +89,7 @@ namespace MapControl
}
}
- base.SetParentMapOverride(parentMap);
+ base.SetParentMap(parentMap);
}
}
}
diff --git a/MapControl/UWP/MapPanel.UWP.cs b/MapControl/UWP/MapPanel.UWP.cs
index 50b60b47..3571c282 100644
--- a/MapControl/UWP/MapPanel.UWP.cs
+++ b/MapControl/UWP/MapPanel.UWP.cs
@@ -12,27 +12,20 @@ namespace MapControl
public static readonly DependencyProperty ParentMapProperty = DependencyProperty.RegisterAttached(
"ParentMap", typeof(MapBase), typeof(MapPanel), new PropertyMetadata(null, ParentMapPropertyChanged));
- public MapPanel()
+ public static void InitMapElement(FrameworkElement element)
{
- if (this is MapBase)
+ if (element is MapBase)
{
- SetValue(ParentMapProperty, this);
+ element.SetValue(ParentMapProperty, element);
}
else
{
- AddParentMapHandlers(this);
- }
- }
+ // Workaround for missing property value inheritance in Windows Runtime.
+ // Loaded and Unloaded handlers set and clear the ParentMap property value.
- ///
- /// Helper method to work around missing property value inheritance in Silverlight and Windows Runtime.
- /// Adds Loaded and Unloaded event handlers to the specified FrameworkElement, which set and clear the
- /// value of the MapPanel.ParentMap attached property.
- ///
- public static void AddParentMapHandlers(FrameworkElement element)
- {
- element.Loaded += (s, e) => GetParentMap(element);
- element.Unloaded += (s, e) => element.ClearValue(ParentMapProperty);
+ element.Loaded += (s, e) => GetParentMap(element);
+ element.Unloaded += (s, e) => element.ClearValue(ParentMapProperty);
+ }
}
public static MapBase GetParentMap(UIElement element)
@@ -49,20 +42,12 @@ namespace MapControl
private static MapBase FindParentMap(UIElement element)
{
- MapBase parentMap = null;
- var parentElement = VisualTreeHelper.GetParent(element) as UIElement;
+ var parent = VisualTreeHelper.GetParent(element) as UIElement;
- if (parentElement != null)
- {
- parentMap = parentElement as MapBase;
-
- if (parentMap == null)
- {
- parentMap = GetParentMap(parentElement);
- }
- }
-
- return parentMap;
+ return parent == null ? null
+ : ((parent as MapBase)
+ ?? (MapBase)element.GetValue(ParentMapProperty)
+ ?? FindParentMap(parent));
}
}
}
diff --git a/MapControl/UWP/MapPath.UWP.cs b/MapControl/UWP/MapPath.UWP.cs
index 43335bd6..1f6ee158 100644
--- a/MapControl/UWP/MapPath.UWP.cs
+++ b/MapControl/UWP/MapPath.UWP.cs
@@ -12,11 +12,6 @@ namespace MapControl
{
private Geometry data;
- public MapPath()
- {
- MapPanel.AddParentMapHandlers(this);
- }
-
protected override Size MeasureOverride(Size availableSize)
{
if (Stretch != Stretch.None)
diff --git a/MapControl/UWP/TileImageLoader.UWP.cs b/MapControl/UWP/TileImageLoader.UWP.cs
index 7d9e7b43..8d228bd8 100644
--- a/MapControl/UWP/TileImageLoader.UWP.cs
+++ b/MapControl/UWP/TileImageLoader.UWP.cs
@@ -9,7 +9,6 @@ using Windows.Storage;
using Windows.Storage.Streams;
using Windows.UI.Core;
using Windows.UI.Xaml.Media.Imaging;
-using Windows.Web.Http;
namespace MapControl
{
@@ -31,11 +30,6 @@ namespace MapControl
var buffer = cacheItem?.Buffer;
var loaded = false;
- //if (buffer != null)
- //{
- // Debug.WriteLine("TileImageLoader: {0}: expire{1} {2}", cacheKey, cacheItem.Expiration < DateTime.UtcNow ? "d" : "s", cacheItem.Expiration);
- //}
-
if (buffer == null || cacheItem.Expiration < DateTime.UtcNow)
{
loaded = await DownloadTileImageAsync(tile, uri, cacheKey);
@@ -49,28 +43,26 @@ namespace MapControl
private async Task DownloadTileImageAsync(Tile tile, Uri uri, string cacheKey)
{
+ var success = false;
+
try
{
- using (var response = await HttpClient.GetAsync(uri))
+ using (var response = await TileSource.HttpClient.GetAsync(uri))
{
- if (response.IsSuccessStatusCode)
+ success = response.IsSuccessStatusCode;
+
+ if (!success)
{
- string tileInfo;
-
- if (!response.Headers.TryGetValue(bingMapsTileInfo, out tileInfo) ||
- tileInfo != bingMapsNoTile)
- {
- var buffer = await response.Content.ReadAsBufferAsync();
-
- await SetTileImageAsync(tile, buffer); // create BitmapImage in UI thread before caching
-
- await Cache.SetAsync(cacheKey, buffer, GetExpiration(response));
- }
-
- return true;
+ Debug.WriteLine("TileImageLoader: {0}: {1} {2}", uri, (int)response.StatusCode, response.ReasonPhrase);
}
+ else if (TileSource.TileAvailable(response.Headers))
+ {
+ var buffer = await response.Content.ReadAsBufferAsync();
- Debug.WriteLine("TileImageLoader: {0}: {1} {2}", uri, (int)response.StatusCode, response.ReasonPhrase);
+ await SetTileImageAsync(tile, buffer); // create BitmapImage before caching
+
+ await Cache.SetAsync(cacheKey, buffer, GetExpiration(response));
+ }
}
}
catch (Exception ex)
@@ -78,7 +70,7 @@ namespace MapControl
Debug.WriteLine("TileImageLoader: {0}: {1}", uri, ex.Message);
}
- return false;
+ return success;
}
private async Task SetTileImageAsync(Tile tile, IBuffer buffer)
diff --git a/MapControl/UWP/TileSource.UWP.cs b/MapControl/UWP/TileSource.UWP.cs
new file mode 100644
index 00000000..46671b21
--- /dev/null
+++ b/MapControl/UWP/TileSource.UWP.cs
@@ -0,0 +1,84 @@
+// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
+// © 2017 Clemens Fischer
+// Licensed under the Microsoft Public License (Ms-PL)
+
+using System;
+using System.Diagnostics;
+using System.Threading.Tasks;
+using Windows.Storage.Streams;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Media.Imaging;
+using Windows.Web.Http;
+using Windows.Web.Http.Headers;
+
+namespace MapControl
+{
+ public partial class TileSource
+ {
+ ///
+ /// The HttpClient instance used when image data is downloaded from a web resource.
+ ///
+ public static HttpClient HttpClient { get; set; } = new HttpClient();
+
+ ///
+ /// Check HTTP response headers for tile unavailability, e.g. X-VE-Tile-Info=no-tile
+ ///
+ public static bool TileAvailable(HttpResponseHeaderCollection responseHeaders)
+ {
+ string tileInfo;
+
+ return !responseHeaders.TryGetValue("X-VE-Tile-Info", out tileInfo) || tileInfo != "no-tile";
+ }
+
+ ///
+ /// Load a tile ImageSource asynchronously from GetUri(x, y, zoomLevel)
+ ///
+ public virtual async Task LoadImageAsync(int x, int y, int zoomLevel)
+ {
+ ImageSource imageSource = null;
+
+ var uri = GetUri(x, y, zoomLevel);
+
+ if (uri != null)
+ {
+ try
+ {
+ if (uri.Scheme == "http")
+ {
+ using (var response = await HttpClient.GetAsync(uri))
+ {
+ if (!response.IsSuccessStatusCode)
+ {
+ Debug.WriteLine("TileSource: {0}: {1} {2}", uri, (int)response.StatusCode, response.ReasonPhrase);
+ }
+ else if (TileAvailable(response.Headers))
+ {
+ var bitmapImage = new BitmapImage();
+
+ using (var stream = new InMemoryRandomAccessStream())
+ {
+ await response.Content.WriteToStreamAsync(stream);
+ stream.Seek(0);
+
+ await bitmapImage.SetSourceAsync(stream);
+ }
+
+ imageSource = bitmapImage;
+ }
+ }
+ }
+ else
+ {
+ imageSource = new BitmapImage(uri);
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("TileSource: {0}: {1}", uri, ex.Message);
+ }
+ }
+
+ return imageSource;
+ }
+ }
+}
diff --git a/MapControl/WPF/MapBase.WPF.cs b/MapControl/WPF/MapBase.WPF.cs
index b21702a0..aa6808a6 100644
--- a/MapControl/WPF/MapBase.WPF.cs
+++ b/MapControl/WPF/MapBase.WPF.cs
@@ -5,11 +5,14 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
+using System.Windows.Media.Animation;
namespace MapControl
{
public partial class MapBase
{
+ private const FillBehavior AnimationFillBehavior = FillBehavior.Stop;
+
public static readonly DependencyProperty ForegroundProperty =
Control.ForegroundProperty.AddOwner(typeof(MapBase));
@@ -49,9 +52,11 @@ namespace MapControl
BackgroundProperty.OverrideMetadata(typeof(MapBase), new FrameworkPropertyMetadata(Brushes.Transparent));
}
- partial void RemoveAnimation(DependencyProperty property)
+ public MapBase()
{
- BeginAnimation(property, null);
+ MapProjection = new WebMercatorProjection();
+ ScaleRotateTransform.Children.Add(ScaleTransform);
+ ScaleRotateTransform.Children.Add(RotateTransform);
}
///
diff --git a/MapControl/WPF/MapControl.WPF.csproj b/MapControl/WPF/MapControl.WPF.csproj
index f970cadf..4179e63f 100644
--- a/MapControl/WPF/MapControl.WPF.csproj
+++ b/MapControl/WPF/MapControl.WPF.csproj
@@ -23,6 +23,8 @@
DEBUG;TRACE
prompt
4
+
+
none
@@ -99,6 +101,9 @@
MapImageLayer.cs
+
+ MapItemsControl.cs
+
MapOverlay.cs
@@ -123,6 +128,9 @@
OrthographicProjection.cs
+
+ Pushpin.cs
+
StereographicProjection.cs
@@ -152,15 +160,12 @@
-
-
-
-
+
diff --git a/MapControl/WPF/MapItem.WPF.cs b/MapControl/WPF/MapItem.WPF.cs
deleted file mode 100644
index 6491ea8e..00000000
--- a/MapControl/WPF/MapItem.WPF.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
-// © 2017 Clemens Fischer
-// Licensed under the Microsoft Public License (Ms-PL)
-
-using System.Windows;
-using System.Windows.Controls;
-
-namespace MapControl
-{
- ///
- /// Container class for an item in a MapItemsControl.
- ///
- public class MapItem : ListBoxItem
- {
- static MapItem()
- {
- DefaultStyleKeyProperty.OverrideMetadata(typeof(MapItem), new FrameworkPropertyMetadata(typeof(MapItem)));
- }
-
- public static readonly DependencyProperty LocationProperty = MapPanel.LocationProperty.AddOwner(typeof(MapItem));
-
- public Location Location
- {
- get { return (Location)GetValue(LocationProperty); }
- set { SetValue(LocationProperty, value); }
- }
- }
-}
diff --git a/MapControl/WPF/MapItemsControl.WPF.cs b/MapControl/WPF/MapItemsControl.WPF.cs
deleted file mode 100644
index 2a7f3808..00000000
--- a/MapControl/WPF/MapItemsControl.WPF.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
-// © 2017 Clemens Fischer
-// Licensed under the Microsoft Public License (Ms-PL)
-
-using System.Windows;
-using System.Windows.Controls;
-
-namespace MapControl
-{
- ///
- /// Manages a collection of selectable items on a Map. Uses MapItem as item container class.
- ///
- public class MapItemsControl : ListBox
- {
- static MapItemsControl()
- {
- DefaultStyleKeyProperty.OverrideMetadata(typeof(MapItemsControl), new FrameworkPropertyMetadata(typeof(MapItemsControl)));
- }
-
- protected override DependencyObject GetContainerForItemOverride()
- {
- return new MapItem();
- }
-
- protected override bool IsItemItsOwnContainerOverride(object item)
- {
- return item is MapItem;
- }
- }
-}
diff --git a/MapControl/WPF/MapOverlay.WPF.cs b/MapControl/WPF/MapOverlay.WPF.cs
index a8ec2f39..a68d1388 100644
--- a/MapControl/WPF/MapOverlay.WPF.cs
+++ b/MapControl/WPF/MapOverlay.WPF.cs
@@ -51,7 +51,7 @@ namespace MapControl
public static readonly DependencyProperty StrokeMiterLimitProperty = Shape.StrokeMiterLimitProperty.AddOwner(
typeof(MapOverlay), new FrameworkPropertyMetadata { AffectsRender = true });
- protected override void SetParentMapOverride(MapBase parentMap)
+ protected override void SetParentMap(MapBase parentMap)
{
if (GetBindingExpression(StrokeProperty) != null)
{
@@ -67,7 +67,7 @@ namespace MapControl
});
}
- base.SetParentMapOverride(parentMap);
+ base.SetParentMap(parentMap);
}
}
}
diff --git a/MapControl/WPF/MapPanel.WPF.cs b/MapControl/WPF/MapPanel.WPF.cs
index e7aa16bd..cf5a754f 100644
--- a/MapControl/WPF/MapPanel.WPF.cs
+++ b/MapControl/WPF/MapPanel.WPF.cs
@@ -14,17 +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);
}
+
+ public static void InitMapElement(FrameworkElement element)
+ {
+ if (element is MapBase)
+ {
+ element.SetValue(ParentMapPropertyKey, element);
+ }
+ }
}
}
diff --git a/MapControl/WPF/Pushpin.WPF.cs b/MapControl/WPF/Pushpin.WPF.cs
deleted file mode 100644
index ef5ef99b..00000000
--- a/MapControl/WPF/Pushpin.WPF.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
-// © 2017 Clemens Fischer
-// Licensed under the Microsoft Public License (Ms-PL)
-
-using System.Windows;
-using System.Windows.Controls;
-
-namespace MapControl
-{
- ///
- /// Displays a pushpin at a geographic location provided by the MapPanel.Location attached property.
- ///
- public class Pushpin : ContentControl
- {
- static Pushpin()
- {
- DefaultStyleKeyProperty.OverrideMetadata(typeof(Pushpin), new FrameworkPropertyMetadata(typeof(Pushpin)));
- }
-
- public static readonly DependencyProperty LocationProperty =
- MapPanel.LocationProperty.AddOwner(typeof(Pushpin));
-
- public Location Location
- {
- get { return (Location)GetValue(LocationProperty); }
- set { SetValue(LocationProperty, value); }
- }
- }
-}
diff --git a/MapControl/WPF/TileImageLoader.WPF.cs b/MapControl/WPF/TileImageLoader.WPF.cs
index 09b59438..5d261664 100644
--- a/MapControl/WPF/TileImageLoader.WPF.cs
+++ b/MapControl/WPF/TileImageLoader.WPF.cs
@@ -3,11 +3,8 @@
// Licensed under the Microsoft Public License (Ms-PL)
using System;
-using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
-using System.Linq;
-using System.Net.Http;
using System.Runtime.Caching;
using System.Text;
using System.Threading.Tasks;
@@ -34,11 +31,6 @@ namespace MapControl
var buffer = GetCachedImage(cacheKey, out expiration);
var loaded = false;
- //if (buffer != null)
- //{
- // Debug.WriteLine("TileImageLoader: {0}: expire{1} {2}", cacheKey, expiration < DateTime.UtcNow ? "d" : "s", expiration);
- //}
-
if (buffer == null || expiration < DateTime.UtcNow)
{
loaded = await DownloadTileImageAsync(tile, uri, cacheKey);
@@ -55,32 +47,31 @@ namespace MapControl
private async Task DownloadTileImageAsync(Tile tile, Uri uri, string cacheKey)
{
+ var success = false;
+
try
{
- using (var response = await HttpClient.GetAsync(uri))
+ using (var response = await TileSource.HttpClient.GetAsync(uri))
{
- if (response.IsSuccessStatusCode)
+ success = response.IsSuccessStatusCode;
+
+ if (!success)
{
- IEnumerable tileInfo;
-
- if (!response.Headers.TryGetValues(bingMapsTileInfo, out tileInfo) ||
- !tileInfo.Contains(bingMapsNoTile))
+ Debug.WriteLine("TileImageLoader: {0}: {1} {2}", uri, (int)response.StatusCode, response.ReasonPhrase);
+ }
+ else if (TileSource.TileAvailable(response.Headers))
+ {
+ using (var stream = new MemoryStream())
{
- using (var stream = new MemoryStream())
- {
- await response.Content.CopyToAsync(stream);
- stream.Seek(0, SeekOrigin.Begin);
+ await response.Content.CopyToAsync(stream);
+ stream.Seek(0, SeekOrigin.Begin);
- await SetTileImageAsync(tile, stream); // create BitmapFrame in UI thread before caching
+ await SetTileImageAsync(tile, stream); // create BitmapFrame before caching
- SetCachedImage(cacheKey, stream, GetExpiration(response));
- }
+ SetCachedImage(cacheKey, stream, GetExpiration(response));
}
-
- return true;
}
- Debug.WriteLine("TileImageLoader: {0}: {1} {2}", uri, (int)response.StatusCode, response.ReasonPhrase);
}
}
catch (Exception ex)
@@ -88,7 +79,7 @@ namespace MapControl
Debug.WriteLine("TileImageLoader: {0}: {1}", uri, ex.Message);
}
- return false;
+ return success;
}
private async Task SetTileImageAsync(Tile tile, MemoryStream stream)
diff --git a/MapControl/WPF/TileSource.WPF.cs b/MapControl/WPF/TileSource.WPF.cs
new file mode 100644
index 00000000..96f1da7b
--- /dev/null
+++ b/MapControl/WPF/TileSource.WPF.cs
@@ -0,0 +1,82 @@
+// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
+// © 2017 Clemens Fischer
+// Licensed under the Microsoft Public License (Ms-PL)
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Threading.Tasks;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+namespace MapControl
+{
+ public partial class TileSource
+ {
+ ///
+ /// The HttpClient instance used when image data is downloaded from a web resource.
+ ///
+ public static HttpClient HttpClient { get; set; } = new HttpClient();
+
+ ///
+ /// Check HTTP response headers for tile unavailability, e.g. X-VE-Tile-Info=no-tile
+ ///
+ public static bool TileAvailable(HttpResponseHeaders responseHeaders)
+ {
+ IEnumerable tileInfo;
+
+ return !responseHeaders.TryGetValues("X-VE-Tile-Info", out tileInfo) || !tileInfo.Contains("no-tile");
+ }
+
+ ///
+ /// Load a tile ImageSource asynchronously from GetUri(x, y, zoomLevel)
+ ///
+ public virtual async Task LoadImageAsync(int x, int y, int zoomLevel)
+ {
+ ImageSource imageSource = null;
+
+ var uri = GetUri(x, y, zoomLevel);
+
+ if (uri != null)
+ {
+ try
+ {
+ if (uri.Scheme == "http")
+ {
+ using (var response = await HttpClient.GetAsync(uri))
+ {
+ if (!response.IsSuccessStatusCode)
+ {
+ Debug.WriteLine("TileSource: {0}: {1} {2}", uri, (int)response.StatusCode, response.ReasonPhrase);
+ }
+ else if (TileAvailable(response.Headers))
+ {
+ using (var stream = new MemoryStream())
+ {
+ await response.Content.CopyToAsync(stream);
+ stream.Seek(0, SeekOrigin.Begin);
+
+ imageSource = await Task.Run(() => BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad));
+ }
+ }
+ }
+ }
+ else
+ {
+ imageSource = BitmapFrame.Create(uri, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("TileSource: {0}: {1}", uri, ex.Message);
+ }
+ }
+
+ return imageSource;
+ }
+ }
+}