Version 5.0: Separated map projection and view transform.

This commit is contained in:
ClemensF 2020-03-26 19:08:20 +01:00
parent 53723844a0
commit c7cb2efcdb
47 changed files with 401 additions and 382 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -16,22 +16,22 @@ namespace MapControl
CrsId = "AUTO2:42004"; CrsId = "AUTO2:42004";
} }
public override Point LocationToPoint(Location location) public override Point LocationToMap(Location location)
{ {
var xScale = Wgs84MetersPerDegree * Math.Cos(ProjectionCenter.Latitude * Math.PI / 180d); var xScale = UnitsPerDegree * Math.Cos(Center.Latitude * Math.PI / 180d);
return new Point( return new Point(
xScale * (location.Longitude - ProjectionCenter.Longitude), xScale * (location.Longitude - Center.Longitude),
Wgs84MetersPerDegree * location.Latitude); UnitsPerDegree * location.Latitude);
} }
public override Location PointToLocation(Point point) public override Location MapToLocation(Point point)
{ {
var xScale = Wgs84MetersPerDegree * Math.Cos(ProjectionCenter.Latitude * Math.PI / 180d); var xScale = UnitsPerDegree * Math.Cos(Center.Latitude * Math.PI / 180d);
return new Location( return new Location(
point.Y / Wgs84MetersPerDegree, point.Y / UnitsPerDegree,
point.X / xScale + ProjectionCenter.Longitude); point.X / xScale + Center.Longitude);
} }
} }
} }

View file

@ -16,35 +16,35 @@ namespace MapControl
{ {
// No standard CRS ID // No standard CRS ID
public override Point LocationToPoint(Location location) public override Point LocationToMap(Location location)
{ {
if (location.Equals(ProjectionCenter)) if (location.Equals(Center))
{ {
return new Point(); return new Point();
} }
double azimuth, distance; double azimuth, distance;
GetAzimuthDistance(ProjectionCenter, location, out azimuth, out distance); GetAzimuthDistance(Center, location, out azimuth, out distance);
var mapDistance = distance * TrueScale * 180d / Math.PI; var mapDistance = distance * UnitsPerDegree * 180d / Math.PI;
return new Point(mapDistance * Math.Sin(azimuth), mapDistance * Math.Cos(azimuth)); return new Point(mapDistance * Math.Sin(azimuth), mapDistance * Math.Cos(azimuth));
} }
public override Location PointToLocation(Point point) public override Location MapToLocation(Point point)
{ {
if (point.X == 0d && point.Y == 0d) if (point.X == 0d && point.Y == 0d)
{ {
return new Location(ProjectionCenter.Latitude, ProjectionCenter.Longitude); return new Location(Center.Latitude, Center.Longitude);
} }
var azimuth = Math.Atan2(point.X, point.Y); var azimuth = Math.Atan2(point.X, point.Y);
var mapDistance = Math.Sqrt(point.X * point.X + point.Y * point.Y); var mapDistance = Math.Sqrt(point.X * point.X + point.Y * point.Y);
var distance = mapDistance / (TrueScale * 180d / Math.PI); var distance = mapDistance / (UnitsPerDegree * 180d / Math.PI);
return GetLocation(ProjectionCenter, azimuth, distance); return GetLocation(Center, azimuth, distance);
} }
} }
} }

View file

@ -27,7 +27,7 @@ namespace MapControl
if (cbbox != null) if (cbbox != null)
{ {
var center = LocationToPoint(cbbox.Center); var center = LocationToMap(cbbox.Center);
return new Rect( return new Rect(
center.X - cbbox.Width / 2d, center.Y - cbbox.Height / 2d, center.X - cbbox.Width / 2d, center.Y - cbbox.Height / 2d,
@ -39,7 +39,7 @@ namespace MapControl
public override BoundingBox RectToBoundingBox(Rect rect) public override BoundingBox RectToBoundingBox(Rect rect)
{ {
var center = PointToLocation(new Point(rect.X + rect.Width / 2d, rect.Y + rect.Height / 2d)); var center = MapToLocation(new Point(rect.X + rect.Width / 2d, rect.Y + rect.Height / 2d));
return new CenteredBoundingBox(center, rect.Width, rect.Height); // width and height in meters return new CenteredBoundingBox(center, rect.Width, rect.Height); // width and height in meters
} }

View file

@ -14,7 +14,7 @@ namespace MapControl
{ {
/// <summary> /// <summary>
/// Equirectangular Projection. /// Equirectangular Projection.
/// Longitude and Latitude values are transformed identically to X and Y. /// Longitude and Latitude values are transformed linearly to X and Y in meters.
/// </summary> /// </summary>
public class EquirectangularProjection : MapProjection public class EquirectangularProjection : MapProjection
{ {
@ -23,33 +23,33 @@ namespace MapControl
CrsId = "EPSG:4326"; CrsId = "EPSG:4326";
} }
public override double TrueScale public override Vector GetRelativeScale(Location location)
{
get { return 1d; }
}
public override Vector GetMapScale(Location location)
{ {
return new Vector( return new Vector(
ViewportScale / (Wgs84MetersPerDegree * Math.Cos(location.Latitude * Math.PI / 180d)), 1d / Math.Cos(location.Latitude * Math.PI / 180d),
ViewportScale / Wgs84MetersPerDegree); 1d);
} }
public override Point LocationToPoint(Location location) public override Point LocationToMap(Location location)
{ {
return new Point(location.Longitude, location.Latitude); return new Point(
location.Longitude * UnitsPerDegree,
location.Latitude * UnitsPerDegree);
} }
public override Location PointToLocation(Point point) public override Location MapToLocation(Point point)
{ {
return new Location(point.Y, point.X); return new Location(
point.Y / UnitsPerDegree,
point.X / UnitsPerDegree);
} }
public override string GetBboxValue(Rect rect) public override string GetBboxValue(Rect rect)
{ {
return string.Format(CultureInfo.InvariantCulture, return string.Format(CultureInfo.InvariantCulture,
CrsId != "CRS:84" ? "{1},{0},{3},{2}" : "{0},{1},{2},{3}", CrsId != "CRS:84" ? "{1},{0},{3},{2}" : "{0},{1},{2},{3}",
rect.X, rect.Y, (rect.X + rect.Width), (rect.Y + rect.Height)); rect.X / UnitsPerDegree, rect.Y / UnitsPerDegree,
(rect.X + rect.Width) / UnitsPerDegree, (rect.Y + rect.Height) / UnitsPerDegree);
} }
} }
} }

View file

@ -19,37 +19,37 @@ namespace MapControl
CrsId = "AUTO2:97001"; // GeoServer non-standard CRS ID CrsId = "AUTO2:97001"; // GeoServer non-standard CRS ID
} }
public override Point LocationToPoint(Location location) public override Point LocationToMap(Location location)
{ {
if (location.Equals(ProjectionCenter)) if (location.Equals(Center))
{ {
return new Point(); return new Point();
} }
double azimuth, distance; double azimuth, distance;
GetAzimuthDistance(ProjectionCenter, location, out azimuth, out distance); GetAzimuthDistance(Center, location, out azimuth, out distance);
var mapDistance = distance < Math.PI / 2d var mapDistance = distance < Math.PI / 2d
? Math.Tan(distance) * TrueScale * 180d / Math.PI ? Math.Tan(distance) * UnitsPerDegree * 180d / Math.PI
: double.PositiveInfinity; : double.PositiveInfinity;
return new Point(mapDistance * Math.Sin(azimuth), mapDistance * Math.Cos(azimuth)); return new Point(mapDistance * Math.Sin(azimuth), mapDistance * Math.Cos(azimuth));
} }
public override Location PointToLocation(Point point) public override Location MapToLocation(Point point)
{ {
if (point.X == 0d && point.Y == 0d) if (point.X == 0d && point.Y == 0d)
{ {
return new Location(ProjectionCenter.Latitude, ProjectionCenter.Longitude); return new Location(Center.Latitude, Center.Longitude);
} }
var azimuth = Math.Atan2(point.X, point.Y); var azimuth = Math.Atan2(point.X, point.Y);
var mapDistance = Math.Sqrt(point.X * point.X + point.Y * point.Y); var mapDistance = Math.Sqrt(point.X * point.X + point.Y * point.Y);
var distance = Math.Atan(mapDistance / (TrueScale * 180d / Math.PI)); var distance = Math.Atan(mapDistance / (UnitsPerDegree * 180d / Math.PI));
return GetLocation(ProjectionCenter, azimuth, distance); return GetLocation(Center, azimuth, distance);
} }
} }
} }

View file

@ -4,6 +4,7 @@
using System; using System;
#if WINDOWS_UWP #if WINDOWS_UWP
using Windows.Foundation;
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Animation; using Windows.UI.Xaml.Media.Animation;
@ -22,7 +23,8 @@ namespace MapControl
} }
/// <summary> /// <summary>
/// The map control. Displays map content provided by one or more MapTileLayers or MapImageLayers. /// The map control. Displays map content provided by one or more tile or image layers,
/// i.e. MapTileLayerBase or MapImageLayer instances.
/// The visible map area is defined by the Center and ZoomLevel properties. /// 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. /// 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. /// MapBase can contain map overlay child elements like other MapPanels or MapItemsControls.
@ -37,7 +39,7 @@ namespace MapControl
public static readonly DependencyProperty MapProjectionProperty = DependencyProperty.Register( public static readonly DependencyProperty MapProjectionProperty = DependencyProperty.Register(
nameof(MapProjection), typeof(MapProjection), typeof(MapBase), nameof(MapProjection), typeof(MapProjection), typeof(MapBase),
new PropertyMetadata(null, (o, e) => ((MapBase)o).MapProjectionPropertyChanged())); new PropertyMetadata(new WebMercatorProjection(), (o, e) => ((MapBase)o).MapProjectionPropertyChanged()));
public static readonly DependencyProperty ProjectionCenterProperty = DependencyProperty.Register( public static readonly DependencyProperty ProjectionCenterProperty = DependencyProperty.Register(
nameof(ProjectionCenter), typeof(Location), typeof(MapBase), nameof(ProjectionCenter), typeof(Location), typeof(MapBase),
@ -224,6 +226,11 @@ namespace MapControl
/// <summary> /// <summary>
/// Gets the transformation from cartesian map coordinates to viewport coordinates. /// Gets the transformation from cartesian map coordinates to viewport coordinates.
/// </summary> /// </summary>
public ViewTransform ViewTransform { get; } = new ViewTransform();
/// <summary>
/// Gets the transformation from cartesian map coordinates to viewport coordinates as MatrixTransform.
/// </summary>
public MatrixTransform ViewportTransform { get; } = new MatrixTransform(); public MatrixTransform ViewportTransform { get; } = new MatrixTransform();
/// <summary> /// <summary>
@ -239,14 +246,23 @@ namespace MapControl
/// <summary> /// <summary>
/// Gets the combination of ScaleTransform and RotateTransform /// Gets the combination of ScaleTransform and RotateTransform
/// </summary> /// </summary>
public TransformGroup ScaleRotateTransform { get; } = new TransformGroup(); public TransformGroup ScaleRotateTransform
{
get
{
var transform = new TransformGroup();
transform.Children.Add(ScaleTransform);
transform.Children.Add(RotateTransform);
return transform;
}
}
/// <summary> /// <summary>
/// Transforms a Location in geographic coordinates to a Point in viewport coordinates. /// Transforms a Location in geographic coordinates to a Point in viewport coordinates.
/// </summary> /// </summary>
public Point LocationToViewportPoint(Location location) public Point LocationToViewportPoint(Location location)
{ {
return MapProjection.LocationToViewportPoint(location); return ViewTransform.MapToView(MapProjection.LocationToMap(location));
} }
/// <summary> /// <summary>
@ -254,7 +270,25 @@ namespace MapControl
/// </summary> /// </summary>
public Location ViewportPointToLocation(Point point) public Location ViewportPointToLocation(Point point)
{ {
return MapProjection.ViewportPointToLocation(point); return MapProjection.MapToLocation(ViewTransform.ViewToMap(point));
}
/// <summary>
/// Transforms a Rect in viewport coordinates to a BoundingBox in geographic coordinates.
/// </summary>
public BoundingBox ViewportRectToBoundingBox(Rect rect)
{
var p1 = ViewTransform.ViewToMap(new Point(rect.X, rect.Y));
var p2 = ViewTransform.ViewToMap(new Point(rect.X, rect.Y + rect.Height));
var p3 = ViewTransform.ViewToMap(new Point(rect.X + rect.Width, rect.Y));
var p4 = ViewTransform.ViewToMap(new Point(rect.X + rect.Width, rect.Y + rect.Height));
rect.X = Math.Min(p1.X, Math.Min(p2.X, Math.Min(p3.X, p4.X)));
rect.Y = Math.Min(p1.Y, Math.Min(p2.Y, Math.Min(p3.Y, p4.Y)));
rect.Width = Math.Max(p1.X, Math.Max(p2.X, Math.Max(p3.X, p4.X))) - rect.X;
rect.Height = Math.Max(p1.Y, Math.Max(p2.Y, Math.Max(p3.Y, p4.Y))) - rect.Y;
return MapProjection.RectToBoundingBox(rect);
} }
/// <summary> /// <summary>
@ -263,7 +297,7 @@ namespace MapControl
/// </summary> /// </summary>
public void SetTransformCenter(Point center) public void SetTransformCenter(Point center)
{ {
transformCenter = MapProjection.ViewportPointToLocation(center); transformCenter = ViewportPointToLocation(center);
viewportCenter = center; viewportCenter = center;
} }
@ -289,7 +323,7 @@ namespace MapControl
if (translation.X != 0d || translation.Y != 0d) if (translation.X != 0d || translation.Y != 0d)
{ {
Center = MapProjection.ViewportPointToLocation(viewportCenter - translation); Center = ViewportPointToLocation(viewportCenter - translation);
} }
} }
@ -302,7 +336,7 @@ namespace MapControl
{ {
if (rotation != 0d || scale != 1d) if (rotation != 0d || scale != 1d)
{ {
transformCenter = MapProjection.ViewportPointToLocation(center); transformCenter = ViewportPointToLocation(center);
viewportCenter = center + translation; viewportCenter = center + translation;
if (rotation != 0d) if (rotation != 0d)
@ -351,10 +385,10 @@ namespace MapControl
var rect = MapProjection.BoundingBoxToRect(boundingBox); var rect = MapProjection.BoundingBoxToRect(boundingBox);
var center = new Point(rect.X + rect.Width / 2d, rect.Y + rect.Height / 2d); 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) var scale = Math.Min(RenderSize.Width / rect.Width, RenderSize.Height / rect.Height)
* MapProjection.TrueScale * 360d / 256d; * MapProjection.Wgs84MetersPerDegree * 360d / 256d;
TargetZoomLevel = Math.Log(scale, 2d); TargetZoomLevel = Math.Log(scale, 2d);
TargetCenter = MapProjection.PointToLocation(center); TargetCenter = MapProjection.MapToLocation(center);
TargetHeading = 0d; TargetHeading = 0d;
} }
@ -669,13 +703,16 @@ namespace MapControl
private void UpdateTransform(bool resetTransformCenter = false, bool projectionChanged = false) private void UpdateTransform(bool resetTransformCenter = false, bool projectionChanged = false)
{ {
var projection = MapProjection; var projection = MapProjection;
var viewportScale = 256d * Math.Pow(2d, ZoomLevel) / (360d * MapProjection.Wgs84MetersPerDegree);
var center = transformCenter ?? Center; var center = transformCenter ?? Center;
projection.SetViewportTransform(ProjectionCenter ?? Center, center, viewportCenter, ZoomLevel, Heading); projection.Center = ProjectionCenter ?? Center;
ViewTransform.SetTransform(projection.LocationToMap(center), viewportCenter, viewportScale, Heading);
if (transformCenter != null) if (transformCenter != null)
{ {
center = projection.ViewportPointToLocation(new Point(RenderSize.Width / 2d, RenderSize.Height / 2d)); center = ViewportPointToLocation(new Point(RenderSize.Width / 2d, RenderSize.Height / 2d));
center.Longitude = Location.NormalizeLongitude(center.Longitude); center.Longitude = Location.NormalizeLongitude(center.Longitude);
if (center.Latitude < -projection.MaxLatitude || center.Latitude > projection.MaxLatitude) if (center.Latitude < -projection.MaxLatitude || center.Latitude > projection.MaxLatitude)
@ -694,17 +731,20 @@ namespace MapControl
if (resetTransformCenter) if (resetTransformCenter)
{ {
ResetTransformCenter(); ResetTransformCenter();
projection.SetViewportTransform(ProjectionCenter ?? center, center, viewportCenter, ZoomLevel, Heading);
projection.Center = ProjectionCenter ?? center;
ViewTransform.SetTransform(projection.LocationToMap(center), viewportCenter, viewportScale, Heading);
} }
} }
ViewportTransform.Matrix = projection.ViewportTransform; ViewportTransform.Matrix = ViewTransform.MapToViewMatrix;
var scale = projection.GetMapScale(center); var scale = projection.GetRelativeScale(center);
ScaleTransform.ScaleX = scale.X; ScaleTransform.ScaleX = scale.X * ViewTransform.Scale;
ScaleTransform.ScaleY = scale.Y; ScaleTransform.ScaleY = scale.Y * ViewTransform.Scale;
RotateTransform.Angle = Heading; RotateTransform.Angle = ViewTransform.Rotation;
OnViewportChanged(new ViewportChangedEventArgs(projectionChanged, Center.Longitude - centerLongitude)); OnViewportChanged(new ViewportChangedEventArgs(projectionChanged, Center.Longitude - centerLongitude));

View file

@ -30,7 +30,7 @@ namespace MapControl
private double GetLineDistance() private double GetLineDistance()
{ {
var pixelPerDegree = ParentMap.MapProjection.ViewportScale * ParentMap.MapProjection.TrueScale; var pixelPerDegree = ParentMap.ViewTransform.Scale * ParentMap.MapProjection.UnitsPerDegree;
var minDistance = MinLineDistance / pixelPerDegree; var minDistance = MinLineDistance / pixelPerDegree;
var scale = 1d; var scale = 1d;

View file

@ -258,7 +258,7 @@ namespace MapControl
var y = (ParentMap.RenderSize.Height - height) / 2d; var y = (ParentMap.RenderSize.Height - height) / 2d;
var rect = new Rect(x, y, width, height); var rect = new Rect(x, y, width, height);
BoundingBox = ParentMap.MapProjection.ViewportRectToBoundingBox(rect); BoundingBox = ParentMap.ViewportRectToBoundingBox(rect);
if (BoundingBox != null) if (BoundingBox != null)
{ {

View file

@ -145,14 +145,13 @@ namespace MapControl
private Point ArrangeElement(FrameworkElement element, Location location) private Point ArrangeElement(FrameworkElement element, Location location)
{ {
var projection = parentMap.MapProjection; var pos = parentMap.LocationToViewportPoint(location);
var pos = projection.LocationToViewportPoint(location);
if (projection.IsNormalCylindrical && if (parentMap.MapProjection.IsNormalCylindrical &&
(pos.X < 0d || pos.X > parentMap.RenderSize.Width || (pos.X < 0d || pos.X > parentMap.RenderSize.Width ||
pos.Y < 0d || pos.Y > parentMap.RenderSize.Height)) pos.Y < 0d || pos.Y > parentMap.RenderSize.Height))
{ {
pos = projection.LocationToViewportPoint(new Location( pos = parentMap.LocationToViewportPoint(new Location(
location.Latitude, location.Latitude,
Location.NearestLongitude(location.Longitude, parentMap.Center.Longitude))); Location.NearestLongitude(location.Longitude, parentMap.Center.Longitude)));
} }
@ -202,20 +201,20 @@ namespace MapControl
var projection = parentMap.MapProjection; var projection = parentMap.MapProjection;
var rect = projection.BoundingBoxToRect(boundingBox); var rect = projection.BoundingBoxToRect(boundingBox);
var center = new Point(rect.X + rect.Width / 2d, rect.Y + rect.Height / 2d); var center = new Point(rect.X + rect.Width / 2d, rect.Y + rect.Height / 2d);
var pos = projection.ViewportTransform.Transform(center); var pos = parentMap.ViewTransform.MapToView(center);
if (projection.IsNormalCylindrical && if (projection.IsNormalCylindrical &&
(pos.X < 0d || pos.X > parentMap.RenderSize.Width || (pos.X < 0d || pos.X > parentMap.RenderSize.Width ||
pos.Y < 0d || pos.Y > parentMap.RenderSize.Height)) pos.Y < 0d || pos.Y > parentMap.RenderSize.Height))
{ {
var location = projection.PointToLocation(center); var location = projection.MapToLocation(center);
location.Longitude = Location.NearestLongitude(location.Longitude, parentMap.Center.Longitude); location.Longitude = Location.NearestLongitude(location.Longitude, parentMap.Center.Longitude);
pos = projection.LocationToViewportPoint(location); pos = parentMap.LocationToViewportPoint(location);
} }
rect.Width *= projection.ViewportScale; rect.Width *= parentMap.ViewTransform.Scale;
rect.Height *= projection.ViewportScale; rect.Height *= parentMap.ViewTransform.Scale;
rect.X = pos.X - rect.Width / 2d; rect.X = pos.X - rect.Width / 2d;
rect.Y = pos.Y - rect.Height / 2d; rect.Y = pos.Y - rect.Height / 2d;

View file

@ -6,24 +6,26 @@ using System;
using System.Globalization; using System.Globalization;
#if WINDOWS_UWP #if WINDOWS_UWP
using Windows.Foundation; using Windows.Foundation;
using Windows.UI.Xaml.Media;
#else #else
using System.Windows; using System.Windows;
using System.Windows.Media;
#endif #endif
namespace MapControl namespace MapControl
{ {
/// <summary> /// <summary>
/// Defines a map projection between geographic coordinates, cartesian map coordinates and viewport coordinates. /// Defines a map projection between geographic coordinates and cartesian map coordinates.
/// </summary> /// </summary>
public abstract class MapProjection public abstract class MapProjection
{ {
public const double Wgs84EquatorialRadius = 6378137d; public const double Wgs84EquatorialRadius = 6378137d;
public const double Wgs84MetersPerDegree = Wgs84EquatorialRadius * Math.PI / 180d;
public const double Wgs84Flattening = 1d / 298.257223563; public const double Wgs84Flattening = 1d / 298.257223563;
public static readonly double Wgs84Eccentricity = Math.Sqrt((2d - Wgs84Flattening) * Wgs84Flattening); public static readonly double Wgs84Eccentricity = Math.Sqrt((2d - Wgs84Flattening) * Wgs84Flattening);
public const double Wgs84MetersPerDegree = Wgs84EquatorialRadius * Math.PI / 180d; /// <summary>
/// Gets or sets the projection center. Only relevant for azimuthal projections.
/// </summary>
public Location Center { get; set; } = new Location();
/// <summary> /// <summary>
/// Gets or sets the WMS 1.3.0 CRS identifier. /// Gets or sets the WMS 1.3.0 CRS identifier.
@ -58,54 +60,28 @@ namespace MapControl
/// Gets the scale factor from geographic to cartesian coordinates, on the line of true scale of a /// Gets the scale factor from geographic to cartesian coordinates, on the line of true scale of a
/// cylindrical projection (usually the equator), or at the projection center of an azimuthal projection. /// cylindrical projection (usually the equator), or at the projection center of an azimuthal projection.
/// </summary> /// </summary>
public virtual double TrueScale public virtual double UnitsPerDegree
{ {
get { return Wgs84MetersPerDegree; } get { return Wgs84MetersPerDegree; }
} }
/// <summary> /// <summary>
/// Gets the projection center. Only relevant for azimuthal projections. /// Gets the relative map scale at the specified Location.
/// </summary> /// </summary>
public Location ProjectionCenter { get; private set; } = new Location(); public virtual Vector GetRelativeScale(Location location)
/// <summary>
/// Gets the transform matrix from cartesian map coordinates to viewport coordinates.
/// </summary>
public Matrix ViewportTransform { get; private set; }
/// <summary>
/// Gets the transform matrix from viewport coordinates to cartesian map coordinates.
/// </summary>
public Matrix InverseViewportTransform { get; private set; }
/// <summary>
/// Gets the rotation angle of the ViewportTransform matrix.
/// </summary>
public double ViewportRotation { get; private set; }
/// <summary>
/// Gets the scaling factor from cartesian map coordinates to viewport coordinates
/// at the projection's point of true scale.
/// </summary>
public double ViewportScale { get; private set; }
/// <summary>
/// Gets the map scale at the specified Location as viewport coordinate units per meter (px/m).
/// </summary>
public virtual Vector GetMapScale(Location location)
{ {
return new Vector(ViewportScale, ViewportScale); return new Vector(1, 1);
} }
/// <summary> /// <summary>
/// Transforms a Location in geographic coordinates to a Point in cartesian map coordinates. /// Transforms a Location in geographic coordinates to a Point in cartesian map coordinates.
/// </summary> /// </summary>
public abstract Point LocationToPoint(Location location); public abstract Point LocationToMap(Location location);
/// <summary> /// <summary>
/// Transforms a Point in cartesian map coordinates to a Location in geographic coordinates. /// Transforms a Point in cartesian map coordinates to a Location in geographic coordinates.
/// </summary> /// </summary>
public abstract Location PointToLocation(Point point); public abstract Location MapToLocation(Point point);
/// <summary> /// <summary>
/// Transforms a BoundingBox in geographic coordinates to a Rect in cartesian map coordinates. /// Transforms a BoundingBox in geographic coordinates to a Rect in cartesian map coordinates.
@ -113,8 +89,8 @@ namespace MapControl
public virtual Rect BoundingBoxToRect(BoundingBox boundingBox) public virtual Rect BoundingBoxToRect(BoundingBox boundingBox)
{ {
return new Rect( return new Rect(
LocationToPoint(new Location(boundingBox.South, boundingBox.West)), LocationToMap(new Location(boundingBox.South, boundingBox.West)),
LocationToPoint(new Location(boundingBox.North, boundingBox.East))); LocationToMap(new Location(boundingBox.North, boundingBox.East)));
} }
/// <summary> /// <summary>
@ -122,53 +98,19 @@ namespace MapControl
/// </summary> /// </summary>
public virtual BoundingBox RectToBoundingBox(Rect rect) public virtual BoundingBox RectToBoundingBox(Rect rect)
{ {
var sw = PointToLocation(new Point(rect.X, rect.Y)); var sw = MapToLocation(new Point(rect.X, rect.Y));
var ne = PointToLocation(new Point(rect.X + rect.Width, rect.Y + rect.Height)); var ne = MapToLocation(new Point(rect.X + rect.Width, rect.Y + rect.Height));
return new BoundingBox(sw.Latitude, sw.Longitude, ne.Latitude, ne.Longitude); return new BoundingBox(sw.Latitude, sw.Longitude, ne.Latitude, ne.Longitude);
} }
/// <summary>
/// Transforms a Location in geographic coordinates to a Point in viewport coordinates.
/// </summary>
public Point LocationToViewportPoint(Location location)
{
return ViewportTransform.Transform(LocationToPoint(location));
}
/// <summary>
/// Transforms a Point in viewport coordinates to a Location in geographic coordinates.
/// </summary>
public Location ViewportPointToLocation(Point point)
{
return PointToLocation(InverseViewportTransform.Transform(point));
}
/// <summary>
/// Transforms a Rect in viewport coordinates to a BoundingBox in geographic coordinates.
/// </summary>
public BoundingBox ViewportRectToBoundingBox(Rect rect)
{
var p1 = InverseViewportTransform.Transform(new Point(rect.X, rect.Y));
var p2 = InverseViewportTransform.Transform(new Point(rect.X, rect.Y + rect.Height));
var p3 = InverseViewportTransform.Transform(new Point(rect.X + rect.Width, rect.Y));
var p4 = InverseViewportTransform.Transform(new Point(rect.X + rect.Width, rect.Y + rect.Height));
rect.X = Math.Min(p1.X, Math.Min(p2.X, Math.Min(p3.X, p4.X)));
rect.Y = Math.Min(p1.Y, Math.Min(p2.Y, Math.Min(p3.Y, p4.Y)));
rect.Width = Math.Max(p1.X, Math.Max(p2.X, Math.Max(p3.X, p4.X))) - rect.X;
rect.Height = Math.Max(p1.Y, Math.Max(p2.Y, Math.Max(p3.Y, p4.Y))) - rect.Y;
return RectToBoundingBox(rect);
}
/// <summary> /// <summary>
/// Gets the CRS parameter value for a WMS GetMap request. /// Gets the CRS parameter value for a WMS GetMap request.
/// </summary> /// </summary>
public virtual string GetCrsValue() public virtual string GetCrsValue()
{ {
return CrsId.StartsWith("AUTO:") || CrsId.StartsWith("AUTO2:") return CrsId.StartsWith("AUTO:") || CrsId.StartsWith("AUTO2:")
? string.Format(CultureInfo.InvariantCulture, "{0},1,{1},{2}", CrsId, ProjectionCenter.Longitude, ProjectionCenter.Latitude) ? string.Format(CultureInfo.InvariantCulture, "{0},1,{1},{2}", CrsId, Center.Longitude, Center.Latitude)
: CrsId; : CrsId;
} }
@ -180,78 +122,5 @@ namespace MapControl
return string.Format(CultureInfo.InvariantCulture, return string.Format(CultureInfo.InvariantCulture,
"{0},{1},{2},{3}", rect.X, rect.Y, (rect.X + rect.Width), (rect.Y + rect.Height)); "{0},{1},{2},{3}", rect.X, rect.Y, (rect.X + rect.Width), (rect.Y + rect.Height));
} }
/// <summary>
/// Sets ProjectionCenter, ViewportScale, ViewportRotation, ViewportTransform and InverseViewportTransform.
/// </summary>
public void SetViewportTransform(Location projectionCenter, Location mapCenter, Point viewportCenter, double zoomLevel, double rotation)
{
ProjectionCenter = projectionCenter;
ViewportScale = 256d * Math.Pow(2d, zoomLevel) / (360d * TrueScale);
ViewportRotation = rotation;
var center = LocationToPoint(mapCenter);
var matrix = CreateViewportTransform(center, viewportCenter);
ViewportTransform = matrix;
matrix.Invert();
InverseViewportTransform = matrix;
}
private Matrix CreateViewportTransform(Point mapCenter, Point viewportCenter)
{
var matrix = new Matrix(ViewportScale, 0d, 0d, -ViewportScale, -ViewportScale * mapCenter.X, ViewportScale * mapCenter.Y);
matrix.Rotate(ViewportRotation);
matrix.Translate(viewportCenter.X, viewportCenter.Y);
return matrix;
}
internal Matrix CreateTileLayerTransform(double tileGridScale, Point tileGridTopLeft, Point tileGridOrigin)
{
var scale = ViewportScale / tileGridScale;
var matrix = new Matrix(scale, 0d, 0d, scale, 0d, 0d);
matrix.Rotate(ViewportRotation);
// tile grid origin in map coordinates
//
var mapOrigin = new Point(
tileGridTopLeft.X + tileGridOrigin.X / tileGridScale,
tileGridTopLeft.Y - tileGridOrigin.Y / tileGridScale);
// tile grid origin in viewport coordinates
//
var viewOrigin = ViewportTransform.Transform(mapOrigin);
matrix.Translate(viewOrigin.X, viewOrigin.Y);
return matrix;
}
internal Rect GetTileBounds(double tileGridScale, Point tileGridTopLeft, Size viewportSize)
{
var scale = tileGridScale / ViewportScale;
var matrix = new Matrix(scale, 0d, 0d, scale, 0d, 0d);
matrix.Rotate(-ViewportRotation);
// viewport origin in map coordinates
//
var origin = InverseViewportTransform.Transform(new Point());
// translate origin to tile grid origin in pixels
//
matrix.Translate(
tileGridScale * (origin.X - tileGridTopLeft.X),
tileGridScale * (tileGridTopLeft.Y - origin.Y));
// transforms viewport bounds to tile pixel bounds
//
var transform = new MatrixTransform { Matrix = matrix };
return transform.TransformBounds(new Rect(0d, 0d, viewportSize.Width, viewportSize.Height));
}
} }
} }

View file

@ -81,9 +81,9 @@ namespace MapControl
MapPanel.InitMapElement(this); MapPanel.InitMapElement(this);
} }
protected Point LocationToPoint(Location location) protected Point LocationToMap(Location location)
{ {
var point = parentMap.MapProjection.LocationToPoint(location); var point = parentMap.MapProjection.LocationToMap(location);
if (point.Y == double.PositiveInfinity) if (point.Y == double.PositiveInfinity)
{ {
@ -99,7 +99,7 @@ namespace MapControl
protected Point LocationToViewportPoint(Location location) protected Point LocationToViewportPoint(Location location)
{ {
return parentMap.MapProjection.ViewportTransform.Transform(LocationToPoint(location)); return parentMap.ViewTransform.MapToView(LocationToMap(location));
} }
protected double GetLongitudeOffset() protected double GetLongitudeOffset()

View file

@ -23,10 +23,10 @@ namespace MapControl
{ {
public const int TileSize = 256; public const int TileSize = 256;
public static readonly Point TileGridTopLeft = new Point( public static readonly Point TileMatrixTopLeft = new Point(
-180d * MapProjection.Wgs84MetersPerDegree, 180d * MapProjection.Wgs84MetersPerDegree); -180d * MapProjection.Wgs84MetersPerDegree, 180d * MapProjection.Wgs84MetersPerDegree);
public static double TileGridScale(int zoomLevel) public static double TileMatrixScale(int zoomLevel)
{ {
return (TileSize << zoomLevel) / (360d * MapProjection.Wgs84MetersPerDegree); return (TileSize << zoomLevel) / (360d * MapProjection.Wgs84MetersPerDegree);
} }
@ -64,7 +64,7 @@ namespace MapControl
{ {
} }
public TileGrid TileGrid { get; private set; } public TileMatrix TileMatrix { get; private set; }
public IReadOnlyCollection<Tile> Tiles { get; private set; } = new List<Tile>(); public IReadOnlyCollection<Tile> Tiles { get; private set; } = new List<Tile>();
@ -88,7 +88,7 @@ namespace MapControl
protected override void TileSourcePropertyChanged() protected override void TileSourcePropertyChanged()
{ {
if (TileGrid != null) if (TileMatrix != null)
{ {
Tiles = new List<Tile>(); Tiles = new List<Tile>();
UpdateTiles(); UpdateTiles();
@ -101,10 +101,10 @@ namespace MapControl
if (ParentMap == null || !ParentMap.MapProjection.IsWebMercator) if (ParentMap == null || !ParentMap.MapProjection.IsWebMercator)
{ {
TileGrid = null; TileMatrix = null;
UpdateTiles(); UpdateTiles();
} }
else if (SetTileGrid()) else if (SetTileMatrix())
{ {
SetRenderTransform(); SetRenderTransform();
UpdateTiles(); UpdateTiles();
@ -113,22 +113,22 @@ namespace MapControl
protected override void SetRenderTransform() protected override void SetRenderTransform()
{ {
// tile grid origin in pixels // tile matrix origin in pixels
// //
var tileGridOrigin = new Point(TileSize * TileGrid.XMin, TileSize * TileGrid.YMin); var tileMatrixOrigin = new Point(TileSize * TileMatrix.XMin, TileSize * TileMatrix.YMin);
((MatrixTransform)RenderTransform).Matrix = ParentMap.MapProjection.CreateTileLayerTransform( ((MatrixTransform)RenderTransform).Matrix = ParentMap.ViewTransform.GetTileLayerTransform(
TileGridScale(TileGrid.ZoomLevel), TileGridTopLeft, tileGridOrigin); TileMatrixScale(TileMatrix.ZoomLevel), TileMatrixTopLeft, tileMatrixOrigin);
} }
private bool SetTileGrid() private bool SetTileMatrix()
{ {
var tileGridZoomLevel = (int)Math.Floor(ParentMap.ZoomLevel + 0.001); // avoid rounding issues var tileMatrixZoomLevel = (int)Math.Floor(ParentMap.ZoomLevel + 0.001); // avoid rounding issues
// bounds in tile pixels from viewport size // bounds in tile pixels from viewport size
// //
var tileBounds = ParentMap.MapProjection.GetTileBounds( var tileBounds = ParentMap.ViewTransform.GetTileMatrixBounds(
TileGridScale(tileGridZoomLevel), TileGridTopLeft, ParentMap.RenderSize); TileMatrixScale(tileMatrixZoomLevel), TileMatrixTopLeft, ParentMap.RenderSize);
// tile column and row index bounds // tile column and row index bounds
// //
@ -137,15 +137,15 @@ namespace MapControl
var xMax = (int)Math.Floor((tileBounds.X + tileBounds.Width) / TileSize); var xMax = (int)Math.Floor((tileBounds.X + tileBounds.Width) / TileSize);
var yMax = (int)Math.Floor((tileBounds.Y + tileBounds.Height) / TileSize); var yMax = (int)Math.Floor((tileBounds.Y + tileBounds.Height) / TileSize);
if (TileGrid != null && if (TileMatrix != null &&
TileGrid.ZoomLevel == tileGridZoomLevel && TileMatrix.ZoomLevel == tileMatrixZoomLevel &&
TileGrid.XMin == xMin && TileGrid.YMin == yMin && TileMatrix.XMin == xMin && TileMatrix.YMin == yMin &&
TileGrid.XMax == xMax && TileGrid.YMax == yMax) TileMatrix.XMax == xMax && TileMatrix.YMax == yMax)
{ {
return false; return false;
} }
TileGrid = new TileGrid(tileGridZoomLevel, xMin, yMin, xMax, yMax); TileMatrix = new TileMatrix(tileMatrixZoomLevel, xMin, yMin, xMax, yMax);
return true; return true;
} }
@ -154,9 +154,9 @@ namespace MapControl
{ {
var newTiles = new List<Tile>(); var newTiles = new List<Tile>();
if (ParentMap != null && TileGrid != null && TileSource != null) if (ParentMap != null && TileMatrix != null && TileSource != null)
{ {
var maxZoomLevel = Math.Min(TileGrid.ZoomLevel, MaxZoomLevel); var maxZoomLevel = Math.Min(TileMatrix.ZoomLevel, MaxZoomLevel);
if (maxZoomLevel >= MinZoomLevel) if (maxZoomLevel >= MinZoomLevel)
{ {
@ -164,16 +164,16 @@ namespace MapControl
if (this == ParentMap.MapLayer) // load background tiles if (this == ParentMap.MapLayer) // load background tiles
{ {
minZoomLevel = Math.Max(TileGrid.ZoomLevel - MaxBackgroundLevels, MinZoomLevel); minZoomLevel = Math.Max(TileMatrix.ZoomLevel - MaxBackgroundLevels, MinZoomLevel);
} }
for (var z = minZoomLevel; z <= maxZoomLevel; z++) for (var z = minZoomLevel; z <= maxZoomLevel; z++)
{ {
var tileSize = 1 << (TileGrid.ZoomLevel - z); var tileSize = 1 << (TileMatrix.ZoomLevel - z);
var x1 = (int)Math.Floor((double)TileGrid.XMin / tileSize); // may be negative var x1 = (int)Math.Floor((double)TileMatrix.XMin / tileSize); // may be negative
var x2 = TileGrid.XMax / tileSize; var x2 = TileMatrix.XMax / tileSize;
var y1 = Math.Max(TileGrid.YMin / tileSize, 0); var y1 = Math.Max(TileMatrix.YMin / tileSize, 0);
var y2 = Math.Min(TileGrid.YMax / tileSize, (1 << z) - 1); var y2 = Math.Min(TileMatrix.YMax / tileSize, (1 << z) - 1);
for (var y = y1; y <= y2; y++) for (var y = y1; y <= y2; y++)
{ {
@ -227,13 +227,13 @@ namespace MapControl
protected override Size ArrangeOverride(Size finalSize) protected override Size ArrangeOverride(Size finalSize)
{ {
if (TileGrid != null) if (TileMatrix != null)
{ {
foreach (var tile in Tiles) foreach (var tile in Tiles)
{ {
var tileSize = TileSize << (TileGrid.ZoomLevel - tile.ZoomLevel); var tileSize = TileSize << (TileMatrix.ZoomLevel - tile.ZoomLevel);
var x = tileSize * tile.X - TileSize * TileGrid.XMin; var x = tileSize * tile.X - TileSize * TileMatrix.XMin;
var y = tileSize * tile.Y - TileSize * TileGrid.YMin; var y = tileSize * tile.Y - TileSize * TileMatrix.YMin;
tile.Image.Width = tileSize; tile.Image.Width = tileSize;
tile.Image.Height = tileSize; tile.Image.Height = tileSize;

View file

@ -19,31 +19,31 @@ namespace MapControl
CrsId = "AUTO2:42003"; CrsId = "AUTO2:42003";
} }
public override Point LocationToPoint(Location location) public override Point LocationToMap(Location location)
{ {
if (location.Equals(ProjectionCenter)) if (location.Equals(Center))
{ {
return new Point(); return new Point();
} }
var lat0 = ProjectionCenter.Latitude * Math.PI / 180d; var lat0 = Center.Latitude * Math.PI / 180d;
var lat = location.Latitude * Math.PI / 180d; var lat = location.Latitude * Math.PI / 180d;
var dLon = (location.Longitude - ProjectionCenter.Longitude) * Math.PI / 180d; var dLon = (location.Longitude - Center.Longitude) * Math.PI / 180d;
var s = TrueScale * 180d / Math.PI; var s = UnitsPerDegree * 180d / Math.PI;
return new Point( return new Point(
s * Math.Cos(lat) * Math.Sin(dLon), s * Math.Cos(lat) * Math.Sin(dLon),
s * (Math.Cos(lat0) * Math.Sin(lat) - Math.Sin(lat0) * Math.Cos(lat) * Math.Cos(dLon))); s * (Math.Cos(lat0) * Math.Sin(lat) - Math.Sin(lat0) * Math.Cos(lat) * Math.Cos(dLon)));
} }
public override Location PointToLocation(Point point) public override Location MapToLocation(Point point)
{ {
if (point.X == 0d && point.Y == 0d) if (point.X == 0d && point.Y == 0d)
{ {
return new Location(ProjectionCenter.Latitude, ProjectionCenter.Longitude); return new Location(Center.Latitude, Center.Longitude);
} }
var s = TrueScale * 180d / Math.PI; var s = UnitsPerDegree * 180d / Math.PI;
var x = point.X / s; var x = point.X / s;
var y = point.Y / s; var y = point.Y / s;
var r2 = x * x + y * y; var r2 = x * x + y * y;
@ -57,13 +57,13 @@ namespace MapControl
var sinC = r; var sinC = r;
var cosC = Math.Sqrt(1 - r2); var cosC = Math.Sqrt(1 - r2);
var lat0 = ProjectionCenter.Latitude * Math.PI / 180d; var lat0 = Center.Latitude * Math.PI / 180d;
var cosLat0 = Math.Cos(lat0); var cosLat0 = Math.Cos(lat0);
var sinLat0 = Math.Sin(lat0); var sinLat0 = Math.Sin(lat0);
return new Location( return new Location(
180d / Math.PI * Math.Asin(cosC * sinLat0 + y * sinC * cosLat0 / r), 180d / Math.PI * Math.Asin(cosC * sinLat0 + y * sinC * cosLat0 / r),
180d / Math.PI * Math.Atan2(x * sinC, r * cosC * cosLat0 - y * sinC * sinLat0) + ProjectionCenter.Longitude); 180d / Math.PI * Math.Atan2(x * sinC, r * cosC * cosLat0 - y * sinC * sinLat0) + Center.Longitude);
} }
} }
} }

View file

@ -19,35 +19,35 @@ namespace MapControl
CrsId = "AUTO2:97002"; // GeoServer non-standard CRS ID CrsId = "AUTO2:97002"; // GeoServer non-standard CRS ID
} }
public override Point LocationToPoint(Location location) public override Point LocationToMap(Location location)
{ {
if (location.Equals(ProjectionCenter)) if (location.Equals(Center))
{ {
return new Point(); return new Point();
} }
double azimuth, distance; double azimuth, distance;
GetAzimuthDistance(ProjectionCenter, location, out azimuth, out distance); GetAzimuthDistance(Center, location, out azimuth, out distance);
var mapDistance = Math.Tan(distance / 2d) * 2d * TrueScale * 180d / Math.PI; var mapDistance = Math.Tan(distance / 2d) * 2d * UnitsPerDegree * 180d / Math.PI;
return new Point(mapDistance * Math.Sin(azimuth), mapDistance * Math.Cos(azimuth)); return new Point(mapDistance * Math.Sin(azimuth), mapDistance * Math.Cos(azimuth));
} }
public override Location PointToLocation(Point point) public override Location MapToLocation(Point point)
{ {
if (point.X == 0d && point.Y == 0d) if (point.X == 0d && point.Y == 0d)
{ {
return new Location(ProjectionCenter.Latitude, ProjectionCenter.Longitude); return new Location(Center.Latitude, Center.Longitude);
} }
var azimuth = Math.Atan2(point.X, point.Y); var azimuth = Math.Atan2(point.X, point.Y);
var mapDistance = Math.Sqrt(point.X * point.X + point.Y * point.Y); var mapDistance = Math.Sqrt(point.X * point.X + point.Y * point.Y);
var distance = 2d * Math.Atan(mapDistance / (2d * TrueScale * 180d / Math.PI)); var distance = 2d * Math.Atan(mapDistance / (2d * UnitsPerDegree * 180d / Math.PI));
return GetLocation(ProjectionCenter, azimuth, distance); return GetLocation(Center, azimuth, distance);
} }
} }
} }

View file

@ -4,7 +4,7 @@
namespace MapControl namespace MapControl
{ {
public class TileGrid public class TileMatrix
{ {
public readonly int ZoomLevel; public readonly int ZoomLevel;
public readonly int XMin; public readonly int XMin;
@ -12,7 +12,7 @@ namespace MapControl
public readonly int XMax; public readonly int XMax;
public readonly int YMax; public readonly int YMax;
public TileGrid(int zoomLevel, int xMin, int yMin, int xMax, int yMax) public TileMatrix(int zoomLevel, int xMin, int yMin, int xMax, int yMax)
{ {
ZoomLevel = zoomLevel; ZoomLevel = zoomLevel;
XMin = xMin; XMin = xMin;

View file

@ -0,0 +1,118 @@
// 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.Foundation;
using Windows.UI.Xaml.Media;
#else
using System.Windows;
using System.Windows.Media;
#endif
namespace MapControl
{
/// <summary>
/// Defines the transformation between cartesian map coordinates and viewport coordinates.
/// </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.
/// </summary>
public double Scale { get; private set; }
/// <summary>
/// Gets the rotation angle of the transform matrix.
/// </summary>
public double Rotation { get; private set; }
/// <summary>
/// Transforms a Point from cartesian map coordinates to viewport coordinates.
/// </summary>
public Point MapToView(Point point)
{
return MapToViewMatrix.Transform(point);
}
/// <summary>
/// Transforms a Point from viewport 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)
{
Scale = scale;
Rotation = rotation;
var transform = new Matrix(Scale, 0d, 0d, -Scale, -Scale * mapCenter.X, Scale * mapCenter.Y);
transform.Rotate(Rotation);
transform.Translate(viewportCenter.X, viewportCenter.Y);
MapToViewMatrix = transform;
transform.Invert();
ViewToMapMatrix = transform;
}
public Matrix GetTileLayerTransform(double tileMatrixScale, Point tileMatrixTopLeft, Point tileMatrixOrigin)
{
var transformScale = Scale / tileMatrixScale;
var transform = new Matrix(transformScale, 0d, 0d, transformScale, 0d, 0d);
transform.Rotate(Rotation);
// tile matrix origin in map coordinates
//
var mapOrigin = new Point(
tileMatrixTopLeft.X + tileMatrixOrigin.X / tileMatrixScale,
tileMatrixTopLeft.Y - tileMatrixOrigin.Y / tileMatrixScale);
// tile matrix origin in viewport coordinates
//
var viewOrigin = MapToView(mapOrigin);
transform.Translate(viewOrigin.X, viewOrigin.Y);
return transform;
}
public Rect GetTileMatrixBounds(double tileMatrixScale, Point tileMatrixTopLeft, Size viewportSize)
{
var transformScale = tileMatrixScale / Scale;
var transform = new Matrix(transformScale, 0d, 0d, transformScale, 0d, 0d);
transform.Rotate(-Rotation);
// viewport origin in map coordinates
//
var origin = ViewToMap(new Point());
// translate origin to tile matrix origin in pixels
//
transform.Translate(
tileMatrixScale * (origin.X - tileMatrixTopLeft.X),
tileMatrixScale * (tileMatrixTopLeft.Y - origin.Y));
// transform viewport bounds to tile pixel bounds
//
return new MatrixTransform { Matrix = transform }
.TransformBounds(new Rect(0d, 0d, viewportSize.Width, viewportSize.Height));
}
}
}

View file

@ -32,25 +32,25 @@ namespace MapControl
get { return maxLatitude; } get { return maxLatitude; }
} }
public override Vector GetMapScale(Location location) public override Vector GetRelativeScale(Location location)
{ {
var k = 1d / Math.Cos(location.Latitude * Math.PI / 180d); // p.44 (7-3) var k = 1d / Math.Cos(location.Latitude * Math.PI / 180d); // p.44 (7-3)
return new Vector(ViewportScale * k, ViewportScale * k); return new Vector(k, k);
} }
public override Point LocationToPoint(Location location) public override Point LocationToMap(Location location)
{ {
return new Point( return new Point(
TrueScale * location.Longitude, UnitsPerDegree * location.Longitude,
TrueScale * LatitudeToY(location.Latitude)); UnitsPerDegree * LatitudeToY(location.Latitude));
} }
public override Location PointToLocation(Point point) public override Location MapToLocation(Point point)
{ {
return new Location( return new Location(
YToLatitude(point.Y / TrueScale), YToLatitude(point.Y / UnitsPerDegree),
point.X / TrueScale); point.X / UnitsPerDegree);
} }
public static double LatitudeToY(double latitude) public static double LatitudeToY(double latitude)

View file

@ -142,8 +142,8 @@ namespace MapControl
uri += "&CRS=" + projection.GetCrsValue(); uri += "&CRS=" + projection.GetCrsValue();
uri += "&BBOX=" + projection.GetBboxValue(rect); uri += "&BBOX=" + projection.GetBboxValue(rect);
uri += "&WIDTH=" + (int)Math.Round(projection.ViewportScale * rect.Width); uri += "&WIDTH=" + (int)Math.Round(ParentMap.ViewTransform.Scale * rect.Width);
uri += "&HEIGHT=" + (int)Math.Round(projection.ViewportScale * rect.Height); uri += "&HEIGHT=" + (int)Math.Round(ParentMap.ViewTransform.Scale * rect.Height);
} }
return uri; return uri;

View file

@ -104,14 +104,14 @@ namespace MapControl
{ {
foreach (var layer in ChildLayers) foreach (var layer in ChildLayers)
{ {
layer.SetRenderTransform(ParentMap.MapProjection); layer.SetRenderTransform(ParentMap.ViewTransform);
} }
} }
private bool UpdateChildLayers(WmtsTileMatrixSet tileMatrixSet) private bool UpdateChildLayers(WmtsTileMatrixSet tileMatrixSet)
{ {
var layersChanged = false; var layersChanged = false;
var maxScale = 1.001 * ParentMap.MapProjection.ViewportScale; // avoid rounding issues var maxScale = 1.001 * ParentMap.ViewTransform.Scale; // avoid rounding issues
// show all TileMatrix layers with Scale <= maxScale, at least the first layer // show all TileMatrix layers with Scale <= maxScale, at least the first layer
// //
@ -142,7 +142,7 @@ namespace MapControl
layersChanged = true; layersChanged = true;
} }
if (layer.SetBounds(ParentMap.MapProjection, ParentMap.RenderSize)) if (layer.SetBounds(ParentMap.ViewTransform, ParentMap.RenderSize))
{ {
layersChanged = true; layersChanged = true;
} }

View file

@ -37,21 +37,21 @@ namespace MapControl
public IReadOnlyCollection<Tile> Tiles { get; private set; } = new List<Tile>(); public IReadOnlyCollection<Tile> Tiles { get; private set; } = new List<Tile>();
public void SetRenderTransform(MapProjection projection) public void SetRenderTransform(ViewTransform viewTransform)
{ {
// tile grid origin in pixels // tile matrix origin in pixels
// //
var tileGridOrigin = new Point(TileMatrix.TileWidth * XMin, TileMatrix.TileHeight * YMin); var tileMatrixOrigin = new Point(TileMatrix.TileWidth * XMin, TileMatrix.TileHeight * YMin);
((MatrixTransform)RenderTransform).Matrix = ((MatrixTransform)RenderTransform).Matrix =
projection.CreateTileLayerTransform(TileMatrix.Scale, TileMatrix.TopLeft, tileGridOrigin); viewTransform.GetTileLayerTransform(TileMatrix.Scale, TileMatrix.TopLeft, tileMatrixOrigin);
} }
public bool SetBounds(MapProjection projection, Size viewportSize) public bool SetBounds(ViewTransform viewTransform, Size viewportSize)
{ {
// bounds in tile pixels from viewport size // bounds in tile pixels from viewport size
// //
var bounds = projection.GetTileBounds(TileMatrix.Scale, TileMatrix.TopLeft, viewportSize); var bounds = viewTransform.GetTileMatrixBounds(TileMatrix.Scale, TileMatrix.TopLeft, viewportSize);
// tile column and row index bounds // tile column and row index bounds
// //

View file

@ -30,27 +30,27 @@ namespace MapControl
get { return maxLatitude; } get { return maxLatitude; }
} }
public override Vector GetMapScale(Location location) public override Vector GetRelativeScale(Location location)
{ {
var lat = location.Latitude * Math.PI / 180d; var lat = location.Latitude * Math.PI / 180d;
var eSinLat = Wgs84Eccentricity * Math.Sin(lat); var eSinLat = Wgs84Eccentricity * Math.Sin(lat);
var k = Math.Sqrt(1d - eSinLat * eSinLat) / Math.Cos(lat); // p.44 (7-8) var k = Math.Sqrt(1d - eSinLat * eSinLat) / Math.Cos(lat); // p.44 (7-8)
return new Vector(ViewportScale * k, ViewportScale * k); return new Vector(k, k);
} }
public override Point LocationToPoint(Location location) public override Point LocationToMap(Location location)
{ {
return new Point( return new Point(
TrueScale * location.Longitude, UnitsPerDegree * location.Longitude,
TrueScale * LatitudeToY(location.Latitude)); UnitsPerDegree * LatitudeToY(location.Latitude));
} }
public override Location PointToLocation(Point point) public override Location MapToLocation(Point point)
{ {
return new Location( return new Location(
YToLatitude(point.Y / TrueScale), YToLatitude(point.Y / UnitsPerDegree),
point.X / TrueScale); point.X / UnitsPerDegree);
} }
public static double LatitudeToY(double latitude) public static double LatitudeToY(double latitude)

View file

@ -45,10 +45,6 @@ namespace MapControl
public MapBase() public MapBase()
{ {
MapProjection = new WebMercatorProjection();
ScaleRotateTransform.Children.Add(ScaleTransform);
ScaleRotateTransform.Children.Add(RotateTransform);
// set Background by Style to enable resetting by ClearValue in MapLayerPropertyChanged // set Background by Style to enable resetting by ClearValue in MapLayerPropertyChanged
var style = new Style(typeof(MapBase)); var style = new Style(typeof(MapBase));
style.Setters.Add(new Setter(BackgroundProperty, new SolidColorBrush(Colors.Transparent))); style.Setters.Add(new Setter(BackgroundProperty, new SolidColorBrush(Colors.Transparent)));

View file

@ -140,18 +140,21 @@
<Compile Include="..\Shared\Tile.cs"> <Compile Include="..\Shared\Tile.cs">
<Link>Tile.cs</Link> <Link>Tile.cs</Link>
</Compile> </Compile>
<Compile Include="..\Shared\TileGrid.cs">
<Link>TileGrid.cs</Link>
</Compile>
<Compile Include="..\Shared\TileImageLoader.cs"> <Compile Include="..\Shared\TileImageLoader.cs">
<Link>TileImageLoader.cs</Link> <Link>TileImageLoader.cs</Link>
</Compile> </Compile>
<Compile Include="..\Shared\TileMatrix.cs">
<Link>TileMatrix.cs</Link>
</Compile>
<Compile Include="..\Shared\TileSource.cs"> <Compile Include="..\Shared\TileSource.cs">
<Link>TileSource.cs</Link> <Link>TileSource.cs</Link>
</Compile> </Compile>
<Compile Include="..\Shared\ViewportChangedEventArgs.cs"> <Compile Include="..\Shared\ViewportChangedEventArgs.cs">
<Link>ViewportChangedEventArgs.cs</Link> <Link>ViewportChangedEventArgs.cs</Link>
</Compile> </Compile>
<Compile Include="..\Shared\ViewTransform.cs">
<Link>ViewTransform.cs</Link>
</Compile>
<Compile Include="..\Shared\WebMercatorProjection.cs"> <Compile Include="..\Shared\WebMercatorProjection.cs">
<Link>WebMercatorProjection.cs</Link> <Link>WebMercatorProjection.cs</Link>
</Compile> </Compile>

View file

@ -24,7 +24,8 @@ namespace MapControl
protected override void OnViewportChanged(ViewportChangedEventArgs e) protected override void OnViewportChanged(ViewportChangedEventArgs e)
{ {
var projection = ParentMap.MapProjection; var map = ParentMap;
var projection = map.MapProjection;
if (projection.IsNormalCylindrical) if (projection.IsNormalCylindrical)
{ {
@ -39,7 +40,7 @@ namespace MapControl
Children.Add(path); Children.Add(path);
} }
var bounds = projection.ViewportRectToBoundingBox(new Rect(0d, 0d, ParentMap.RenderSize.Width, ParentMap.RenderSize.Height)); var bounds = map.ViewportRectToBoundingBox(new Rect(0d, 0d, map.RenderSize.Width, map.RenderSize.Height));
var lineDistance = GetLineDistance(); var lineDistance = GetLineDistance();
var labelStart = new Location( var labelStart = new Location(
@ -65,14 +66,14 @@ namespace MapControl
{ {
var figure = new PathFigure var figure = new PathFigure
{ {
StartPoint = projection.LocationToViewportPoint(new Location(lat, lineStart.Longitude)), StartPoint = map.LocationToViewportPoint(new Location(lat, lineStart.Longitude)),
IsClosed = false, IsClosed = false,
IsFilled = false IsFilled = false
}; };
figure.Segments.Add(new LineSegment figure.Segments.Add(new LineSegment
{ {
Point = projection.LocationToViewportPoint(new Location(lat, lineEnd.Longitude)) Point = map.LocationToViewportPoint(new Location(lat, lineEnd.Longitude))
}); });
geometry.Figures.Add(figure); geometry.Figures.Add(figure);
@ -82,14 +83,14 @@ namespace MapControl
{ {
var figure = new PathFigure var figure = new PathFigure
{ {
StartPoint = projection.LocationToViewportPoint(new Location(lineStart.Latitude, lon)), StartPoint = map.LocationToViewportPoint(new Location(lineStart.Latitude, lon)),
IsClosed = false, IsClosed = false,
IsFilled = false IsFilled = false
}; };
figure.Segments.Add(new LineSegment figure.Segments.Add(new LineSegment
{ {
Point = projection.LocationToViewportPoint(new Location(lineEnd.Latitude, lon)) Point = map.LocationToViewportPoint(new Location(lineEnd.Latitude, lon))
}); });
geometry.Figures.Add(figure); geometry.Figures.Add(figure);
@ -112,7 +113,7 @@ namespace MapControl
{ {
var renderTransform = new TransformGroup(); var renderTransform = new TransformGroup();
renderTransform.Children.Add(new TranslateTransform()); renderTransform.Children.Add(new TranslateTransform());
renderTransform.Children.Add(ParentMap.RotateTransform); renderTransform.Children.Add(map.RotateTransform);
renderTransform.Children.Add(new TranslateTransform()); renderTransform.Children.Add(new TranslateTransform());
label = new TextBlock { RenderTransform = renderTransform }; label = new TextBlock { RenderTransform = renderTransform };
@ -153,7 +154,7 @@ namespace MapControl
var label = (TextBlock)Children[i]; var label = (TextBlock)Children[i];
var location = (Location)label.Tag; var location = (Location)label.Tag;
var viewportTransform = (TranslateTransform)((TransformGroup)label.RenderTransform).Children[2]; var viewportTransform = (TranslateTransform)((TransformGroup)label.RenderTransform).Children[2];
var viewportPosition = projection.LocationToViewportPoint(location); var viewportPosition = map.LocationToViewportPoint(location);
viewportTransform.X = viewportPosition.X; viewportTransform.X = viewportPosition.X;
viewportTransform.Y = viewportPosition.Y; viewportTransform.Y = viewportPosition.Y;
} }

View file

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

View file

@ -53,13 +53,6 @@ namespace MapControl
BackgroundProperty.OverrideMetadata(typeof(MapBase), new FrameworkPropertyMetadata(Brushes.Transparent)); BackgroundProperty.OverrideMetadata(typeof(MapBase), new FrameworkPropertyMetadata(Brushes.Transparent));
} }
public MapBase()
{
MapProjection = new WebMercatorProjection();
ScaleRotateTransform.Children.Add(ScaleTransform);
ScaleRotateTransform.Children.Add(RotateTransform);
}
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{ {
base.OnRenderSizeChanged(sizeInfo); base.OnRenderSizeChanged(sizeInfo);

View file

@ -9,7 +9,7 @@
<AssemblyOriginatorKeyFile>..\..\MapControl.snk</AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>..\..\MapControl.snk</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign> <DelaySign>false</DelaySign>
<Product>XAML Map Control</Product> <Product>XAML Map Control</Product>
<Version>4.17.0</Version> <Version>5.0.0</Version>
<Description>XAML Map Control Library</Description> <Description>XAML Map Control Library</Description>
<Authors>Clemens Fischer</Authors> <Authors>Clemens Fischer</Authors>
<Copyright>Copyright © 2020 Clemens Fischer</Copyright> <Copyright>Copyright © 2020 Clemens Fischer</Copyright>

View file

@ -46,7 +46,7 @@ namespace MapControl
if (projection.IsNormalCylindrical) if (projection.IsNormalCylindrical)
{ {
DrawCylindricalGraticule(drawingContext, projection, lineDistance, labelFormat); DrawCylindricalGraticule(drawingContext, lineDistance, labelFormat);
} }
else else
{ {
@ -54,9 +54,9 @@ namespace MapControl
} }
} }
private void DrawCylindricalGraticule(DrawingContext drawingContext, MapProjection projection, double lineDistance, string labelFormat) private void DrawCylindricalGraticule(DrawingContext drawingContext, double lineDistance, string labelFormat)
{ {
var boundingBox = projection.ViewportRectToBoundingBox(new Rect(ParentMap.RenderSize)); var boundingBox = ParentMap.ViewportRectToBoundingBox(new Rect(ParentMap.RenderSize));
var latLabelStart = Math.Ceiling(boundingBox.South / lineDistance) * lineDistance; var latLabelStart = Math.Ceiling(boundingBox.South / lineDistance) * lineDistance;
var lonLabelStart = Math.Ceiling(boundingBox.West / lineDistance) * lineDistance; var lonLabelStart = Math.Ceiling(boundingBox.West / lineDistance) * lineDistance;
var latLabels = new List<Label>((int)((boundingBox.North - latLabelStart) / lineDistance) + 1); 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))); CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Foreground, pixelsPerDip)));
drawingContext.DrawLine(pen, drawingContext.DrawLine(pen,
projection.LocationToViewportPoint(new Location(lat, boundingBox.West)), ParentMap.LocationToViewportPoint(new Location(lat, boundingBox.West)),
projection.LocationToViewportPoint(new Location(lat, boundingBox.East))); ParentMap.LocationToViewportPoint(new Location(lat, boundingBox.East)));
} }
for (var lon = lonLabelStart; lon <= boundingBox.East; lon += lineDistance) for (var lon = lonLabelStart; lon <= boundingBox.East; lon += lineDistance)
@ -83,15 +83,15 @@ namespace MapControl
CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Foreground, pixelsPerDip))); CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Foreground, pixelsPerDip)));
drawingContext.DrawLine(pen, drawingContext.DrawLine(pen,
projection.LocationToViewportPoint(new Location(boundingBox.South, lon)), ParentMap.LocationToViewportPoint(new Location(boundingBox.South, lon)),
projection.LocationToViewportPoint(new Location(boundingBox.North, lon))); ParentMap.LocationToViewportPoint(new Location(boundingBox.North, lon)));
} }
foreach (var latLabel in latLabels) foreach (var latLabel in latLabels)
{ {
foreach (var lonLabel in lonLabels) foreach (var lonLabel in lonLabels)
{ {
var position = projection.LocationToViewportPoint(new Location(latLabel.Position, lonLabel.Position)); var position = ParentMap.LocationToViewportPoint(new Location(latLabel.Position, lonLabel.Position));
drawingContext.PushTransform(new RotateTransform(ParentMap.Heading, position.X, position.Y)); drawingContext.PushTransform(new RotateTransform(ParentMap.Heading, position.X, position.Y));
drawingContext.DrawText(latLabel.Text, drawingContext.DrawText(latLabel.Text,

View file

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

View file

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

View file

@ -25,11 +25,11 @@ namespace MapControl.Projections
private ICoordinateSystem coordinateSystem; private ICoordinateSystem coordinateSystem;
private bool isNormalCylindrical; private bool isNormalCylindrical;
private bool isWebMercator; private bool isWebMercator;
private double trueScale; private double unitsPerDegree;
private string bboxFormat; private string bboxFormat;
public IMathTransform LocationToPointTransform { get; private set; } public IMathTransform LocationToMapTransform { get; private set; }
public IMathTransform PointToLocationTransform { get; private set; } public IMathTransform MapToLocationTransform { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the ICoordinateSystem of the MapProjection. /// Gets or sets the ICoordinateSystem of the MapProjection.
@ -48,11 +48,11 @@ namespace MapControl.Projections
var transformFactory = new CoordinateTransformationFactory(); var transformFactory = new CoordinateTransformationFactory();
LocationToPointTransform = transformFactory LocationToMapTransform = transformFactory
.CreateFromCoordinateSystems(GeographicCoordinateSystem.WGS84, coordinateSystem) .CreateFromCoordinateSystems(GeographicCoordinateSystem.WGS84, coordinateSystem)
.MathTransform; .MathTransform;
PointToLocationTransform = transformFactory MapToLocationTransform = transformFactory
.CreateFromCoordinateSystems(coordinateSystem, GeographicCoordinateSystem.WGS84) .CreateFromCoordinateSystems(coordinateSystem, GeographicCoordinateSystem.WGS84)
.MathTransform; .MathTransform;
@ -76,14 +76,14 @@ namespace MapControl.Projections
(falseEasting == null || falseEasting.Value == 0d) && (falseEasting == null || falseEasting.Value == 0d) &&
(falseNorthing == null || falseNorthing.Value == 0d); (falseNorthing == null || falseNorthing.Value == 0d);
isWebMercator = CrsId == "EPSG:3857" || CrsId == "EPSG:900913"; isWebMercator = CrsId == "EPSG:3857" || CrsId == "EPSG:900913";
trueScale = (scaleFactor != null ? scaleFactor.Value : 1d) * Wgs84MetersPerDegree; unitsPerDegree = (scaleFactor != null ? scaleFactor.Value : 1d) * Wgs84MetersPerDegree;
bboxFormat = "{0},{1},{2},{3}"; bboxFormat = "{0},{1},{2},{3}";
} }
else else
{ {
isNormalCylindrical = true; isNormalCylindrical = true;
isWebMercator = false; isWebMercator = false;
trueScale = 1d; unitsPerDegree = 1d;
bboxFormat = "{1},{0},{3},{2}"; bboxFormat = "{1},{0},{3},{2}";
} }
} }
@ -110,31 +110,31 @@ namespace MapControl.Projections
get { return isWebMercator; } get { return isWebMercator; }
} }
public override double TrueScale public override double UnitsPerDegree
{ {
get { return trueScale; } get { return unitsPerDegree; }
} }
public override Point LocationToPoint(Location location) public override Point LocationToMap(Location location)
{ {
if (LocationToPointTransform == null) if (LocationToMapTransform == null)
{ {
throw new InvalidOperationException("The CoordinateSystem property is not set."); throw new InvalidOperationException("The CoordinateSystem property is not set.");
} }
var coordinate = LocationToPointTransform.Transform(new Coordinate(location.Longitude, location.Latitude)); var coordinate = LocationToMapTransform.Transform(new Coordinate(location.Longitude, location.Latitude));
return new Point(coordinate.X, coordinate.Y); return new Point(coordinate.X, coordinate.Y);
} }
public override Location PointToLocation(Point point) public override Location MapToLocation(Point point)
{ {
if (PointToLocationTransform == null) if (MapToLocationTransform == null)
{ {
throw new InvalidOperationException("The CoordinateSystem property is not set."); throw new InvalidOperationException("The CoordinateSystem property is not set.");
} }
var coordinate = PointToLocationTransform.Transform(new Coordinate(point.X, point.Y)); var coordinate = MapToLocationTransform.Transform(new Coordinate(point.X, point.Y));
return new Location(coordinate.Y, coordinate.X); return new Location(coordinate.Y, coordinate.X);
} }

View file

@ -33,12 +33,12 @@ namespace MapControl.Projections
this.falseNorthing = falseNorthing; this.falseNorthing = falseNorthing;
} }
public override double TrueScale public override double UnitsPerDegree
{ {
get { return scaleFactor * Wgs84MetersPerDegree; } get { return scaleFactor * Wgs84MetersPerDegree; }
} }
public override Vector GetMapScale(Location location) public override Vector GetRelativeScale(Location location)
{ {
var lat = (north ? location.Latitude : -location.Latitude) * Math.PI / 180d; var lat = (north ? location.Latitude : -location.Latitude) * Math.PI / 180d;
var a = Wgs84EquatorialRadius; var a = Wgs84EquatorialRadius;
@ -50,10 +50,10 @@ namespace MapControl.Projections
var m = Math.Cos(lat) / Math.Sqrt(1d - eSinLat * eSinLat); var m = Math.Cos(lat) / Math.Sqrt(1d - eSinLat * eSinLat);
var k = rho / (a * m); var k = rho / (a * m);
return new Vector(ViewportScale * k, ViewportScale * k); return new Vector(k, k);
} }
public override Point LocationToPoint(Location location) public override Point LocationToMap(Location location)
{ {
var lat = location.Latitude * Math.PI / 180d; var lat = location.Latitude * Math.PI / 180d;
var lon = location.Longitude * Math.PI / 180d; var lon = location.Longitude * Math.PI / 180d;
@ -76,7 +76,7 @@ namespace MapControl.Projections
return new Point(rho * Math.Sin(lon) + falseEasting, rho * Math.Cos(lon) + falseNorthing); return new Point(rho * Math.Sin(lon) + falseEasting, rho * Math.Cos(lon) + falseNorthing);
} }
public override Location PointToLocation(Point point) public override Location MapToLocation(Point point)
{ {
point.X -= falseEasting; point.X -= falseEasting;
point.Y -= falseNorthing; point.Y -= falseNorthing;

View file

@ -21,11 +21,11 @@ namespace MapControl.Projections
CoordinateSystem = ProjectedCoordinateSystem.WebMercator; CoordinateSystem = ProjectedCoordinateSystem.WebMercator;
} }
public override Vector GetMapScale(Location location) public override Vector GetRelativeScale(Location location)
{ {
var k = 1d / Math.Cos(location.Latitude * Math.PI / 180d); // p.44 (7-3) var k = 1d / Math.Cos(location.Latitude * Math.PI / 180d); // p.44 (7-3)
return new Vector(ViewportScale * k, ViewportScale * k); return new Vector(k, k);
} }
} }
} }

View file

@ -37,13 +37,13 @@ namespace MapControl.Projections
"AUTHORITY[\"EPSG\",\"3395\"]]"; "AUTHORITY[\"EPSG\",\"3395\"]]";
} }
public override Vector GetMapScale(Location location) public override Vector GetRelativeScale(Location location)
{ {
var lat = location.Latitude * Math.PI / 180d; var lat = location.Latitude * Math.PI / 180d;
var eSinLat = Wgs84Eccentricity * Math.Sin(lat); var eSinLat = Wgs84Eccentricity * Math.Sin(lat);
var k = Math.Sqrt(1d - eSinLat * eSinLat) / Math.Cos(lat); // p.44 (7-8) var k = Math.Sqrt(1d - eSinLat * eSinLat) / Math.Cos(lat); // p.44 (7-8)
return new Vector(ViewportScale * k, ViewportScale * k); return new Vector(k, k);
} }
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -66,7 +66,7 @@ namespace ProjectionDemo
var map = (MapBase)sender; var map = (MapBase)sender;
var pos = e.GetPosition(map); var pos = e.GetPosition(map);
viewModel.PushpinLocation = map.MapProjection.ViewportPointToLocation(pos); viewModel.PushpinLocation = map.ViewportPointToLocation(pos);
} }
} }

View file

@ -4,7 +4,7 @@
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<Version>4.17.0</Version> <Version>5.0.0</Version>
<Authors>Clemens Fischer</Authors> <Authors>Clemens Fischer</Authors>
<Description>XAML Map Control Map Projection Demo Application</Description> <Description>XAML Map Control Map Projection Demo Application</Description>
<Product>XAML Map Control</Product> <Product>XAML Map Control</Product>

View file

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

View file

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

View file

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