diff --git a/FileDbCache/UWP/FileDbCache.UWP.csproj b/FileDbCache/UWP/FileDbCache.UWP.csproj
index 04b88368..547ab1f9 100644
--- a/FileDbCache/UWP/FileDbCache.UWP.csproj
+++ b/FileDbCache/UWP/FileDbCache.UWP.csproj
@@ -52,7 +52,7 @@
7.4.4
- 6.2.9
+ 6.2.10
diff --git a/FileDbCache/UWP/Properties/AssemblyInfo.cs b/FileDbCache/UWP/Properties/AssemblyInfo.cs
index b0da94d3..fa00c14f 100644
--- a/FileDbCache/UWP/Properties/AssemblyInfo.cs
+++ b/FileDbCache/UWP/Properties/AssemblyInfo.cs
@@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
-[assembly: AssemblyVersion("4.16.0")]
-[assembly: AssemblyFileVersion("4.16.0")]
+[assembly: AssemblyVersion("4.17.0")]
+[assembly: AssemblyFileVersion("4.17.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
diff --git a/FileDbCache/WPF/Properties/AssemblyInfo.cs b/FileDbCache/WPF/Properties/AssemblyInfo.cs
index 79ea184d..a2e41b5d 100644
--- a/FileDbCache/WPF/Properties/AssemblyInfo.cs
+++ b/FileDbCache/WPF/Properties/AssemblyInfo.cs
@@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
-[assembly: AssemblyVersion("4.16.0")]
-[assembly: AssemblyFileVersion("4.16.0")]
+[assembly: AssemblyVersion("4.17.0")]
+[assembly: AssemblyFileVersion("4.17.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
diff --git a/MBTiles/UWP/MBTiles.UWP.csproj b/MBTiles/UWP/MBTiles.UWP.csproj
index 18a6c367..d78cb990 100644
--- a/MBTiles/UWP/MBTiles.UWP.csproj
+++ b/MBTiles/UWP/MBTiles.UWP.csproj
@@ -54,7 +54,7 @@
- 6.2.9
+ 6.2.10
1.0.112
diff --git a/MBTiles/UWP/Properties/AssemblyInfo.cs b/MBTiles/UWP/Properties/AssemblyInfo.cs
index 07a30da4..163ccd7c 100644
--- a/MBTiles/UWP/Properties/AssemblyInfo.cs
+++ b/MBTiles/UWP/Properties/AssemblyInfo.cs
@@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
-[assembly: AssemblyVersion("4.16.0")]
-[assembly: AssemblyFileVersion("4.16.0")]
+[assembly: AssemblyVersion("4.17.0")]
+[assembly: AssemblyFileVersion("4.17.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
diff --git a/MBTiles/WPF/Properties/AssemblyInfo.cs b/MBTiles/WPF/Properties/AssemblyInfo.cs
index f8fe7440..01f28b26 100644
--- a/MBTiles/WPF/Properties/AssemblyInfo.cs
+++ b/MBTiles/WPF/Properties/AssemblyInfo.cs
@@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
-[assembly: AssemblyVersion("4.16.0")]
-[assembly: AssemblyFileVersion("4.16.0")]
+[assembly: AssemblyVersion("4.17.0")]
+[assembly: AssemblyFileVersion("4.17.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
diff --git a/MapControl/Shared/BingMapsTileLayer.cs b/MapControl/Shared/BingMapsTileLayer.cs
index ba45a438..a959fc09 100644
--- a/MapControl/Shared/BingMapsTileLayer.cs
+++ b/MapControl/Shared/BingMapsTileLayer.cs
@@ -60,9 +60,10 @@ namespace MapControl
try
{
- var stream = await ImageLoader.HttpClient.GetStreamAsync(metadataUri);
-
- ProcessImageryMetadata(XDocument.Load(stream).Root);
+ using (var stream = await ImageLoader.HttpClient.GetStreamAsync(metadataUri))
+ {
+ ReadImageryMetadata(XDocument.Load(stream).Root);
+ }
}
catch (Exception ex)
{
@@ -70,7 +71,7 @@ namespace MapControl
}
}
- private void ProcessImageryMetadata(XElement metadataResponse)
+ private void ReadImageryMetadata(XElement metadataResponse)
{
var ns = metadataResponse.Name.Namespace;
var metadata = metadataResponse.Descendants(ns + "ImageryMetadata").FirstOrDefault();
diff --git a/MapControl/Shared/MapBase.cs b/MapControl/Shared/MapBase.cs
index fe9d2b41..15916f96 100644
--- a/MapControl/Shared/MapBase.cs
+++ b/MapControl/Shared/MapBase.cs
@@ -222,7 +222,7 @@ namespace MapControl
}
///
- /// Gets the transformation from cartesian map coordinates to viewport coordinates (pixels).
+ /// Gets the transformation from cartesian map coordinates to viewport coordinates.
///
public MatrixTransform ViewportTransform { get; } = new MatrixTransform();
diff --git a/MapControl/Shared/MapProjection.cs b/MapControl/Shared/MapProjection.cs
index 524b5408..bdcc5c77 100644
--- a/MapControl/Shared/MapProjection.cs
+++ b/MapControl/Shared/MapProjection.cs
@@ -65,22 +65,22 @@ namespace MapControl
}
///
- /// Gets the projection center. Only relevant for azimuthal pprojections.
+ /// Gets the projection center. Only relevant for azimuthal projections.
///
public Location ProjectionCenter { get; private set; } = new Location();
///
- /// Gets the transform matrix from cartesian map coordinates to viewport coordinates (pixels).
+ /// Gets the transform matrix from cartesian map coordinates to viewport coordinates.
///
public Matrix ViewportTransform { get; private set; }
///
- /// Gets the transform matrix from viewport coordinates (pixels) to cartesian map coordinates.
+ /// Gets the transform matrix from viewport coordinates to cartesian map coordinates.
///
public Matrix InverseViewportTransform { get; private set; }
///
- /// Gets the scaling factor from cartesian map coordinates to viewport coordinates (pixels)
+ /// Gets the scaling factor from cartesian map coordinates to viewport coordinates
/// at the projection's point of true scale.
///
public double ViewportScale { get; private set; }
@@ -186,26 +186,11 @@ namespace MapControl
ViewportScale = Math.Pow(2d, zoomLevel) * TileSize / (360d * TrueScale);
var center = LocationToPoint(mapCenter);
- var matrix = CreateTransformMatrix(center, ViewportScale, -ViewportScale, heading, viewportCenter);
+ var matrix = MatrixFactory.Create(center, ViewportScale, -ViewportScale, heading, viewportCenter);
ViewportTransform = matrix;
matrix.Invert();
InverseViewportTransform = matrix;
}
-
- internal static Matrix CreateTransformMatrix(
- Point translation1, double scale, double rotation, Point translation2)
- {
- return CreateTransformMatrix(translation1, scale, scale, rotation, translation2);
- }
-
- internal static Matrix CreateTransformMatrix(
- Point translation1, double scaleX, double scaleY, double rotation, Point translation2)
- {
- var matrix = new Matrix(scaleX, 0d, 0d, scaleY, -translation1.X * scaleX, -translation1.Y * scaleY);
- matrix.Rotate(rotation);
- matrix.Translate(translation2.X, translation2.Y);
- return matrix;
- }
}
}
diff --git a/MapControl/Shared/MapTileLayer.cs b/MapControl/Shared/MapTileLayer.cs
index 139e8553..8c2246df 100644
--- a/MapControl/Shared/MapTileLayer.cs
+++ b/MapControl/Shared/MapTileLayer.cs
@@ -8,27 +8,21 @@ using System.Linq;
#if WINDOWS_UWP
using Windows.Foundation;
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;
-using System.Windows.Threading;
#endif
namespace MapControl
{
- public interface ITileImageLoader
- {
- void LoadTilesAsync(IEnumerable tiles, TileSource tileSource, string sourceName);
- }
-
///
/// Fills the map viewport with map tiles from a TileSource.
///
- public class MapTileLayer : Panel, IMapLayer
+ public class MapTileLayer : MapTileLayerBase
{
+ private const double WebMercatorMapSize = 2 * Math.PI * MapProjection.Wgs84EquatorialRadius;
+
///
/// A default MapTileLayer using OpenStreetMap data.
///
@@ -46,103 +40,25 @@ namespace MapControl
}
}
- public static readonly DependencyProperty TileSourceProperty = DependencyProperty.Register(
- nameof(TileSource), typeof(TileSource), typeof(MapTileLayer),
- new PropertyMetadata(null, (o, e) => ((MapTileLayer)o).TileSourcePropertyChanged()));
-
- public static readonly DependencyProperty SourceNameProperty = DependencyProperty.Register(
- nameof(SourceName), typeof(string), typeof(MapTileLayer), new PropertyMetadata(null));
-
- public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register(
- nameof(Description), typeof(string), typeof(MapTileLayer), new PropertyMetadata(null));
-
- public static readonly DependencyProperty ZoomLevelOffsetProperty = DependencyProperty.Register(
- nameof(ZoomLevelOffset), typeof(double), typeof(MapTileLayer),
- new PropertyMetadata(0d, (o, e) => ((MapTileLayer)o).UpdateTileGrid()));
-
public static readonly DependencyProperty MinZoomLevelProperty = DependencyProperty.Register(
nameof(MinZoomLevel), typeof(int), typeof(MapTileLayer), new PropertyMetadata(0));
public static readonly DependencyProperty MaxZoomLevelProperty = DependencyProperty.Register(
nameof(MaxZoomLevel), typeof(int), typeof(MapTileLayer), new PropertyMetadata(18));
- public static readonly DependencyProperty MaxBackgroundLevelsProperty = DependencyProperty.Register(
- nameof(MaxBackgroundLevels), typeof(int), typeof(MapTileLayer), new PropertyMetadata(8));
-
- 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));
-
- public static readonly DependencyProperty UpdateWhileViewportChangingProperty = DependencyProperty.Register(
- nameof(UpdateWhileViewportChanging), typeof(bool), typeof(MapTileLayer), new PropertyMetadata(false));
-
- public static readonly DependencyProperty MapBackgroundProperty = DependencyProperty.Register(
- nameof(MapBackground), typeof(Brush), typeof(MapTileLayer), new PropertyMetadata(null));
-
- public static readonly DependencyProperty MapForegroundProperty = DependencyProperty.Register(
- nameof(MapForeground), typeof(Brush), typeof(MapTileLayer), new PropertyMetadata(null));
-
- private readonly DispatcherTimer updateTimer;
- private MapBase parentMap;
-
public MapTileLayer()
: this(new TileImageLoader())
{
}
public MapTileLayer(ITileImageLoader tileImageLoader)
+ : base(tileImageLoader)
{
- IsHitTestVisible = false;
- RenderTransform = new MatrixTransform();
- TileImageLoader = tileImageLoader;
-
- updateTimer = new DispatcherTimer { Interval = UpdateInterval };
- updateTimer.Tick += (s, e) => UpdateTileGrid();
-
- MapPanel.InitMapElement(this);
}
- public ITileImageLoader TileImageLoader { get; }
-
- public TileGrid TileGrid { get; private set; }
-
public IReadOnlyCollection Tiles { get; private set; } = new List();
- ///
- /// Provides map tile URIs or images.
- ///
- public TileSource TileSource
- {
- get { return (TileSource)GetValue(TileSourceProperty); }
- set { SetValue(TileSourceProperty, value); }
- }
-
- ///
- /// Name of the TileSource. Used as component of a tile cache key.
- ///
- public string SourceName
- {
- get { return (string)GetValue(SourceNameProperty); }
- set { SetValue(SourceNameProperty, value); }
- }
-
- ///
- /// Description of the MapTileLayer. Used to display copyright information on top of the map.
- ///
- public string Description
- {
- get { return (string)GetValue(DescriptionProperty); }
- set { SetValue(DescriptionProperty, value); }
- }
-
- ///
- /// Adds an offset to the Map's ZoomLevel for a relative scale between the Map and the MapTileLayer.
- ///
- public double ZoomLevelOffset
- {
- get { return (double)GetValue(ZoomLevelOffsetProperty); }
- set { SetValue(ZoomLevelOffsetProperty, value); }
- }
+ public TileGrid TileGrid { get; private set; }
///
/// Minimum zoom level supported by the MapTileLayer. Default value is 0.
@@ -162,73 +78,6 @@ namespace MapControl
set { SetValue(MaxZoomLevelProperty, value); }
}
- ///
- /// Maximum number of background tile levels. Default value is 8.
- /// Is only effective in a MapTileLayer that is the MapLayer of its ParentMap.
- ///
- public int MaxBackgroundLevels
- {
- get { return (int)GetValue(MaxBackgroundLevelsProperty); }
- set { SetValue(MaxBackgroundLevelsProperty, value); }
- }
-
- ///
- /// Minimum time interval between tile updates.
- ///
- public TimeSpan UpdateInterval
- {
- get { return (TimeSpan)GetValue(UpdateIntervalProperty); }
- set { SetValue(UpdateIntervalProperty, value); }
- }
-
- ///
- /// Controls if tiles are updated while the viewport is still changing.
- ///
- public bool UpdateWhileViewportChanging
- {
- get { return (bool)GetValue(UpdateWhileViewportChangingProperty); }
- set { SetValue(UpdateWhileViewportChangingProperty, value); }
- }
-
- ///
- /// Optional background brush. Sets MapBase.Background if not null and the MapTileLayer is the base map layer.
- ///
- public Brush MapBackground
- {
- get { return (Brush)GetValue(MapBackgroundProperty); }
- set { SetValue(MapBackgroundProperty, value); }
- }
-
- ///
- /// Optional foreground brush. Sets MapBase.Foreground if not null and the MapTileLayer is the base map layer.
- ///
- public Brush MapForeground
- {
- get { return (Brush)GetValue(MapForegroundProperty); }
- set { SetValue(MapForegroundProperty, value); }
- }
-
- public MapBase ParentMap
- {
- get { return parentMap; }
- set
- {
- if (parentMap != null)
- {
- parentMap.ViewportChanged -= OnViewportChanged;
- }
-
- parentMap = value;
-
- if (parentMap != null)
- {
- parentMap.ViewportChanged += OnViewportChanged;
- }
-
- UpdateTileGrid();
- }
- }
-
protected override Size MeasureOverride(Size availableSize)
{
availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
@@ -260,29 +109,7 @@ namespace MapControl
return finalSize;
}
- protected virtual void UpdateTileGrid()
- {
- updateTimer.Stop();
-
- if (parentMap != null && parentMap.MapProjection.IsWebMercator)
- {
- var tileGrid = GetTileGrid();
-
- if (!tileGrid.Equals(TileGrid))
- {
- TileGrid = tileGrid;
- SetRenderTransform();
- UpdateTiles();
- }
- }
- else
- {
- TileGrid = null;
- UpdateTiles();
- }
- }
-
- private void TileSourcePropertyChanged()
+ protected override void TileSourcePropertyChanged()
{
if (TileGrid != null)
{
@@ -291,68 +118,90 @@ namespace MapControl
}
}
- private void OnViewportChanged(object sender, ViewportChangedEventArgs e)
+ protected override void UpdateTileLayer()
{
- if (TileGrid == null || e.ProjectionChanged || Math.Abs(e.LongitudeOffset) > 180d)
+ UpdateTimer.Stop();
+
+ if (ParentMap == null || !ParentMap.MapProjection.IsWebMercator)
{
- UpdateTileGrid(); // update immediately when projection has changed or center has moved across 180° longitude
+ TileGrid = null;
+ UpdateTiles();
}
- else
+ else if (SetTileGrid())
{
SetRenderTransform();
-
- if (updateTimer.IsEnabled && !UpdateWhileViewportChanging)
- {
- updateTimer.Stop(); // restart
- }
-
- if (!updateTimer.IsEnabled)
- {
- updateTimer.Start();
- }
+ UpdateTiles();
}
}
- private TileGrid GetTileGrid()
+ protected override void SetRenderTransform()
{
- var tileZoomLevel = Math.Max(0, (int)Math.Round(parentMap.ZoomLevel + ZoomLevelOffset));
- var tileScale = (double)(1 << tileZoomLevel);
- var scale = tileScale / (Math.Pow(2d, parentMap.ZoomLevel) * MapProjection.TileSize);
- var tileCenter = new Point(tileScale * (0.5 + parentMap.Center.Longitude / 360d),
- tileScale * (0.5 - WebMercatorProjection.LatitudeToY(parentMap.Center.Latitude) / 360d));
- var viewCenter = new Point(parentMap.RenderSize.Width / 2d, parentMap.RenderSize.Height / 2d);
+ var tileGridSize = (double)(1 << TileGrid.ZoomLevel);
- var transform = new MatrixTransform
- {
- Matrix = MapProjection.CreateTransformMatrix(viewCenter, scale, -parentMap.Heading, tileCenter)
- };
+ // top/left tile grid corner in map coordinates
+ //
+ var tileGridOrigin = new Point(
+ WebMercatorMapSize * (TileGrid.XMin / tileGridSize - 0.5),
+ WebMercatorMapSize * (0.5 - TileGrid.YMin / tileGridSize));
- var bounds = transform.TransformBounds(new Rect(0d, 0d, parentMap.RenderSize.Width, parentMap.RenderSize.Height));
+ // top/left tile grid corner in viewport coordinates
+ //
+ var viewOrigin = ParentMap.MapProjection.ViewportTransform.Transform(tileGridOrigin);
- return new TileGrid(tileZoomLevel,
- (int)Math.Floor(bounds.X), (int)Math.Floor(bounds.Y),
- (int)Math.Floor(bounds.X + bounds.Width), (int)Math.Floor(bounds.Y + bounds.Height));
+ // tile pixels per viewport unit, 0.5 .. 2
+ //
+ var tileScale = Math.Pow(2d, ParentMap.ZoomLevel - TileGrid.ZoomLevel);
+
+ ((MatrixTransform)RenderTransform).Matrix = MatrixFactory.Create(tileScale, ParentMap.Heading, viewOrigin);
}
- private void SetRenderTransform()
+ private bool SetTileGrid()
{
- var tileScale = (double)(1 << TileGrid.ZoomLevel);
- var scale = Math.Pow(2d, parentMap.ZoomLevel) / tileScale;
- var tileCenter = new Point(tileScale * (0.5 + parentMap.Center.Longitude / 360d),
- tileScale * (0.5 - WebMercatorProjection.LatitudeToY(parentMap.Center.Latitude) / 360d));
- var tileOrigin = new Point(MapProjection.TileSize * (tileCenter.X - TileGrid.XMin),
- MapProjection.TileSize * (tileCenter.Y - TileGrid.YMin));
- var viewCenter = new Point(parentMap.RenderSize.Width / 2d, parentMap.RenderSize.Height / 2d);
+ var tileGridZoomLevel = (int)Math.Round(ParentMap.ZoomLevel);
+ var tileGridSize = (double)(1 << tileGridZoomLevel);
- ((MatrixTransform)RenderTransform).Matrix =
- MapProjection.CreateTransformMatrix(tileOrigin, scale, parentMap.Heading, viewCenter);
+ // top/left viewport corner in map coordinates
+ //
+ var tileOrigin = ParentMap.MapProjection.InverseViewportTransform.Transform(new Point());
+
+ // top/left viewport corner in tile grid coordinates
+ //
+ var tileGridOrigin = new Point(
+ tileGridSize * (0.5 + tileOrigin.X / WebMercatorMapSize),
+ tileGridSize * (0.5 - tileOrigin.Y / WebMercatorMapSize));
+
+ // transforms viewport bounds to tile grid bounds
+ //
+ var transform = new MatrixTransform
+ {
+ Matrix = MatrixFactory.Create(1d / MapProjection.TileSize, -ParentMap.Heading, tileGridOrigin)
+ };
+
+ var bounds = transform.TransformBounds(new Rect(0d, 0d, ParentMap.RenderSize.Width, ParentMap.RenderSize.Height));
+
+ var xMin = (int)Math.Floor(bounds.X);
+ var yMin = (int)Math.Floor(bounds.Y);
+ var xMax = (int)Math.Floor(bounds.X + bounds.Width);
+ var yMax = (int)Math.Floor(bounds.Y + bounds.Height);
+
+ if (TileGrid != null &&
+ TileGrid.ZoomLevel == tileGridZoomLevel &&
+ TileGrid.XMin == xMin && TileGrid.YMin == yMin &&
+ TileGrid.XMax == xMax && TileGrid.YMax == yMax)
+ {
+ return false;
+ }
+
+ TileGrid = new TileGrid(tileGridZoomLevel, xMin, yMin, xMax, yMax);
+
+ return true;
}
private void UpdateTiles()
{
var newTiles = new List();
- if (parentMap != null && TileGrid != null && TileSource != null)
+ if (ParentMap != null && TileGrid != null && TileSource != null)
{
var maxZoomLevel = Math.Min(TileGrid.ZoomLevel, MaxZoomLevel);
@@ -360,7 +209,7 @@ namespace MapControl
{
var minZoomLevel = maxZoomLevel;
- if (this == parentMap.MapLayer) // load background tiles
+ if (this == ParentMap.MapLayer) // load background tiles
{
minZoomLevel = Math.Max(TileGrid.ZoomLevel - MaxBackgroundLevels, MinZoomLevel);
}
diff --git a/MapControl/Shared/MapTileLayerBase.cs b/MapControl/Shared/MapTileLayerBase.cs
new file mode 100644
index 00000000..fa3f19a9
--- /dev/null
+++ b/MapControl/Shared/MapTileLayerBase.cs
@@ -0,0 +1,198 @@
+// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
+// © 2020 Clemens Fischer
+// Licensed under the Microsoft Public License (Ms-PL)
+
+using System;
+using System.Collections.Generic;
+#if WINDOWS_UWP
+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;
+using System.Windows.Threading;
+#endif
+
+namespace MapControl
+{
+ public interface ITileImageLoader
+ {
+ void LoadTilesAsync(IEnumerable tiles, TileSource tileSource, string sourceName);
+ }
+
+ public abstract class MapTileLayerBase : Panel, IMapLayer
+ {
+ public static readonly DependencyProperty TileSourceProperty = DependencyProperty.Register(
+ nameof(TileSource), typeof(TileSource), typeof(MapTileLayerBase),
+ new PropertyMetadata(null, (o, e) => ((MapTileLayerBase)o).TileSourcePropertyChanged()));
+
+ public static readonly DependencyProperty SourceNameProperty = DependencyProperty.Register(
+ nameof(SourceName), typeof(string), typeof(MapTileLayerBase), new PropertyMetadata(null));
+
+ public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register(
+ nameof(Description), typeof(string), typeof(MapTileLayerBase), new PropertyMetadata(null));
+
+ public static readonly DependencyProperty MaxBackgroundLevelsProperty = DependencyProperty.Register(
+ nameof(MaxBackgroundLevels), typeof(int), typeof(MapTileLayerBase), new PropertyMetadata(8));
+
+ public static readonly DependencyProperty UpdateIntervalProperty = DependencyProperty.Register(
+ nameof(UpdateInterval), typeof(TimeSpan), typeof(MapTileLayerBase),
+ new PropertyMetadata(TimeSpan.FromSeconds(0.2), (o, e) => ((MapTileLayerBase)o).UpdateTimer.Interval = (TimeSpan)e.NewValue));
+
+ public static readonly DependencyProperty UpdateWhileViewportChangingProperty = DependencyProperty.Register(
+ nameof(UpdateWhileViewportChanging), typeof(bool), typeof(MapTileLayerBase), new PropertyMetadata(false));
+
+ public static readonly DependencyProperty MapBackgroundProperty = DependencyProperty.Register(
+ nameof(MapBackground), typeof(Brush), typeof(MapTileLayerBase), new PropertyMetadata(null));
+
+ public static readonly DependencyProperty MapForegroundProperty = DependencyProperty.Register(
+ nameof(MapForeground), typeof(Brush), typeof(MapTileLayerBase), new PropertyMetadata(null));
+
+ private MapBase parentMap;
+
+ public MapTileLayerBase()
+ : this(new TileImageLoader())
+ {
+ }
+
+ public MapTileLayerBase(ITileImageLoader tileImageLoader)
+ {
+ IsHitTestVisible = false;
+ RenderTransform = new MatrixTransform();
+ TileImageLoader = tileImageLoader;
+
+ UpdateTimer = new DispatcherTimer { Interval = UpdateInterval };
+ UpdateTimer.Tick += (s, e) => UpdateTileLayer();
+
+ MapPanel.InitMapElement(this);
+ }
+
+ public ITileImageLoader TileImageLoader { get; }
+
+ public DispatcherTimer UpdateTimer { get; }
+
+ ///
+ /// Provides map tile URIs or images.
+ ///
+ public TileSource TileSource
+ {
+ get { return (TileSource)GetValue(TileSourceProperty); }
+ set { SetValue(TileSourceProperty, value); }
+ }
+
+ ///
+ /// Name of the TileSource. Used as component of a tile cache key.
+ ///
+ public string SourceName
+ {
+ get { return (string)GetValue(SourceNameProperty); }
+ set { SetValue(SourceNameProperty, value); }
+ }
+
+ ///
+ /// Description of the tile layer. Used to display copyright information on top of the map.
+ ///
+ public string Description
+ {
+ get { return (string)GetValue(DescriptionProperty); }
+ set { SetValue(DescriptionProperty, value); }
+ }
+
+ ///
+ /// Maximum number of background tile levels. Default value is 8.
+ /// Is only effective in a MapTileLayer that is the MapLayer of its ParentMap.
+ ///
+ public int MaxBackgroundLevels
+ {
+ get { return (int)GetValue(MaxBackgroundLevelsProperty); }
+ set { SetValue(MaxBackgroundLevelsProperty, value); }
+ }
+
+ ///
+ /// Minimum time interval between tile updates.
+ ///
+ public TimeSpan UpdateInterval
+ {
+ get { return (TimeSpan)GetValue(UpdateIntervalProperty); }
+ set { SetValue(UpdateIntervalProperty, value); }
+ }
+
+ ///
+ /// Controls if tiles are updated while the viewport is still changing.
+ ///
+ public bool UpdateWhileViewportChanging
+ {
+ get { return (bool)GetValue(UpdateWhileViewportChangingProperty); }
+ set { SetValue(UpdateWhileViewportChangingProperty, value); }
+ }
+
+ ///
+ /// Optional background brush. Sets MapBase.Background if not null and this layer is the base map layer.
+ ///
+ public Brush MapBackground
+ {
+ get { return (Brush)GetValue(MapBackgroundProperty); }
+ set { SetValue(MapBackgroundProperty, value); }
+ }
+
+ ///
+ /// Optional foreground brush. Sets MapBase.Foreground if not null and this layer is the base map layer.
+ ///
+ public Brush MapForeground
+ {
+ get { return (Brush)GetValue(MapForegroundProperty); }
+ set { SetValue(MapForegroundProperty, value); }
+ }
+
+ public MapBase ParentMap
+ {
+ get { return parentMap; }
+ set
+ {
+ if (parentMap != null)
+ {
+ parentMap.ViewportChanged -= OnViewportChanged;
+ }
+
+ parentMap = value;
+
+ if (parentMap != null)
+ {
+ parentMap.ViewportChanged += OnViewportChanged;
+ }
+
+ UpdateTileLayer();
+ }
+ }
+
+ private void OnViewportChanged(object sender, ViewportChangedEventArgs e)
+ {
+ if (Children.Count == 0 || e.ProjectionChanged || Math.Abs(e.LongitudeOffset) > 180d)
+ {
+ UpdateTileLayer(); // update immediately when projection has changed or center has moved across 180° longitude
+ }
+ else
+ {
+ SetRenderTransform();
+
+ if (UpdateTimer.IsEnabled && !UpdateWhileViewportChanging)
+ {
+ UpdateTimer.Stop(); // restart
+ }
+
+ if (!UpdateTimer.IsEnabled)
+ {
+ UpdateTimer.Start();
+ }
+ }
+ }
+
+ protected abstract void UpdateTileLayer();
+
+ protected abstract void SetRenderTransform();
+
+ protected abstract void TileSourcePropertyChanged();
+ }
+}
diff --git a/MapControl/Shared/MatrixFactory.cs b/MapControl/Shared/MatrixFactory.cs
new file mode 100644
index 00000000..df837bcb
--- /dev/null
+++ b/MapControl/Shared/MatrixFactory.cs
@@ -0,0 +1,30 @@
+// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
+// © 2020 Clemens Fischer
+// Licensed under the Microsoft Public License (Ms-PL)
+
+#if !WINDOWS_UWP
+using System.Windows;
+using System.Windows.Media;
+#endif
+
+namespace MapControl
+{
+ public static class MatrixFactory
+ {
+ public static Matrix Create(Point translation1, double scaleX, double scaleY, double rotation, Point translation2)
+ {
+ var matrix = new Matrix(scaleX, 0d, 0d, scaleY, -scaleX * translation1.X, -scaleY * translation1.Y);
+ matrix.Rotate(rotation);
+ matrix.Translate(translation2.X, translation2.Y);
+ return matrix;
+ }
+
+ public static Matrix Create(double scale, double rotation, Point translation)
+ {
+ var matrix = new Matrix(scale, 0d, 0d, scale, 0d, 0d);
+ matrix.Rotate(rotation);
+ matrix.Translate(translation.X, translation.Y);
+ return matrix;
+ }
+ }
+}
diff --git a/MapControl/Shared/TileGrid.cs b/MapControl/Shared/TileGrid.cs
index c8c7fd78..825efbb9 100644
--- a/MapControl/Shared/TileGrid.cs
+++ b/MapControl/Shared/TileGrid.cs
@@ -2,11 +2,9 @@
// © 2020 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
-using System;
-
namespace MapControl
{
- public class TileGrid : IEquatable
+ public class TileGrid
{
public readonly int ZoomLevel;
public readonly int XMin;
@@ -22,25 +20,5 @@ namespace MapControl
XMax = xMax;
YMax = yMax;
}
-
- public bool Equals(TileGrid tileGrid)
- {
- return tileGrid != null
- && tileGrid.ZoomLevel == ZoomLevel
- && tileGrid.XMin == XMin
- && tileGrid.YMin == YMin
- && tileGrid.XMax == XMax
- && tileGrid.YMax == YMax;
- }
-
- public override bool Equals(object obj)
- {
- return Equals(obj as TileGrid);
- }
-
- public override int GetHashCode()
- {
- return ZoomLevel ^ XMin ^ YMin ^ XMax ^ YMax;
- }
}
}
diff --git a/MapControl/Shared/WmsImageLayer.cs b/MapControl/Shared/WmsImageLayer.cs
index 61c845ea..7d9f9d72 100644
--- a/MapControl/Shared/WmsImageLayer.cs
+++ b/MapControl/Shared/WmsImageLayer.cs
@@ -74,8 +74,13 @@ namespace MapControl
try
{
- var stream = await ImageLoader.HttpClient.GetStreamAsync(uri);
- var capabilities = XDocument.Load(stream).Root;
+ XElement capabilities;
+
+ using (var stream = await ImageLoader.HttpClient.GetStreamAsync(uri))
+ {
+ capabilities = XDocument.Load(stream).Root;
+ }
+
var ns = capabilities.Name.Namespace;
layerNames = capabilities
diff --git a/MapControl/Shared/WmtsTileLayer.cs b/MapControl/Shared/WmtsTileLayer.cs
new file mode 100644
index 00000000..d76fb6e4
--- /dev/null
+++ b/MapControl/Shared/WmtsTileLayer.cs
@@ -0,0 +1,336 @@
+// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
+// © 2020 Clemens Fischer
+// Licensed under the Microsoft Public License (Ms-PL)
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+using System.Xml.Linq;
+#if WINDOWS_UWP
+using Windows.Foundation;
+using Windows.UI.Xaml;
+#else
+using System.Windows;
+#endif
+
+namespace MapControl
+{
+ public class WmtsTileLayer : MapTileLayerBase
+ {
+ public static readonly DependencyProperty CapabilitiesProperty = DependencyProperty.Register(
+ nameof(Capabilities), typeof(Uri), typeof(WmtsTileLayer), new PropertyMetadata(null));
+
+ public static readonly DependencyProperty LayerIdentifierProperty = DependencyProperty.Register(
+ nameof(LayerIdentifier), typeof(string), typeof(WmtsTileLayer), new PropertyMetadata(null));
+
+ public static readonly DependencyProperty TileMatrixSetProperty = DependencyProperty.Register(
+ nameof(TileMatrixSet), typeof(WmtsTileMatrixSet), typeof(WmtsTileLayer),
+ new PropertyMetadata(null, (o, e) => ((WmtsTileLayer)o).UpdateTileLayer()));
+
+ public WmtsTileLayer()
+ : this(new TileImageLoader())
+ {
+ }
+
+ public WmtsTileLayer(ITileImageLoader tileImageLoader)
+ : base(tileImageLoader)
+ {
+ Loaded += OnLoaded;
+ }
+
+ public Uri Capabilities
+ {
+ get { return (Uri)GetValue(CapabilitiesProperty); }
+ set { SetValue(CapabilitiesProperty, value); }
+ }
+
+ public string LayerIdentifier
+ {
+ get { return (string)GetValue(LayerIdentifierProperty); }
+ set { SetValue(LayerIdentifierProperty, value); }
+ }
+
+ public WmtsTileMatrixSet TileMatrixSet
+ {
+ get { return (WmtsTileMatrixSet)GetValue(TileMatrixSetProperty); }
+ set { SetValue(TileMatrixSetProperty, value); }
+ }
+
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ foreach (var layer in Children.Cast())
+ {
+ layer.Measure(availableSize);
+ }
+
+ return new Size();
+ }
+
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ foreach (var layer in Children.Cast())
+ {
+ layer.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height));
+ }
+
+ return finalSize;
+ }
+
+ protected override void TileSourcePropertyChanged()
+ {
+ }
+
+ protected override void UpdateTileLayer()
+ {
+ UpdateTimer.Stop();
+
+ if (ParentMap == null ||
+ TileMatrixSet == null ||
+ ParentMap.MapProjection.CrsId != TileMatrixSet.SupportedCrs)
+ {
+ Children.Clear();
+ UpdateTiles();
+ }
+ else if (UpdateChildLayers())
+ {
+ SetRenderTransform();
+ UpdateTiles();
+ }
+ }
+
+ protected override void SetRenderTransform()
+ {
+ foreach (var layer in Children.Cast())
+ {
+ layer.SetRenderTransform(ParentMap.MapProjection, ParentMap.Heading);
+ }
+ }
+
+ private bool UpdateChildLayers()
+ {
+ bool layersChanged = false;
+
+ if (TileMatrixSet != null)
+ {
+ // show all TileMatrix layers with Scale <= ViewportScale, or at least the first layer
+ //
+ var currentMatrixes = TileMatrixSet.TileMatrixes
+ .Where((matrix, i) => i == 0 || matrix.Scale <= ParentMap.MapProjection.ViewportScale)
+ .ToList();
+
+ if (this != ParentMap.MapLayer) // do not load background tiles
+ {
+ currentMatrixes = currentMatrixes.Skip(currentMatrixes.Count - 1).ToList(); // last element only
+ }
+
+ var currentLayers = Children.Cast()
+ .Where(layer => currentMatrixes.Contains(layer.TileMatrix))
+ .ToList();
+
+ Children.Clear();
+
+ foreach (var tileMatrix in currentMatrixes)
+ {
+ var layer = currentLayers.FirstOrDefault(l => l.TileMatrix == tileMatrix);
+
+ if (layer == null)
+ {
+ layer = new WmtsTileMatrixLayer(tileMatrix, TileMatrixSet.TileMatrixes.IndexOf(tileMatrix));
+ layersChanged = true;
+ }
+
+ if (layer.SetBounds(ParentMap.MapProjection, ParentMap.Heading, ParentMap.RenderSize))
+ {
+ layersChanged = true;
+ }
+
+ Children.Add(layer);
+ }
+ }
+
+ return layersChanged;
+ }
+
+ private void UpdateTiles()
+ {
+ var tiles = new List();
+
+ foreach (var layer in Children.Cast())
+ {
+ layer.UpdateTiles();
+
+ tiles.AddRange(layer.Tiles);
+ }
+
+ TileImageLoader.LoadTilesAsync(tiles, TileSource, SourceName);
+ }
+
+ private async void OnLoaded(object sender, RoutedEventArgs e)
+ {
+ if (Capabilities != null)
+ {
+ try
+ {
+ if (Capabilities.IsAbsoluteUri && (Capabilities.Scheme == "http" || Capabilities.Scheme == "https"))
+ {
+ using (var stream = await ImageLoader.HttpClient.GetStreamAsync(Capabilities))
+ {
+ ReadCapabilities(XDocument.Load(stream).Root);
+ }
+ }
+ else
+ {
+ ReadCapabilities(XDocument.Load(Capabilities.ToString()).Root);
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("WmtsTileLayer: {0}: {1}", Capabilities, ex.Message);
+ }
+ }
+ }
+
+ private void ReadCapabilities(XElement capabilities)
+ {
+ var ns = capabilities.Name.Namespace;
+ var contentsElement = capabilities.Element(ns + "Contents");
+
+ if (contentsElement == null)
+ {
+ throw new ArgumentException("Contents element not found.");
+ }
+
+ XNamespace ows = "http://www.opengis.net/ows/1.1";
+ XElement layerElement;
+
+ if (!string.IsNullOrEmpty(LayerIdentifier))
+ {
+ layerElement = contentsElement.Descendants(ns + "Layer")
+ .FirstOrDefault(e => e.Element(ows + "Identifier")?.Value == LayerIdentifier);
+
+ if (layerElement == null)
+ {
+ throw new ArgumentException("Layer element \"" + LayerIdentifier + "\" not found.");
+ }
+ }
+ else
+ {
+ layerElement = capabilities.Descendants(ns + "Layer").FirstOrDefault();
+
+ if (layerElement == null)
+ {
+ throw new ArgumentException("No Layer element found.");
+ }
+
+ LayerIdentifier = layerElement.Element(ows + "Identifier")?.Value ?? "";
+ }
+
+ var tileMatrixSetId = layerElement.Element(ns + "TileMatrixSetLink")?.Element(ns + "TileMatrixSet")?.Value;
+
+ if (tileMatrixSetId == null)
+ {
+ throw new ArgumentException("TileMatrixSetLink element not found.");
+ }
+
+ var urlTemplate = layerElement.Element(ns + "ResourceURL")?.Attribute("template")?.Value;
+
+ if (urlTemplate == null)
+ {
+ throw new ArgumentException("ResourceURL element (or template attribute) not found in Layer \"" + LayerIdentifier + "\".");
+ }
+
+ var tileMatrixSetElement = capabilities.Descendants(ns + "TileMatrixSet")
+ .FirstOrDefault(e => e.Element(ows + "Identifier")?.Value == tileMatrixSetId);
+
+ if (tileMatrixSetElement == null)
+ {
+ throw new ArgumentException("Linked TileMatrixSet element not found in Layer \"" + LayerIdentifier + "\".");
+ }
+
+ var supportedCrs = tileMatrixSetElement.Element(ows + "SupportedCRS")?.Value;
+
+ if (supportedCrs == null)
+ {
+ throw new ArgumentException("ows:SupportedCRS element not found in TileMatrixSet \"" + tileMatrixSetId + "\".");
+ }
+
+ var tileMatrixes = new List();
+
+ foreach (var tileMatrix in tileMatrixSetElement.Descendants(ns + "TileMatrix"))
+ {
+ var tileMatrixId = tileMatrix.Element(ows + "Identifier")?.Value;
+
+ if (string.IsNullOrEmpty(tileMatrixId))
+ {
+ throw new ArgumentException("ows:Identifier element not found in TileMatrix.");
+ }
+
+ string[] topLeftCornerStrings;
+ double scaleDenominator, top, left;
+ int tileWidth, tileHeight, matrixWidth, matrixHeight;
+
+ var valueString = tileMatrix.Element(ns + "ScaleDenominator")?.Value;
+
+ if (string.IsNullOrEmpty(valueString) ||
+ !double.TryParse(valueString, NumberStyles.Float, CultureInfo.InvariantCulture, out scaleDenominator))
+ {
+ throw new ArgumentException("ScaleDenominator element not found in TileMatrix \"" + tileMatrixId + "\".");
+ }
+
+ valueString = tileMatrix.Element(ns + "TopLeftCorner")?.Value;
+
+ if (string.IsNullOrEmpty(valueString) ||
+ (topLeftCornerStrings = valueString.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)).Length < 2 ||
+ !double.TryParse(topLeftCornerStrings[0], NumberStyles.Float, CultureInfo.InvariantCulture, out left) ||
+ !double.TryParse(topLeftCornerStrings[1], NumberStyles.Float, CultureInfo.InvariantCulture, out top))
+ {
+ throw new ArgumentException("TopLeftCorner element not found in TileMatrix \"" + tileMatrixId + "\".");
+ }
+
+ valueString = tileMatrix.Element(ns + "TileWidth")?.Value;
+
+ if (string.IsNullOrEmpty(valueString) || !int.TryParse(valueString, out tileWidth))
+ {
+ throw new ArgumentException("TileWidth element not found in TileMatrix \"" + tileMatrixId + "\".");
+ }
+
+ valueString = tileMatrix.Element(ns + "TileHeight")?.Value;
+
+ if (string.IsNullOrEmpty(valueString) || !int.TryParse(valueString, out tileHeight))
+ {
+ throw new ArgumentException("TileHeight element not found in TileMatrix \"" + tileMatrixId + "\".");
+ }
+
+ valueString = tileMatrix.Element(ns + "MatrixWidth")?.Value;
+
+ if (string.IsNullOrEmpty(valueString) || !int.TryParse(valueString, out matrixWidth))
+ {
+ throw new ArgumentException("MatrixWidth element not found in TileMatrix \"" + tileMatrixId + "\".");
+ }
+
+ valueString = tileMatrix.Element(ns + "MatrixHeight")?.Value;
+
+ if (string.IsNullOrEmpty(valueString) || !int.TryParse(valueString, out matrixHeight))
+ {
+ throw new ArgumentException("MatrixHeight element not found in TileMatrix \"" + tileMatrixId + "\".");
+ }
+
+ tileMatrixes.Add(new WmtsTileMatrix(
+ tileMatrixId, scaleDenominator, new Point(left, top), tileWidth, tileHeight, matrixWidth, matrixHeight));
+ }
+
+ if (tileMatrixes.Count <= 0)
+ {
+ throw new ArgumentException("No TileMatrix elements found in TileMatrixSet \"" + tileMatrixSetId + "\".");
+ }
+
+ var tileMatrixSet = new WmtsTileMatrixSet(tileMatrixSetId, supportedCrs, tileMatrixes);
+
+ SourceName = tileMatrixSet.Identifier;
+ TileSource = new WmtsTileSource(urlTemplate, tileMatrixSet);
+ TileMatrixSet = tileMatrixSet; // calls UpdateTileLayer()
+ }
+ }
+}
diff --git a/MapControl/Shared/WmtsTileMatrix.cs b/MapControl/Shared/WmtsTileMatrix.cs
new file mode 100644
index 00000000..b963eccc
--- /dev/null
+++ b/MapControl/Shared/WmtsTileMatrix.cs
@@ -0,0 +1,33 @@
+// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
+// © 2020 Clemens Fischer
+// Licensed under the Microsoft Public License (Ms-PL)
+
+#if !WINDOWS_UWP
+using System.Windows;
+#endif
+
+namespace MapControl
+{
+ public class WmtsTileMatrix
+ {
+ public WmtsTileMatrix(string identifier, double scaleDenominator, Point topLeft,
+ int tileWidth, int tileHeight, int matrixWidth, int matrixHeight)
+ {
+ Identifier = identifier;
+ Scale = 1 / (scaleDenominator * 0.00028);
+ TopLeft = topLeft;
+ TileWidth = tileWidth;
+ TileHeight = tileHeight;
+ MatrixWidth = matrixWidth;
+ MatrixHeight = matrixHeight;
+ }
+
+ public string Identifier { get; }
+ public double Scale { get; }
+ public Point TopLeft { get; }
+ public int TileWidth { get; }
+ public int TileHeight { get; }
+ public int MatrixWidth { get; }
+ public int MatrixHeight { get; }
+ }
+}
diff --git a/MapControl/Shared/WmtsTileMatrixLayer.cs b/MapControl/Shared/WmtsTileMatrixLayer.cs
new file mode 100644
index 00000000..9009678f
--- /dev/null
+++ b/MapControl/Shared/WmtsTileMatrixLayer.cs
@@ -0,0 +1,147 @@
+// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
+// © 2020 Clemens Fischer
+// Licensed under the Microsoft Public License (Ms-PL)
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+#if WINDOWS_UWP
+using Windows.Foundation;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Media;
+#else
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+#endif
+
+namespace MapControl
+{
+ public class WmtsTileMatrixLayer : Panel
+ {
+ public WmtsTileMatrixLayer(WmtsTileMatrix tileMatrix, int zoomLevel)
+ {
+ IsHitTestVisible = false;
+ RenderTransform = new MatrixTransform();
+ TileMatrix = tileMatrix;
+ ZoomLevel = zoomLevel;
+ }
+
+ public WmtsTileMatrix TileMatrix { get; }
+ public int ZoomLevel { get; }
+ public int XMin { get; private set; }
+ public int YMin { get; private set; }
+ public int XMax { get; private set; }
+ public int YMax { get; private set; }
+
+ public IReadOnlyCollection Tiles { get; private set; } = new List();
+
+ public bool SetBounds(MapProjection projection, double heading, Size mapSize)
+ {
+ // top/left viewport corner in map coordinates
+ //
+ var tileOrigin = projection.InverseViewportTransform.Transform(new Point());
+
+ // top/left viewport corner in tile matrix coordinates
+ //
+ var tileMatrixOrigin = new Point(
+ TileMatrix.Scale * (tileOrigin.X - TileMatrix.TopLeft.X),
+ TileMatrix.Scale * (TileMatrix.TopLeft.Y - tileOrigin.Y));
+
+ var transform = new MatrixTransform
+ {
+ Matrix = MatrixFactory.Create(1, -heading, tileMatrixOrigin)
+ };
+
+ var bounds = transform.TransformBounds(new Rect(0d, 0d, mapSize.Width, mapSize.Height));
+
+ var xMin = (int)Math.Floor(bounds.X / TileMatrix.TileWidth);
+ var yMin = (int)Math.Floor(bounds.Y / TileMatrix.TileHeight);
+ var xMax = (int)Math.Floor((bounds.X + bounds.Width) / TileMatrix.TileWidth);
+ var yMax = (int)Math.Floor((bounds.Y + bounds.Height) / TileMatrix.TileHeight);
+
+ xMin = Math.Max(xMin, 0);
+ yMin = Math.Max(yMin, 0);
+ xMax = Math.Min(Math.Max(xMax, 0), TileMatrix.MatrixWidth - 1);
+ yMax = Math.Min(Math.Max(yMax, 0), TileMatrix.MatrixHeight - 1);
+
+ if (XMin == xMin && YMin == yMin && XMax == xMax && YMax == yMax)
+ {
+ return false;
+ }
+
+ XMin = xMin;
+ YMin = yMin;
+ XMax = xMax;
+ YMax = yMax;
+
+ return true;
+ }
+
+ public void SetRenderTransform(MapProjection projection, double heading)
+ {
+ // XMin/YMin corner in map and viewport coordinates
+ //
+ var mapOrigin = new Point(
+ TileMatrix.TopLeft.X + XMin * TileMatrix.TileWidth / TileMatrix.Scale,
+ TileMatrix.TopLeft.Y - YMin * TileMatrix.TileHeight / TileMatrix.Scale);
+
+ var viewOrigin = projection.ViewportTransform.Transform(mapOrigin);
+
+ var tileScale = projection.ViewportScale / TileMatrix.Scale; // relative scale
+
+ ((MatrixTransform)RenderTransform).Matrix = MatrixFactory.Create(tileScale, heading, viewOrigin);
+ }
+
+ public void UpdateTiles()
+ {
+ var newTiles = new List();
+
+ for (var y = YMin; y <= YMax; y++)
+ {
+ for (var x = XMin; x <= XMax; x++)
+ {
+ newTiles.Add(Tiles.FirstOrDefault(t => t.X == x && t.Y == y) ?? new Tile(ZoomLevel, x, y));
+ }
+ }
+
+ Tiles = newTiles;
+
+ Children.Clear();
+
+ foreach (var tile in Tiles)
+ {
+ Children.Add(tile.Image);
+ }
+ }
+
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
+
+ foreach (var tile in Tiles)
+ {
+ tile.Image.Measure(availableSize);
+ }
+
+ return new Size();
+ }
+
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ foreach (var tile in Tiles)
+ {
+ // arrange tiles relative to XMin/YMin
+ //
+ var x = TileMatrix.TileWidth * (tile.X - XMin);
+ var y = TileMatrix.TileHeight * (tile.Y - YMin);
+
+ tile.Image.Width = TileMatrix.TileWidth;
+ tile.Image.Height = TileMatrix.TileHeight;
+ tile.Image.Arrange(new Rect(x, y, TileMatrix.TileWidth, TileMatrix.TileHeight));
+ }
+
+ return finalSize;
+ }
+ }
+}
diff --git a/MapControl/Shared/WmtsTileMatrixSet.cs b/MapControl/Shared/WmtsTileMatrixSet.cs
new file mode 100644
index 00000000..7ac271b4
--- /dev/null
+++ b/MapControl/Shared/WmtsTileMatrixSet.cs
@@ -0,0 +1,39 @@
+// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
+// © 2020 Clemens Fischer
+// Licensed under the Microsoft Public License (Ms-PL)
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MapControl
+{
+ public class WmtsTileMatrixSet
+ {
+ public WmtsTileMatrixSet(string identifier, string supportedCrs, IEnumerable tileMatrixes)
+ {
+ if (string.IsNullOrEmpty(identifier))
+ {
+ throw new ArgumentException("The parameter identifier must not be null or empty.");
+ }
+
+ if (string.IsNullOrEmpty(supportedCrs))
+ {
+ throw new ArgumentException("The parameter supportedCrs must not be null or empty.");
+ }
+
+ if (tileMatrixes == null || tileMatrixes.Count() <= 0)
+ {
+ throw new ArgumentException("The parameter tileMatrixes must not be null or an empty collection.");
+ }
+
+ Identifier = identifier;
+ SupportedCrs = supportedCrs;
+ TileMatrixes = tileMatrixes.OrderBy(m => m.Scale).ToList();
+ }
+
+ public string Identifier { get; }
+ public string SupportedCrs { get; }
+ public IList TileMatrixes { get; }
+ }
+}
diff --git a/MapControl/Shared/WmtsTileSource.cs b/MapControl/Shared/WmtsTileSource.cs
new file mode 100644
index 00000000..d71993fb
--- /dev/null
+++ b/MapControl/Shared/WmtsTileSource.cs
@@ -0,0 +1,35 @@
+// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
+// © 2020 Clemens Fischer
+// Licensed under the Microsoft Public License (Ms-PL)
+
+using System;
+using System.Collections.Generic;
+
+namespace MapControl
+{
+ public class WmtsTileSource : TileSource
+ {
+ private readonly IList tileMatrixes;
+
+ public WmtsTileSource(string uriFormat, WmtsTileMatrixSet tileMatrixSet)
+ : base(uriFormat.Replace("{TileMatrixSet}", tileMatrixSet.Identifier))
+ {
+ tileMatrixes = tileMatrixSet.TileMatrixes;
+ }
+
+ public override Uri GetUri(int x, int y, int zoomLevel)
+ {
+ if (zoomLevel < 0 || zoomLevel >= tileMatrixes.Count)
+ {
+ return null;
+ }
+
+ var url = UriFormat
+ .Replace("{TileMatrix}", tileMatrixes[zoomLevel].Identifier)
+ .Replace("{TileCol}", x.ToString())
+ .Replace("{TileRow}", y.ToString());
+
+ return new Uri(url);
+ }
+ }
+}
diff --git a/MapControl/UWP/Extensions.UWP.cs b/MapControl/UWP/Extensions.UWP.cs
index d41c66f3..198b5ff0 100644
--- a/MapControl/UWP/Extensions.UWP.cs
+++ b/MapControl/UWP/Extensions.UWP.cs
@@ -3,12 +3,18 @@
// Licensed under the Microsoft Public License (Ms-PL)
using Windows.UI.Xaml;
+using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Animation;
namespace MapControl
{
internal static class Extensions
{
+ public static Point Transform(this GeneralTransform transform, Point point)
+ {
+ return transform.TransformPoint(point);
+ }
+
public static void BeginAnimation(this DependencyObject obj, DependencyProperty property, Timeline animation)
{
if (animation != null)
diff --git a/MapControl/UWP/MapControl.UWP.csproj b/MapControl/UWP/MapControl.UWP.csproj
index 14a0acb6..61765503 100644
--- a/MapControl/UWP/MapControl.UWP.csproj
+++ b/MapControl/UWP/MapControl.UWP.csproj
@@ -125,6 +125,12 @@
MapTileLayer.cs
+
+ MapTileLayerBase.cs
+
+
+ MatrixFactory.cs
+
OrthographicProjection.cs
@@ -155,6 +161,21 @@
WmsImageLayer.cs
+
+ WmtsTileLayer.cs
+
+
+ WmtsTileMatrix.cs
+
+
+ WmtsTileMatrixLayer.cs
+
+
+ WmtsTileMatrixSet.cs
+
+
+ WmtsTileSource.cs
+
WorldMercatorProjection.cs
@@ -179,7 +200,7 @@
- 6.2.9
+ 6.2.10
diff --git a/MapControl/UWP/Properties/AssemblyInfo.cs b/MapControl/UWP/Properties/AssemblyInfo.cs
index 77f138a7..b54101d4 100644
--- a/MapControl/UWP/Properties/AssemblyInfo.cs
+++ b/MapControl/UWP/Properties/AssemblyInfo.cs
@@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
-[assembly: AssemblyVersion("4.16.0")]
-[assembly: AssemblyFileVersion("4.16.0")]
+[assembly: AssemblyVersion("4.17.0")]
+[assembly: AssemblyFileVersion("4.17.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
diff --git a/MapControl/WPF/MapControl.WPF.csproj b/MapControl/WPF/MapControl.WPF.csproj
index d0ea08ef..da4b7d47 100644
--- a/MapControl/WPF/MapControl.WPF.csproj
+++ b/MapControl/WPF/MapControl.WPF.csproj
@@ -9,7 +9,7 @@
..\..\MapControl.snk
false
XAML Map Control
- 4.16.0
+ 4.17.0
XAML Map Control Library
Clemens Fischer
Copyright © 2020 Clemens Fischer
@@ -45,5 +45,4 @@
-
diff --git a/MapImages/UWP/MapImages.UWP.csproj b/MapImages/UWP/MapImages.UWP.csproj
index a9eec841..b99e6e21 100644
--- a/MapImages/UWP/MapImages.UWP.csproj
+++ b/MapImages/UWP/MapImages.UWP.csproj
@@ -51,7 +51,7 @@
- 6.2.9
+ 6.2.10
diff --git a/MapImages/UWP/Properties/AssemblyInfo.cs b/MapImages/UWP/Properties/AssemblyInfo.cs
index 723a6963..2d29f820 100644
--- a/MapImages/UWP/Properties/AssemblyInfo.cs
+++ b/MapImages/UWP/Properties/AssemblyInfo.cs
@@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
-[assembly: AssemblyVersion("4.16.0")]
-[assembly: AssemblyFileVersion("4.16.0")]
+[assembly: AssemblyVersion("4.17.0")]
+[assembly: AssemblyFileVersion("4.17.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
diff --git a/MapImages/WPF/Properties/AssemblyInfo.cs b/MapImages/WPF/Properties/AssemblyInfo.cs
index cd345721..b5e2f65f 100644
--- a/MapImages/WPF/Properties/AssemblyInfo.cs
+++ b/MapImages/WPF/Properties/AssemblyInfo.cs
@@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
-[assembly: AssemblyVersion("4.16.0")]
-[assembly: AssemblyFileVersion("4.16.0")]
+[assembly: AssemblyVersion("4.17.0")]
+[assembly: AssemblyFileVersion("4.17.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
diff --git a/MapProjections/UWP/MapProjections.UWP.csproj b/MapProjections/UWP/MapProjections.UWP.csproj
index c8882aa3..b1ea5027 100644
--- a/MapProjections/UWP/MapProjections.UWP.csproj
+++ b/MapProjections/UWP/MapProjections.UWP.csproj
@@ -57,7 +57,7 @@
- 6.2.9
+ 6.2.10
1.4.1
diff --git a/MapProjections/UWP/Properties/AssemblyInfo.cs b/MapProjections/UWP/Properties/AssemblyInfo.cs
index c23de4ec..74383bad 100644
--- a/MapProjections/UWP/Properties/AssemblyInfo.cs
+++ b/MapProjections/UWP/Properties/AssemblyInfo.cs
@@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
-[assembly: AssemblyVersion("4.16.0")]
-[assembly: AssemblyFileVersion("4.16.0")]
+[assembly: AssemblyVersion("4.17.0")]
+[assembly: AssemblyFileVersion("4.17.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
diff --git a/MapProjections/WPF/Properties/AssemblyInfo.cs b/MapProjections/WPF/Properties/AssemblyInfo.cs
index 3f037b2e..8599d3a4 100644
--- a/MapProjections/WPF/Properties/AssemblyInfo.cs
+++ b/MapProjections/WPF/Properties/AssemblyInfo.cs
@@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
-[assembly: AssemblyVersion("4.16.0")]
-[assembly: AssemblyFileVersion("4.16.0")]
+[assembly: AssemblyVersion("4.17.0")]
+[assembly: AssemblyFileVersion("4.17.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
diff --git a/SQLiteCache/UWP/Properties/AssemblyInfo.cs b/SQLiteCache/UWP/Properties/AssemblyInfo.cs
index 6fddfa50..0678a14e 100644
--- a/SQLiteCache/UWP/Properties/AssemblyInfo.cs
+++ b/SQLiteCache/UWP/Properties/AssemblyInfo.cs
@@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
-[assembly: AssemblyVersion("4.16.0")]
-[assembly: AssemblyFileVersion("4.16.0")]
+[assembly: AssemblyVersion("4.17.0")]
+[assembly: AssemblyFileVersion("4.17.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
diff --git a/SQLiteCache/UWP/SQLiteCache.UWP.csproj b/SQLiteCache/UWP/SQLiteCache.UWP.csproj
index e2787b36..823520f8 100644
--- a/SQLiteCache/UWP/SQLiteCache.UWP.csproj
+++ b/SQLiteCache/UWP/SQLiteCache.UWP.csproj
@@ -49,7 +49,7 @@
- 6.2.9
+ 6.2.10
1.0.112
diff --git a/SQLiteCache/WPF/Properties/AssemblyInfo.cs b/SQLiteCache/WPF/Properties/AssemblyInfo.cs
index 3b7a8bd5..45fdccd0 100644
--- a/SQLiteCache/WPF/Properties/AssemblyInfo.cs
+++ b/SQLiteCache/WPF/Properties/AssemblyInfo.cs
@@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
-[assembly: AssemblyVersion("4.16.0")]
-[assembly: AssemblyFileVersion("4.16.0")]
+[assembly: AssemblyVersion("4.17.0")]
+[assembly: AssemblyFileVersion("4.17.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
diff --git a/SampleApps/UniversalApp/MainPage.xaml.cs b/SampleApps/UniversalApp/MainPage.xaml.cs
index 62ea6081..f214f66c 100644
--- a/SampleApps/UniversalApp/MainPage.xaml.cs
+++ b/SampleApps/UniversalApp/MainPage.xaml.cs
@@ -1,4 +1,5 @@
using MapControl;
+using System;
using ViewModel;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
@@ -13,12 +14,29 @@ namespace UniversalApp
public MainPage()
{
ImageLoader.HttpClient.DefaultRequestHeaders.Add("User-Agent", "XAML Map Control Test Application");
- //TileImageLoader.Cache = new MapControl.Caching.ImageFileCache(TileImageLoader.DefaultCacheFolder);
+ TileImageLoader.Cache = new MapControl.Caching.ImageFileCache(TileImageLoader.DefaultCacheFolder);
//TileImageLoader.Cache = new MapControl.Caching.FileDbCache(TileImageLoader.DefaultCacheFolder);
//TileImageLoader.Cache = new MapControl.Caching.SQLiteCache(TileImageLoader.DefaultCacheFolder);
InitializeComponent();
DataContext = ViewModel;
+
+ Loaded += MainWindow_Loaded;
+ }
+
+ private async void MainWindow_Loaded(object sender, RoutedEventArgs e)
+ {
+ var seachartLayer = new WmsImageLayer
+ {
+ ServiceUri = new Uri("https://wms.sevencs.com:9090")
+ };
+
+ var layers = await seachartLayer.GetLayerNamesAsync();
+
+ foreach (var layer in layers)
+ {
+ System.Diagnostics.Debug.WriteLine(layer);
+ }
}
private void ImageOpacitySliderValueChanged(object sender, RangeBaseValueChangedEventArgs e)
diff --git a/SampleApps/UniversalApp/Properties/AssemblyInfo.cs b/SampleApps/UniversalApp/Properties/AssemblyInfo.cs
index b54ac6f6..3069403f 100644
--- a/SampleApps/UniversalApp/Properties/AssemblyInfo.cs
+++ b/SampleApps/UniversalApp/Properties/AssemblyInfo.cs
@@ -8,7 +8,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
-[assembly: AssemblyVersion("4.16.0")]
-[assembly: AssemblyFileVersion("4.16.0")]
+[assembly: AssemblyVersion("4.17.0")]
+[assembly: AssemblyFileVersion("4.17.0")]
[assembly: AssemblyConfiguration("")]
[assembly: ComVisible(false)]
diff --git a/SampleApps/UniversalApp/UniversalApp.csproj b/SampleApps/UniversalApp/UniversalApp.csproj
index b37f9668..4a4c296b 100644
--- a/SampleApps/UniversalApp/UniversalApp.csproj
+++ b/SampleApps/UniversalApp/UniversalApp.csproj
@@ -152,7 +152,7 @@
- 6.2.9
+ 6.2.10
diff --git a/SampleApps/WpfApplication/Properties/AssemblyInfo.cs b/SampleApps/WpfApplication/Properties/AssemblyInfo.cs
index 27a47018..d5df9058 100644
--- a/SampleApps/WpfApplication/Properties/AssemblyInfo.cs
+++ b/SampleApps/WpfApplication/Properties/AssemblyInfo.cs
@@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2020 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
-[assembly: AssemblyVersion("4.16.0")]
-[assembly: AssemblyFileVersion("4.16.0")]
+[assembly: AssemblyVersion("4.17.0")]
+[assembly: AssemblyFileVersion("4.17.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
diff --git a/SampleApps/WpfApplication/WpfApplication.csproj b/SampleApps/WpfApplication/WpfApplication.csproj
index f5c87cec..70fba3cd 100644
--- a/SampleApps/WpfApplication/WpfApplication.csproj
+++ b/SampleApps/WpfApplication/WpfApplication.csproj
@@ -94,6 +94,10 @@
{62f1726b-3144-49f4-8bcc-94160a3b2186}
MapControl.WPF
+
+ {0109c2f0-ba2c-420f-b2ca-db5b29b1a349}
+ SQLiteCache.WPF
+