2026-01-19 21:38:18 +01:00
|
|
|
|
using System;
|
2026-01-21 14:15:26 +01:00
|
|
|
|
#if WPF
|
|
|
|
|
|
using System.Windows.Media;
|
|
|
|
|
|
#endif
|
2026-01-19 21:38:18 +01:00
|
|
|
|
|
|
|
|
|
|
namespace MapControl
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
2026-01-20 15:48:30 +01:00
|
|
|
|
/// See "Map Projections - A Working Manual" (https://pubs.usgs.gov/publication/pp1395), p.141.
|
2026-01-19 21:38:18 +01:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
public abstract class AzimuthalProjection : MapProjection
|
|
|
|
|
|
{
|
|
|
|
|
|
protected AzimuthalProjection()
|
2026-01-20 22:21:26 +01:00
|
|
|
|
: base(true)
|
2026-01-19 21:38:18 +01:00
|
|
|
|
{
|
|
|
|
|
|
Type = MapProjectionType.Azimuthal;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public double EarthRadius { get; set; } = Wgs84MeanRadius;
|
|
|
|
|
|
|
2026-01-20 15:48:30 +01:00
|
|
|
|
public readonly struct ProjectedPoint
|
2026-01-19 21:38:18 +01:00
|
|
|
|
{
|
2026-01-20 15:48:30 +01:00
|
|
|
|
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);
|
2026-01-21 14:15:26 +01:00
|
|
|
|
var cosC = sinPhi1 * sinPhi + cosPhi1 * cosPhi * cosLambda; // (5-3)
|
2026-01-19 21:38:18 +01:00
|
|
|
|
|
2026-01-20 15:48:30 +01:00
|
|
|
|
X = cosPhi * sinLambda;
|
|
|
|
|
|
Y = cosPhi1 * sinPhi - sinPhi1 * cosPhi * cosLambda;
|
2026-01-21 14:15:26 +01:00
|
|
|
|
CosC = Math.Min(Math.Max(cosC, -1d), 1d); // protect against rounding errors
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Matrix RelativeScale(double radialScale, double perpendicularScale)
|
|
|
|
|
|
{
|
|
|
|
|
|
var scale = new Matrix(radialScale, 0d, 0d, perpendicularScale, 0d, 0d);
|
|
|
|
|
|
|
|
|
|
|
|
if (radialScale != perpendicularScale)
|
|
|
|
|
|
{
|
|
|
|
|
|
scale.Rotate(-Math.Atan2(Y, X) * 180d / Math.PI);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return scale;
|
2026-01-20 15:48:30 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected ProjectedPoint GetProjectedPoint(double latitude, double longitude)
|
|
|
|
|
|
{
|
|
|
|
|
|
return new ProjectedPoint(Center.Latitude, Center.Longitude, latitude, longitude);
|
2026-01-19 21:38:18 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected Location GetLocation(double x, double y, double rho, double sinC)
|
|
|
|
|
|
{
|
2026-01-21 12:08:12 +01:00
|
|
|
|
var cos2C = 1d - sinC * sinC;
|
|
|
|
|
|
|
|
|
|
|
|
if (cos2C < 0d)
|
|
|
|
|
|
{
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var cosC = Math.Sqrt(cos2C);
|
2026-01-19 21:38:18 +01:00
|
|
|
|
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)
|
2026-01-21 12:08:12 +01:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-01-19 21:38:18 +01:00
|
|
|
|
|
2026-01-21 12:08:12 +01:00
|
|
|
|
return new Location(
|
|
|
|
|
|
phi * 180d / Math.PI,
|
|
|
|
|
|
Math.Atan2(u, v) * 180d / Math.PI + Center.Longitude);
|
2026-01-19 21:38:18 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|