Removed azimuthal and "auto" map projections

Removing these projections - which were never really well implemented - greatly simplifies the code. There is no ProjectionCenter anymore and MapProjection methods do not return null Locations or Points.
This commit is contained in:
ClemensFischer 2026-01-29 16:27:34 +01:00
parent f4481c31f0
commit 76b879dfac
37 changed files with 181 additions and 1076 deletions

View file

@ -13,8 +13,8 @@ namespace MapControl
public static readonly AttachedProperty<BoundingBox> BoundingBoxProperty =
DependencyPropertyHelper.RegisterAttached<BoundingBox>("BoundingBox", typeof(MapPanel));
public static readonly AttachedProperty<MapRect> MapRectProperty =
DependencyPropertyHelper.RegisterAttached<MapRect>("MapRect", typeof(MapPanel));
public static readonly AttachedProperty<Rect?> MapRectProperty =
DependencyPropertyHelper.RegisterAttached<Rect?>("MapRect", typeof(MapPanel));
static MapPanel()
{

View file

@ -41,10 +41,7 @@ namespace MapControl
private void AddPolylinePoints(PathFigures figures, IEnumerable<Location> locations, double longitudeOffset, bool closed)
{
var points = locations
.Select(location => LocationToView(location, longitudeOffset))
.Where(point => point.HasValue)
.Select(point => point.Value);
var points = locations.Select(location => LocationToView(location, longitudeOffset));
if (points.Any())
{

View file

@ -1,78 +0,0 @@
using System;
#if WPF
using System.Windows;
using System.Windows.Media;
#elif AVALONIA
using Avalonia;
#endif
namespace MapControl
{
/// <summary>
/// Spherical Azimuthal Equidistant Projection - AUTO2:97003.
/// See "Map Projections - A Working Manual" (https://pubs.usgs.gov/publication/pp1395), p.195-197.
/// </summary>
public class AzimuthalEquidistantProjection : AzimuthalProjection
{
public const string DefaultCrsId = "AUTO2:97003"; // GeoServer non-standard CRS identifier
public AzimuthalEquidistantProjection() // parameterless constructor for XAML
: this(DefaultCrsId)
{
}
public AzimuthalEquidistantProjection(string crsId)
{
CrsId = crsId;
}
public override Matrix RelativeTransform(double latitude, double longitude)
{
var p = GetProjectedPoint(latitude, longitude);
var scaleX = 1d;
var scaleY = 1d;
if (p.CosC == -1d)
{
scaleY = double.PositiveInfinity;
}
else if (p.CosC != 1d)
{
var c = Math.Acos(p.CosC);
var k = c / Math.Sin(c); // p.195 (25-2)
(scaleX, scaleY) = p.RelativeScale(1d, k);
}
return RelativeTransform(latitude, longitude, scaleX, scaleY);
}
public override Point? LocationToMap(double latitude, double longitude)
{
var p = GetProjectedPoint(latitude, longitude);
if (p.CosC == 1d) // p.195 "If cos c = 1, ... k' = 1, and x = y = 0."
{
return new Point();
}
if (p.CosC == -1)
{
return null; // p.195 "If cos c = -1, the point ... is plotted as a circle of radius πR."
}
var c = Math.Acos(p.CosC);
var k = c / Math.Sin(c); // p.195 (25-2)
return new Point(EquatorialRadius * k * p.X, EquatorialRadius * k * p.Y); // p.195 (22-4/5)
}
public override Location MapToLocation(double x, double y)
{
var rho = Math.Sqrt(x * x + y * y);
var c = rho / EquatorialRadius; // p.196 (25-15)
return GetLocation(x, y, rho, Math.Sin(c));
}
}
}

View file

@ -1,101 +0,0 @@
using System;
namespace MapControl
{
public abstract class AzimuthalProjection : MapProjection
{
protected AzimuthalProjection()
{
EnableCenterUpdates();
}
public readonly struct ProjectedPoint
{
public double X { get; }
public double Y { get; }
public double CosC { get; }
public ProjectedPoint(double centerLatitude, double centerLongitude, double latitude, double longitude)
{
var phi = latitude * Math.PI / 180d;
var phi1 = centerLatitude * Math.PI / 180d;
var dLambda = (longitude - centerLongitude) * Math.PI / 180d; // λ - λ0
var cosPhi = Math.Cos(phi);
var sinPhi = Math.Sin(phi);
var cosPhi1 = Math.Cos(phi1);
var sinPhi1 = Math.Sin(phi1);
var cosLambda = Math.Cos(dLambda);
var sinLambda = Math.Sin(dLambda);
var cosC = sinPhi1 * sinPhi + cosPhi1 * cosPhi * cosLambda; // (5-3)
X = cosPhi * sinLambda;
Y = cosPhi1 * sinPhi - sinPhi1 * cosPhi * cosLambda;
CosC = Math.Min(Math.Max(cosC, -1d), 1d); // protect against rounding errors
}
public (double, double) RelativeScale(double radialScale, double perpendicularScale)
{
var scaleX = radialScale;
var scaleY = perpendicularScale;
if (scaleX != scaleY)
{
var s = Math.Sqrt(X * X + Y * Y);
// sin and cos of azimuth from projection center, i.e. Atan2(Y/X)
var cos = X / s;
var sin = Y / s;
var x1 = scaleX * cos;
var y1 = scaleY * sin;
var x2 = scaleX * sin;
var y2 = scaleY * cos;
scaleX = Math.Sqrt(x1 * x1 + y1 * y1);
scaleY = Math.Sqrt(x2 * x2 + y2 * y2);
}
return (scaleX, scaleY);
}
}
protected ProjectedPoint GetProjectedPoint(double latitude, double longitude)
{
return new ProjectedPoint(Center.Latitude, Center.Longitude, latitude, longitude);
}
protected Location GetLocation(double x, double y, double rho, double sinC)
{
var cos2C = 1d - sinC * sinC;
if (cos2C < 0d)
{
return null;
}
var cosC = Math.Sqrt(cos2C);
var phi1 = Center.Latitude * Math.PI / 180d;
var cosPhi1 = Math.Cos(phi1);
var sinPhi1 = Math.Sin(phi1);
var phi = Math.Asin(cosC * sinPhi1 + y * sinC * cosPhi1 / rho); // (20-14)
double u, v;
if (Center.Latitude == 90d) // (20-16)
{
u = x;
v = -y;
}
else if (Center.Latitude == -90d) // (20-17)
{
u = x;
v = y;
}
else // (20-15)
{
u = x * sinC;
v = rho * cosPhi1 * cosC - y * sinPhi1 * sinC;
}
return new Location(
phi * 180d / Math.PI,
Math.Atan2(u, v) * 180d / Math.PI + Center.Longitude);
}
}
}

View file

@ -33,7 +33,7 @@ namespace MapControl
return new Matrix(1d / Math.Cos(latitude * Math.PI / 180d), 0d, 0d, 1d, 0d, 0d);
}
public override Point? LocationToMap(double latitude, double longitude)
public override Point LocationToMap(double latitude, double longitude)
{
return new Point(MeterPerDegree * longitude, MeterPerDegree * latitude);
}

View file

@ -1,61 +0,0 @@
using System;
#if WPF
using System.Windows;
using System.Windows.Media;
#elif AVALONIA
using Avalonia;
#endif
namespace MapControl
{
/// <summary>
/// Spherical Gnomonic Projection - AUTO2:97001.
/// See "Map Projections - A Working Manual" (https://pubs.usgs.gov/publication/pp1395), p.165-167.
/// </summary>
public class GnomonicProjection : AzimuthalProjection
{
public const string DefaultCrsId = "AUTO2:97001"; // GeoServer non-standard CRS identifier
public GnomonicProjection() // parameterless constructor for XAML
: this(DefaultCrsId)
{
}
public GnomonicProjection(string crsId)
{
CrsId = crsId;
}
public override Matrix RelativeTransform(double latitude, double longitude)
{
var p = GetProjectedPoint(latitude, longitude);
var k = 1d / p.CosC; // p.165 (22-3)
var h = k * k; // p.165 (22-2)
(var scaleX, var scaleY) = p.RelativeScale(h, k);
return RelativeTransform(latitude, longitude, scaleX, scaleY);
}
public override Point? LocationToMap(double latitude, double longitude)
{
var p = GetProjectedPoint(latitude, longitude);
if (p.CosC <= 0d) // p.167 "If cos c is zero or negative, the point is to be rejected."
{
return null;
}
var k = 1d / p.CosC; // p.165 (22-3)
return new Point(EquatorialRadius * k * p.X, EquatorialRadius * k * p.Y); // p.165 (22-4/5)
}
public override Location MapToLocation(double x, double y)
{
var rho = Math.Sqrt(x * x + y * y);
var c = Math.Atan(rho / EquatorialRadius); // p.167 (22-16)
return GetLocation(x, y, rho, Math.Sin(c));
}
}
}

View file

@ -43,10 +43,6 @@ namespace MapControl
DependencyPropertyHelper.Register<MapBase, MapProjection>(nameof(MapProjection), new WebMercatorProjection(),
(map, oldValue, newValue) => map.MapProjectionPropertyChanged(newValue));
public static readonly DependencyProperty ProjectionCenterProperty =
DependencyPropertyHelper.Register<MapBase, Location>(nameof(ProjectionCenter), null,
(map, oldValue, newValue) => map.ProjectionCenterPropertyChanged(newValue));
private Location transformCenter;
private Point viewCenter;
private double centerLongitude;
@ -86,17 +82,6 @@ namespace MapControl
set => SetValue(MapProjectionProperty, value);
}
/// <summary>
/// Gets or sets a projection center e.g. for azimuthal projections.
/// If ProjectionCenter is null, the value of the Center property is used as projection center.
/// This behavior is overridden when the Center property of a MapProjection is set explicitly.
/// </summary>
public Location ProjectionCenter
{
get => (Location)GetValue(ProjectionCenterProperty);
set => SetValue(ProjectionCenterProperty, value);
}
/// <summary>
/// Gets or sets the location of the center point of the map.
/// </summary>
@ -172,8 +157,8 @@ namespace MapControl
}
/// <summary>
/// Gets the ViewTransform instance that is used to transform between projected
/// map coordinates and view coordinates.
/// Gets the ViewTransform instance that is used to transform between
/// projected map coordinates and view coordinates.
/// </summary>
public ViewTransform ViewTransform { get; } = new ViewTransform();
@ -186,7 +171,6 @@ namespace MapControl
var transform = MapProjection.RelativeTransform(latitude, longitude);
transform.Scale(ViewTransform.Scale, ViewTransform.Scale);
transform.Rotate(ViewTransform.Rotation);
return transform;
}
@ -194,57 +178,27 @@ namespace MapControl
/// Gets a transform Matrix from meters to view coordinates for scaling and rotating
/// at the specified Location.
/// </summary>
public Matrix GetMapToViewTransform(Location location)
{
return GetMapToViewTransform(location.Latitude, location.Longitude);
}
public Matrix GetMapToViewTransform(Location location) => GetMapToViewTransform(location.Latitude, location.Longitude);
/// <summary>
/// Transforms geographic coordinates to a Point in view coordinates.
/// </summary>
public Point? LocationToView(double latitude, double longitude)
{
var point = MapProjection.LocationToMap(latitude, longitude);
if (point.HasValue)
{
point = ViewTransform.MapToView(point.Value);
}
return point;
}
public Point LocationToView(double latitude, double longitude) => ViewTransform.MapToView(MapProjection.LocationToMap(latitude, longitude));
/// <summary>
/// Transforms a Location in geographic coordinates to a Point in view coordinates.
/// </summary>
public Point? LocationToView(Location location)
{
return LocationToView(location.Latitude, location.Longitude);
}
public Point LocationToView(Location location) => LocationToView(location.Latitude, location.Longitude);
/// <summary>
/// Transforms a Point in view coordinates to a Location in geographic coordinates.
/// </summary>
public Location ViewToLocation(Point point)
{
return MapProjection.MapToLocation(ViewTransform.ViewToMap(point));
}
/// <summary>
/// Gets a MapRect in projected map coordinates that covers a rectangle in view coordinates.
/// </summary>
public MapRect ViewToMapRect(Rect rect)
{
return new MapRect(ViewTransform.ViewToMapBounds(rect), MapProjection.Center);
}
public Location ViewToLocation(Point point) => MapProjection.MapToLocation(ViewTransform.ViewToMap(point));
/// <summary>
/// Gets a BoundingBox in geographic coordinates that covers a rectangle in view coordinates.
/// </summary>
public BoundingBox ViewToBoundingBox(Rect rect)
{
return MapProjection.MapToBoundingBox(ViewTransform.ViewToMapBounds(rect));
}
public BoundingBox ViewToBoundingBox(Rect rect) => MapProjection.MapToBoundingBox(ViewTransform.ViewToMapBounds(rect));
/// <summary>
/// Sets a temporary center point in view coordinates for scaling and rotation transformations.
@ -253,8 +207,8 @@ namespace MapControl
/// </summary>
public void SetTransformCenter(Point center)
{
viewCenter = center;
transformCenter = ViewToLocation(center);
viewCenter = transformCenter != null ? center : new Point(ActualWidth / 2d, ActualHeight / 2d);
}
/// <summary>
@ -262,8 +216,8 @@ namespace MapControl
/// </summary>
public void ResetTransformCenter()
{
transformCenter = null;
viewCenter = new Point(ActualWidth / 2d, ActualHeight / 2d);
transformCenter = null;
}
/// <summary>
@ -273,12 +227,7 @@ namespace MapControl
{
if (translation.X != 0d || translation.Y != 0d)
{
var center = ViewToLocation(new Point(viewCenter.X - translation.X, viewCenter.Y - translation.Y));
if (center != null)
{
Center = center;
}
Center = ViewToLocation(new Point(viewCenter.X - translation.X, viewCenter.Y - translation.Y));
}
}
@ -350,22 +299,11 @@ namespace MapControl
/// </summary>
public void ZoomToBounds(BoundingBox bounds)
{
(var mapRect, var _) = MapProjection.BoundingBoxToMap(bounds);
if (mapRect.HasValue)
{
var targetCenter = MapProjection.MapToLocation(
mapRect.Value.X + mapRect.Value.Width / 2d,
mapRect.Value.Y + mapRect.Value.Height / 2d);
if (targetCenter != null)
{
var scale = Math.Min(ActualWidth / mapRect.Value.Width, ActualHeight / mapRect.Value.Height);
TargetZoomLevel = ScaleToZoomLevel(scale);
TargetCenter = targetCenter;
TargetHeading = 0d;
}
}
(var rect, var _) = MapProjection.BoundingBoxToMap(bounds);
var scale = Math.Min(ActualWidth / rect.Width, ActualHeight / rect.Height);
TargetZoomLevel = ScaleToZoomLevel(scale);
TargetCenter = new Location((bounds.South + bounds.North) / 2d, (bounds.West + bounds.East) / 2d);
TargetHeading = 0d;
}
internal bool InsideViewBounds(Point point)
@ -441,107 +379,65 @@ namespace MapControl
if (projection.IsNormalCylindrical)
{
var maxLocation = projection.MapToLocation(0d, 180d * MapProjection.Wgs84MeterPerDegree);
if (maxLocation != null && maxLocation.Latitude < 90d)
{
maxLatitude = maxLocation.Latitude;
Center = CoerceCenterProperty(Center);
}
maxLatitude = maxLocation.Latitude;
Center = CoerceCenterProperty(Center);
}
ResetTransformCenter();
UpdateTransform(false, true);
}
private void ProjectionCenterPropertyChanged(Location center)
{
if (!internalPropertyChange)
{
if (center != null)
{
var longitude = Location.NormalizeLongitude(center.Longitude);
if (!center.LongitudeEquals(longitude))
{
SetValueInternal(ProjectionCenterProperty, new Location(center.Latitude, longitude));
}
}
ResetTransformCenter();
UpdateTransform();
}
}
private void UpdateTransform(bool resetTransformCenter = false, bool projectionChanged = false)
{
var transformCenterChanged = false;
var viewScale = ZoomLevelToScale(ZoomLevel);
var projection = MapProjection;
var mapCenter = MapProjection.LocationToMap(transformCenter ?? Center);
projection.SetCenter(ProjectionCenter ?? Center);
ViewTransform.SetTransform(mapCenter, viewCenter, viewScale, -Heading);
var mapCenter = projection.LocationToMap(transformCenter ?? Center);
if (mapCenter.HasValue)
if (transformCenter != null)
{
ViewTransform.SetTransform(mapCenter.Value, viewCenter, viewScale, -Heading);
var center = ViewToLocation(new Point(ActualWidth / 2d, ActualHeight / 2d));
var latitude = center.Latitude;
var longitude = Location.NormalizeLongitude(center.Longitude);
if (transformCenter != null)
if (latitude < -maxLatitude || latitude > maxLatitude)
{
var center = ViewToLocation(new Point(ActualWidth / 2d, ActualHeight / 2d));
if (center != null)
{
var latitude = center.Latitude;
var longitude = Location.NormalizeLongitude(center.Longitude);
if (latitude < -maxLatitude || latitude > maxLatitude)
{
latitude = Math.Min(Math.Max(latitude, -maxLatitude), maxLatitude);
resetTransformCenter = true;
}
if (!center.Equals(latitude, longitude))
{
center = new Location(latitude, longitude);
}
SetValueInternal(CenterProperty, center);
if (centerAnimation == null)
{
SetValueInternal(TargetCenterProperty, center);
}
if (resetTransformCenter)
{
// Check if transform center has moved across 180° longitude.
//
transformCenterChanged = Math.Abs(center.Longitude - transformCenter.Longitude) > 180d;
ResetTransformCenter();
projection.SetCenter(ProjectionCenter ?? Center);
mapCenter = projection.LocationToMap(center);
if (mapCenter.HasValue)
{
ViewTransform.SetTransform(mapCenter.Value, viewCenter, viewScale, -Heading);
}
}
}
latitude = Math.Min(Math.Max(latitude, -maxLatitude), maxLatitude);
resetTransformCenter = true;
}
ViewScale = ViewTransform.Scale;
if (!center.Equals(latitude, longitude))
{
center = new Location(latitude, longitude);
}
// Check if view center has moved across 180° longitude.
//
transformCenterChanged = transformCenterChanged || Math.Abs(Center.Longitude - centerLongitude) > 180d;
centerLongitude = Center.Longitude;
SetValueInternal(CenterProperty, center);
OnViewportChanged(new ViewportChangedEventArgs(projectionChanged, transformCenterChanged));
if (centerAnimation == null)
{
SetValueInternal(TargetCenterProperty, center);
}
if (resetTransformCenter)
{
// Check if transform center has moved across 180° longitude.
//
transformCenterChanged = Math.Abs(center.Longitude - transformCenter.Longitude) > 180d;
ResetTransformCenter();
mapCenter = MapProjection.LocationToMap(center);
ViewTransform.SetTransform(mapCenter, viewCenter, viewScale, -Heading);
}
}
ViewScale = ViewTransform.Scale;
// Check if view center has moved across 180° longitude.
//
transformCenterChanged = transformCenterChanged || Math.Abs(Center.Longitude - centerLongitude) > 180d;
centerLongitude = Center.Longitude;
OnViewportChanged(new ViewportChangedEventArgs(projectionChanged, transformCenterChanged));
}
protected override void OnViewportChanged(ViewportChangedEventArgs e)

View file

@ -35,7 +35,7 @@ namespace MapControl
return (bool)element.GetValue(OnBorderProperty);
}
protected override void SetViewPosition(FrameworkElement element, ref Point? position)
protected override Point SetViewPosition(FrameworkElement element, Point position)
{
var onBorder = false;
var w = ParentMap.ActualWidth;
@ -45,12 +45,11 @@ namespace MapControl
var maxX = w - BorderWidth / 2d;
var maxY = h - BorderWidth / 2d;
if (position.HasValue &&
(position.Value.X < minX || position.Value.X > maxX ||
position.Value.Y < minY || position.Value.Y > maxY))
if (position.X < minX || position.X > maxX ||
position.Y < minY || position.Y > maxY)
{
var dx = position.Value.X - w / 2d;
var dy = position.Value.Y - h / 2d;
var dx = position.X - w / 2d;
var dy = position.Y - h / 2d;
var cx = (maxX - minX) / 2d;
var cy = (maxY - minY) / 2d;
double x, y;
@ -86,7 +85,7 @@ namespace MapControl
element.SetValue(OnBorderProperty, onBorder);
base.SetViewPosition(element, ref position);
return base.SetViewPosition(element, position);
}
}
}

View file

@ -127,10 +127,7 @@ namespace MapControl
//
var pos = ParentMap.LocationToView(latitude, longitude + 10d / PixelPerDegree);
if (pos.HasValue)
{
rotation = Math.Atan2(pos.Value.Y - position.Y, pos.Value.X - position.X) * 180d / Math.PI;
}
rotation = Math.Atan2(pos.Y - position.Y, pos.X - position.X) * 180d / Math.PI;
}
if (rotation.HasValue)
@ -173,31 +170,19 @@ namespace MapControl
{
var p1 = ParentMap.LocationToView(lat, boundingBox.West);
var p2 = ParentMap.LocationToView(lat, boundingBox.East);
if (p1.HasValue && p2.HasValue)
{
figures.Add(CreateLineFigure(p1.Value, p2.Value));
}
figures.Add(CreateLineFigure(p1, p2));
}
for (var lon = lonLabelStart; lon <= boundingBox.East; lon += lineDistance)
{
var p1 = ParentMap.LocationToView(boundingBox.South, lon);
var p2 = ParentMap.LocationToView(boundingBox.North, lon);
if (p1.HasValue && p2.HasValue)
{
figures.Add(CreateLineFigure(p1.Value, p2.Value));
}
figures.Add(CreateLineFigure(p1, p2));
for (var lat = latLabelStart; lat <= boundingBox.North; lat += lineDistance)
{
var position = ParentMap.LocationToView(lat, lon);
if (position.HasValue)
{
AddLabel(labels, lat, lon, position.Value, ParentMap.ViewTransform.Rotation);
}
AddLabel(labels, lat, lon, position, ParentMap.ViewTransform.Rotation);
}
}
}
@ -210,7 +195,6 @@ namespace MapControl
var interpolationCount = Math.Max(1, (int)Math.Ceiling(lineDistance / LineInterpolationResolution));
var interpolationDistance = lineDistance / interpolationCount;
var latPoints = latSegments * interpolationCount;
var centerLon = Math.Round(ParentMap.Center.Longitude / lineDistance) * lineDistance;
var minLon = centerLon - lineDistance;
var maxLon = centerLon + lineDistance;
@ -239,11 +223,8 @@ namespace MapControl
var points = new List<Point>();
var position = ParentMap.LocationToView(lat, lon);
if (position.HasValue)
{
points.Add(position.Value);
AddLabel(labels, lat, lon, position.Value);
}
points.Add(position);
AddLabel(labels, lat, lon, position);
for (int j = 0; j < lonSegments; j++)
{
@ -251,17 +232,10 @@ namespace MapControl
{
lon = minLon + j * lineDistance + k * interpolationDistance;
position = ParentMap.LocationToView(lat, lon);
if (position.HasValue)
{
points.Add(position.Value);
}
points.Add(position);
}
if (position.HasValue)
{
AddLabel(labels, lat, lon, position.Value);
}
AddLabel(labels, lat, lon, position);
}
if (points.Count >= 2)
@ -281,14 +255,11 @@ namespace MapControl
{
var p = ParentMap.LocationToView(startLatitude + i * deltaLatitude, longitude);
if (p.HasValue)
{
visible = visible ||
p.Value.X >= 0d && p.Value.X <= ParentMap.ActualWidth &&
p.Value.Y >= 0d && p.Value.Y <= ParentMap.ActualHeight;
visible = visible ||
p.X >= 0d && p.X <= ParentMap.ActualWidth &&
p.Y >= 0d && p.Y <= ParentMap.ActualHeight;
points.Add(p.Value);
}
points.Add(p);
}
if (points.Count >= 2)

View file

@ -180,7 +180,7 @@ namespace MapControl
updateTimer.Stop();
ImageSource image = null;
MapRect mapRect = null;
Rect? mapRect = null;
if (ParentMap != null &&
(SupportedCrsIds == null || SupportedCrsIds.Contains(ParentMap.MapProjection.CrsId)))
@ -193,8 +193,8 @@ namespace MapControl
var x = (ParentMap.ActualWidth - width) / 2d;
var y = (ParentMap.ActualHeight - height) / 2d;
mapRect = ParentMap.ViewToMapRect(new Rect(x, y, width, height));
image = await GetImageAsync(mapRect.Rect, loadingProgress);
mapRect = ParentMap.ViewTransform.ViewToMapBounds(new Rect(x, y, width, height));
image = await GetImageAsync(mapRect.Value, loadingProgress);
}
}
@ -216,7 +216,7 @@ namespace MapControl
}
}
private void SwapImages(ImageSource image, MapRect mapRect)
private void SwapImages(ImageSource image, Rect? mapRect)
{
if (Children.Count >= 2)
{

View file

@ -131,15 +131,15 @@ namespace MapControl
/// <summary>
/// Gets the MapRect of an element.
/// </summary>
public static MapRect GetMapRect(FrameworkElement element)
public static Rect? GetMapRect(FrameworkElement element)
{
return (MapRect)element.GetValue(MapRectProperty);
return (Rect?)element.GetValue(MapRectProperty);
}
/// <summary>
/// Sets the MapRect of an element.
/// </summary>
public static void SetMapRect(FrameworkElement element, MapRect value)
public static void SetMapRect(FrameworkElement element, Rect? value)
{
element.SetValue(MapRectProperty, value);
}
@ -157,9 +157,11 @@ namespace MapControl
/// ArrangeOverride and may be overridden to modify the actual view position value.
/// An overridden method should call this method to set the attached property.
/// </summary>
protected virtual void SetViewPosition(FrameworkElement element, ref Point? position)
protected virtual Point SetViewPosition(FrameworkElement element, Point position)
{
element.SetValue(ViewPositionProperty, position);
return position;
}
protected virtual void SetParentMap(MapBase map)
@ -214,19 +216,17 @@ namespace MapControl
return finalSize;
}
private Point? GetViewPosition(Location location)
private Point GetViewPosition(Location location)
{
var position = parentMap.LocationToView(location);
if (parentMap.MapProjection.IsNormalCylindrical &&
position.HasValue && !parentMap.InsideViewBounds(position.Value))
if (parentMap.MapProjection.IsNormalCylindrical && !parentMap.InsideViewBounds(position))
{
var nearestPosition = parentMap.LocationToView(
location.Latitude, parentMap.NearestLongitude(location.Longitude));
var longitude = parentMap.NearestLongitude(location.Longitude);
if (nearestPosition.HasValue)
if (longitude != location.Longitude)
{
position = nearestPosition;
position = parentMap.LocationToView(location.Latitude, longitude);
}
}
@ -238,20 +238,14 @@ namespace MapControl
var center = new Point(mapRect.X + mapRect.Width / 2d, mapRect.Y + mapRect.Height / 2d);
var position = parentMap.ViewTransform.MapToView(center);
if (parentMap.MapProjection.IsNormalCylindrical &&
!parentMap.InsideViewBounds(position))
if (parentMap.MapProjection.IsNormalCylindrical && !parentMap.InsideViewBounds(position))
{
var location = parentMap.MapProjection.MapToLocation(center);
var longitude = parentMap.NearestLongitude(location.Longitude);
if (location != null)
if (longitude != location.Longitude)
{
var nearestPosition = parentMap.LocationToView(
location.Latitude, parentMap.NearestLongitude(location.Longitude));
if (nearestPosition.HasValue)
{
position = nearestPosition.Value;
}
position = parentMap.LocationToView(location.Latitude, longitude);
}
}
@ -269,19 +263,14 @@ namespace MapControl
if (location != null)
{
var position = GetViewPosition(location);
SetViewPosition(element, ref position);
var position = SetViewPosition(element, GetViewPosition(location));
if (GetAutoCollapse(element))
{
element.SetVisible(position.HasValue && parentMap.InsideViewBounds(position.Value));
element.SetVisible(parentMap.InsideViewBounds(position));
}
if (position.HasValue)
{
ArrangeElement(element, position.Value);
}
ArrangeElement(element, position);
}
else
{
@ -289,11 +278,9 @@ namespace MapControl
var mapRect = GetMapRect(element);
if (mapRect != null)
if (mapRect.HasValue)
{
mapRect.Update(parentMap.MapProjection);
ArrangeElement(element, mapRect.Rect, null);
ArrangeElement(element, mapRect.Value, null);
}
else
{
@ -303,10 +290,7 @@ namespace MapControl
{
(var rect, var transform) = parentMap.MapProjection.BoundingBoxToMap(boundingBox);
if (rect.HasValue)
{
ArrangeElement(element, rect.Value, transform);
}
ArrangeElement(element, rect, transform);
}
else
{

View file

@ -80,7 +80,7 @@ namespace MapControl
{
var position = ParentMap.LocationToView(location);
if (position.HasValue && !ParentMap.InsideViewBounds(position.Value))
if (!ParentMap.InsideViewBounds(position))
{
longitudeOffset = ParentMap.NearestLongitude(location.Longitude) - location.Longitude;
}
@ -89,35 +89,25 @@ namespace MapControl
return longitudeOffset;
}
protected Point? LocationToMap(Location location, double longitudeOffset)
protected Point LocationToMap(Location location, double longitudeOffset)
{
var point = ParentMap.MapProjection.LocationToMap(location.Latitude, location.Longitude + longitudeOffset);
if (point.HasValue)
if (point.Y == double.PositiveInfinity)
{
if (point.Value.Y == double.PositiveInfinity)
{
point = new Point(point.Value.X, 1e9);
}
else if (point.Value.Y == double.NegativeInfinity)
{
point = new Point(point.Value.X, -1e9);
}
point = new Point(point.X, 1e9);
}
else if (point.Y == double.NegativeInfinity)
{
point = new Point(point.X, -1e9);
}
return point;
}
protected Point? LocationToView(Location location, double longitudeOffset)
protected Point LocationToView(Location location, double longitudeOffset)
{
var point = LocationToMap(location, longitudeOffset);
if (point.HasValue)
{
point = ParentMap.ViewTransform.MapToView(point.Value);
}
return point;
return ParentMap.ViewTransform.MapToView(LocationToMap(location, longitudeOffset));
}
}
}

View file

@ -55,54 +55,6 @@ namespace MapControl
public double MeterPerDegree => EquatorialRadius * Math.PI / 180d;
private Location center;
private bool updateCenter;
/// <summary>
/// Gets or sets an optional projection center. If the property is set to a non-null value,
/// it overrides the projection center set by MapBase.Center or MapBase.ProjectionCenter.
/// </summary>
public Location Center
{
get => center;
set
{
updateCenter = true;
if (value != null)
{
var longitude = Location.NormalizeLongitude(value.Longitude);
SetCenter(value.LongitudeEquals(longitude) ? value : new Location(value.Latitude, longitude));
updateCenter = false;
}
}
}
protected void EnableCenterUpdates()
{
updateCenter = true;
SetCenter(new Location());
}
/// <summary>
/// Called by MapBase.UpdateTransform().
/// </summary>
internal void SetCenter(Location value)
{
if (updateCenter)
{
if (center == null || !center.Equals(value))
{
center = value;
CenterChanged();
}
}
}
protected virtual void CenterChanged() { }
/// <summary>
/// Gets the relative transform at the specified geographic coordinates.
/// The returned Matrix represents the local distortion of the map projection.
@ -111,13 +63,11 @@ namespace MapControl
/// <summary>
/// Transforms geographic coordinates to a Point in projected map coordinates.
/// Returns null when the location can not be transformed.
/// </summary>
public abstract Point? LocationToMap(double latitude, double longitude);
public abstract Point LocationToMap(double latitude, double longitude);
/// <summary>
/// Transforms projected map coordinates to a Location in geographic coordinates.
/// Returns null when the coordinates can not be transformed.
/// </summary>
public abstract Location MapToLocation(double x, double y);
@ -128,51 +78,40 @@ namespace MapControl
/// <summary>
/// Transforms a Location in geographic coordinates to a Point in projected map coordinates.
/// Returns null when the Location can not be transformed.
/// </summary>
public Point? LocationToMap(Location location) => LocationToMap(location.Latitude, location.Longitude);
public Point LocationToMap(Location location) => LocationToMap(location.Latitude, location.Longitude);
/// <summary>
/// Transforms a Point in projected map coordinates to a Location in geographic coordinates.
/// Returns null when the Point can not be transformed.
/// </summary>
public Location MapToLocation(Point point) => MapToLocation(point.X, point.Y);
/// <summary>
/// Transforms a BoundingBox in geographic coordinates to a Rect in projected map coordinates with
/// an optional transform Matrix. Returns (null, null) when the BoundingBox can not be transformed.
/// Transforms a BoundingBox in geographic coordinates to a Rect in projected map coordinates
/// with an optional transform Matrix.
/// </summary>
public (Rect?, Matrix?) BoundingBoxToMap(BoundingBox boundingBox)
public (Rect, Matrix?) BoundingBoxToMap(BoundingBox boundingBox)
{
Rect? rect = null;
Rect rect;
Matrix? transform = null;
var sw = LocationToMap(boundingBox.South, boundingBox.West);
var se = LocationToMap(boundingBox.South, boundingBox.East);
var nw = LocationToMap(boundingBox.North, boundingBox.West);
var ne = LocationToMap(boundingBox.North, boundingBox.East);
if (sw.HasValue && se.HasValue && nw.HasValue && ne.HasValue)
if (IsNormalCylindrical)
{
var south = new Point((sw.Value.X + se.Value.X) / 2d, (sw.Value.Y + se.Value.Y) / 2d); // south midpoint
var north = new Point((nw.Value.X + ne.Value.X) / 2d, (nw.Value.Y + ne.Value.Y) / 2d); // north midpoint
var west = new Point((nw.Value.X + sw.Value.X) / 2d, (nw.Value.Y + sw.Value.Y) / 2d); // west midpoint
var east = new Point((ne.Value.X + se.Value.X) / 2d, (ne.Value.Y + se.Value.Y) / 2d); // east midpoint
var center = new Point((west.X + east.X) / 2d, (west.Y + east.Y) / 2d); // midpoint of segment west-east
var dx1 = east.X - west.X;
var dy1 = east.Y - west.Y;
var dx2 = north.X - south.X;
var dy2 = north.Y - south.Y;
var width = Math.Sqrt(dx1 * dx1 + dy1 * dy1); // distance west-east
var height = Math.Sqrt(dx2 * dx2 + dy2 * dy2); // distance south-north
var southWest = LocationToMap(boundingBox.South, boundingBox.West);
var northEast = LocationToMap(boundingBox.North, boundingBox.East);
rect = new Rect(southWest.X, southWest.Y, northEast.X - southWest.X, northEast.Y - southWest.Y);
}
else
{
var latitude = (boundingBox.South + boundingBox.North) / 2d;
var longitude = (boundingBox.West + boundingBox.East) / 2d;
var center = LocationToMap(latitude, longitude);
var width = MeterPerDegree * (boundingBox.East - boundingBox.West) * Math.Cos(latitude * Math.PI / 180d);
var height = MeterPerDegree * (boundingBox.North - boundingBox.South);
rect = new Rect(center.X - width / 2d, center.Y - height / 2d, width, height);
if (dy1 != 0d || dx2 != 0d)
{
// Skew matrix with skewX = Atan(-dx2 / dy2) and skewY = Atan(-dy1 / dx1).
//
transform = new Matrix(1d, -dy1 / dx1, -dx2 / dy2, 1d, 0d, 0d);
}
transform = RelativeTransform(latitude, longitude);
}
return (rect, transform);
@ -180,44 +119,18 @@ namespace MapControl
/// <summary>
/// Transforms a Rect in projected map coordinates to a BoundingBox in geographic coordinates.
/// Returns null when the Rect can not be transformed.
/// </summary>
public BoundingBox MapToBoundingBox(Rect rect)
{
var sw = MapToLocation(rect.X, rect.Y);
var ne = MapToLocation(rect.X + rect.Width, rect.Y + rect.Height);
var southWest = MapToLocation(rect.X, rect.Y);
var northEast = MapToLocation(rect.X + rect.Width, rect.Y + rect.Height);
return sw != null && ne != null ? new BoundingBox(sw.Latitude, sw.Longitude, ne.Latitude, ne.Longitude) : null;
return new BoundingBox(southWest.Latitude, southWest.Longitude, northEast.Latitude, northEast.Longitude);
}
public override string ToString()
{
return CrsId;
}
/// <summary>
/// Used by azimuthal projections, where local skewing can not be directly calculated.
/// </summary>
protected Matrix RelativeTransform(double latitude, double longitude, double scaleX, double scaleY)
{
var north = LocationToMap(latitude - 1e-3, longitude);
var south = LocationToMap(latitude + 1e-3, longitude);
var west = LocationToMap(latitude, longitude - 1e-3);
var east = LocationToMap(latitude, longitude + 1e-3);
var tanSkewX = 0d;
var tanSkewY = 0d;
if (north.HasValue && south.HasValue && west.HasValue && east.HasValue)
{
var dx1 = east.Value.X - west.Value.X;
var dy1 = east.Value.Y - west.Value.Y;
var dx2 = north.Value.X - south.Value.X;
var dy2 = north.Value.Y - south.Value.Y;
tanSkewX = -dx2 / dy2;
tanSkewY = -dy1 / dx1;
}
return new Matrix(scaleX, scaleX * tanSkewY, scaleY * tanSkewX, scaleY, 0d, 0d);
}
}
}

View file

@ -13,12 +13,6 @@ namespace MapControl
EquirectangularProjection.DefaultCrsId or "CRS:84" => new EquirectangularProjection(crsId),
Wgs84UpsNorthProjection.DefaultCrsId => new Wgs84UpsNorthProjection(),
Wgs84UpsSouthProjection.DefaultCrsId => new Wgs84UpsSouthProjection(),
Wgs84AutoUtmProjection.DefaultCrsId => new Wgs84AutoUtmProjection(),
Wgs84AutoTmProjection.DefaultCrsId => new Wgs84AutoTmProjection(),
OrthographicProjection.DefaultCrsId => new OrthographicProjection(),
GnomonicProjection.DefaultCrsId => new GnomonicProjection(),
StereographicProjection.DefaultCrsId => new StereographicProjection(),
AzimuthalEquidistantProjection.DefaultCrsId => new AzimuthalEquidistantProjection(),
_ => GetProjectionFromEpsgCode(crsId),
};

View file

@ -1,27 +0,0 @@
#if WPF
using System.Windows;
#elif AVALONIA
using Avalonia;
#endif
namespace MapControl
{
public class MapRect(Rect rect, Location origin)
{
public Rect Rect { get; private set; } = rect;
public Location Origin { get; private set; } = origin;
public void Update(MapProjection projection)
{
Point? origin;
if (Origin != null && projection.Center != null &&
!Origin.Equals(projection.Center) &&
(origin = projection.LocationToMap(Origin)).HasValue)
{
Rect = new Rect(Rect.X + origin.Value.X, Rect.Y + origin.Value.Y, Rect.Width, Rect.Height);
Origin = projection.Center;
}
}
}
}

View file

@ -92,35 +92,31 @@ namespace MapControl
var y0 = ParentMap.ActualHeight / 2d;
var p1 = ParentMap.ViewToLocation(new Point(x0 - 50d, y0));
var p2 = ParentMap.ViewToLocation(new Point(x0 + 50d, y0));
var scale = 100d / p1.GetDistance(p2);
var length = MinWidth / scale;
var magnitude = Math.Pow(10d, Math.Floor(Math.Log10(length)));
if (p1 != null && p2 != null)
{
var scale = 100d / p1.GetDistance(p2);
var length = MinWidth / scale;
var magnitude = Math.Pow(10d, Math.Floor(Math.Log10(length)));
length = length / magnitude < 2d ? 2d * magnitude
: length / magnitude < 5d ? 5d * magnitude
: 10d * magnitude;
length = length / magnitude < 2d ? 2d * magnitude
: length / magnitude < 5d ? 5d * magnitude
: 10d * magnitude;
size = new Size(
length * scale + StrokeThickness + Padding.Left + Padding.Right,
1.5 * label.FontSize + 2 * StrokeThickness + Padding.Top + Padding.Bottom);
size = new Size(
length * scale + StrokeThickness + Padding.Left + Padding.Right,
1.5 * label.FontSize + 2 * StrokeThickness + Padding.Top + Padding.Bottom);
var x1 = Padding.Left + StrokeThickness / 2d;
var x2 = size.Width - Padding.Right - StrokeThickness / 2d;
var y1 = size.Height / 2d;
var y2 = size.Height - Padding.Bottom - StrokeThickness / 2d;
var x1 = Padding.Left + StrokeThickness / 2d;
var x2 = size.Width - Padding.Right - StrokeThickness / 2d;
var y1 = size.Height / 2d;
var y2 = size.Height - Padding.Bottom - StrokeThickness / 2d;
line.Points = [new Point(x1, y1), new Point(x1, y2), new Point(x2, y2), new Point(x2, y1)];
line.Points = [new Point(x1, y1), new Point(x1, y2), new Point(x2, y2), new Point(x2, y1)];
label.Text = length >= 1000d
? string.Format(CultureInfo.InvariantCulture, "{0:F0} km", length / 1000d)
: string.Format(CultureInfo.InvariantCulture, "{0:F0} m", length);
label.Text = length >= 1000d
? string.Format(CultureInfo.InvariantCulture, "{0:F0} km", length / 1000d)
: string.Format(CultureInfo.InvariantCulture, "{0:F0} m", length);
line.Measure(size);
label.Measure(size);
}
line.Measure(size);
label.Measure(size);
}
return size;

View file

@ -1,57 +0,0 @@
using System;
#if WPF
using System.Windows;
using System.Windows.Media;
#elif AVALONIA
using Avalonia;
#endif
namespace MapControl
{
/// <summary>
/// Spherical Orthographic Projection - AUTO2:42003.
/// See "Map Projections - A Working Manual" (https://pubs.usgs.gov/publication/pp1395), p.148-150.
/// </summary>
public class OrthographicProjection : AzimuthalProjection
{
public const string DefaultCrsId = "AUTO2:42003";
public OrthographicProjection() // parameterless constructor for XAML
: this(DefaultCrsId)
{
}
public OrthographicProjection(string crsId)
{
CrsId = crsId;
}
public override Matrix RelativeTransform(double latitude, double longitude)
{
var p = GetProjectedPoint(latitude, longitude);
(var scaleX, var scaleY) = p.RelativeScale(p.CosC, 1d); // p.149 (20-5), k == 1
return RelativeTransform(latitude, longitude, scaleX, scaleY);
}
public override Point? LocationToMap(double latitude, double longitude)
{
var p = GetProjectedPoint(latitude, longitude);
if (p.CosC < 0d) // p.149 "If cos c is negative, the point is not to be plotted."
{
return null;
}
return new Point(EquatorialRadius * p.X, EquatorialRadius * p.Y); // p.149 (20-3/4)
}
public override Location MapToLocation(double x, double y)
{
var rho = Math.Sqrt(x * x + y * y);
var sinC = rho / EquatorialRadius; // p.150 (20-19)
return GetLocation(x, y, rho, sinC);
}
}
}

View file

@ -21,7 +21,7 @@ namespace MapControl
public double FalseNorthing { get; set; } = 2e6;
public Hemisphere Hemisphere { get; set; }
public static double RelativeScale(Hemisphere hemisphere, double flattening, double scaleFactor, double latitude)
public static Matrix RelativeScale(Hemisphere hemisphere, double flattening, double scaleFactor, double latitude, double longitude)
{
var sign = hemisphere == Hemisphere.North ? 1d : -1d;
var phi = sign * latitude * Math.PI / 180d;
@ -33,20 +33,21 @@ namespace MapControl
/ Math.Pow((1d - eSinPhi) / (1d + eSinPhi), e / 2d); // p.161 (15-9)
// r == ρ/a
var r = scaleFactor * 2d * t / Math.Sqrt(Math.Pow(1d + e, 1d + e) * Math.Pow(1d - e, 1d - e)); // p.161 (21-33)
var r = 2d * scaleFactor * t / Math.Sqrt(Math.Pow(1d + e, 1d + e) * Math.Pow(1d - e, 1d - e)); // p.161 (21-33)
var m = Math.Cos(phi) / Math.Sqrt(1d - eSinPhi * eSinPhi); // p.160 (14-15)
var k = r / m; // p.161 (21-32)
return r / m; // p.161 (21-32)
var transform = new Matrix(k, 0d, 0d, k, 0d, 0d);
transform.Rotate(-sign * longitude);
return transform;
}
public override Matrix RelativeTransform(double latitude, double longitude)
{
var k = RelativeScale(Hemisphere, Flattening, ScaleFactor, latitude);
return RelativeTransform(latitude, longitude, k, k);
return RelativeScale(Hemisphere, Flattening, ScaleFactor, latitude, longitude);
}
public override Point? LocationToMap(double latitude, double longitude)
public override Point LocationToMap(double latitude, double longitude)
{
var sign = Hemisphere == Hemisphere.North ? 1d : -1d;
var phi = sign * latitude * Math.PI / 180d;

View file

@ -1,53 +0,0 @@
using System;
#if WPF
using System.Windows;
using System.Windows.Media;
#elif AVALONIA
using Avalonia;
#endif
namespace MapControl
{
/// <summary>
/// Spherical Stereographic Projection - AUTO2:97002.
/// See "Map Projections - A Working Manual" (https://pubs.usgs.gov/publication/pp1395), p.157-160.
/// </summary>
public class StereographicProjection : AzimuthalProjection
{
public const string DefaultCrsId = "AUTO2:97002"; // GeoServer non-standard CRS identifier
public StereographicProjection() // parameterless constructor for XAML
: this(DefaultCrsId)
{
}
public StereographicProjection(string crsId)
{
CrsId = crsId;
}
public override Matrix RelativeTransform(double latitude, double longitude)
{
var p = GetProjectedPoint(latitude, longitude);
var k = 2d / (1d + p.CosC); // p.157 (21-4), k0 == 1
return RelativeTransform(latitude, longitude, k, k);
}
public override Point? LocationToMap(double latitude, double longitude)
{
var p = GetProjectedPoint(latitude, longitude);
var k = 2d / (1d + p.CosC); // p.157 (21-4), k0 == 1
return new Point(EquatorialRadius * k * p.X, EquatorialRadius * k * p.Y); // p.157 (21-2/3)
}
public override Location MapToLocation(double x, double y)
{
var rho = Math.Sqrt(x * x + y * y);
var c = 2d * Math.Atan(rho / (2d * EquatorialRadius)); // p.159 (21-15), k0 == 1
return GetLocation(x, y, rho, Math.Sin(c));
}
}
}

View file

@ -100,7 +100,7 @@ namespace MapControl
return transform;
}
public override Point? LocationToMap(double latitude, double longitude)
public override Point LocationToMap(double latitude, double longitude)
{
// φ
var phi = latitude * Math.PI / 180d;

View file

@ -80,7 +80,7 @@ namespace MapControl
return transform;
}
public override Point? LocationToMap(double latitude, double longitude)
public override Point LocationToMap(double latitude, double longitude)
{
var phi = latitude * Math.PI / 180d;
var M = MeridianDistance(phi);

View file

@ -34,7 +34,7 @@ namespace MapControl
return new Matrix(k, 0d, 0d, k, 0d, 0d);
}
public override Point? LocationToMap(double latitude, double longitude)
public override Point LocationToMap(double latitude, double longitude)
{
return new Point(
MeterPerDegree * longitude,

View file

@ -1,27 +0,0 @@
namespace MapControl
{
/// <summary>
/// WGS84 Auto Transverse Mercator Projection - AUTO2:42002.
/// CentralMeridian is automatically set to Center.Longitude.
/// </summary>
public class Wgs84AutoTmProjection : TransverseMercatorProjection
{
public const string DefaultCrsId = "AUTO2:42002";
public Wgs84AutoTmProjection() // parameterless constructor for XAML
: this(DefaultCrsId)
{
}
public Wgs84AutoTmProjection(string crsId)
{
CrsId = crsId;
EnableCenterUpdates();
}
protected override void CenterChanged()
{
CentralMeridian = Center.Longitude;
}
}
}

View file

@ -1,51 +0,0 @@
using System;
namespace MapControl
{
/// <summary>
/// WGS84 Universal Transverse Mercator Projection - AUTO2:42002.
/// Zone and Hemisphere are automatically set according to the projection's Center.
/// If the CRS identifier passed to the constructor is null or empty, appropriate
/// values from EPSG:32601 to EPSG:32660 and EPSG:32701 to EPSG:32760 are used.
/// </summary>
public class Wgs84AutoUtmProjection : Wgs84UtmProjection
{
public const string DefaultCrsId = "AUTO2:42001";
private readonly string autoCrsId;
public Wgs84AutoUtmProjection() // parameterless constructor for XAML
: this(DefaultCrsId)
{
}
public Wgs84AutoUtmProjection(string crsId)
: base(31, Hemisphere.North)
{
autoCrsId = crsId;
if (!string.IsNullOrEmpty(autoCrsId))
{
CrsId = autoCrsId;
}
EnableCenterUpdates();
}
protected override void CenterChanged()
{
var zone = (int)Math.Floor(Center.Longitude / 6d) + 31;
var hemisphere = Center.Latitude >= 0d ? Hemisphere.North : Hemisphere.South;
if (Zone != zone || Hemisphere != hemisphere)
{
SetZone(zone, hemisphere);
if (!string.IsNullOrEmpty(autoCrsId))
{
CrsId = autoCrsId;
}
}
}
}
}

View file

@ -29,7 +29,7 @@ namespace MapControl
SetZone(zone, hemisphere);
}
protected void SetZone(int zone, Hemisphere hemisphere)
public void SetZone(int zone, Hemisphere hemisphere)
{
if (zone < FirstZone || zone > LastZone)
{

View file

@ -249,7 +249,7 @@ namespace MapControl
{ "LAYERS", RequestLayers ?? AvailableLayers?.FirstOrDefault() ?? "" },
{ "STYLES", RequestStyles ?? "" },
{ "FORMAT", "image/png" },
{ "CRS", GetCrsValue() },
{ "CRS", ParentMap.MapProjection.CrsId },
{ "BBOX", GetBboxValue(bbox) },
{ "WIDTH", Math.Ceiling(width).ToString("F0") },
{ "HEIGHT", Math.Ceiling(height).ToString("F0") }
@ -285,7 +285,7 @@ namespace MapControl
{ "LAYERS", RequestLayers ?? AvailableLayers?.FirstOrDefault() ?? "" },
{ "STYLES", RequestStyles ?? "" },
{ "INFO_FORMAT", format },
{ "CRS", GetCrsValue() },
{ "CRS", ParentMap.MapProjection.CrsId },
{ "BBOX", GetBboxValue(bbox) },
{ "WIDTH", Math.Ceiling(width).ToString("F0") },
{ "HEIGHT", Math.Ceiling(height).ToString("F0") },
@ -322,28 +322,6 @@ namespace MapControl
return new Uri(ServiceUri.GetLeftPart(UriPartial.Path) + "?" + query);
}
protected virtual string GetCrsValue()
{
var projection = ParentMap.MapProjection;
var crs = projection.CrsId;
if (crs.StartsWith("AUTO2:") || crs.StartsWith("AUTO:"))
{
var lon = 0d;
var lat = 0d; ;
if (projection.Center != null)
{
lon = projection.Center.Longitude;
lat = projection.Center.Latitude;
}
crs = string.Format(CultureInfo.InvariantCulture, "{0},1,{1:0.########},{2:0.########}", crs, lon, lat);
}
return crs;
}
protected virtual string GetBboxValue(Rect bbox)
{
var crs = ParentMap.MapProjection.CrsId;

View file

@ -36,7 +36,7 @@ namespace MapControl
return new Matrix(k, 0d, 0d, k, 0d, 0d);
}
public override Point? LocationToMap(double latitude, double longitude)
public override Point LocationToMap(double latitude, double longitude)
{
return new Point(
MeterPerDegree * longitude,

View file

@ -16,7 +16,7 @@ namespace MapControl
FrameworkPropertyMetadataOptions.AffectsParentArrange);
public static readonly DependencyProperty MapRectProperty =
DependencyPropertyHelper.RegisterAttached<MapRect>("MapRect", typeof(MapPanel), null,
DependencyPropertyHelper.RegisterAttached<Rect?>("MapRect", typeof(MapPanel), null,
FrameworkPropertyMetadataOptions.AffectsParentArrange);
public static MapBase GetParentMap(FrameworkElement element)

View file

@ -36,10 +36,7 @@ namespace MapControl
private void AddPolylinePoints(StreamGeometryContext context, IEnumerable<Location> locations, double longitudeOffset, bool closed)
{
var points = locations
.Select(location => LocationToView(location, longitudeOffset))
.Where(point => point.HasValue)
.Select(point => point.Value);
var points = locations.Select(location => LocationToView(location, longitudeOffset));
if (points.Any())
{

View file

@ -22,7 +22,7 @@ namespace MapControl
(element, oldValue, newValue) => (element.Parent as MapPanel)?.InvalidateArrange());
public static readonly DependencyProperty MapRectProperty =
DependencyPropertyHelper.RegisterAttached<MapRect>("MapRect", typeof(MapPanel), null,
DependencyPropertyHelper.RegisterAttached<Rect?>("MapRect", typeof(MapPanel), null,
(element, oldValue, newValue) => (element.Parent as MapPanel)?.InvalidateArrange());
public static void InitMapElement(FrameworkElement element)

View file

@ -42,10 +42,7 @@ namespace MapControl
private void AddPolylinePoints(PathFigureCollection figures, IEnumerable<Location> locations, double longitudeOffset, bool closed)
{
var points = locations
.Select(location => LocationToView(location, longitudeOffset))
.Where(point => point.HasValue)
.Select(point => point.Value);
var points = locations.Select(location => LocationToView(location, longitudeOffset));
if (points.Any())
{

View file

@ -76,22 +76,16 @@ namespace MapControl.Projections
return new Matrix(1d, 0d, 0d, 1d, 0d, 0d);
}
public override Point? LocationToMap(double latitude, double longitude)
public override Point LocationToMap(double latitude, double longitude)
{
if (LocationToMapTransform == null)
{
throw new InvalidOperationException("The CoordinateSystem property is not set.");
}
try
{
var coordinate = LocationToMapTransform.Transform([longitude, latitude]);
return new Point(coordinate[0], coordinate[1]);
}
catch
{
return null;
}
var coordinate = LocationToMapTransform.Transform([longitude, latitude]);
return new Point(coordinate[0], coordinate[1]);
}
public override Location MapToLocation(double x, double y)
@ -101,15 +95,9 @@ namespace MapControl.Projections
throw new InvalidOperationException("The CoordinateSystem property is not set.");
}
try
{
var coordinate = MapToLocationTransform.Transform([x, y]);
return new Location(coordinate[1], coordinate[0]);
}
catch
{
return null;
}
var coordinate = MapToLocationTransform.Transform([x, y]);
return new Location(coordinate[1], coordinate[0]);
}
}
}

View file

@ -32,9 +32,6 @@ namespace MapControl.Projections
MapControl.WorldMercatorProjection.DefaultCrsId => new WorldMercatorProjection(),
MapControl.Wgs84UpsNorthProjection.DefaultCrsId => new Wgs84UpsNorthProjection(),
MapControl.Wgs84UpsSouthProjection.DefaultCrsId => new Wgs84UpsSouthProjection(),
MapControl.Wgs84AutoUtmProjection.DefaultCrsId => new Wgs84AutoUtmProjection(),
MapControl.OrthographicProjection.DefaultCrsId => new Wgs84OrthographicProjection(),
MapControl.StereographicProjection.DefaultCrsId => new Wgs84StereographicProjection(),
_ => GetProjectionFromEpsgCode(crsId) ?? base.GetProjection(crsId)
};
}

View file

@ -1,46 +0,0 @@
using System;
namespace MapControl.Projections
{
/// <summary>
/// WGS84 Universal Transverse Mercator Projection with automatic zone selection from
/// the projection center. If the CRS identifier passed to the constructor is null or empty,
/// appropriate values from EPSG:32601 to EPSG:32660 and EPSG:32701 to EPSG:32760 are used.
/// </summary>
public class Wgs84AutoUtmProjection : Wgs84UtmProjection
{
private readonly string autoCrsId;
public Wgs84AutoUtmProjection() // parameterless constructor for XAML
: this(MapControl.Wgs84AutoUtmProjection.DefaultCrsId)
{
}
public Wgs84AutoUtmProjection(string crsId)
: base(31, Hemisphere.North)
{
autoCrsId = crsId;
if (!string.IsNullOrEmpty(autoCrsId))
{
CrsId = autoCrsId;
}
}
protected override void CenterChanged()
{
var zone = (int)Math.Floor(Center.Longitude / 6d) + 31;
var hemisphere = Center.Latitude >= 0d ? Hemisphere.North : Hemisphere.South;
if (Zone != zone || Hemisphere != hemisphere)
{
SetZone(zone, hemisphere);
if (!string.IsNullOrEmpty(autoCrsId))
{
CrsId = autoCrsId;
}
}
}
}
}

View file

@ -1,44 +0,0 @@
using System.Globalization;
#if WPF
using System.Windows.Media;
#endif
namespace MapControl.Projections
{
/// <summary>
/// Spherical Orthographic Projection - AUTO2:42003.
/// See "Map Projections - A Working Manual" (https://pubs.usgs.gov/publication/pp1395), p.148-150.
/// </summary>
public class Wgs84OrthographicProjection : ProjNetMapProjection
{
public Wgs84OrthographicProjection()
{
EnableCenterUpdates();
}
protected override void CenterChanged()
{
var wktFormat =
"PROJCS[\"WGS 84 / World Mercator\"," +
WktConstants.GeogCsWgs84 + "," +
"PROJECTION[\"Orthographic\"]," +
"PARAMETER[\"latitude_of_origin\",{0:0.########}]," +
"PARAMETER[\"central_meridian\",{1:0.########}]," +
"UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]]," +
"AXIS[\"Easting\",EAST]," +
"AXIS[\"Northing\",NORTH]" +
"AUTHORITY[\"AUTO2\",\"42003\"]]";
CoordinateSystemWkt = string.Format(
CultureInfo.InvariantCulture, wktFormat, Center.Latitude, Center.Longitude);
}
public override Matrix RelativeTransform(double latitude, double longitude)
{
var p = new AzimuthalProjection.ProjectedPoint(Center.Latitude, Center.Longitude, latitude, longitude);
(var scaleX, var scaleY) = p.RelativeScale(p.CosC, 1d); // p.149 (20-5), k == 1
return RelativeTransform(latitude, longitude, scaleX, scaleY);
}
}
}

View file

@ -1,44 +0,0 @@
using System.Globalization;
#if WPF
using System.Windows.Media;
#endif
namespace MapControl.Projections
{
/// <summary>
/// Spherical Stereographic Projection - AUTO2:97002.
/// See "Map Projections - A Working Manual" (https://pubs.usgs.gov/publication/pp1395), p.157-160.
/// </summary>
public class Wgs84StereographicProjection : ProjNetMapProjection
{
public Wgs84StereographicProjection()
{
EnableCenterUpdates();
}
protected override void CenterChanged()
{
var wktFormat =
"PROJCS[\"WGS 84 / World Mercator\"," +
WktConstants.GeogCsWgs84 + "," +
"PROJECTION[\"Oblique_Stereographic\"]," +
"PARAMETER[\"latitude_of_origin\",{0:0.########}]," +
"PARAMETER[\"central_meridian\",{1:0.########}]," +
"UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]]," +
"AXIS[\"Easting\",EAST]," +
"AXIS[\"Northing\",NORTH]" +
"AUTHORITY[\"AUTO2\",\"97002\"]]";
CoordinateSystemWkt = string.Format(
CultureInfo.InvariantCulture, wktFormat, Center.Latitude, Center.Longitude);
}
public override Matrix RelativeTransform(double latitude, double longitude)
{
var p = new AzimuthalProjection.ProjectedPoint(Center.Latitude, Center.Longitude, latitude, longitude);
var k = 2d / (1d + p.CosC); // p.157 (21-4), k0 == 1
return RelativeTransform(latitude, longitude, k, k);
}
}
}

View file

@ -23,9 +23,7 @@ namespace MapControl.Projections
public override Matrix RelativeTransform(double latitude, double longitude)
{
var k = PolarStereographicProjection.RelativeScale(Hemisphere.North, Wgs84Flattening, 0.994, latitude);
return RelativeTransform(latitude, longitude, k, k);
return PolarStereographicProjection.RelativeScale(Hemisphere.North, Wgs84Flattening, 0.994, latitude, longitude);
}
}
@ -48,9 +46,7 @@ namespace MapControl.Projections
public override Matrix RelativeTransform(double latitude, double longitude)
{
var k = PolarStereographicProjection.RelativeScale(Hemisphere.South, Wgs84Flattening, 0.994, latitude);
return RelativeTransform(latitude, longitude, k, k);
return PolarStereographicProjection.RelativeScale(Hemisphere.North, Wgs84Flattening, 0.994, latitude, longitude);
}
}
}