2018-12-20 21:55:12 +01:00
|
|
|
|
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
|
2022-01-14 20:22:56 +01:00
|
|
|
|
// © 2022 Clemens Fischer
|
2018-12-20 21:55:12 +01:00
|
|
|
|
// Licensed under the Microsoft Public License (Ms-PL)
|
|
|
|
|
|
|
|
|
|
|
|
using System;
|
2021-11-17 23:46:48 +01:00
|
|
|
|
#if !UWP
|
2018-12-20 21:55:12 +01:00
|
|
|
|
using System.Windows;
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
namespace MapControl.Projections
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Elliptical Polar Stereographic Projection with a given scale factor at the pole and
|
|
|
|
|
|
/// optional false easting and northing, as used by the UPS North and UPS South projections.
|
|
|
|
|
|
/// See "Map Projections - A Working Manual" (https://pubs.usgs.gov/pp/1395/report.pdf), p.160-162.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class PolarStereographicProjection : MapProjection
|
|
|
|
|
|
{
|
2022-03-06 23:28:25 +01:00
|
|
|
|
private const double ConvergenceTolerance = 1e-6;
|
|
|
|
|
|
private const int MaxIterations = 10;
|
2018-12-20 21:55:12 +01:00
|
|
|
|
|
2022-03-07 17:28:08 +01:00
|
|
|
|
public bool IsNorth { get; }
|
2022-02-23 18:47:45 +01:00
|
|
|
|
public double ScaleFactor { get; }
|
|
|
|
|
|
public double FalseEasting { get; }
|
|
|
|
|
|
public double FalseNorthing { get; }
|
2018-12-20 21:55:12 +01:00
|
|
|
|
|
2022-03-07 17:28:08 +01:00
|
|
|
|
public PolarStereographicProjection(bool isNorth, double scaleFactor, double falseEasting, double falseNorthing)
|
2018-12-20 21:55:12 +01:00
|
|
|
|
{
|
2022-03-07 17:28:08 +01:00
|
|
|
|
Type = MapProjectionType.Azimuthal;
|
|
|
|
|
|
IsNorth = isNorth;
|
2022-02-23 18:47:45 +01:00
|
|
|
|
ScaleFactor = scaleFactor;
|
|
|
|
|
|
FalseEasting = falseEasting;
|
|
|
|
|
|
FalseNorthing = falseNorthing;
|
2018-12-20 21:55:12 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-12-02 17:45:58 +01:00
|
|
|
|
public override Scale GetRelativeScale(Location location)
|
2018-12-20 21:55:12 +01:00
|
|
|
|
{
|
2022-03-07 17:28:08 +01:00
|
|
|
|
var lat = (IsNorth ? location.Latitude : -location.Latitude) * Math.PI / 180d;
|
2018-12-20 21:55:12 +01:00
|
|
|
|
var a = Wgs84EquatorialRadius;
|
|
|
|
|
|
var e = Wgs84Eccentricity;
|
|
|
|
|
|
var s = Math.Sqrt(Math.Pow(1 + e, 1 + e) * Math.Pow(1 - e, 1 - e));
|
|
|
|
|
|
var t = Math.Tan(Math.PI / 4d - lat / 2d) / ConformalFactor(lat);
|
2022-02-23 18:47:45 +01:00
|
|
|
|
var rho = 2d * a * ScaleFactor * t / s;
|
2018-12-20 21:55:12 +01:00
|
|
|
|
var eSinLat = e * Math.Sin(lat);
|
|
|
|
|
|
var m = Math.Cos(lat) / Math.Sqrt(1d - eSinLat * eSinLat);
|
|
|
|
|
|
var k = rho / (a * m);
|
|
|
|
|
|
|
2022-12-02 17:45:58 +01:00
|
|
|
|
return new Scale(k, k);
|
2018-12-20 21:55:12 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-12-02 17:45:58 +01:00
|
|
|
|
public override Point? LocationToMap(Location location)
|
2018-12-20 21:55:12 +01:00
|
|
|
|
{
|
|
|
|
|
|
var lat = location.Latitude * Math.PI / 180d;
|
|
|
|
|
|
var lon = location.Longitude * Math.PI / 180d;
|
|
|
|
|
|
|
2022-03-07 17:28:08 +01:00
|
|
|
|
if (IsNorth)
|
2018-12-20 21:55:12 +01:00
|
|
|
|
{
|
|
|
|
|
|
lon = Math.PI - lon;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
lat = -lat;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var a = Wgs84EquatorialRadius;
|
|
|
|
|
|
var e = Wgs84Eccentricity;
|
|
|
|
|
|
var s = Math.Sqrt(Math.Pow(1 + e, 1 + e) * Math.Pow(1 - e, 1 - e));
|
|
|
|
|
|
var t = Math.Tan(Math.PI / 4d - lat / 2d) / ConformalFactor(lat);
|
2022-02-23 18:47:45 +01:00
|
|
|
|
var rho = 2d * a * ScaleFactor * t / s;
|
2018-12-20 21:55:12 +01:00
|
|
|
|
|
2022-02-23 18:47:45 +01:00
|
|
|
|
return new Point(rho * Math.Sin(lon) + FalseEasting, rho * Math.Cos(lon) + FalseNorthing);
|
2018-12-20 21:55:12 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-03-26 19:08:20 +01:00
|
|
|
|
public override Location MapToLocation(Point point)
|
2018-12-20 21:55:12 +01:00
|
|
|
|
{
|
2022-02-23 18:47:45 +01:00
|
|
|
|
point.X -= FalseEasting;
|
|
|
|
|
|
point.Y -= FalseNorthing;
|
2018-12-20 21:55:12 +01:00
|
|
|
|
|
|
|
|
|
|
var lon = Math.Atan2(point.X, point.Y);
|
|
|
|
|
|
var rho = Math.Sqrt(point.X * point.X + point.Y * point.Y);
|
|
|
|
|
|
var a = Wgs84EquatorialRadius;
|
|
|
|
|
|
var e = Wgs84Eccentricity;
|
|
|
|
|
|
var s = Math.Sqrt(Math.Pow(1 + e, 1 + e) * Math.Pow(1 - e, 1 - e));
|
2022-02-23 18:47:45 +01:00
|
|
|
|
var t = rho * s / (2d * a * ScaleFactor);
|
2018-12-20 21:55:12 +01:00
|
|
|
|
var lat = Math.PI / 2d - 2d * Math.Atan(t);
|
|
|
|
|
|
var relChange = 1d;
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < MaxIterations && relChange > ConvergenceTolerance; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var newLat = Math.PI / 2d - 2d * Math.Atan(t * ConformalFactor(lat));
|
|
|
|
|
|
relChange = Math.Abs(1d - newLat / lat);
|
|
|
|
|
|
lat = newLat;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-03-07 17:28:08 +01:00
|
|
|
|
if (IsNorth)
|
2018-12-20 21:55:12 +01:00
|
|
|
|
{
|
|
|
|
|
|
lon = Math.PI - lon;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
lat = -lat;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return new Location(lat * 180d / Math.PI, lon * 180d / Math.PI);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static double ConformalFactor(double lat)
|
|
|
|
|
|
{
|
|
|
|
|
|
var eSinLat = Wgs84Eccentricity * Math.Sin(lat);
|
|
|
|
|
|
|
|
|
|
|
|
return Math.Pow((1d - eSinLat) / (1d + eSinLat), Wgs84Eccentricity / 2d);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-23 23:19:32 +01:00
|
|
|
|
/// <summary>
|
2022-03-06 23:28:25 +01:00
|
|
|
|
/// Universal Polar Stereographic North Projection - EPSG:32661.
|
2022-02-23 23:19:32 +01:00
|
|
|
|
/// </summary>
|
2018-12-20 21:55:12 +01:00
|
|
|
|
public class UpsNorthProjection : PolarStereographicProjection
|
|
|
|
|
|
{
|
2022-02-23 18:47:45 +01:00
|
|
|
|
public const string DefaultCrsId = "EPSG:32661";
|
|
|
|
|
|
|
2022-01-19 23:40:05 +01:00
|
|
|
|
public UpsNorthProjection()
|
2022-03-07 17:28:08 +01:00
|
|
|
|
: base(true, 0.994, 2e6, 2e6)
|
2018-12-20 21:55:12 +01:00
|
|
|
|
{
|
2022-03-07 17:28:08 +01:00
|
|
|
|
CrsId = DefaultCrsId;
|
2018-12-20 21:55:12 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-23 23:19:32 +01:00
|
|
|
|
/// <summary>
|
2022-03-06 23:28:25 +01:00
|
|
|
|
/// Universal Polar Stereographic South Projection - EPSG:32761.
|
2022-02-23 23:19:32 +01:00
|
|
|
|
/// </summary>
|
2018-12-20 21:55:12 +01:00
|
|
|
|
public class UpsSouthProjection : PolarStereographicProjection
|
|
|
|
|
|
{
|
2022-02-23 18:47:45 +01:00
|
|
|
|
public const string DefaultCrsId = "EPSG:32761";
|
|
|
|
|
|
|
2022-01-19 23:40:05 +01:00
|
|
|
|
public UpsSouthProjection()
|
2022-03-07 17:28:08 +01:00
|
|
|
|
: base(false, 0.994, 2e6, 2e6)
|
2018-12-20 21:55:12 +01:00
|
|
|
|
{
|
2022-03-07 17:28:08 +01:00
|
|
|
|
CrsId = DefaultCrsId;
|
2018-12-20 21:55:12 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|