Version 5.0: Reworked MapBase and MapPath

This commit is contained in:
ClemensF 2020-03-28 21:53:38 +01:00
parent 06fd31c17b
commit 49e15ce424
41 changed files with 466 additions and 1068 deletions

View file

@ -69,12 +69,12 @@ namespace MapControl
private DoubleAnimation zoomLevelAnimation;
private DoubleAnimation headingAnimation;
private Location transformCenter;
private Point viewportCenter;
private Point viewCenter;
private double centerLongitude;
private bool internalPropertyChange;
/// <summary>
/// Raised when the current viewport has changed.
/// Raised when the current map viewport has changed.
/// </summary>
public event EventHandler<ViewportChangedEventArgs> ViewportChanged;
@ -224,59 +224,56 @@ namespace MapControl
}
/// <summary>
/// Gets the transformation from cartesian map coordinates to viewport coordinates.
/// Gets the scaling factor from cartesian map coordinates to view coordinates,
/// i.e. pixels per meter, as a read-only dependency property.
/// </summary>
public double ViewScale
{
get { return (double)GetValue(ViewScaleProperty); }
private set { SetViewScale(value); }
}
/// <summary>
/// Gets the ViewTransform instance that is used to transform between cartesian map coordinates
/// and view coordinates.
/// </summary>
public ViewTransform ViewTransform { get; } = new ViewTransform();
/// <summary>
/// Gets the transformation from cartesian map coordinates to viewport coordinates as MatrixTransform.
/// Gets a MatrixTransform that can be used to transform from cartesian map coordinates
/// to view coordinates.
/// </summary>
public MatrixTransform ViewportTransform { get; } = new MatrixTransform();
public MatrixTransform MapToViewTransform { get; } = new MatrixTransform();
/// <summary>
/// Gets the scaling transformation from meters to viewport coordinates at the Center location.
/// Gets the horizontal and vertical scaling factors from cartesian map coordinates to view
/// coordinates at the specified location, i.e. pixels per meter.
/// </summary>
public ScaleTransform ScaleTransform { get; } = new ScaleTransform();
/// <summary>
/// Gets the transformation that rotates by the value of the Heading property.
/// </summary>
public RotateTransform RotateTransform { get; } = new RotateTransform();
/// <summary>
/// Gets the combination of ScaleTransform and RotateTransform
/// </summary>
public TransformGroup ScaleRotateTransform
public Vector GetScale(Location location)
{
get
{
var transform = new TransformGroup();
transform.Children.Add(ScaleTransform);
transform.Children.Add(RotateTransform);
return transform;
}
return ViewTransform.Scale * MapProjection.GetRelativeScale(location);
}
/// <summary>
/// Transforms a Location in geographic coordinates to a Point in viewport coordinates.
/// Transforms a Location in geographic coordinates to a Point in view coordinates.
/// </summary>
public Point LocationToViewportPoint(Location location)
public Point LocationToView(Location location)
{
return ViewTransform.MapToView(MapProjection.LocationToMap(location));
}
/// <summary>
/// Transforms a Point in viewport coordinates to a Location in geographic coordinates.
/// Transforms a Point in view coordinates to a Location in geographic coordinates.
/// </summary>
public Location ViewportPointToLocation(Point point)
public Location ViewToLocation(Point point)
{
return MapProjection.MapToLocation(ViewTransform.ViewToMap(point));
}
/// <summary>
/// Transforms a Rect in viewport coordinates to a BoundingBox in geographic coordinates.
/// Transforms a Rect in view coordinates to a BoundingBox in geographic coordinates.
/// </summary>
public BoundingBox ViewportRectToBoundingBox(Rect rect)
public BoundingBox ViewRectToBoundingBox(Rect rect)
{
var p1 = ViewTransform.ViewToMap(new Point(rect.X, rect.Y));
var p2 = ViewTransform.ViewToMap(new Point(rect.X, rect.Y + rect.Height));
@ -292,13 +289,13 @@ namespace MapControl
}
/// <summary>
/// Sets a temporary center point in viewport coordinates for scaling and rotation transformations.
/// Sets a temporary center point in view coordinates for scaling and rotation transformations.
/// This center point is automatically reset when the Center property is set by application code.
/// </summary>
public void SetTransformCenter(Point center)
{
transformCenter = ViewportPointToLocation(center);
viewportCenter = center;
transformCenter = ViewToLocation(center);
viewCenter = center;
}
/// <summary>
@ -307,11 +304,11 @@ namespace MapControl
public void ResetTransformCenter()
{
transformCenter = null;
viewportCenter = new Point(RenderSize.Width / 2d, RenderSize.Height / 2d);
viewCenter = new Point(RenderSize.Width / 2d, RenderSize.Height / 2d);
}
/// <summary>
/// Changes the Center property according to the specified translation in viewport coordinates.
/// Changes the Center property according to the specified translation in view coordinates.
/// </summary>
public void TranslateMap(Vector translation)
{
@ -323,21 +320,21 @@ namespace MapControl
if (translation.X != 0d || translation.Y != 0d)
{
Center = ViewportPointToLocation(viewportCenter - translation);
Center = ViewToLocation(viewCenter - translation);
}
}
/// <summary>
/// Changes the Center, Heading and ZoomLevel properties according to the specified
/// viewport coordinate translation, rotation and scale delta values. Rotation and scaling
/// is performed relative to the specified center point in viewport coordinates.
/// view coordinate translation, rotation and scale delta values. Rotation and scaling
/// is performed relative to the specified center point in view coordinates.
/// </summary>
public void TransformMap(Point center, Vector translation, double rotation, double scale)
{
if (rotation != 0d || scale != 1d)
{
transformCenter = ViewportPointToLocation(center);
viewportCenter = center + translation;
transformCenter = ViewToLocation(center);
viewCenter = center + translation;
if (rotation != 0d)
{
@ -363,7 +360,7 @@ namespace MapControl
/// <summary>
/// Sets the value of the TargetZoomLevel property while retaining the specified center point
/// in viewport coordinates.
/// in view coordinates.
/// </summary>
public void ZoomMap(Point center, double zoomLevel)
{
@ -378,16 +375,15 @@ namespace MapControl
/// <summary>
/// Sets the TargetZoomLevel and TargetCenter properties so that the specified bounding box
/// fits into the current viewport. The TargetHeading property is set to zero.
/// fits into the current view. The TargetHeading property is set to zero.
/// </summary>
public void ZoomToBounds(BoundingBox boundingBox)
{
var rect = MapProjection.BoundingBoxToRect(boundingBox);
var center = new Point(rect.X + rect.Width / 2d, rect.Y + rect.Height / 2d);
var scale = Math.Min(RenderSize.Width / rect.Width, RenderSize.Height / rect.Height)
* MapProjection.Wgs84MetersPerDegree * 360d / 256d;
var scale = Math.Min(RenderSize.Width / rect.Width, RenderSize.Height / rect.Height);
TargetZoomLevel = Math.Log(scale, 2d);
TargetZoomLevel = ViewTransform.ScaleToZoomLevel(scale);
TargetCenter = MapProjection.MapToLocation(center);
TargetHeading = 0d;
}
@ -703,16 +699,19 @@ namespace MapControl
private void UpdateTransform(bool resetTransformCenter = false, bool projectionChanged = false)
{
var projection = MapProjection;
var viewportScale = 256d * Math.Pow(2d, ZoomLevel) / (360d * MapProjection.Wgs84MetersPerDegree);
var center = transformCenter ?? Center;
projection.Center = ProjectionCenter ?? Center;
ViewTransform.SetTransform(projection.LocationToMap(center), viewportCenter, viewportScale, Heading);
var center = transformCenter ?? Center;
var mapCenter = projection.LocationToMap(center);
var viewScale = ViewTransform.ZoomLevelToScale(ZoomLevel);
ViewTransform.SetTransform(mapCenter, viewCenter, viewScale, Heading);
if (transformCenter != null)
{
center = ViewportPointToLocation(new Point(RenderSize.Width / 2d, RenderSize.Height / 2d));
center = ViewToLocation(new Point(RenderSize.Width / 2d, RenderSize.Height / 2d));
center.Longitude = Location.NormalizeLongitude(center.Longitude);
if (center.Latitude < -projection.MaxLatitude || center.Latitude > projection.MaxLatitude)
@ -733,18 +732,14 @@ namespace MapControl
ResetTransformCenter();
projection.Center = ProjectionCenter ?? center;
mapCenter = projection.LocationToMap(center);
ViewTransform.SetTransform(projection.LocationToMap(center), viewportCenter, viewportScale, Heading);
ViewTransform.SetTransform(mapCenter, viewCenter, viewScale, Heading);
}
}
ViewportTransform.Matrix = ViewTransform.MapToViewMatrix;
var scale = projection.GetRelativeScale(center);
ScaleTransform.ScaleX = scale.X * ViewTransform.Scale;
ScaleTransform.ScaleY = scale.Y * ViewTransform.Scale;
RotateTransform.Angle = ViewTransform.Rotation;
ViewScale = ViewTransform.Scale;
MapToViewTransform.Matrix = ViewTransform.MapToViewMatrix;
OnViewportChanged(new ViewportChangedEventArgs(projectionChanged, Center.Longitude - centerLongitude));

View file

@ -23,11 +23,14 @@ using System.Windows.Threading;
namespace MapControl
{
/// <summary>
/// Map image layer. Fills the entire viewport with a map image, e.g. provided by a Web Map Service.
/// Map image layer. Fills the viewport with a single map image, e.g. provided by a Web Map Service.
/// The image must be provided by the abstract GetImageAsync method.
/// </summary>
public abstract class MapImageLayer : MapPanel, IMapLayer
{
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register(
nameof(Description), typeof(string), typeof(MapImageLayer), new PropertyMetadata(null));
public static readonly DependencyProperty MinLatitudeProperty = DependencyProperty.Register(
nameof(MinLatitude), typeof(double), typeof(MapImageLayer), new PropertyMetadata(double.NaN));
@ -53,9 +56,6 @@ namespace MapControl
public static readonly DependencyProperty UpdateWhileViewportChangingProperty = DependencyProperty.Register(
nameof(UpdateWhileViewportChanging), typeof(bool), typeof(MapImageLayer), new PropertyMetadata(false));
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register(
nameof(Description), typeof(string), typeof(MapImageLayer), new PropertyMetadata(null));
public static readonly DependencyProperty MapBackgroundProperty = DependencyProperty.Register(
nameof(MapBackground), typeof(Brush), typeof(MapImageLayer), new PropertyMetadata(null));
@ -74,6 +74,16 @@ namespace MapControl
updateTimer.Tick += async (s, e) => await UpdateImageAsync();
}
/// <summary>
/// Description of the MapImageLayer.
/// Used to display copyright information on top of the map.
/// </summary>
public string Description
{
get { return (string)GetValue(DescriptionProperty); }
set { SetValue(DescriptionProperty, value); }
}
/// <summary>
/// Optional minimum latitude value. Default is NaN.
/// </summary>
@ -120,9 +130,9 @@ namespace MapControl
}
/// <summary>
/// Relative size of the map image in relation to the current viewport size.
/// Relative size of the map image in relation to the current view size.
/// Setting a value greater than one will let MapImageLayer request images that
/// are larger than the viewport, in order to support smooth panning.
/// are larger than the view, in order to support smooth panning.
/// </summary>
public double RelativeImageSize
{
@ -148,16 +158,6 @@ namespace MapControl
set { SetValue(UpdateWhileViewportChangingProperty, value); }
}
/// <summary>
/// Description of the MapImageLayer.
/// Used to display copyright information on top of the map.
/// </summary>
public string Description
{
get { return (string)GetValue(DescriptionProperty); }
set { SetValue(DescriptionProperty, value); }
}
/// <summary>
/// Optional foreground brush.
/// Sets MapBase.Foreground if not null and the MapImageLayer is the base map layer.
@ -258,7 +258,7 @@ namespace MapControl
var y = (ParentMap.RenderSize.Height - height) / 2d;
var rect = new Rect(x, y, width, height);
BoundingBox = ParentMap.ViewportRectToBoundingBox(rect);
BoundingBox = ParentMap.ViewRectToBoundingBox(rect);
if (BoundingBox != null)
{

View file

@ -74,7 +74,7 @@ namespace MapControl
{
SelectItems(item =>
{
var pos = MapPanel.GetViewportPosition(ContainerFromItem(item));
var pos = MapPanel.GetViewPosition(ContainerFromItem(item));
return pos.HasValue && predicate(pos.Value);
});
}
@ -116,10 +116,10 @@ namespace MapControl
}
else if (shiftKey && SelectedItem != null)
{
// Extended with Shift -> select items in viewport rectangle
// Extended with Shift -> select items in view rectangle
var p1 = MapPanel.GetViewportPosition(ContainerFromItem(SelectedItem));
var p2 = MapPanel.GetViewportPosition(mapItem);
var p1 = MapPanel.GetViewPosition(ContainerFromItem(SelectedItem));
var p2 = MapPanel.GetViewPosition(mapItem);
if (p1.HasValue && p2.HasValue)
{

View file

@ -58,9 +58,9 @@ namespace MapControl
element.SetValue(BoundingBoxProperty, value);
}
public static Point? GetViewportPosition(FrameworkElement element)
public static Point? GetViewPosition(FrameworkElement element)
{
return (Point?)element.GetValue(ViewportPositionProperty);
return (Point?)element.GetValue(ViewPositionProperty);
}
public MapBase ParentMap
@ -118,9 +118,9 @@ namespace MapControl
if (location != null)
{
var viewportPosition = ArrangeElement(element, location);
var viewPosition = ArrangeElement(element, location);
SetViewportPosition(element, viewportPosition);
SetViewPosition(element, viewPosition);
}
else
{
@ -135,7 +135,7 @@ namespace MapControl
ArrangeElement(element, finalSize);
}
SetViewportPosition(element, null);
SetViewPosition(element, null);
}
}
}
@ -145,13 +145,13 @@ namespace MapControl
private Point ArrangeElement(FrameworkElement element, Location location)
{
var pos = parentMap.LocationToViewportPoint(location);
var pos = parentMap.LocationToView(location);
if (parentMap.MapProjection.IsNormalCylindrical &&
(pos.X < 0d || pos.X > parentMap.RenderSize.Width ||
pos.Y < 0d || pos.Y > parentMap.RenderSize.Height))
{
pos = parentMap.LocationToViewportPoint(new Location(
pos = parentMap.LocationToView(new Location(
location.Latitude,
Location.NearestLongitude(location.Longitude, parentMap.Center.Longitude)));
}
@ -210,7 +210,7 @@ namespace MapControl
var location = projection.MapToLocation(center);
location.Longitude = Location.NearestLongitude(location.Longitude, parentMap.Center.Longitude);
pos = parentMap.LocationToViewportPoint(location);
pos = parentMap.LocationToView(location);
}
rect.Width *= parentMap.ViewTransform.Scale;
@ -229,14 +229,15 @@ namespace MapControl
element.Arrange(rect);
var rotateTransform = element.RenderTransform as RotateTransform;
var rotation = parentMap.ViewTransform.Rotation;
if (rotateTransform != null)
{
rotateTransform.Angle = parentMap.Heading;
rotateTransform.Angle = rotation;
}
else if (parentMap.Heading != 0d)
else if (rotation != 0d)
{
rotateTransform = new RotateTransform { Angle = parentMap.Heading };
rotateTransform = new RotateTransform { Angle = rotation };
element.RenderTransform = rotateTransform;
element.RenderTransformOrigin = new Point(0.5, 0.5);
}

View file

@ -0,0 +1,142 @@
// 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 Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
#else
using System.Windows;
using System.Windows.Media;
#endif
namespace MapControl
{
/// <summary>
/// A path element with a Data property that holds a Geometry in cartesian map coordinates
/// or view coordinates. Cartesian coordinates can optionally be relative to an origin Location.
/// </summary>
public partial class MapPath : IMapElement
{
public static readonly DependencyProperty LocationProperty = DependencyProperty.Register(
nameof(Location), typeof(Location), typeof(MapPath),
new PropertyMetadata(null, (o, e) => ((MapPath)o).LocationOrViewportChanged()));
private MapBase parentMap;
private MatrixTransform dataTransform;
private double longitudeOffset;
public MapPath()
{
MapPanel.InitMapElement(this);
}
/// <summary>
/// Gets or sets a Location that is used as
/// - either the origin point of a geometry specified in cartesian map units (meters)
/// - or as an optional value to constrain the view position of MapPaths with multiple
/// Locations (like MapPolyline or MapPolygon) to the visible map viewport, as done
/// for elements where the MapPanel.Location property is set.
/// </summary>
public Location Location
{
get { return (Location)GetValue(LocationProperty); }
set { SetValue(LocationProperty, value); }
}
public MapBase ParentMap
{
get { return parentMap; }
set
{
if (parentMap != null)
{
parentMap.ViewportChanged -= OnViewportChanged;
}
parentMap = value;
if (parentMap != null)
{
parentMap.ViewportChanged += OnViewportChanged;
}
LocationOrViewportChanged();
}
}
private void OnViewportChanged(object sender, ViewportChangedEventArgs e)
{
LocationOrViewportChanged();
}
private void LocationOrViewportChanged()
{
longitudeOffset = 0d;
if (parentMap != null && parentMap.MapProjection.IsNormalCylindrical && Location != null)
{
var viewPos = LocationToView(Location);
if (viewPos.X < 0d || viewPos.X > parentMap.RenderSize.Width ||
viewPos.Y < 0d || viewPos.Y > parentMap.RenderSize.Height)
{
longitudeOffset = Location.NearestLongitude(Location.Longitude, parentMap.Center.Longitude) - Location.Longitude;
}
}
UpdateData();
}
protected virtual void UpdateData()
{
if (parentMap != null && Data != null)
{
if (dataTransform == null)
{
Data.Transform = dataTransform = new MatrixTransform();
}
if (Location != null)
{
var viewPos = LocationToView(Location);
var scale = parentMap.GetScale(Location);
var matrix = new Matrix(scale.X, 0, 0, scale.Y, 0, 0);
matrix.Rotate(parentMap.Heading);
matrix.Translate(viewPos.X, viewPos.Y);
dataTransform.Matrix = matrix;
}
else
{
dataTransform.Matrix = parentMap.ViewTransform.MapToViewMatrix;
}
}
}
protected Point LocationToMap(Location location)
{
if (longitudeOffset != 0d)
{
location = new Location(location.Latitude, location.Longitude + longitudeOffset);
}
var point = parentMap.MapProjection.LocationToMap(location);
if (point.Y == double.PositiveInfinity)
{
point.Y = 1e9;
}
else if (point.X == double.NegativeInfinity)
{
point.Y = -1e9;
}
return point;
}
protected Point LocationToView(Location location)
{
return parentMap.ViewTransform.MapToView(LocationToMap(location));
}
}
}

View file

@ -17,7 +17,7 @@ namespace MapControl
/// <summary>
/// A polygon defined by a collection of Locations.
/// </summary>
public class MapPolygon : MapShape
public class MapPolygon : MapPath
{
public static readonly DependencyProperty LocationsProperty = DependencyProperty.Register(
nameof(Locations), typeof(IEnumerable<Location>), typeof(MapPolygon),
@ -35,6 +35,11 @@ namespace MapControl
set { SetValue(LocationsProperty, value); }
}
public MapPolygon()
{
Data = new PathGeometry();
}
protected override void UpdateData()
{
var figures = ((PathGeometry)Data).Figures;

View file

@ -17,7 +17,7 @@ namespace MapControl
/// <summary>
/// A polyline defined by a collection of Locations.
/// </summary>
public class MapPolyline : MapShape
public class MapPolyline : MapPath
{
public static readonly DependencyProperty LocationsProperty = DependencyProperty.Register(
nameof(Locations), typeof(IEnumerable<Location>), typeof(MapPolyline),
@ -35,6 +35,11 @@ namespace MapControl
set { SetValue(LocationsProperty, value); }
}
public MapPolyline()
{
Data = new PathGeometry();
}
protected override void UpdateData()
{
var figures = ((PathGeometry)Data).Figures;

View file

@ -59,9 +59,10 @@ namespace MapControl
{
var size = new Size();
if (ParentMap != null && ParentMap.ScaleTransform.ScaleX > 0d)
if (ParentMap != null)
{
var length = MinWidth / ParentMap.ScaleTransform.ScaleX;
var scale = ParentMap.GetScale(ParentMap.Center).X;
var length = MinWidth / scale;
var magnitude = Math.Pow(10d, Math.Floor(Math.Log10(length)));
if (length / magnitude < 2d)
@ -77,7 +78,7 @@ namespace MapControl
length = 10d * magnitude;
}
size.Width = length * ParentMap.ScaleTransform.ScaleX + StrokeThickness + Padding.Left + Padding.Right;
size.Width = length * scale + StrokeThickness + Padding.Left + Padding.Right;
size.Height = 1.25 * FontSize + StrokeThickness + Padding.Top + Padding.Bottom;
var x1 = Padding.Left + StrokeThickness / 2d;

View file

@ -1,125 +0,0 @@
// 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 Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
#else
using System.Windows;
using System.Windows.Media;
#endif
namespace MapControl
{
/// <summary>
/// Base class for MapPolyline and MapPolygon.
/// </summary>
public abstract partial class MapShape : IMapElement
{
public static readonly DependencyProperty LocationProperty = DependencyProperty.Register(
nameof(Location), typeof(Location), typeof(MapShape),
new PropertyMetadata(null, (o, e) => ((MapShape)o).LocationPropertyChanged()));
/// <summary>
/// Gets or sets an optional Location to constrain the viewport position to the visible
/// map viewport, as done for elements where the MapPanel.Location property is set.
/// </summary>
public Location Location
{
get { return (Location)GetValue(LocationProperty); }
set { SetValue(LocationProperty, value); }
}
private void LocationPropertyChanged()
{
if (parentMap != null)
{
UpdateData();
}
}
private MapBase parentMap;
public MapBase ParentMap
{
get { return parentMap; }
set
{
if (parentMap != null)
{
parentMap.ViewportChanged -= OnViewportChanged;
}
parentMap = value;
if (parentMap != null)
{
parentMap.ViewportChanged += OnViewportChanged;
}
UpdateData();
}
}
private void OnViewportChanged(object sender, ViewportChangedEventArgs e)
{
UpdateData();
}
protected abstract void UpdateData();
protected MapShape()
: this(new PathGeometry())
{
}
protected MapShape(Geometry data)
{
Data = data;
MapPanel.InitMapElement(this);
}
protected Point LocationToMap(Location location)
{
var point = parentMap.MapProjection.LocationToMap(location);
if (point.Y == double.PositiveInfinity)
{
point.Y = 1e9;
}
else if (point.X == double.NegativeInfinity)
{
point.Y = -1e9;
}
return point;
}
protected Point LocationToViewportPoint(Location location)
{
return parentMap.ViewTransform.MapToView(LocationToMap(location));
}
protected double GetLongitudeOffset()
{
var longitudeOffset = 0d;
if (parentMap.MapProjection.IsNormalCylindrical && Location != null)
{
var viewportPosition = LocationToViewportPoint(Location);
if (viewportPosition.X < 0d || viewportPosition.X > parentMap.RenderSize.Width ||
viewportPosition.Y < 0d || viewportPosition.Y > parentMap.RenderSize.Height)
{
var nearestLongitude = Location.NearestLongitude(Location.Longitude, parentMap.Center.Longitude);
longitudeOffset = nearestLongitude - Location.Longitude;
}
}
return longitudeOffset;
}
}
}

View file

@ -17,7 +17,7 @@ using System.Windows.Media;
namespace MapControl
{
/// <summary>
/// Fills the map viewport with map tiles from a TileSource.
/// Fills the viewport with map tiles from a TileSource.
/// </summary>
public class MapTileLayer : MapTileLayerBase
{
@ -26,11 +26,6 @@ namespace MapControl
public static readonly Point TileMatrixTopLeft = new Point(
-180d * MapProjection.Wgs84MetersPerDegree, 180d * MapProjection.Wgs84MetersPerDegree);
public static double TileMatrixScale(int zoomLevel)
{
return (TileSize << zoomLevel) / (360d * MapProjection.Wgs84MetersPerDegree);
}
/// <summary>
/// A default MapTileLayer using OpenStreetMap data.
/// </summary>
@ -117,18 +112,22 @@ namespace MapControl
//
var tileMatrixOrigin = new Point(TileSize * TileMatrix.XMin, TileSize * TileMatrix.YMin);
var tileMatrixScale = ViewTransform.ZoomLevelToScale(TileMatrix.ZoomLevel);
((MatrixTransform)RenderTransform).Matrix = ParentMap.ViewTransform.GetTileLayerTransform(
TileMatrixScale(TileMatrix.ZoomLevel), TileMatrixTopLeft, tileMatrixOrigin);
tileMatrixScale, TileMatrixTopLeft, tileMatrixOrigin);
}
private bool SetTileMatrix()
{
var tileMatrixZoomLevel = (int)Math.Floor(ParentMap.ZoomLevel + 0.001); // avoid rounding issues
// bounds in tile pixels from viewport size
var tileMatrixScale = ViewTransform.ZoomLevelToScale(tileMatrixZoomLevel);
// bounds in tile pixels from view size
//
var tileBounds = ParentMap.ViewTransform.GetTileMatrixBounds(
TileMatrixScale(tileMatrixZoomLevel), TileMatrixTopLeft, ParentMap.RenderSize);
tileMatrixScale, TileMatrixTopLeft, ParentMap.RenderSize);
// tile column and row index bounds
//

View file

@ -2,6 +2,7 @@
// © 2020 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
#if WINDOWS_UWP
using Windows.Foundation;
using Windows.UI.Xaml.Media;
@ -13,22 +14,14 @@ using System.Windows.Media;
namespace MapControl
{
/// <summary>
/// Defines the transformation between cartesian map coordinates and viewport coordinates.
/// Defines the transformation between cartesian map coordinates in meters
/// and view coordinates in pixels.
/// </summary>
public class ViewTransform
{
/// <summary>
/// Gets the transform matrix from cartesian map coordinates to viewport coordinates.
/// </summary>
public Matrix MapToViewMatrix { get; private set; }
/// <summary>
/// Gets the transform matrix from viewport coordinates to cartesian map coordinates.
/// </summary>
public Matrix ViewToMapMatrix { get; private set; }
/// <summary>
/// Gets the scaling factor from cartesian map coordinates to viewport coordinates.
/// Gets the scaling factor from cartesian map coordinates to view coordinates,
/// i.e. pixels per meter.
/// </summary>
public double Scale { get; private set; }
@ -38,7 +31,17 @@ namespace MapControl
public double Rotation { get; private set; }
/// <summary>
/// Transforms a Point from cartesian map coordinates to viewport coordinates.
/// Gets the transform matrix from cartesian map coordinates to view coordinates.
/// </summary>
public Matrix MapToViewMatrix { get; private set; }
/// <summary>
/// Gets the transform matrix from view coordinates to cartesian map coordinates.
/// </summary>
public Matrix ViewToMapMatrix { get; private set; }
/// <summary>
/// Transforms a Point from cartesian map coordinates to view coordinates.
/// </summary>
public Point MapToView(Point point)
{
@ -46,14 +49,14 @@ namespace MapControl
}
/// <summary>
/// Transforms a Point from viewport coordinates to cartesian map coordinates.
/// Transforms a Point from view coordinates to cartesian map coordinates.
/// </summary>
public Point ViewToMap(Point point)
{
return ViewToMapMatrix.Transform(point);
}
public void SetTransform(Point mapCenter, Point viewportCenter, double scale, double rotation)
public void SetTransform(Point mapCenter, Point viewCenter, double scale, double rotation)
{
Scale = scale;
Rotation = rotation;
@ -61,7 +64,7 @@ namespace MapControl
var transform = new Matrix(Scale, 0d, 0d, -Scale, -Scale * mapCenter.X, Scale * mapCenter.Y);
transform.Rotate(Rotation);
transform.Translate(viewportCenter.X, viewportCenter.Y);
transform.Translate(viewCenter.X, viewCenter.Y);
MapToViewMatrix = transform;
@ -83,7 +86,7 @@ namespace MapControl
tileMatrixTopLeft.X + tileMatrixOrigin.X / tileMatrixScale,
tileMatrixTopLeft.Y - tileMatrixOrigin.Y / tileMatrixScale);
// tile matrix origin in viewport coordinates
// tile matrix origin in view coordinates
//
var viewOrigin = MapToView(mapOrigin);
@ -92,14 +95,14 @@ namespace MapControl
return transform;
}
public Rect GetTileMatrixBounds(double tileMatrixScale, Point tileMatrixTopLeft, Size viewportSize)
public Rect GetTileMatrixBounds(double tileMatrixScale, Point tileMatrixTopLeft, Size viewSize)
{
var transformScale = tileMatrixScale / Scale;
var transform = new Matrix(transformScale, 0d, 0d, transformScale, 0d, 0d);
transform.Rotate(-Rotation);
// viewport origin in map coordinates
// view origin in map coordinates
//
var origin = ViewToMap(new Point());
@ -109,10 +112,20 @@ namespace MapControl
tileMatrixScale * (origin.X - tileMatrixTopLeft.X),
tileMatrixScale * (tileMatrixTopLeft.Y - origin.Y));
// transform viewport bounds to tile pixel bounds
// transform view bounds to tile pixel bounds
//
return new MatrixTransform { Matrix = transform }
.TransformBounds(new Rect(0d, 0d, viewportSize.Width, viewportSize.Height));
.TransformBounds(new Rect(0d, 0d, viewSize.Width, viewSize.Height));
}
public static double ZoomLevelToScale(double zoomLevel)
{
return 256d * Math.Pow(2d, zoomLevel) / (360d * MapProjection.Wgs84MetersPerDegree);
}
public static double ScaleToZoomLevel(double scale)
{
return Math.Log(scale * 360d * MapProjection.Wgs84MetersPerDegree / 256d, 2d);
}
}
}

View file

@ -16,7 +16,7 @@ namespace MapControl
/// <summary>
/// Indicates if the map projection has changed, i.e. if a MapTileLayer or MapImageLayer should
/// be updated immediately, or MapShape Data in cartesian map coordinates should be recalculated.
/// be updated immediately, or MapPath Data in cartesian map coordinates should be recalculated.
/// </summary>
public bool ProjectionChanged { get; }

View file

@ -47,11 +47,11 @@ namespace MapControl
viewTransform.GetTileLayerTransform(TileMatrix.Scale, TileMatrix.TopLeft, tileMatrixOrigin);
}
public bool SetBounds(ViewTransform viewTransform, Size viewportSize)
public bool SetBounds(ViewTransform viewTransform, Size viewSize)
{
// bounds in tile pixels from viewport size
// bounds in tile pixels from view size
//
var bounds = viewTransform.GetTileMatrixBounds(TileMatrix.Scale, TileMatrix.TopLeft, viewportSize);
var bounds = viewTransform.GetTileMatrixBounds(TileMatrix.Scale, TileMatrix.TopLeft, viewSize);
// tile column and row index bounds
//

View file

@ -15,11 +15,11 @@ namespace MapControl
/// </summary>
public class WorldMercatorProjection : MapProjection
{
private static readonly double maxLatitude = YToLatitude(180d);
public static double ConvergenceTolerance = 1e-6;
public static int MaxIterations = 10;
private static readonly double maxLatitude = YToLatitude(180d);
public WorldMercatorProjection()
{
CrsId = "EPSG:3395";