Version 2.5.0:

- moved tile rect calculation from MapBase to TileLayer
- added TileLayer.ZoomLevelOffset property
- Windows Universal sample application in solution MapControl VS2015
This commit is contained in:
ClemensF 2015-08-09 20:04:44 +02:00
parent 36a8600faf
commit 98cdc95c5d
80 changed files with 16033 additions and 456 deletions

View file

@ -8,7 +8,7 @@ using System.Diagnostics;
using System.Globalization;
using System.Net;
using System.Xml;
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media.Imaging;
#else

View file

@ -31,9 +31,9 @@ namespace MapControl
quadkey[z] = (char)('0' + 2 * (y % 2) + (x % 2));
}
return new Uri(UriFormat.
Replace("{subdomain}", subdomain).
Replace("{quadkey}", new string(quadkey)));
return new Uri(UriFormat
.Replace("{subdomain}", subdomain)
.Replace("{quadkey}", new string(quadkey)));
}
}
}

View file

@ -5,7 +5,7 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Documents;
@ -45,7 +45,7 @@ namespace MapControl
link.Inlines.Add(new Run { Text = match.Groups[1].Value });
#if SILVERLIGHT
link.TargetName = "_blank";
#elif !WINDOWS_RUNTIME
#elif !NETFX_CORE
link.ToolTip = uri.ToString();
link.RequestNavigate += (s, e) => System.Diagnostics.Process.Start(e.Uri.ToString());
#endif
@ -86,7 +86,7 @@ namespace MapControl
{
inlines = ((Paragraph)obj).Inlines;
}
#if WINDOWS_RUNTIME || SILVERLIGHT
#if NETFX_CORE || SILVERLIGHT
else if (obj is RichTextBlock)
{
var paragraph = new Paragraph();

View file

@ -2,7 +2,7 @@
// © 2015 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
#else

View file

@ -3,16 +3,16 @@
// Licensed under the Microsoft Public License (Ms-PL)
using System;
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
#else
using System.Windows;
using System.Windows.Media;
using System.Windows.Controls;
using System.Windows.Media;
#endif
namespace MapControl
@ -61,43 +61,21 @@ namespace MapControl
private void OnRenderSizeChanged(object sender, SizeChangedEventArgs e)
{
((RectangleGeometry)Clip).Rect = new Rect(new Point(), e.NewSize);
ResetTransformOrigin();
UpdateTransform();
}
private void SetViewportTransform(Point mapOrigin)
private void SetViewportTransform(Location origin)
{
viewportTransform.Matrix = Matrix.Identity
.Translate(-mapOrigin.X, -mapOrigin.Y)
MapOrigin = mapTransform.Transform(origin);
ViewportScale = Math.Pow(2d, ZoomLevel) * TileSource.TileSize / 360d;
viewportTransform.Matrix =
new Matrix(1d, 0d, 0d, 1d, -MapOrigin.X, -MapOrigin.Y)
.Scale(ViewportScale, -ViewportScale)
.Rotate(Heading)
.Translate(viewportOrigin.X, viewportOrigin.Y);
}
private void SetTileLayerTransform()
{
var scale = Math.Pow(2d, ZoomLevel - TileZoomLevel);
tileLayerTransform.Matrix = Matrix.Identity
.Translate(TileGrid.X * TileSource.TileSize, TileGrid.Y * TileSource.TileSize)
.Scale(scale, scale)
.Translate(tileLayerOffset.X, tileLayerOffset.Y)
.RotateAt(Heading, viewportOrigin.X, viewportOrigin.Y); ;
}
private void SetTransformMatrixes()
{
scaleTransform.Matrix = Matrix.Identity.Scale(CenterScale, CenterScale);
rotateTransform.Matrix = Matrix.Identity.Rotate(Heading);
scaleRotateTransform.Matrix = scaleTransform.Matrix.Multiply(rotateTransform.Matrix);
}
private Matrix GetTileIndexMatrix(double scale)
{
return viewportTransform.Matrix
.Invert() // view to map coordinates
.Translate(180d, -180d)
.Scale(scale, -scale); // map coordinates to tile indices
.Translate(ViewportOrigin.X, ViewportOrigin.Y);
}
}
}

View file

@ -61,54 +61,22 @@ namespace MapControl
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
ResetTransformOrigin();
UpdateTransform();
}
private void SetViewportTransform(Point mapOrigin)
private void SetViewportTransform(Location origin)
{
var transform = Matrix.Identity;
transform.Translate(-mapOrigin.X, -mapOrigin.Y);
MapOrigin = mapTransform.Transform(origin);
ViewportScale = Math.Pow(2d, ZoomLevel) * TileSource.TileSize / 360d;
var transform = new Matrix(1d, 0d, 0d, 1d, -MapOrigin.X, -MapOrigin.Y);
transform.Scale(ViewportScale, -ViewportScale);
transform.Rotate(Heading);
transform.Translate(viewportOrigin.X, viewportOrigin.Y);
transform.Translate(ViewportOrigin.X, ViewportOrigin.Y);
viewportTransform.Matrix = transform;
}
private void SetTileLayerTransform()
{
var scale = Math.Pow(2d, ZoomLevel - TileZoomLevel);
var transform = Matrix.Identity;
transform.Translate(TileGrid.X * TileSource.TileSize, TileGrid.Y * TileSource.TileSize);
transform.Scale(scale, scale);
transform.Translate(tileLayerOffset.X, tileLayerOffset.Y);
transform.RotateAt(Heading, viewportOrigin.X, viewportOrigin.Y);
tileLayerTransform.Matrix = transform;
}
private void SetTransformMatrixes()
{
var rotateMatrix = Matrix.Identity;
rotateMatrix.Rotate(Heading);
rotateTransform.Matrix = rotateMatrix;
var scaleMatrix = Matrix.Identity;
scaleMatrix.Scale(CenterScale, CenterScale);
scaleTransform.Matrix = scaleMatrix;
scaleRotateTransform.Matrix = scaleMatrix * rotateMatrix;
}
private Matrix GetTileIndexMatrix(double scale)
{
var transform = viewportTransform.Matrix;
transform.Invert(); // view to map coordinates
transform.Translate(180d, -180d);
transform.Scale(scale, -scale); // map coordinates to tile indices
return transform;
}
}
}

View file

@ -7,7 +7,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
@ -16,24 +16,20 @@ using Windows.UI.Xaml.Media.Animation;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;
#endif
namespace MapControl
{
/// <summary>
/// The map control. Draws map content provided by the TileLayers or the TileLayer property.
/// The visible map area is defined by the Center and ZoomLevel properties. The map can be rotated
/// by an angle that is given by the Heading property.
/// MapBase is a MapPanel and hence can contain map overlays like other MapPanels or MapItemsControls.
/// The map control. Renders map content provided by the TileLayer or TileLayers property.
/// The visible map area is defined by the Center and ZoomLevel properties.
/// The map can be rotated by an angle that is given by the Heading property.
/// MapBase can contain map overlay child elements like other MapPanels or MapItemsControls.
/// </summary>
public partial class MapBase : MapPanel
{
private const double MaximumZoomLevel = 22d;
public static double ZoomLevelSwitchDelta = 0d;
public static bool UpdateTilesWhileViewportChanging = true;
public static TimeSpan TileUpdateInterval = TimeSpan.FromSeconds(0.5);
public static TimeSpan AnimationDuration = TimeSpan.FromSeconds(0.3);
public static EasingFunctionBase AnimationEasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut };
@ -50,7 +46,7 @@ namespace MapControl
(o, e) => ((MapBase)o).MinZoomLevelPropertyChanged((double)e.NewValue)));
public static readonly DependencyProperty MaxZoomLevelProperty = DependencyProperty.Register(
"MaxZoomLevel", typeof(double), typeof(MapBase), new PropertyMetadata(18d,
"MaxZoomLevel", typeof(double), typeof(MapBase), new PropertyMetadata(19d,
(o, e) => ((MapBase)o).MaxZoomLevelPropertyChanged((double)e.NewValue)));
internal static readonly DependencyProperty CenterPointProperty = DependencyProperty.Register(
@ -58,46 +54,40 @@ namespace MapControl
(o, e) => ((MapBase)o).CenterPointPropertyChanged((Point)e.NewValue)));
private readonly PanelBase tileLayerPanel = new PanelBase();
private readonly DispatcherTimer tileUpdateTimer = new DispatcherTimer { Interval = TileUpdateInterval };
private readonly MapTransform mapTransform = new MercatorTransform();
private readonly MatrixTransform viewportTransform = new MatrixTransform();
private readonly MatrixTransform tileLayerTransform = new MatrixTransform();
private readonly MatrixTransform scaleTransform = new MatrixTransform();
private readonly MatrixTransform rotateTransform = new MatrixTransform();
private readonly MatrixTransform scaleRotateTransform = new MatrixTransform();
private readonly ScaleTransform scaleTransform = new ScaleTransform();
private readonly RotateTransform rotateTransform = new RotateTransform();
private readonly TransformGroup scaleRotateTransform = new TransformGroup();
private Location transformOrigin;
private Point viewportOrigin;
private Point tileLayerOffset;
private PointAnimation centerAnimation;
private DoubleAnimation zoomLevelAnimation;
private DoubleAnimation headingAnimation;
private bool internalPropertyChange;
internal Point MapOrigin { get; private set; }
internal Point ViewportOrigin { get; private set; }
public MapBase()
{
Initialize();
scaleRotateTransform.Children.Add(scaleTransform);
scaleRotateTransform.Children.Add(rotateTransform);
Children.Add(tileLayerPanel);
TileLayers = new ObservableCollection<TileLayer>();
tileUpdateTimer.Tick += UpdateTiles;
Loaded += MapLoaded;
Initialize();
}
partial void Initialize();
partial void RemoveAnimation(DependencyProperty property);
partial void Initialize(); // Windows Runtime and Silverlight only
partial void RemoveAnimation(DependencyProperty property); // WPF only
/// <summary>
/// Raised when the current viewport has changed.
/// </summary>
public event EventHandler ViewportChanged;
/// <summary>
/// Raised when the TileZoomLevel or TileGrid properties have changed.
/// </summary>
public event EventHandler TileGridChanged;
/// <summary>
/// Gets or sets the map foreground Brush.
/// </summary>
@ -118,9 +108,8 @@ namespace MapControl
/// <summary>
/// Gets or sets optional multiple TileLayers that are used simultaneously.
/// The first element in the collection is equal to the value of the TileLayer property.
/// The additional TileLayers usually have transparent backgrounds and their IsOverlay
/// property is set to true.
/// The first element in the collection is equal to the value of the TileLayer
/// property. The additional TileLayers usually have transparent backgrounds.
/// </summary>
public IList<TileLayer> TileLayers
{
@ -185,7 +174,7 @@ namespace MapControl
}
/// <summary>
/// Gets or sets the map heading, or clockwise rotation angle in degrees.
/// Gets or sets the map heading, i.e. a clockwise rotation angle in degrees.
/// </summary>
public double Heading
{
@ -211,25 +200,17 @@ namespace MapControl
}
/// <summary>
/// Gets the transformation from cartesian map coordinates to viewport coordinates.
/// Gets the transformation from cartesian map coordinates to viewport coordinates (i.e. pixels).
/// </summary>
public Transform ViewportTransform
public MatrixTransform ViewportTransform
{
get { return viewportTransform; }
}
/// <summary>
/// Gets the RenderTransform to be used by TileLayers, with origin at TileGrid.X and TileGrid.Y.
/// Gets the scaling transformation from meters to viewport coordinate units at the Center location.
/// </summary>
public Transform TileLayerTransform
{
get { return tileLayerTransform; }
}
/// <summary>
/// Gets the scaling transformation from meters to viewport coordinate units (pixels) at the Center location.
/// </summary>
public Transform ScaleTransform
public ScaleTransform ScaleTransform
{
get { return scaleTransform; }
}
@ -237,7 +218,7 @@ namespace MapControl
/// <summary>
/// Gets the transformation that rotates by the value of the Heading property.
/// </summary>
public Transform RotateTransform
public RotateTransform RotateTransform
{
get { return rotateTransform; }
}
@ -245,7 +226,7 @@ namespace MapControl
/// <summary>
/// Gets the combination of ScaleTransform and RotateTransform
/// </summary>
public Transform ScaleRotateTransform
public TransformGroup ScaleRotateTransform
{
get { return scaleRotateTransform; }
}
@ -256,26 +237,17 @@ namespace MapControl
public double ViewportScale { get; private set; }
/// <summary>
/// Gets the scaling factor from meters to viewport coordinate units (pixels) at the Center location.
/// Gets the scaling factor from meters to viewport coordinate units at the Center location.
/// </summary>
public double CenterScale { get; private set; }
/// <summary>
/// Gets the zoom level to be used by TileLayers.
/// </summary>
public int TileZoomLevel { get; private set; }
/// <summary>
/// Gets the tile grid to be used by TileLayers.
/// </summary>
public Int32Rect TileGrid { get; private set; }
/// <summary>
/// Gets the map scale at the specified location as viewport coordinate units (pixels) per meter.
/// Gets the map scale at the specified location as viewport coordinate units per meter.
/// </summary>
public double GetMapScale(Location location)
{
return mapTransform.RelativeScale(location) * Math.Pow(2d, ZoomLevel) * TileSource.TileSize / (TileSource.MetersPerDegree * 360d);
return mapTransform.RelativeScale(location) *
Math.Pow(2d, ZoomLevel) * TileSource.TileSize / (TileSource.MetersPerDegree * 360d);
}
/// <summary>
@ -296,32 +268,33 @@ namespace MapControl
/// <summary>
/// Sets a temporary origin location in geographic coordinates for scaling and rotation transformations.
/// This origin location is automatically removed when the Center property is set by application code.
/// This origin location is automatically reset when the Center property is set by application code.
/// </summary>
public void SetTransformOrigin(Location origin)
{
transformOrigin = origin;
viewportOrigin = LocationToViewportPoint(origin);
ViewportOrigin = LocationToViewportPoint(origin);
}
/// <summary>
/// Sets a temporary origin point in viewport coordinates for scaling and rotation transformations.
/// This origin point is automatically removed when the Center property is set by application code.
/// This origin point is automatically reset when the Center property is set by application code.
/// </summary>
public void SetTransformOrigin(Point origin)
{
viewportOrigin.X = Math.Min(Math.Max(origin.X, 0d), RenderSize.Width);
viewportOrigin.Y = Math.Min(Math.Max(origin.Y, 0d), RenderSize.Height);
transformOrigin = ViewportPointToLocation(viewportOrigin);
ViewportOrigin = new Point(
Math.Min(Math.Max(origin.X, 0d), RenderSize.Width),
Math.Min(Math.Max(origin.Y, 0d), RenderSize.Height));
transformOrigin = ViewportPointToLocation(ViewportOrigin);
}
/// <summary>
/// Removes the temporary transform origin point set by SetTransformOrigin.
/// Resets the temporary transform origin point set by SetTransformOrigin.
/// </summary>
public void ResetTransformOrigin()
{
transformOrigin = null;
viewportOrigin = new Point(RenderSize.Width / 2d, RenderSize.Height / 2d);
ViewportOrigin = new Point(RenderSize.Width / 2d, RenderSize.Height / 2d);
}
/// <summary>
@ -336,7 +309,7 @@ namespace MapControl
if (translation.X != 0d || translation.Y != 0d)
{
Center = ViewportPointToLocation(new Point(viewportOrigin.X - translation.X, viewportOrigin.Y - translation.Y));
Center = ViewportPointToLocation(new Point(ViewportOrigin.X - translation.X, ViewportOrigin.Y - translation.Y));
}
}
@ -349,8 +322,7 @@ namespace MapControl
{
SetTransformOrigin(origin);
viewportOrigin.X += translation.X;
viewportOrigin.Y += translation.Y;
ViewportOrigin = new Point(ViewportOrigin.X + translation.X, ViewportOrigin.Y + translation.Y);
if (rotation != 0d)
{
@ -403,28 +375,6 @@ namespace MapControl
}
}
protected override void OnViewportChanged()
{
base.OnViewportChanged();
var viewportChanged = ViewportChanged;
if (viewportChanged != null)
{
viewportChanged(this, EventArgs.Empty);
}
}
private void MapLoaded(object sender, RoutedEventArgs e)
{
Loaded -= MapLoaded;
if (tileLayerPanel.Children.Count == 0 && !Children.OfType<TileLayer>().Any())
{
TileLayer = TileLayer.Default;
}
}
private void TileLayerPropertyChanged(TileLayer tileLayer)
{
if (tileLayer != null)
@ -849,74 +799,23 @@ namespace MapControl
}
}
CenterScale = ViewportScale * mapTransform.RelativeScale(center) / TileSource.MetersPerDegree; // Pixels per meter at center latitude
CenterScale = ViewportScale * mapTransform.RelativeScale(center) / TileSource.MetersPerDegree; // pixels per meter at center latitude
scaleTransform.ScaleX = CenterScale;
scaleTransform.ScaleY = CenterScale;
rotateTransform.Angle = Heading;
SetTransformMatrixes();
OnViewportChanged();
}
private void SetViewportTransform(Location origin)
protected override void OnViewportChanged()
{
var oldMapOriginX = (viewportOrigin.X - tileLayerOffset.X) / ViewportScale - 180d;
var mapOrigin = mapTransform.Transform(origin);
base.OnViewportChanged();
ViewportScale = Math.Pow(2d, ZoomLevel) * TileSource.TileSize / 360d;
SetViewportTransform(mapOrigin);
var viewportChanged = ViewportChanged;
tileLayerOffset.X = viewportOrigin.X - (180d + mapOrigin.X) * ViewportScale;
tileLayerOffset.Y = viewportOrigin.Y - (180d - mapOrigin.Y) * ViewportScale;
if (Math.Abs(mapOrigin.X - oldMapOriginX) > 180d)
if (viewportChanged != null)
{
// immediately handle map origin leap when map center moves across 180° longitude
UpdateTiles(this, EventArgs.Empty);
}
else
{
SetTileLayerTransform();
if (!UpdateTilesWhileViewportChanging)
{
tileUpdateTimer.Stop();
}
tileUpdateTimer.Start();
}
}
private void UpdateTiles(object sender, object e)
{
tileUpdateTimer.Stop();
var zoomLevel = (int)Math.Round(ZoomLevel + ZoomLevelSwitchDelta);
var transform = GetTileIndexMatrix((double)(1 << zoomLevel) / 360d);
// tile indices of visible rectangle
var p1 = transform.Transform(new Point(0d, 0d));
var p2 = transform.Transform(new Point(RenderSize.Width, 0d));
var p3 = transform.Transform(new Point(0d, RenderSize.Height));
var p4 = transform.Transform(new Point(RenderSize.Width, RenderSize.Height));
// index ranges of visible tiles
var x1 = (int)Math.Floor(Math.Min(p1.X, Math.Min(p2.X, Math.Min(p3.X, p4.X))));
var y1 = (int)Math.Floor(Math.Min(p1.Y, Math.Min(p2.Y, Math.Min(p3.Y, p4.Y))));
var x2 = (int)Math.Floor(Math.Max(p1.X, Math.Max(p2.X, Math.Max(p3.X, p4.X))));
var y2 = (int)Math.Floor(Math.Max(p1.Y, Math.Max(p2.Y, Math.Max(p3.Y, p4.Y))));
var grid = new Int32Rect(x1, y1, x2 - x1 + 1, y2 - y1 + 1);
if (TileZoomLevel != zoomLevel || TileGrid != grid)
{
TileZoomLevel = zoomLevel;
TileGrid = grid;
SetTileLayerTransform();
var tileGridChanged = TileGridChanged;
if (tileGridChanged != null)
{
tileGridChanged(this, EventArgs.Empty);
}
viewportChanged(this, EventArgs.Empty);
}
}
}

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

View file

@ -4,9 +4,8 @@
using System;
using System.Linq;
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;

View file

@ -3,7 +3,7 @@
// Licensed under the Microsoft Public License (Ms-PL)
using System;
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.UI.Xaml;
#else
using System.Windows;

View file

@ -2,7 +2,7 @@
// © 2015 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
#else

View file

@ -2,7 +2,7 @@
// © 2015 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media.Imaging;
#else

View file

@ -4,7 +4,7 @@
using System;
using System.Globalization;
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
@ -33,6 +33,10 @@ namespace MapControl
public static readonly DependencyProperty RelativeImageSizeProperty = DependencyProperty.Register(
"RelativeImageSize", typeof(double), typeof(MapImageLayer), new PropertyMetadata(1d));
public static readonly DependencyProperty UpdateIntervalProperty = DependencyProperty.Register(
"UpdateInterval", typeof(TimeSpan), typeof(MapImageLayer),
new PropertyMetadata(TimeSpan.FromSeconds(0.5), (o, e) => ((MapImageLayer)o).updateTimer.Interval = (TimeSpan)e.NewValue));
private readonly DispatcherTimer updateTimer;
private int currentImageIndex;
private bool updateInProgress;
@ -42,7 +46,7 @@ namespace MapControl
Children.Add(new MapImage { Opacity = 0d });
Children.Add(new MapImage { Opacity = 0d });
updateTimer = new DispatcherTimer { Interval = MapBase.TileUpdateInterval };
updateTimer = new DispatcherTimer { Interval = UpdateInterval };
updateTimer.Tick += (s, e) => UpdateImage();
}
@ -69,6 +73,15 @@ namespace MapControl
set { SetValue(RelativeImageSizeProperty, value); }
}
/// <summary>
/// Minimum time interval between images updates.
/// </summary>
public TimeSpan UpdateInterval
{
get { return (TimeSpan)GetValue(UpdateIntervalProperty); }
set { SetValue(UpdateIntervalProperty, value); }
}
protected override void OnViewportChanged()
{
base.OnViewportChanged();
@ -119,26 +132,28 @@ namespace MapControl
{
if (UriFormat != null && width > 0 && height > 0)
{
var uri = UriFormat.Replace("{X}", width.ToString()).Replace("{Y}", height.ToString());
var uri = UriFormat
.Replace("{X}", width.ToString())
.Replace("{Y}", height.ToString());
if (uri.Contains("{W}") && uri.Contains("{S}") && uri.Contains("{E}") && uri.Contains("{N}"))
{
var p1 = ParentMap.MapTransform.Transform(new Location(south, west));
var p2 = ParentMap.MapTransform.Transform(new Location(north, east));
uri = uri.
Replace("{W}", (TileSource.MetersPerDegree * p1.X).ToString(CultureInfo.InvariantCulture)).
Replace("{S}", (TileSource.MetersPerDegree * p1.Y).ToString(CultureInfo.InvariantCulture)).
Replace("{E}", (TileSource.MetersPerDegree * p2.X).ToString(CultureInfo.InvariantCulture)).
Replace("{N}", (TileSource.MetersPerDegree * p2.Y).ToString(CultureInfo.InvariantCulture));
uri = uri
.Replace("{W}", (TileSource.MetersPerDegree * p1.X).ToString(CultureInfo.InvariantCulture))
.Replace("{S}", (TileSource.MetersPerDegree * p1.Y).ToString(CultureInfo.InvariantCulture))
.Replace("{E}", (TileSource.MetersPerDegree * p2.X).ToString(CultureInfo.InvariantCulture))
.Replace("{N}", (TileSource.MetersPerDegree * p2.Y).ToString(CultureInfo.InvariantCulture));
}
else
{
uri = uri.
Replace("{w}", west.ToString(CultureInfo.InvariantCulture)).
Replace("{s}", south.ToString(CultureInfo.InvariantCulture)).
Replace("{e}", east.ToString(CultureInfo.InvariantCulture)).
Replace("{n}", north.ToString(CultureInfo.InvariantCulture));
uri = uri
.Replace("{w}", west.ToString(CultureInfo.InvariantCulture))
.Replace("{s}", south.ToString(CultureInfo.InvariantCulture))
.Replace("{e}", east.ToString(CultureInfo.InvariantCulture))
.Replace("{n}", north.ToString(CultureInfo.InvariantCulture));
}
UpdateImage(west, east, south, north, new Uri(uri));

View file

@ -2,7 +2,7 @@
// © 2015 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.UI.Xaml.Controls;
#else
using System.Windows.Controls;

View file

@ -2,7 +2,7 @@
// © 2015 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
#else

View file

@ -2,15 +2,21 @@
// © 2015 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
#if WINDOWS_RUNTIME
#if !NETFX_CORE
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
#else
using Windows.UI.Text;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Media;
#else
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
namespace MapControl
{
class FontStyles { public const FontStyle Normal = FontStyle.Normal; }
class FontStretches { public const FontStretch Normal = FontStretch.Normal; }
}
#endif
namespace MapControl
@ -21,16 +27,16 @@ namespace MapControl
"FontSize", typeof(double), typeof(MapOverlay), new PropertyMetadata(10d));
public static readonly DependencyProperty FontFamilyProperty = DependencyProperty.Register(
"FontFamily", typeof(FontFamily), typeof(MapOverlay), new PropertyMetadata(default(FontFamily)));
"FontFamily", typeof(FontFamily), typeof(MapOverlay), new PropertyMetadata(null));
public static readonly DependencyProperty FontStyleProperty = DependencyProperty.Register(
"FontStyle", typeof(FontStyle), typeof(MapOverlay), new PropertyMetadata(default(FontStyle)));
"FontStyle", typeof(FontStyle), typeof(MapOverlay), new PropertyMetadata(FontStyles.Normal));
public static readonly DependencyProperty FontStretchProperty = DependencyProperty.Register(
"FontStretch", typeof(FontStretch), typeof(MapOverlay), new PropertyMetadata(default(FontStretch)));
"FontStretch", typeof(FontStretch), typeof(MapOverlay), new PropertyMetadata(FontStretches.Normal));
public static readonly DependencyProperty FontWeightProperty = DependencyProperty.Register(
"FontWeight", typeof(FontWeight), typeof(MapOverlay), new PropertyMetadata(default(FontWeight)));
"FontWeight", typeof(FontWeight), typeof(MapOverlay), new PropertyMetadata(FontWeights.Normal));
public static readonly DependencyProperty ForegroundProperty = DependencyProperty.Register(
"Foreground", typeof(Brush), typeof(MapOverlay), new PropertyMetadata(null));
@ -48,16 +54,16 @@ namespace MapControl
"StrokeDashOffset", typeof(double), typeof(MapOverlay), new PropertyMetadata(0d));
public static readonly DependencyProperty StrokeDashCapProperty = DependencyProperty.Register(
"StrokeDashCap", typeof(PenLineCap), typeof(MapOverlay), new PropertyMetadata(default(PenLineCap)));
"StrokeDashCap", typeof(PenLineCap), typeof(MapOverlay), new PropertyMetadata(PenLineCap.Flat));
public static readonly DependencyProperty StrokeStartLineCapProperty = DependencyProperty.Register(
"StrokeStartLineCap", typeof(PenLineCap), typeof(MapOverlay), new PropertyMetadata(default(PenLineCap)));
"StrokeStartLineCap", typeof(PenLineCap), typeof(MapOverlay), new PropertyMetadata(PenLineCap.Flat));
public static readonly DependencyProperty StrokeEndLineCapProperty = DependencyProperty.Register(
"StrokeEndLineCap", typeof(PenLineCap), typeof(MapOverlay), new PropertyMetadata(default(PenLineCap)));
"StrokeEndLineCap", typeof(PenLineCap), typeof(MapOverlay), new PropertyMetadata(PenLineCap.Flat));
public static readonly DependencyProperty StrokeLineJoinProperty = DependencyProperty.Register(
"StrokeLineJoin", typeof(PenLineJoin), typeof(MapOverlay), new PropertyMetadata(default(PenLineJoin)));
"StrokeLineJoin", typeof(PenLineJoin), typeof(MapOverlay), new PropertyMetadata(PenLineJoin.Miter));
public static readonly DependencyProperty StrokeMiterLimitProperty = DependencyProperty.Register(
"StrokeMiterLimit", typeof(double), typeof(MapOverlay), new PropertyMetadata(1d));

View file

@ -2,7 +2,7 @@
// © 2015 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.UI.Text;
using Windows.UI.Xaml.Media;
#else

View file

@ -2,7 +2,7 @@
// © 2015 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
#else

View file

@ -3,7 +3,7 @@
// Licensed under the Microsoft Public License (Ms-PL)
using System;
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
@ -15,9 +15,9 @@ using System.Windows.Media;
namespace MapControl
{
/// <summary>
/// Arranges child elements on a Map at positions specified by the attached property Location,
/// which is transformed to a viewport position by ParentMap.MapTransform and ParentMap.ViewportTransform
/// and assigned to the child element's RenderTransform as a TranslateTransform.
/// Arranges child elements on a Map at positions specified by the attached property Location.
/// The Location value is transformed to a viewport position and assigned as TranslateTransform
/// to the RenderTransform of the element, either directly or as last child of a TransformGroup.
/// </summary>
public partial class MapPanel : PanelBase, IMapElement
{
@ -42,22 +42,6 @@ namespace MapControl
set { SetParentMapOverride(value); }
}
protected virtual void SetParentMapOverride(MapBase map)
{
if (parentMap != null && parentMap != this)
{
parentMap.ViewportChanged -= OnViewportChanged;
}
parentMap = map;
if (parentMap != null && parentMap != this)
{
parentMap.ViewportChanged += OnViewportChanged;
OnViewportChanged();
}
}
protected override Size ArrangeOverride(Size finalSize)
{
foreach (UIElement element in Children)
@ -78,6 +62,22 @@ namespace MapControl
return finalSize;
}
protected virtual void SetParentMapOverride(MapBase map)
{
if (parentMap != null && parentMap != this)
{
parentMap.ViewportChanged -= OnViewportChanged;
}
parentMap = map;
if (parentMap != null && parentMap != this)
{
parentMap.ViewportChanged += OnViewportChanged;
OnViewportChanged();
}
}
protected virtual void OnViewportChanged()
{
foreach (UIElement element in Children)
@ -112,8 +112,6 @@ namespace MapControl
if (element != null)
{
var mapElement = element as IMapElement;
var parentMap = mapElement != null ? mapElement.ParentMap : GetParentMap(element);
var location = e.NewValue as Location;
if (location == null)
@ -125,7 +123,7 @@ namespace MapControl
ArrangeElementWithLocation(element); // arrange element when Location was null before
}
SetViewportPosition(element, parentMap, location);
SetViewportPosition(element, GetParentMap(element), location);
}
}
@ -139,9 +137,7 @@ namespace MapControl
viewportPosition = parentMap.ViewportTransform.Transform(mapPosition);
var useLayoutRounding = element.GetValue(FrameworkElement.UseLayoutRoundingProperty);
if (useLayoutRounding != null && (bool)useLayoutRounding)
if ((bool)element.GetValue(FrameworkElement.UseLayoutRoundingProperty))
{
viewportPosition.X = Math.Round(viewportPosition.X);
viewportPosition.Y = Math.Round(viewportPosition.Y);

View file

@ -2,7 +2,7 @@
// © 2015 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.Foundation;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Shapes;
@ -30,7 +30,7 @@ namespace MapControl
Stretch = Stretch.None;
}
// Work-around for missing PropertyChangedCallback for the Data property.
// Workaround for missing PropertyChangedCallback for the Data property.
if (data != Data)
{
data = Data;

View file

@ -2,7 +2,7 @@
// © 2015 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.UI.Xaml.Media;
#else
using System.Windows.Media;

View file

@ -3,7 +3,7 @@
// Licensed under the Microsoft Public License (Ms-PL)
using System.Linq;
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
#else

View file

@ -4,7 +4,7 @@
using System.Collections.Generic;
using System.Collections.Specialized;
#if WINDOWS_RUNTIME
#if NETFX_CORE
using System.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
@ -21,7 +21,7 @@ namespace MapControl
/// </summary>
public partial class MapPolyline : MapPath
{
#if WINDOWS_RUNTIME
#if NETFX_CORE
// Binding fails on Windows Phone when property type is IEnumerable<Location>
public static readonly DependencyProperty LocationsProperty = DependencyProperty.Register(
"Locations", typeof(IEnumerable), typeof(MapPolyline),
@ -38,7 +38,7 @@ namespace MapControl
/// <summary>
/// Gets or sets the locations that define the polyline points.
/// </summary>
#if !WINDOWS_RUNTIME
#if !NETFX_CORE
[TypeConverter(typeof(LocationCollectionConverter))]
#endif
public IEnumerable<Location> Locations

View file

@ -2,7 +2,7 @@
// © 2015 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
@ -94,7 +94,7 @@ namespace MapControl
{
var rect = new Rect(ParentMap.MapTransform.Transform(new Location(South, West)),
ParentMap.MapTransform.Transform(new Location(North, East)));
var transform = ParentMap.ViewportTransform;
Transform transform = ParentMap.ViewportTransform;
ScaleRect(ref rect, ref transform);
@ -109,6 +109,6 @@ namespace MapControl
}
}
static partial void ScaleRect(ref Rect rect, ref Transform transform);
static partial void ScaleRect(ref Rect rect, ref Transform transform); // WPF only
}
}

View file

@ -2,7 +2,7 @@
// © 2015 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.Foundation;
#else
using System.Windows;

View file

@ -3,7 +3,7 @@
// Licensed under the Microsoft Public License (Ms-PL)
using System;
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.UI.Xaml.Media;
#else
using System.Windows.Media;

View file

@ -3,7 +3,7 @@
// Licensed under the Microsoft Public License (Ms-PL)
using System;
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.Foundation;
#else
using System.Windows;

View file

@ -2,7 +2,7 @@
// © 2015 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

View file

@ -17,8 +17,8 @@ using System.Windows;
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("© 2015 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.4.12")]
[assembly: AssemblyFileVersion("2.4.12")]
[assembly: AssemblyVersion("2.5.0")]
[assembly: AssemblyFileVersion("2.5.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]

View file

@ -2,7 +2,7 @@
// © 2015 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.UI.Xaml.Controls;
#else
using System.Windows.Controls;

View file

@ -3,7 +3,7 @@
// Licensed under the Microsoft Public License (Ms-PL)
using System;
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;

View file

@ -3,7 +3,7 @@
// Licensed under the Microsoft Public License (Ms-PL)
using System;
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.UI.Xaml.Controls;
#else
using System.Windows.Controls;

View file

@ -2,6 +2,13 @@
// © 2015 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
#if NETFX_CORE
using Windows.UI.Xaml.Media;
#else
using System.Windows.Media;
#endif
namespace MapControl
{
public partial class TileLayer
@ -9,7 +16,31 @@ namespace MapControl
partial void Initialize()
{
IsHitTestVisible = false;
MapPanel.AddParentMapHandlers(this);
}
private Matrix GetTileIndexMatrix(int zoomLevel)
{
var scale = (double)(1 << zoomLevel) / 360d;
return parentMap.ViewportTransform.Matrix
.Invert() // view to map coordinates
.Translate(180d, -180d)
.Scale(scale, -scale); // map coordinates to tile indices
}
private void SetRenderTransform()
{
var scale = Math.Pow(2d, parentMap.ZoomLevel - TileZoomLevel);
var offsetX = parentMap.ViewportOrigin.X - (180d + parentMap.MapOrigin.X) * parentMap.ViewportScale;
var offsetY = parentMap.ViewportOrigin.Y - (180d - parentMap.MapOrigin.Y) * parentMap.ViewportScale;
((MatrixTransform)RenderTransform).Matrix =
new Matrix(1d, 0d, 0d, 1d, TileRect.X * TileSource.TileSize, TileRect.Y * TileSource.TileSize)
.Scale(scale, scale)
.Translate(offsetX, offsetY)
.RotateAt(parentMap.Heading, parentMap.ViewportOrigin.X, parentMap.ViewportOrigin.Y); ;
}
}
}

View file

@ -2,7 +2,9 @@
// © 2015 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Windows;
using System.Windows.Media;
namespace MapControl
{
@ -13,5 +15,31 @@ namespace MapControl
UIElement.IsHitTestVisibleProperty.OverrideMetadata(
typeof(TileLayer), new FrameworkPropertyMetadata(false));
}
private Matrix GetTileIndexMatrix(int zoomLevel)
{
var scale = (double)(1 << zoomLevel) / 360d;
var transform = parentMap.ViewportTransform.Matrix;
transform.Invert(); // view to map coordinates
transform.Translate(180d, -180d);
transform.Scale(scale, -scale); // map coordinates to tile indices
return transform;
}
private void SetRenderTransform()
{
var scale = Math.Pow(2d, parentMap.ZoomLevel - TileZoomLevel);
var offsetX = parentMap.ViewportOrigin.X - (180d + parentMap.MapOrigin.X) * parentMap.ViewportScale;
var offsetY = parentMap.ViewportOrigin.Y - (180d - parentMap.MapOrigin.Y) * parentMap.ViewportScale;
var transform = new Matrix(1d, 0d, 0d, 1d, TileRect.X * TileSource.TileSize, TileRect.Y * TileSource.TileSize);
transform.Scale(scale, scale);
transform.Translate(offsetX, offsetY);
transform.RotateAt(parentMap.Heading, parentMap.ViewportOrigin.X, parentMap.ViewportOrigin.Y);
((MatrixTransform)RenderTransform).Matrix = transform;
}
}
}

View file

@ -5,7 +5,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
#if WINDOWS_RUNTIME
#if NETFX_CORE
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Markup;
@ -14,6 +14,8 @@ using Windows.UI.Xaml.Media;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Threading;
using System.Diagnostics;
#endif
namespace MapControl
@ -21,14 +23,14 @@ namespace MapControl
/// <summary>
/// Fills a rectangular area with map tiles from a TileSource.
/// </summary>
#if WINDOWS_RUNTIME
#if NETFX_CORE
[ContentProperty(Name = "TileSource")]
#else
[ContentProperty("TileSource")]
#endif
public partial class TileLayer : PanelBase, IMapElement
{
public static TileLayer Default
public static TileLayer OpenStreetMapTileLayer
{
get
{
@ -54,6 +56,10 @@ namespace MapControl
public static readonly DependencyProperty LogoImageProperty = DependencyProperty.Register(
"LogoImage", typeof(ImageSource), typeof(TileLayer), new PropertyMetadata(null));
public static readonly DependencyProperty ZoomLevelOffsetProperty = DependencyProperty.Register(
"ZoomLevelOffset", typeof(double), typeof(TileLayer),
new PropertyMetadata(0d, (o, e) => ((TileLayer)o).UpdateTileRect()));
public static readonly DependencyProperty MinZoomLevelProperty = DependencyProperty.Register(
"MinZoomLevel", typeof(int), typeof(TileLayer), new PropertyMetadata(0));
@ -63,15 +69,22 @@ namespace MapControl
public static readonly DependencyProperty MaxParallelDownloadsProperty = DependencyProperty.Register(
"MaxParallelDownloads", typeof(int), typeof(TileLayer), new PropertyMetadata(4));
public static readonly DependencyProperty UpdateIntervalProperty = DependencyProperty.Register(
"UpdateInterval", typeof(TimeSpan), typeof(TileLayer),
new PropertyMetadata(TimeSpan.FromSeconds(0.5), (o, e) => ((TileLayer)o).updateTimer.Interval = (TimeSpan)e.NewValue));
public static readonly DependencyProperty UpdateWhileViewportChangingProperty = DependencyProperty.Register(
"UpdateWhileViewportChanging", typeof(bool), typeof(TileLayer), new PropertyMetadata(true));
public static readonly DependencyProperty ForegroundProperty = DependencyProperty.Register(
"Foreground", typeof(Brush), typeof(TileLayer), new PropertyMetadata(null));
public static readonly new DependencyProperty BackgroundProperty = DependencyProperty.Register(
"Background", typeof(Brush), typeof(TileLayer), new PropertyMetadata(null));
private readonly ITileImageLoader tileImageLoader;
private List<Tile> tiles = new List<Tile>();
private readonly DispatcherTimer updateTimer;
private MapBase parentMap;
private double mapOriginX;
public TileLayer()
: this(new TileImageLoader())
@ -80,11 +93,23 @@ namespace MapControl
public TileLayer(ITileImageLoader tileImageLoader)
{
this.tileImageLoader = tileImageLoader;
Initialize();
RenderTransform = new MatrixTransform();
TileImageLoader = tileImageLoader;
Tiles = new List<Tile>();
TileZoomLevel = -1;
updateTimer = new DispatcherTimer { Interval = UpdateInterval };
updateTimer.Tick += (s, e) => UpdateTileRect();
}
partial void Initialize();
partial void Initialize(); // Windows Runtime and Silverlight only
public ITileImageLoader TileImageLoader { get; private set; }
public ICollection<Tile> Tiles { get; private set; }
public Int32Rect TileRect { get; private set; }
public int TileZoomLevel { get; private set; }
/// <summary>
/// Provides map tile URIs or images.
@ -122,6 +147,15 @@ namespace MapControl
set { SetValue(LogoImageProperty, value); }
}
/// <summary>
/// Adds an offset to the Map's ZoomLevel for a relative scale between the Map and the TileLayer.
/// </summary>
public double ZoomLevelOffset
{
get { return (double)GetValue(ZoomLevelOffsetProperty); }
set { SetValue(ZoomLevelOffsetProperty, value); }
}
/// <summary>
/// Minimum zoom level supported by the TileLayer.
/// </summary>
@ -149,6 +183,24 @@ namespace MapControl
set { SetValue(MaxParallelDownloadsProperty, 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 updates while the viewport is still changing.
/// </summary>
public bool UpdateWhileViewportChanging
{
get { return (bool)GetValue(UpdateWhileViewportChangingProperty); }
set { SetValue(UpdateWhileViewportChangingProperty, value); }
}
/// <summary>
/// Optional foreground brush. Sets MapBase.Foreground, if not null.
/// </summary>
@ -175,63 +227,110 @@ namespace MapControl
{
if (parentMap != null)
{
parentMap.TileGridChanged -= UpdateTiles;
ClearValue(RenderTransformProperty);
parentMap.ViewportChanged -= ViewportChanged;
TileZoomLevel = -1;
UpdateTiles(true);
}
parentMap = value;
if (parentMap != null)
{
parentMap.TileGridChanged += UpdateTiles;
RenderTransform = parentMap.TileLayerTransform;
parentMap.ViewportChanged += ViewportChanged;
ViewportChanged(this, EventArgs.Empty);
}
UpdateTiles();
}
}
protected virtual void UpdateTiles(bool clearTiles = false)
private void ViewportChanged(object sender, EventArgs e)
{
if (tiles.Count > 0)
if (TileZoomLevel < 0 || Math.Abs(parentMap.MapOrigin.X - mapOriginX) > 180d)
{
tileImageLoader.CancelLoadTiles(this);
// immediately handle map origin leap when map center moves across 180° longitude
UpdateTileRect();
}
else
{
SetRenderTransform();
if (!UpdateWhileViewportChanging)
{
updateTimer.Stop();
}
updateTimer.Start();
}
mapOriginX = parentMap.MapOrigin.X;
}
protected void UpdateTileRect()
{
updateTimer.Stop();
if (parentMap != null)
{
var zoomLevel = (int)Math.Round(parentMap.ZoomLevel + ZoomLevelOffset);
var transform = GetTileIndexMatrix(zoomLevel);
// tile indices of visible rectangle
var p1 = transform.Transform(new Point(0d, 0d));
var p2 = transform.Transform(new Point(parentMap.RenderSize.Width, 0d));
var p3 = transform.Transform(new Point(0d, parentMap.RenderSize.Height));
var p4 = transform.Transform(new Point(parentMap.RenderSize.Width, parentMap.RenderSize.Height));
// index ranges of visible tiles
var x1 = (int)Math.Floor(Math.Min(p1.X, Math.Min(p2.X, Math.Min(p3.X, p4.X))));
var y1 = (int)Math.Floor(Math.Min(p1.Y, Math.Min(p2.Y, Math.Min(p3.Y, p4.Y))));
var x2 = (int)Math.Floor(Math.Max(p1.X, Math.Max(p2.X, Math.Max(p3.X, p4.X))));
var y2 = (int)Math.Floor(Math.Max(p1.Y, Math.Max(p2.Y, Math.Max(p3.Y, p4.Y))));
var rect = new Int32Rect(x1, y1, x2 - x1 + 1, y2 - y1 + 1);
if (TileZoomLevel != zoomLevel || TileRect != rect)
{
TileZoomLevel = zoomLevel;
TileRect = rect;
SetRenderTransform();
UpdateTiles(false);
}
}
}
protected virtual void UpdateTiles(bool clearTiles)
{
if (Tiles.Count > 0)
{
TileImageLoader.CancelLoadTiles(this);
}
if (clearTiles)
{
tiles.Clear();
Tiles.Clear();
}
SelectTiles();
Children.Clear();
if (tiles.Count > 0)
if (Tiles.Count > 0)
{
foreach (var tile in tiles)
foreach (var tile in Tiles)
{
Children.Add(tile.Image);
}
tileImageLoader.BeginLoadTiles(this, tiles.Where(t => t.Pending));
TileImageLoader.BeginLoadTiles(this, Tiles.Where(t => t.Pending).OrderByDescending(t => t.ZoomLevel));
}
}
private void UpdateTiles(object sender, EventArgs e)
{
UpdateTiles();
}
private void SelectTiles()
protected void SelectTiles()
{
var newTiles = new List<Tile>();
if (parentMap != null && TileSource != null)
if (TileZoomLevel >= 0 && parentMap != null && TileSource != null)
{
var grid = parentMap.TileGrid;
var zoomLevel = parentMap.TileZoomLevel;
var maxZoomLevel = Math.Min(zoomLevel, MaxZoomLevel);
var maxZoomLevel = Math.Min(TileZoomLevel, MaxZoomLevel);
var minZoomLevel = MinZoomLevel;
if (minZoomLevel < maxZoomLevel && this != parentMap.TileLayers.FirstOrDefault())
@ -242,23 +341,23 @@ namespace MapControl
for (var z = minZoomLevel; z <= maxZoomLevel; z++)
{
var tileSize = 1 << (zoomLevel - z);
var x1 = (int)Math.Floor((double)grid.X / tileSize); // may be negative
var x2 = (grid.X + grid.Width - 1) / tileSize;
var y1 = Math.Max(grid.Y / tileSize, 0);
var y2 = Math.Min((grid.Y + grid.Height - 1) / tileSize, (1 << z) - 1);
var tileSize = 1 << (TileZoomLevel - z);
var x1 = (int)Math.Floor((double)TileRect.X / tileSize); // may be negative
var x2 = (TileRect.X + TileRect.Width - 1) / tileSize;
var y1 = Math.Max(TileRect.Y / tileSize, 0);
var y2 = Math.Min((TileRect.Y + TileRect.Height - 1) / tileSize, (1 << z) - 1);
for (var y = y1; y <= y2; y++)
{
for (var x = x1; x <= x2; x++)
{
var tile = tiles.FirstOrDefault(t => t.ZoomLevel == z && t.X == x && t.Y == y);
var tile = Tiles.FirstOrDefault(t => t.ZoomLevel == z && t.X == x && t.Y == y);
if (tile == null)
{
tile = new Tile(z, x, y);
var equivalentTile = tiles.FirstOrDefault(
var equivalentTile = Tiles.FirstOrDefault(
t => t.ZoomLevel == z && t.XIndex == tile.XIndex && t.Y == y && t.Image.Source != null);
if (equivalentTile != null)
@ -274,18 +373,18 @@ namespace MapControl
}
}
tiles = newTiles;
Tiles = newTiles;
}
protected override Size ArrangeOverride(Size finalSize)
{
if (parentMap != null)
if (TileZoomLevel >= 0)
{
foreach (var tile in tiles)
foreach (var tile in Tiles)
{
var tileSize = (double)(256 << (parentMap.TileZoomLevel - tile.ZoomLevel));
var x = tileSize * tile.X - 256 * parentMap.TileGrid.X;
var y = tileSize * tile.Y - 256 * parentMap.TileGrid.Y;
var tileSize = (double)(256 << (TileZoomLevel - tile.ZoomLevel));
var x = tileSize * tile.X - 256 * TileRect.X;
var y = tileSize * tile.Y - 256 * TileRect.Y;
tile.Image.Width = tileSize;
tile.Image.Height = tileSize;

View file

@ -12,7 +12,7 @@ namespace MapControl
/// </summary>
public partial class TileSource
{
public const int TileSize = 256;
public const double TileSize = 256;
public const double MetersPerDegree = 6378137d * Math.PI / 180d; // WGS 84 semi major axis
private Func<int, int, int, Uri> getUri;
@ -84,10 +84,10 @@ namespace MapControl
private Uri GetBasicUri(int x, int y, int zoomLevel)
{
return new Uri(uriFormat.
Replace("{x}", x.ToString()).
Replace("{y}", y.ToString()).
Replace("{z}", zoomLevel.ToString()),
return new Uri(uriFormat
.Replace("{x}", x.ToString())
.Replace("{y}", y.ToString())
.Replace("{z}", zoomLevel.ToString()),
UriKind.RelativeOrAbsolute);
}
@ -95,11 +95,11 @@ namespace MapControl
{
var hostIndex = (x + y) % 3;
return new Uri(uriFormat.
Replace("{c}", "abc".Substring(hostIndex, 1)).
Replace("{x}", x.ToString()).
Replace("{y}", y.ToString()).
Replace("{z}", zoomLevel.ToString()),
return new Uri(uriFormat
.Replace("{c}", "abc".Substring(hostIndex, 1))
.Replace("{x}", x.ToString())
.Replace("{y}", y.ToString())
.Replace("{z}", zoomLevel.ToString()),
UriKind.RelativeOrAbsolute);
}
@ -107,11 +107,11 @@ namespace MapControl
{
var hostIndex = (x + y) % 4;
return new Uri(uriFormat.
Replace("{i}", hostIndex.ToString()).
Replace("{x}", x.ToString()).
Replace("{y}", y.ToString()).
Replace("{z}", zoomLevel.ToString()),
return new Uri(uriFormat
.Replace("{i}", hostIndex.ToString())
.Replace("{x}", x.ToString())
.Replace("{y}", y.ToString())
.Replace("{z}", zoomLevel.ToString()),
UriKind.RelativeOrAbsolute);
}
@ -119,11 +119,11 @@ namespace MapControl
{
var hostIndex = (x + y) % 4 + 1;
return new Uri(uriFormat.
Replace("{n}", hostIndex.ToString()).
Replace("{x}", x.ToString()).
Replace("{y}", y.ToString()).
Replace("{z}", zoomLevel.ToString()),
return new Uri(uriFormat
.Replace("{n}", hostIndex.ToString())
.Replace("{x}", x.ToString())
.Replace("{y}", y.ToString())
.Replace("{z}", zoomLevel.ToString()),
UriKind.RelativeOrAbsolute);
}
@ -131,10 +131,10 @@ namespace MapControl
{
y = (1 << zoomLevel) - 1 - y;
return new Uri(uriFormat.
Replace("{x}", x.ToString()).
Replace("{v}", y.ToString()).
Replace("{z}", zoomLevel.ToString()),
return new Uri(uriFormat
.Replace("{x}", x.ToString())
.Replace("{v}", y.ToString())
.Replace("{z}", zoomLevel.ToString()),
UriKind.RelativeOrAbsolute);
}
@ -152,9 +152,9 @@ namespace MapControl
quadkey[z] = (char)('0' + 2 * (y % 2) + (x % 2));
}
return new Uri(uriFormat.
Replace("{i}", new string(quadkey[zoomLevel - 1], 1)).
Replace("{q}", new string(quadkey)),
return new Uri(uriFormat
.Replace("{i}", new string(quadkey[zoomLevel - 1], 1))
.Replace("{q}", new string(quadkey)),
UriKind.RelativeOrAbsolute);
}
@ -165,12 +165,15 @@ namespace MapControl
var east = MetersPerDegree * ((double)(x + 1) * tileSize - 180d);
var south = MetersPerDegree * (180d - (double)(y + 1) * tileSize);
var north = MetersPerDegree * (180d - (double)y * tileSize);
var imageSize = TileSize.ToString("F0");
return new Uri(uriFormat.
Replace("{W}", west.ToString(CultureInfo.InvariantCulture)).
Replace("{S}", south.ToString(CultureInfo.InvariantCulture)).
Replace("{E}", east.ToString(CultureInfo.InvariantCulture)).
Replace("{N}", north.ToString(CultureInfo.InvariantCulture)));
return new Uri(uriFormat
.Replace("{W}", west.ToString(CultureInfo.InvariantCulture))
.Replace("{S}", south.ToString(CultureInfo.InvariantCulture))
.Replace("{E}", east.ToString(CultureInfo.InvariantCulture))
.Replace("{N}", north.ToString(CultureInfo.InvariantCulture))
.Replace("{X}", imageSize)
.Replace("{Y}", imageSize));
}
private Uri GetLatLonBoundingBoxUri(int x, int y, int zoomLevel)
@ -180,12 +183,15 @@ namespace MapControl
var east = (double)(x + 1) * tileSize - 180d;
var south = MercatorTransform.YToLatitude(180d - (double)(y + 1) * tileSize);
var north = MercatorTransform.YToLatitude(180d - (double)y * tileSize);
var imageSize = TileSize.ToString("F0");
return new Uri(uriFormat.
Replace("{w}", west.ToString(CultureInfo.InvariantCulture)).
Replace("{s}", south.ToString(CultureInfo.InvariantCulture)).
Replace("{e}", east.ToString(CultureInfo.InvariantCulture)).
Replace("{n}", north.ToString(CultureInfo.InvariantCulture)));
return new Uri(uriFormat
.Replace("{w}", west.ToString(CultureInfo.InvariantCulture))
.Replace("{s}", south.ToString(CultureInfo.InvariantCulture))
.Replace("{e}", east.ToString(CultureInfo.InvariantCulture))
.Replace("{n}", north.ToString(CultureInfo.InvariantCulture))
.Replace("{X}", imageSize)
.Replace("{Y}", imageSize));
}
}
}

View file

@ -23,7 +23,7 @@
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\bin\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG;WINDOWS_RUNTIME</DefineConstants>
<DefineConstants>TRACE;DEBUG;NETFX_CORE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
@ -31,7 +31,7 @@
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\bin\Release\</OutputPath>
<DefineConstants>TRACE;WINDOWS_RUNTIME</DefineConstants>
<DefineConstants>TRACE;NETFX_CORE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
@ -169,17 +169,16 @@
<Link>MapControl.snk</Link>
</None>
</ItemGroup>
<ItemGroup>
<TargetPlatform Include="Windows, Version=8.1" />
<TargetPlatform Include="WindowsPhoneApp, Version=8.1" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Page Include="Themes\Generic.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<TargetPlatform Include="Windows, Version=8.1" />
<TargetPlatform Include="WindowsPhoneApp, Version=8.1" />
</ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '12.0' ">
<VisualStudioVersion>12.0</VisualStudioVersion>
</PropertyGroup>
@ -198,8 +197,7 @@
</PreBuildEvent>
</PropertyGroup>
<PropertyGroup>
<PostBuildEvent>
</PostBuildEvent>
<PostBuildEvent>copy "$(ProjectDir)MapControl.WinRT.xr.xml" "$(TargetDir)"</PostBuildEvent>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View file

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<Roots>
<Roots.RootTypes>
<RootType FullName="Windows.UI.Xaml.ResourceDictionary" />
<RootType FullName="MapControl.MapItemsControl" />
<RootType FullName="Windows.UI.Xaml.Controls.Control">
<RootProperty Name="Template" />
<RootProperty Name="HorizontalContentAlignment" />
<RootProperty Name="VerticalContentAlignment" />
<RootProperty Name="Padding" />
<RootProperty Name="Foreground" />
<RootProperty Name="Background" />
</RootType>
<RootType FullName="Windows.UI.Xaml.Controls.ItemsControl">
<RootProperty Name="ItemsPanel" />
</RootType>
<RootType FullName="MapControl.MapItem" />
<RootType FullName="Windows.UI.Xaml.FrameworkElement">
<RootProperty Name="HorizontalAlignment" />
<RootProperty Name="VerticalAlignment" />
</RootType>
<RootType FullName="MapControl.Pushpin" />
<RootType FullName="Windows.UI.Xaml.Setter">
<RootProperty Name="Property" />
<RootProperty Name="Value" />
</RootType>
<RootType FullName="Windows.UI.Xaml.Controls.ControlTemplate">
<RootProperty Name="TargetType" />
<RootProperty Name="Template" />
</RootType>
<RootType FullName="Windows.UI.Xaml.Controls.Grid">
<RootProperty Name="RowDefinitions" />
<RootProperty Name="Children" />
<RootMethod Name="GetRow" />
<RootMethod Name="SetRow" />
</RootType>
<RootType FullName="Windows.UI.Xaml.Controls.RowDefinition">
<RootProperty Name="Height" />
</RootType>
<RootType FullName="Windows.UI.Xaml.Shapes.Rectangle">
<RootProperty Name="Fill" />
</RootType>
<RootType FullName="Windows.UI.Xaml.Shapes.Path">
<RootProperty Name="Fill" />
<RootProperty Name="Data" />
</RootType>
<RootType FullName="Windows.UI.Xaml.Controls.ContentPresenter">
<RootProperty Name="Content" />
<RootProperty Name="ContentTemplate" />
<RootProperty Name="Margin" />
<RootProperty Name="HorizontalAlignment" />
<RootProperty Name="VerticalAlignment" />
</RootType>
<RootType FullName="Microsoft.Xaml.Tools.DirectUI.ProxyTypes.TemplateBindingExtension" />
<RootType FullName="Windows.UI.Xaml.Controls.ItemsPanelTemplate">
<RootProperty Name="Template" />
</RootType>
<RootType FullName="MapControl.MapPanel" />
<RootType FullName="Windows.UI.Xaml.Controls.ItemsPresenter" />
</Roots.RootTypes>
</Roots>

View file

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

View file

@ -1,3 +1,8 @@
The Visual Studio Project for Windows Runtime resides in a separate folder
MapControl/WinRT, because it needs to have its own Themes/Generic.xaml file.
Output is generated to ../bin.
Output is generated to ../bin.
This folder also contains the file MapControl.WinRT.xr.xml, which overwrites
the generated file in the output folder. This is to remove the XML namespace
declaration on the <Roots> element, which prevents a .NET Native build of a
Universal Windows App that uses the MapControl.WinRT portable library.