Version 4.17.0: Added support for WMTS

This commit is contained in:
ClemensF 2020-03-20 18:12:56 +01:00
parent e650c9081f
commit e821e39841
38 changed files with 991 additions and 307 deletions

View file

@ -52,7 +52,7 @@
<Version>7.4.4</Version> <Version>7.4.4</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform"> <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.9</Version> <Version>6.2.10</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

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

View file

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

View file

@ -54,7 +54,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform"> <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.9</Version> <Version>6.2.10</Version>
</PackageReference> </PackageReference>
<PackageReference Include="System.Data.SQLite.Core"> <PackageReference Include="System.Data.SQLite.Core">
<Version>1.0.112</Version> <Version>1.0.112</Version>

View file

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

View file

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

View file

@ -60,9 +60,10 @@ namespace MapControl
try try
{ {
var stream = await ImageLoader.HttpClient.GetStreamAsync(metadataUri); using (var stream = await ImageLoader.HttpClient.GetStreamAsync(metadataUri))
{
ProcessImageryMetadata(XDocument.Load(stream).Root); ReadImageryMetadata(XDocument.Load(stream).Root);
}
} }
catch (Exception ex) 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 ns = metadataResponse.Name.Namespace;
var metadata = metadataResponse.Descendants(ns + "ImageryMetadata").FirstOrDefault(); var metadata = metadataResponse.Descendants(ns + "ImageryMetadata").FirstOrDefault();

View file

@ -222,7 +222,7 @@ namespace MapControl
} }
/// <summary> /// <summary>
/// Gets the transformation from cartesian map coordinates to viewport coordinates (pixels). /// Gets the transformation from cartesian map coordinates to viewport coordinates.
/// </summary> /// </summary>
public MatrixTransform ViewportTransform { get; } = new MatrixTransform(); public MatrixTransform ViewportTransform { get; } = new MatrixTransform();

View file

@ -65,22 +65,22 @@ namespace MapControl
} }
/// <summary> /// <summary>
/// Gets the projection center. Only relevant for azimuthal pprojections. /// Gets the projection center. Only relevant for azimuthal projections.
/// </summary> /// </summary>
public Location ProjectionCenter { get; private set; } = new Location(); public Location ProjectionCenter { get; private set; } = new Location();
/// <summary> /// <summary>
/// Gets the transform matrix from cartesian map coordinates to viewport coordinates (pixels). /// Gets the transform matrix from cartesian map coordinates to viewport coordinates.
/// </summary> /// </summary>
public Matrix ViewportTransform { get; private set; } public Matrix ViewportTransform { get; private set; }
/// <summary> /// <summary>
/// Gets the transform matrix from viewport coordinates (pixels) to cartesian map coordinates. /// Gets the transform matrix from viewport coordinates to cartesian map coordinates.
/// </summary> /// </summary>
public Matrix InverseViewportTransform { get; private set; } public Matrix InverseViewportTransform { get; private set; }
/// <summary> /// <summary>
/// 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. /// at the projection's point of true scale.
/// </summary> /// </summary>
public double ViewportScale { get; private set; } public double ViewportScale { get; private set; }
@ -186,26 +186,11 @@ namespace MapControl
ViewportScale = Math.Pow(2d, zoomLevel) * TileSize / (360d * TrueScale); ViewportScale = Math.Pow(2d, zoomLevel) * TileSize / (360d * TrueScale);
var center = LocationToPoint(mapCenter); var center = LocationToPoint(mapCenter);
var matrix = CreateTransformMatrix(center, ViewportScale, -ViewportScale, heading, viewportCenter); var matrix = MatrixFactory.Create(center, ViewportScale, -ViewportScale, heading, viewportCenter);
ViewportTransform = matrix; ViewportTransform = matrix;
matrix.Invert(); matrix.Invert();
InverseViewportTransform = matrix; 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;
}
} }
} }

View file

@ -8,27 +8,21 @@ using System.Linq;
#if WINDOWS_UWP #if WINDOWS_UWP
using Windows.Foundation; using Windows.Foundation;
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media;
#else #else
using System.Windows; using System.Windows;
using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Threading;
#endif #endif
namespace MapControl namespace MapControl
{ {
public interface ITileImageLoader
{
void LoadTilesAsync(IEnumerable<Tile> tiles, TileSource tileSource, string sourceName);
}
/// <summary> /// <summary>
/// Fills the map viewport with map tiles from a TileSource. /// Fills the map viewport with map tiles from a TileSource.
/// </summary> /// </summary>
public class MapTileLayer : Panel, IMapLayer public class MapTileLayer : MapTileLayerBase
{ {
private const double WebMercatorMapSize = 2 * Math.PI * MapProjection.Wgs84EquatorialRadius;
/// <summary> /// <summary>
/// A default MapTileLayer using OpenStreetMap data. /// A default MapTileLayer using OpenStreetMap data.
/// </summary> /// </summary>
@ -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( public static readonly DependencyProperty MinZoomLevelProperty = DependencyProperty.Register(
nameof(MinZoomLevel), typeof(int), typeof(MapTileLayer), new PropertyMetadata(0)); nameof(MinZoomLevel), typeof(int), typeof(MapTileLayer), new PropertyMetadata(0));
public static readonly DependencyProperty MaxZoomLevelProperty = DependencyProperty.Register( public static readonly DependencyProperty MaxZoomLevelProperty = DependencyProperty.Register(
nameof(MaxZoomLevel), typeof(int), typeof(MapTileLayer), new PropertyMetadata(18)); 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() public MapTileLayer()
: this(new TileImageLoader()) : this(new TileImageLoader())
{ {
} }
public MapTileLayer(ITileImageLoader 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<Tile> Tiles { get; private set; } = new List<Tile>(); public IReadOnlyCollection<Tile> Tiles { get; private set; } = new List<Tile>();
/// <summary> public TileGrid TileGrid { get; private set; }
/// Provides map tile URIs or images.
/// </summary>
public TileSource TileSource
{
get { return (TileSource)GetValue(TileSourceProperty); }
set { SetValue(TileSourceProperty, value); }
}
/// <summary>
/// Name of the TileSource. Used as component of a tile cache key.
/// </summary>
public string SourceName
{
get { return (string)GetValue(SourceNameProperty); }
set { SetValue(SourceNameProperty, value); }
}
/// <summary>
/// Description of the MapTileLayer. Used to display copyright information on top of the map.
/// </summary>
public string Description
{
get { return (string)GetValue(DescriptionProperty); }
set { SetValue(DescriptionProperty, value); }
}
/// <summary>
/// Adds an offset to the Map's ZoomLevel for a relative scale between the Map and the MapTileLayer.
/// </summary>
public double ZoomLevelOffset
{
get { return (double)GetValue(ZoomLevelOffsetProperty); }
set { SetValue(ZoomLevelOffsetProperty, value); }
}
/// <summary> /// <summary>
/// Minimum zoom level supported by the MapTileLayer. Default value is 0. /// Minimum zoom level supported by the MapTileLayer. Default value is 0.
@ -162,73 +78,6 @@ namespace MapControl
set { SetValue(MaxZoomLevelProperty, value); } set { SetValue(MaxZoomLevelProperty, value); }
} }
/// <summary>
/// Maximum number of background tile levels. Default value is 8.
/// Is only effective in a MapTileLayer that is the MapLayer of its ParentMap.
/// </summary>
public int MaxBackgroundLevels
{
get { return (int)GetValue(MaxBackgroundLevelsProperty); }
set { SetValue(MaxBackgroundLevelsProperty, value); }
}
/// <summary>
/// Minimum time interval between tile updates.
/// </summary>
public TimeSpan UpdateInterval
{
get { return (TimeSpan)GetValue(UpdateIntervalProperty); }
set { SetValue(UpdateIntervalProperty, value); }
}
/// <summary>
/// Controls if tiles are updated while the viewport is still changing.
/// </summary>
public bool UpdateWhileViewportChanging
{
get { return (bool)GetValue(UpdateWhileViewportChangingProperty); }
set { SetValue(UpdateWhileViewportChangingProperty, value); }
}
/// <summary>
/// Optional background brush. Sets MapBase.Background if not null and the MapTileLayer is the base map layer.
/// </summary>
public Brush MapBackground
{
get { return (Brush)GetValue(MapBackgroundProperty); }
set { SetValue(MapBackgroundProperty, value); }
}
/// <summary>
/// Optional foreground brush. Sets MapBase.Foreground if not null and the MapTileLayer is the base map layer.
/// </summary>
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) protected override Size MeasureOverride(Size availableSize)
{ {
availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity); availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
@ -260,29 +109,7 @@ namespace MapControl
return finalSize; return finalSize;
} }
protected virtual void UpdateTileGrid() protected override void TileSourcePropertyChanged()
{
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()
{ {
if (TileGrid != null) 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(); SetRenderTransform();
UpdateTiles();
if (updateTimer.IsEnabled && !UpdateWhileViewportChanging)
{
updateTimer.Stop(); // restart
}
if (!updateTimer.IsEnabled)
{
updateTimer.Start();
}
} }
} }
private TileGrid GetTileGrid() protected override void SetRenderTransform()
{ {
var tileZoomLevel = Math.Max(0, (int)Math.Round(parentMap.ZoomLevel + ZoomLevelOffset)); var tileGridSize = (double)(1 << TileGrid.ZoomLevel);
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 transform = new MatrixTransform // top/left tile grid corner in map coordinates
{ //
Matrix = MapProjection.CreateTransformMatrix(viewCenter, scale, -parentMap.Heading, tileCenter) 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, // tile pixels per viewport unit, 0.5 .. 2
(int)Math.Floor(bounds.X), (int)Math.Floor(bounds.Y), //
(int)Math.Floor(bounds.X + bounds.Width), (int)Math.Floor(bounds.Y + bounds.Height)); 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 tileGridZoomLevel = (int)Math.Round(ParentMap.ZoomLevel);
var scale = Math.Pow(2d, parentMap.ZoomLevel) / tileScale; var tileGridSize = (double)(1 << tileGridZoomLevel);
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);
((MatrixTransform)RenderTransform).Matrix = // top/left viewport corner in map coordinates
MapProjection.CreateTransformMatrix(tileOrigin, scale, parentMap.Heading, viewCenter); //
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() private void UpdateTiles()
{ {
var newTiles = new List<Tile>(); var newTiles = new List<Tile>();
if (parentMap != null && TileGrid != null && TileSource != null) if (ParentMap != null && TileGrid != null && TileSource != null)
{ {
var maxZoomLevel = Math.Min(TileGrid.ZoomLevel, MaxZoomLevel); var maxZoomLevel = Math.Min(TileGrid.ZoomLevel, MaxZoomLevel);
@ -360,7 +209,7 @@ namespace MapControl
{ {
var minZoomLevel = maxZoomLevel; var minZoomLevel = maxZoomLevel;
if (this == parentMap.MapLayer) // load background tiles if (this == ParentMap.MapLayer) // load background tiles
{ {
minZoomLevel = Math.Max(TileGrid.ZoomLevel - MaxBackgroundLevels, MinZoomLevel); minZoomLevel = Math.Max(TileGrid.ZoomLevel - MaxBackgroundLevels, MinZoomLevel);
} }

View file

@ -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<Tile> 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; }
/// <summary>
/// Provides map tile URIs or images.
/// </summary>
public TileSource TileSource
{
get { return (TileSource)GetValue(TileSourceProperty); }
set { SetValue(TileSourceProperty, value); }
}
/// <summary>
/// Name of the TileSource. Used as component of a tile cache key.
/// </summary>
public string SourceName
{
get { return (string)GetValue(SourceNameProperty); }
set { SetValue(SourceNameProperty, value); }
}
/// <summary>
/// Description of the tile layer. Used to display copyright information on top of the map.
/// </summary>
public string Description
{
get { return (string)GetValue(DescriptionProperty); }
set { SetValue(DescriptionProperty, value); }
}
/// <summary>
/// Maximum number of background tile levels. Default value is 8.
/// Is only effective in a MapTileLayer that is the MapLayer of its ParentMap.
/// </summary>
public int MaxBackgroundLevels
{
get { return (int)GetValue(MaxBackgroundLevelsProperty); }
set { SetValue(MaxBackgroundLevelsProperty, value); }
}
/// <summary>
/// Minimum time interval between tile updates.
/// </summary>
public TimeSpan UpdateInterval
{
get { return (TimeSpan)GetValue(UpdateIntervalProperty); }
set { SetValue(UpdateIntervalProperty, value); }
}
/// <summary>
/// Controls if tiles are updated while the viewport is still changing.
/// </summary>
public bool UpdateWhileViewportChanging
{
get { return (bool)GetValue(UpdateWhileViewportChangingProperty); }
set { SetValue(UpdateWhileViewportChangingProperty, value); }
}
/// <summary>
/// Optional background brush. Sets MapBase.Background if not null and this layer is the base map layer.
/// </summary>
public Brush MapBackground
{
get { return (Brush)GetValue(MapBackgroundProperty); }
set { SetValue(MapBackgroundProperty, value); }
}
/// <summary>
/// Optional foreground brush. Sets MapBase.Foreground if not null and this layer is the base map layer.
/// </summary>
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();
}
}

View file

@ -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;
}
}
}

View file

@ -2,11 +2,9 @@
// © 2020 Clemens Fischer // © 2020 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL) // Licensed under the Microsoft Public License (Ms-PL)
using System;
namespace MapControl namespace MapControl
{ {
public class TileGrid : IEquatable<TileGrid> public class TileGrid
{ {
public readonly int ZoomLevel; public readonly int ZoomLevel;
public readonly int XMin; public readonly int XMin;
@ -22,25 +20,5 @@ namespace MapControl
XMax = xMax; XMax = xMax;
YMax = yMax; 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;
}
} }
} }

View file

@ -74,8 +74,13 @@ namespace MapControl
try try
{ {
var stream = await ImageLoader.HttpClient.GetStreamAsync(uri); XElement capabilities;
var capabilities = XDocument.Load(stream).Root;
using (var stream = await ImageLoader.HttpClient.GetStreamAsync(uri))
{
capabilities = XDocument.Load(stream).Root;
}
var ns = capabilities.Name.Namespace; var ns = capabilities.Name.Namespace;
layerNames = capabilities layerNames = capabilities

View file

@ -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<WmtsTileMatrixLayer>())
{
layer.Measure(availableSize);
}
return new Size();
}
protected override Size ArrangeOverride(Size finalSize)
{
foreach (var layer in Children.Cast<WmtsTileMatrixLayer>())
{
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<WmtsTileMatrixLayer>())
{
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<WmtsTileMatrixLayer>()
.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<Tile>();
foreach (var layer in Children.Cast<WmtsTileMatrixLayer>())
{
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<WmtsTileMatrix>();
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()
}
}
}

View file

@ -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; }
}
}

View file

@ -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<Tile> Tiles { get; private set; } = new List<Tile>();
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<Tile>();
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;
}
}
}

View file

@ -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<WmtsTileMatrix> 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<WmtsTileMatrix> TileMatrixes { get; }
}
}

View file

@ -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<WmtsTileMatrix> 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);
}
}
}

View file

@ -3,12 +3,18 @@
// Licensed under the Microsoft Public License (Ms-PL) // Licensed under the Microsoft Public License (Ms-PL)
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Animation; using Windows.UI.Xaml.Media.Animation;
namespace MapControl namespace MapControl
{ {
internal static class Extensions 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) public static void BeginAnimation(this DependencyObject obj, DependencyProperty property, Timeline animation)
{ {
if (animation != null) if (animation != null)

View file

@ -125,6 +125,12 @@
<Compile Include="..\Shared\MapTileLayer.cs"> <Compile Include="..\Shared\MapTileLayer.cs">
<Link>MapTileLayer.cs</Link> <Link>MapTileLayer.cs</Link>
</Compile> </Compile>
<Compile Include="..\Shared\MapTileLayerBase.cs">
<Link>MapTileLayerBase.cs</Link>
</Compile>
<Compile Include="..\Shared\MatrixFactory.cs">
<Link>MatrixFactory.cs</Link>
</Compile>
<Compile Include="..\Shared\OrthographicProjection.cs"> <Compile Include="..\Shared\OrthographicProjection.cs">
<Link>OrthographicProjection.cs</Link> <Link>OrthographicProjection.cs</Link>
</Compile> </Compile>
@ -155,6 +161,21 @@
<Compile Include="..\Shared\WmsImageLayer.cs"> <Compile Include="..\Shared\WmsImageLayer.cs">
<Link>WmsImageLayer.cs</Link> <Link>WmsImageLayer.cs</Link>
</Compile> </Compile>
<Compile Include="..\Shared\WmtsTileLayer.cs">
<Link>WmtsTileLayer.cs</Link>
</Compile>
<Compile Include="..\Shared\WmtsTileMatrix.cs">
<Link>WmtsTileMatrix.cs</Link>
</Compile>
<Compile Include="..\Shared\WmtsTileMatrixLayer.cs">
<Link>WmtsTileMatrixLayer.cs</Link>
</Compile>
<Compile Include="..\Shared\WmtsTileMatrixSet.cs">
<Link>WmtsTileMatrixSet.cs</Link>
</Compile>
<Compile Include="..\Shared\WmtsTileSource.cs">
<Link>WmtsTileSource.cs</Link>
</Compile>
<Compile Include="..\Shared\WorldMercatorProjection.cs"> <Compile Include="..\Shared\WorldMercatorProjection.cs">
<Link>WorldMercatorProjection.cs</Link> <Link>WorldMercatorProjection.cs</Link>
</Compile> </Compile>
@ -179,7 +200,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform"> <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.9</Version> <Version>6.2.10</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

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

View file

@ -9,7 +9,7 @@
<AssemblyOriginatorKeyFile>..\..\MapControl.snk</AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>..\..\MapControl.snk</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign> <DelaySign>false</DelaySign>
<Product>XAML Map Control</Product> <Product>XAML Map Control</Product>
<Version>4.16.0</Version> <Version>4.17.0</Version>
<Description>XAML Map Control Library</Description> <Description>XAML Map Control Library</Description>
<Authors>Clemens Fischer</Authors> <Authors>Clemens Fischer</Authors>
<Copyright>Copyright © 2020 Clemens Fischer</Copyright> <Copyright>Copyright © 2020 Clemens Fischer</Copyright>
@ -45,5 +45,4 @@
<Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http" />
<Reference Include="System.Runtime.Caching" /> <Reference Include="System.Runtime.Caching" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -51,7 +51,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform"> <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.9</Version> <Version>6.2.10</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

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

View file

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

View file

@ -57,7 +57,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform"> <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.9</Version> <Version>6.2.10</Version>
</PackageReference> </PackageReference>
<PackageReference Include="ProjNET4GeoAPI"> <PackageReference Include="ProjNET4GeoAPI">
<Version>1.4.1</Version> <Version>1.4.1</Version>

View file

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

View file

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

View file

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

View file

@ -49,7 +49,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform"> <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.9</Version> <Version>6.2.10</Version>
</PackageReference> </PackageReference>
<PackageReference Include="System.Data.SQLite.Core"> <PackageReference Include="System.Data.SQLite.Core">
<Version>1.0.112</Version> <Version>1.0.112</Version>

View file

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

View file

@ -1,4 +1,5 @@
using MapControl; using MapControl;
using System;
using ViewModel; using ViewModel;
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
@ -13,12 +14,29 @@ namespace UniversalApp
public MainPage() public MainPage()
{ {
ImageLoader.HttpClient.DefaultRequestHeaders.Add("User-Agent", "XAML Map Control Test Application"); 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.FileDbCache(TileImageLoader.DefaultCacheFolder);
//TileImageLoader.Cache = new MapControl.Caching.SQLiteCache(TileImageLoader.DefaultCacheFolder); //TileImageLoader.Cache = new MapControl.Caching.SQLiteCache(TileImageLoader.DefaultCacheFolder);
InitializeComponent(); InitializeComponent();
DataContext = ViewModel; 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) private void ImageOpacitySliderValueChanged(object sender, RangeBaseValueChangedEventArgs e)

View file

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

View file

@ -152,7 +152,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform"> <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.9</Version> <Version>6.2.10</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' "> <PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">

View file

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

View file

@ -94,6 +94,10 @@
<Project>{62f1726b-3144-49f4-8bcc-94160a3b2186}</Project> <Project>{62f1726b-3144-49f4-8bcc-94160a3b2186}</Project>
<Name>MapControl.WPF</Name> <Name>MapControl.WPF</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\..\SQLiteCache\WPF\SQLiteCache.WPF.csproj">
<Project>{0109c2f0-ba2c-420f-b2ca-db5b29b1a349}</Project>
<Name>SQLiteCache.WPF</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.

View file

@ -6,7 +6,7 @@
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<RootNamespace>WpfCoreApp</RootNamespace> <RootNamespace>WpfCoreApp</RootNamespace>
<Product>XAML Map Control</Product> <Product>XAML Map Control</Product>
<Version>4.16.0</Version> <Version>4.17.0</Version>
<Description>XAML Map Control WPF Sample Application</Description> <Description>XAML Map Control WPF Sample Application</Description>
<Authors>Clemens Fischer</Authors> <Authors>Clemens Fischer</Authors>
<Copyright>Copyright © 2020 Clemens Fischer</Copyright> <Copyright>Copyright © 2020 Clemens Fischer</Copyright>