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";

View file

@ -39,6 +39,9 @@ namespace MapControl
nameof(TargetHeading), typeof(double), typeof(MapBase),
new PropertyMetadata(0d, (o, e) => ((MapBase)o).TargetHeadingPropertyChanged((double)e.NewValue)));
public static readonly DependencyProperty ViewScaleProperty = DependencyProperty.Register(
nameof(ViewScale), typeof(double), typeof(MapBase), new PropertyMetadata(0d));
internal static readonly DependencyProperty CenterPointProperty = DependencyProperty.Register(
"CenterPoint", typeof(Windows.Foundation.Point), typeof(MapBase),
new PropertyMetadata(new Windows.Foundation.Point(), (o, e) => ((MapBase)o).CenterPointPropertyChanged((Windows.Foundation.Point)e.NewValue)));
@ -62,6 +65,11 @@ namespace MapControl
};
}
private void SetViewScale(double scale)
{
SetValue(ViewScaleProperty, scale);
}
private void CenterPointPropertyChanged(Windows.Foundation.Point center)
{
CenterPointPropertyChanged(new Location(center.Y, center.X));

View file

@ -107,6 +107,9 @@
<Compile Include="..\Shared\MapPanel.cs">
<Link>MapPanel.cs</Link>
</Compile>
<Compile Include="..\Shared\MapPath.cs">
<Link>MapPath.cs</Link>
</Compile>
<Compile Include="..\Shared\MapPolygon.cs">
<Link>MapPolygon.cs</Link>
</Compile>
@ -119,9 +122,6 @@
<Compile Include="..\Shared\MapScale.cs">
<Link>MapScale.cs</Link>
</Compile>
<Compile Include="..\Shared\MapShape.cs">
<Link>MapShape.cs</Link>
</Compile>
<Compile Include="..\Shared\MapTileLayer.cs">
<Link>MapTileLayer.cs</Link>
</Compile>
@ -188,7 +188,7 @@
<Compile Include="MapItemsControl.UWP.cs" />
<Compile Include="MapOverlay.UWP.cs" />
<Compile Include="MapPanel.UWP.cs" />
<Compile Include="MapShape.UWP.cs" />
<Compile Include="MapPath.UWP.cs" />
<Compile Include="Matrix.UWP.cs" />
<Compile Include="Point.UWP.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />

View file

@ -4,11 +4,9 @@
using System;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Shapes;
using Windows.UI.Xaml.Data;
namespace MapControl
{
@ -40,7 +38,7 @@ namespace MapControl
Children.Add(path);
}
var bounds = map.ViewportRectToBoundingBox(new Rect(0d, 0d, map.RenderSize.Width, map.RenderSize.Height));
var bounds = map.ViewRectToBoundingBox(new Rect(0d, 0d, map.RenderSize.Width, map.RenderSize.Height));
var lineDistance = GetLineDistance();
var labelStart = new Location(
@ -66,14 +64,14 @@ namespace MapControl
{
var figure = new PathFigure
{
StartPoint = map.LocationToViewportPoint(new Location(lat, lineStart.Longitude)),
StartPoint = map.LocationToView(new Location(lat, lineStart.Longitude)),
IsClosed = false,
IsFilled = false
};
figure.Segments.Add(new LineSegment
{
Point = map.LocationToViewportPoint(new Location(lat, lineEnd.Longitude))
Point = map.LocationToView(new Location(lat, lineEnd.Longitude))
});
geometry.Figures.Add(figure);
@ -83,14 +81,14 @@ namespace MapControl
{
var figure = new PathFigure
{
StartPoint = map.LocationToViewportPoint(new Location(lineStart.Latitude, lon)),
StartPoint = map.LocationToView(new Location(lineStart.Latitude, lon)),
IsClosed = false,
IsFilled = false
};
figure.Segments.Add(new LineSegment
{
Point = map.LocationToViewportPoint(new Location(lineEnd.Latitude, lon))
Point = map.LocationToView(new Location(lineEnd.Latitude, lon))
});
geometry.Figures.Add(figure);
@ -111,22 +109,18 @@ namespace MapControl
}
else
{
var renderTransform = new TransformGroup();
renderTransform.Children.Add(new TranslateTransform());
renderTransform.Children.Add(map.RotateTransform);
renderTransform.Children.Add(new TranslateTransform());
label = new TextBlock { RenderTransform = renderTransform };
if (FontFamily != null)
{
label.SetBinding(TextBlock.FontFamilyProperty, GetBinding(FontFamilyProperty, nameof(FontFamily)));
}
label = new TextBlock { RenderTransform = new MatrixTransform() };
label.SetBinding(TextBlock.FontSizeProperty, GetBinding(FontSizeProperty, nameof(FontSize)));
label.SetBinding(TextBlock.FontStyleProperty, GetBinding(FontStyleProperty, nameof(FontStyle)));
label.SetBinding(TextBlock.FontStretchProperty, GetBinding(FontStretchProperty, nameof(FontStretch)));
label.SetBinding(TextBlock.FontWeightProperty, GetBinding(FontWeightProperty, nameof(FontWeight)));
label.SetBinding(TextBlock.ForegroundProperty, GetBinding(ForegroundProperty, nameof(Foreground)));
if (FontFamily != null)
{
label.SetBinding(TextBlock.FontFamilyProperty, GetBinding(FontFamilyProperty, nameof(FontFamily)));
}
Children.Add(label);
}
@ -135,10 +129,6 @@ namespace MapControl
label.Text = GetLabelText(lat, labelFormat, "NS") + "\n" + GetLabelText(Location.NormalizeLongitude(lon), labelFormat, "EW");
label.Tag = new Location(lat, lon);
label.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
var translateTransform = (TranslateTransform)((TransformGroup)label.RenderTransform).Children[0];
translateTransform.X = StrokeThickness / 2d + 2d;
translateTransform.Y = -label.DesiredSize.Height / 2d;
}
while (Children.Count > childIndex)
@ -153,10 +143,14 @@ namespace MapControl
{
var label = (TextBlock)Children[i];
var location = (Location)label.Tag;
var viewportTransform = (TranslateTransform)((TransformGroup)label.RenderTransform).Children[2];
var viewportPosition = map.LocationToViewportPoint(location);
viewportTransform.X = viewportPosition.X;
viewportTransform.Y = viewportPosition.Y;
var viewPosition = map.LocationToView(location);
var matrix = new Matrix(1, 0, 0, 1, 0, 0);
matrix.Translate(StrokeThickness / 2d + 2d, -label.DesiredSize.Height / 2d);
matrix.Rotate(map.ViewTransform.Rotation);
matrix.Translate(viewPosition.X, viewPosition.Y);
((MatrixTransform)label.RenderTransform).Matrix = matrix;
}
}
else if (path != null)

View file

@ -20,8 +20,8 @@ namespace MapControl
public static readonly DependencyProperty ParentMapProperty = DependencyProperty.RegisterAttached(
"ParentMap", typeof(MapBase), typeof(MapPanel), new PropertyMetadata(null, ParentMapPropertyChanged));
private static readonly DependencyProperty ViewportPositionProperty = DependencyProperty.RegisterAttached(
"ViewportPosition", typeof(Point?), typeof(MapPanel), new PropertyMetadata(null));
private static readonly DependencyProperty ViewPositionProperty = DependencyProperty.RegisterAttached(
"ViewPosition", typeof(Point?), typeof(MapPanel), new PropertyMetadata(null));
public static void InitMapElement(FrameworkElement element)
{
@ -61,9 +61,9 @@ namespace MapControl
?? FindParentMap(parent));
}
private static void SetViewportPosition(FrameworkElement element, Point? viewportPosition)
private static void SetViewPosition(FrameworkElement element, Point? viewPosition)
{
element.SetValue(ViewportPositionProperty, viewportPosition);
element.SetValue(ViewPositionProperty, viewPosition);
}
}
}

View file

@ -12,7 +12,7 @@ using Windows.UI.Xaml.Shapes;
namespace MapControl
{
public abstract partial class MapShape : Path
public partial class MapPath : Path
{
protected void DataCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
@ -40,13 +40,7 @@ namespace MapControl
{
if (locations != null && locations.Count() >= 2)
{
var offset = GetLongitudeOffset();
if (offset != 0d)
{
locations = locations.Select(loc => new Location(loc.Latitude, loc.Longitude + offset));
}
var points = locations.Select(loc => LocationToViewportPoint(loc)).ToList();
var points = locations.Select(loc => LocationToView(loc)).ToList();
if (closed)
{
points.Add(points[0]);

View file

@ -50,6 +50,16 @@ namespace MapControl
return new Vector(v1.X - v2.X, v1.Y - v2.Y);
}
public static Vector operator *(double f, Vector v)
{
return new Vector(f * v.X, f * v.Y);
}
public static Vector operator *(Vector v, double f)
{
return new Vector(f * v.X, f * v.Y);
}
public static bool operator ==(Vector v1, Vector v2)
{
return v1.X == v2.X && v1.Y == v2.Y;

View file

@ -43,6 +43,11 @@ namespace MapControl
0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
(o, e) => ((MapBase)o).TargetHeadingPropertyChanged((double)e.NewValue)));
private static readonly DependencyPropertyKey ViewScalePropertyKey = DependencyProperty.RegisterReadOnly(
nameof(ViewScale), typeof(double), typeof(MapBase), new PropertyMetadata(0d));
public static readonly DependencyProperty ViewScaleProperty = ViewScalePropertyKey.DependencyProperty;
private static readonly DependencyProperty CenterPointProperty = DependencyProperty.Register(
"CenterPoint", typeof(Point), typeof(MapBase),
new PropertyMetadata(new Point(), (o, e) => ((MapBase)o).CenterPointPropertyChanged((Point)e.NewValue)));
@ -61,6 +66,11 @@ namespace MapControl
UpdateTransform();
}
private void SetViewScale(double scale)
{
SetValue(ViewScalePropertyKey, scale);
}
private void CenterPointPropertyChanged(Point center)
{
CenterPointPropertyChanged(new Location(center.Y, center.X));

View file

@ -56,7 +56,7 @@ namespace MapControl
private void DrawCylindricalGraticule(DrawingContext drawingContext, double lineDistance, string labelFormat)
{
var boundingBox = ParentMap.ViewportRectToBoundingBox(new Rect(ParentMap.RenderSize));
var boundingBox = ParentMap.ViewRectToBoundingBox(new Rect(ParentMap.RenderSize));
var latLabelStart = Math.Ceiling(boundingBox.South / lineDistance) * lineDistance;
var lonLabelStart = Math.Ceiling(boundingBox.West / lineDistance) * lineDistance;
var latLabels = new List<Label>((int)((boundingBox.North - latLabelStart) / lineDistance) + 1);
@ -72,8 +72,8 @@ namespace MapControl
CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Foreground, pixelsPerDip)));
drawingContext.DrawLine(pen,
ParentMap.LocationToViewportPoint(new Location(lat, boundingBox.West)),
ParentMap.LocationToViewportPoint(new Location(lat, boundingBox.East)));
ParentMap.LocationToView(new Location(lat, boundingBox.West)),
ParentMap.LocationToView(new Location(lat, boundingBox.East)));
}
for (var lon = lonLabelStart; lon <= boundingBox.East; lon += lineDistance)
@ -83,17 +83,17 @@ namespace MapControl
CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Foreground, pixelsPerDip)));
drawingContext.DrawLine(pen,
ParentMap.LocationToViewportPoint(new Location(boundingBox.South, lon)),
ParentMap.LocationToViewportPoint(new Location(boundingBox.North, lon)));
ParentMap.LocationToView(new Location(boundingBox.South, lon)),
ParentMap.LocationToView(new Location(boundingBox.North, lon)));
}
foreach (var latLabel in latLabels)
{
foreach (var lonLabel in lonLabels)
{
var position = ParentMap.LocationToViewportPoint(new Location(latLabel.Position, lonLabel.Position));
var position = ParentMap.LocationToView(new Location(latLabel.Position, lonLabel.Position));
drawingContext.PushTransform(new RotateTransform(ParentMap.Heading, position.X, position.Y));
drawingContext.PushTransform(new RotateTransform(ParentMap.ViewTransform.Rotation, position.X, position.Y));
drawingContext.DrawText(latLabel.Text,
new Point(position.X + StrokeThickness / 2d + 2d, position.Y - StrokeThickness / 2d - latLabel.Text.Height));
drawingContext.DrawText(lonLabel.Text,

View file

@ -16,7 +16,7 @@ namespace MapControl
/// for the Polygons property if collection changes of the property itself and its
/// elements are both supposed to trigger a UI update.
/// </summary>
public class MapMultiPolygon : MapShape
public class MapMultiPolygon : MapPath
{
public static readonly DependencyProperty PolygonsProperty = DependencyProperty.Register(
nameof(Polygons), typeof(IEnumerable<IEnumerable<Location>>), typeof(MapMultiPolygon),
@ -31,6 +31,11 @@ namespace MapControl
set { SetValue(PolygonsProperty, value); }
}
public MapMultiPolygon()
{
Data = new PathGeometry();
}
protected override void UpdateData()
{
var figures = ((PathGeometry)Data).Figures;

View file

@ -20,11 +20,11 @@ namespace MapControl
"ParentMap", typeof(MapBase), typeof(MapPanel),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits, ParentMapPropertyChanged));
private static readonly DependencyPropertyKey ViewportPositionPropertyKey = DependencyProperty.RegisterAttachedReadOnly(
"ViewportPosition", typeof(Point?), typeof(MapPanel), new PropertyMetadata());
private static readonly DependencyPropertyKey ViewPositionPropertyKey = DependencyProperty.RegisterAttachedReadOnly(
"ViewPosition", typeof(Point?), typeof(MapPanel), new PropertyMetadata());
public static readonly DependencyProperty ParentMapProperty = ParentMapPropertyKey.DependencyProperty;
public static readonly DependencyProperty ViewportPositionProperty = ViewportPositionPropertyKey.DependencyProperty;
public static readonly DependencyProperty ViewPositionProperty = ViewPositionPropertyKey.DependencyProperty;
public static MapBase GetParentMap(FrameworkElement element)
{
@ -39,9 +39,9 @@ namespace MapControl
}
}
private static void SetViewportPosition(FrameworkElement element, Point? viewportPosition)
private static void SetViewPosition(FrameworkElement element, Point? viewPosition)
{
element.SetValue(ViewportPositionPropertyKey, viewportPosition);
element.SetValue(ViewPositionPropertyKey, viewPosition);
}
}
}

View file

@ -12,9 +12,15 @@ using System.Windows.Shapes;
namespace MapControl
{
public abstract partial class MapShape : Shape, IWeakEventListener
public partial class MapPath : Shape, IWeakEventListener
{
public Geometry Data { get; }
public static readonly DependencyProperty DataProperty = Path.DataProperty.AddOwner(typeof(MapPath));
public Geometry Data
{
get { return (Geometry)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
protected override Geometry DefiningGeometry
{
@ -48,13 +54,7 @@ namespace MapControl
{
if (locations != null && locations.Count() >= 2)
{
var offset = GetLongitudeOffset();
if (offset != 0d)
{
locations = locations.Select(loc => new Location(loc.Latitude, loc.Longitude + offset));
}
var points = locations.Select(loc => LocationToViewportPoint(loc));
var points = locations.Select(loc => LocationToView(loc));
var figure = new PathFigure
{
StartPoint = points.First(),