Skip to content

Commit

Permalink
Add polar stereographic projection
Browse files Browse the repository at this point in the history
  • Loading branch information
stigrj authored and FObermaier committed May 14, 2024
1 parent 48d1d1d commit 246879e
Show file tree
Hide file tree
Showing 3 changed files with 330 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
// Copyright 2015
//
// This file is part of ProjNet.
// ProjNet is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// ProjNet is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.

// You should have received a copy of the GNU Lesser General Public License
// along with ProjNet; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

/*
* Copyright (C) 2002 Urban Science Applications, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/

using System;
using System.Collections.Generic;
using ProjNet.CoordinateSystems.Transformations;

namespace ProjNet.CoordinateSystems.Projections
{
/// <summary>
/// Implements the Polar Stereographic Projection.
/// </summary>
[Serializable]
internal class PolarStereographicProjection : MapProjection
{
private readonly double _globalScale;
private readonly double _reciprocGlobalScale;

private static int MAXIMUM_ITERATIONS = 15;
private static double ITERATION_TOLERANCE = 1E-14;
private static double EPS15 = 1E-15;
private static double M_HALFPI = 0.5 * Math.PI;
private double phits, akm1;
private bool N_POLE;


/// <summary>
/// Initializes the PolarStereographicProjection object with the specified parameters.
/// </summary>
/// <param name="parameters">List of parameters to initialize the projection.</param>
/// <remarks>
/// <para>The parameters this projection expects are listed below.</para>
/// <list type="table">
/// <listheader><term>Items</term><description>Descriptions</description></listheader>
/// <item><term>central_meridian</term><description>The longitude of the point from which the values of both the geographical coordinates on the ellipsoid and the grid coordinates on the projection are deemed to increment or decrement for computational purposes. Alternatively it may be considered as the longitude of the point which in the absence of application of false coordinates has grid coordinates of (0,0).</description></item>
/// <item><term>latitude_of_origin</term><description>The latitude of the point from which the values of both the geographical coordinates on the ellipsoid and the grid coordinates on the projection are deemed to increment or decrement for computational purposes. Alternatively it may be considered as the latitude of the point which in the absence of application of false coordinates has grid coordinates of (0,0).</description></item>
/// <item><term>scale_factor</term><description>The factor by which the map grid is reduced or enlarged during the projection process, defined by its value at the natural origin.</description></item>
/// <item><term>false_easting</term><description>Since the natural origin may be at or near the centre of the projection and under normal coordinate circumstances would thus give rise to negative coordinates over parts of the mapped area, this origin is usually given false coordinates which are large enough to avoid this inconvenience. The False Easting, FE, is the easting value assigned to the abscissa (east).</description></item>
/// <item><term>false_northing</term><description>Since the natural origin may be at or near the centre of the projection and under normal coordinate circumstances would thus give rise to negative coordinates over parts of the mapped area, this origin is usually given false coordinates which are large enough to avoid this inconvenience. The False Northing, FN, is the northing value assigned to the ordinate.</description></item>
/// </list>
/// </remarks>
public PolarStereographicProjection(IEnumerable<ProjectionParameter> parameters)
: this(parameters, null)
{
}

/// <summary>
/// Initializes the PolarStereographicProjection object with the specified parameters.
/// </summary>
/// <param name="parameters">List of parameters to initialize the projection.</param>
/// <param name="inverse">Inverse projection</param>
/// <remarks>
/// <para>The parameters this projection expects are listed below.</para>
/// <list type="table">
/// <listheader><term>Items</term><description>Descriptions</description></listheader>
/// <item><term>central_meridian</term><description>The longitude of the point from which the values of both the geographical coordinates on the ellipsoid and the grid coordinates on the projection are deemed to increment or decrement for computational purposes. Alternatively it may be considered as the longitude of the point which in the absence of application of false coordinates has grid coordinates of (0,0).</description></item>
/// <item><term>latitude_of_origin</term><description>The latitude of the point from which the values of both the geographical coordinates on the ellipsoid and the grid coordinates on the projection are deemed to increment or decrement for computational purposes. Alternatively it may be considered as the latitude of the point which in the absence of application of false coordinates has grid coordinates of (0,0).</description></item>
/// <item><term>scale_factor</term><description>The factor by which the map grid is reduced or enlarged during the projection process, defined by its value at the natural origin.</description></item>
/// <item><term>false_easting</term><description>Since the natural origin may be at or near the centre of the projection and under normal coordinate circumstances would thus give rise to negative coordinates over parts of the mapped area, this origin is usually given false coordinates which are large enough to avoid this inconvenience. The False Easting, FE, is the easting value assigned to the abscissa (east).</description></item>
/// <item><term>false_northing</term><description>Since the natural origin may be at or near the centre of the projection and under normal coordinate circumstances would thus give rise to negative coordinates over parts of the mapped area, this origin is usually given false coordinates which are large enough to avoid this inconvenience. The False Northing, FN, is the northing value assigned to the ordinate.</description></item>
/// </list>
/// </remarks>
public PolarStereographicProjection(IEnumerable<ProjectionParameter> parameters, PolarStereographicProjection inverse)
: base(parameters, inverse)
{
Name = "Polar_Stereographic";

_globalScale = scale_factor * _semiMajor;
_reciprocGlobalScale = 1.0 / _globalScale;

if (_e == 0.0) throw new Exception("Polar Stereographics: only ellipsoidal formulation");
N_POLE = (lat_origin > 0.0); // N or S hemisphere
phits = Math.Abs(lat_origin);

if (Math.Abs(phits - M_HALFPI) < EPS10)
{
double one_p_e = 1.0 + _e;
double one_m_e = 1.0 - _e;
double pow_p = Math.Pow(one_p_e, one_p_e);
double pow_m = Math.Pow(one_m_e, one_m_e);
akm1 = 2.0 / Math.Sqrt(pow_p * pow_m);
}
else
{
double sinphits = Math.Sin(phits);
double cosphits = Math.Cos(phits);
akm1 = cosphits / tsfn(cosphits, sinphits, _e);

double t = _e * sinphits;
akm1 /= Math.Sqrt(1.0 - t * t);
}
}

/// <summary>
/// Converts coordinates in projected meters to radians.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
protected override void MetersToRadians(ref double x, ref double y)
{
x *= _reciprocGlobalScale;
y *= _reciprocGlobalScale;

if (N_POLE) y = -y;
double rho = Math.Sqrt(x * x + y * y);
double tp = -rho / akm1;
double phi_l = M_HALFPI - 2.0 * Math.Atan(tp);
double halfe = -0.5 * _e;

double lp_phi = 0.0;
for (int iter = MAXIMUM_ITERATIONS; ;)
{
double sinphi = _e * Math.Sin(phi_l);
double one_p_sinphi = 1.0 + sinphi;
double one_m_sinphi = 1.0 - sinphi;
lp_phi = 2.0 * Math.Atan(tp * Math.Pow(one_p_sinphi / one_m_sinphi, halfe)) + M_HALFPI;
if (Math.Abs(phi_l - lp_phi) < ITERATION_TOLERANCE)
{
break;
}

phi_l = lp_phi;
if (--iter < 0)
{
throw new Exception("Polar Stereographics doesn't converge");
}

}

if (!N_POLE) lp_phi = -lp_phi;
double lp_lam = (x == 0.0 && y == 0.0) ? 0.0 : Math.Atan2(x, y);

x = lp_lam + central_meridian;
y = lp_phi;
}

/// <summary>
/// Method to convert a point (lon, lat) in radians to (x, y) in meters
/// </summary>
/// <param name="lon">The longitude of the point in radians when entering, its x-ordinate in meters after exit.</param>
/// <param name="lat">The latitude of the point in radians when entering, its y-ordinate in meters after exit.</param>
protected override void RadiansToMeters(ref double lon, ref double lat)
{
double lp_lam = lon - central_meridian;
double lp_phi = lat;

double coslam = Math.Cos(lp_lam);
double sinlam = Math.Sin(lp_lam);

if (!N_POLE)
{
lp_phi = -lp_phi;
coslam = -coslam;
}

double sinphi = Math.Sin(lp_phi);
double cosphi = Math.Cos(lp_phi);

double x = (Math.Abs(lp_phi - M_HALFPI) < EPS15) ? 0.0 : akm1 * tsfn(cosphi, sinphi, _e);
lon = x * sinlam * _globalScale;
lat = -x * coslam * _globalScale;
}


/// <summary>
/// Returns the inverse of this projection.
/// </summary>
/// <returns>IMathTransform that is the reverse of the current projection.</returns>
public override MathTransform Inverse()
{
if (_inverse == null)
{
_inverse = new PolarStereographicProjection(_Parameters.ToProjectionParameter(), this);
}

return _inverse;
}

private double tsfn(double cosphi, double sinphi, double e)
{
double t = (sinphi > 0.0) ? cosphi / (1.0 + sinphi) : (1.0 - sinphi) / cosphi;
return Math.Exp(e * Math.Atanh(e * sinphi)) * t;

Check failure on line 215 in src/ProjNet/CoordinateSystems/Projections/PolarStereographicProjection.cs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest)

'Math' does not contain a definition for 'Atanh'

Check failure on line 215 in src/ProjNet/CoordinateSystems/Projections/PolarStereographicProjection.cs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest)

'Math' does not contain a definition for 'Atanh'

Check failure on line 215 in src/ProjNet/CoordinateSystems/Projections/PolarStereographicProjection.cs

View workflow job for this annotation

GitHub Actions / Build (macOS-latest)

'Math' does not contain a definition for 'Atanh'

Check failure on line 215 in src/ProjNet/CoordinateSystems/Projections/PolarStereographicProjection.cs

View workflow job for this annotation

GitHub Actions / Build (macOS-latest)

'Math' does not contain a definition for 'Atanh'
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ static ProjectionsRegistry()
Register("oblique_mercator", typeof(ObliqueMercatorProjection));
Register("oblique_stereographic", typeof(ObliqueStereographicProjection));
Register("orthographic", typeof(OrthographicProjection));
Register("polar_stereographic", typeof(PolarStereographicProjection));
}

/// <summary>
Expand Down
111 changes: 111 additions & 0 deletions test/ProjNet.Tests/CoordinateTransformTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,117 @@ public void TestObliqueStereographicProjection()
Assert.AreEqual(Coord2171[1], transformedCoord2171[1], 1);
}

[Test]
public void TestUniversalPolarStereographicProjection()
{
//test data from http://epsg.io/transform
double[] Coord4326 = new double[] { 15.00, 73.00 };
double[] Coord32661 = new double[] { 2491967.01029204, 163954.12194234435 };

string wkt4326 = "" +
"GEOGCS[\"WGS 84\"," +
"DATUM[\"WGS_1984\"," +
"SPHEROID[\"WGS 84\",6378137,298.257223563," +
"AUTHORITY[\"EPSG\",\"7030\"]]," +
"AUTHORITY[\"EPSG\",\"6326\"]]," +
"PRIMEM[\"Greenwich\",0," +
"AUTHORITY[\"EPSG\",\"8901\"]]," +
"UNIT[\"degree\",0.01745329251994328," +
"AUTHORITY[\"EPSG\",\"9122\"]]," +
"AUTHORITY[\"EPSG\",\"4326\"]]";

string wkt32661 = "" +
"PROJCS[\"WGS 84 / UPS North (N,E)\"," +
"GEOGCS[\"WGS 84\"," +
"DATUM[\"WGS_1984\"," +
"SPHEROID[\"WGS 84\",6378137,298.257223563," +
"AUTHORITY[\"EPSG\",\"7030\"]]," +
"AUTHORITY[\"EPSG\",\"6326\"]]," +
"PRIMEM[\"Greenwich\",0," +
"AUTHORITY[\"EPSG\",\"8901\"]]," +
"UNIT[\"degree\",0.0174532925199433," +
"AUTHORITY[\"EPSG\",\"9122\"]]," +
"AUTHORITY[\"EPSG\",\"4326\"]]," +
"PROJECTION[\"Polar_Stereographic\"]," +
"PARAMETER[\"latitude_of_origin\",90]," +
"PARAMETER[\"central_meridian\",0]," +
"PARAMETER[\"scale_factor\",0.994]," +
"PARAMETER[\"false_easting\",2000000]," +
"PARAMETER[\"false_northing\",2000000]," +
"UNIT[\"metre\",1," +
"AUTHORITY[\"EPSG\",\"9001\"]]," +
"AUTHORITY[\"EPSG\",\"32661\"]]";

var cs1 = CoordinateSystemFactory.CreateFromWkt(wkt4326);
var cs2 = CoordinateSystemFactory.CreateFromWkt(wkt32661);
var ctf = new CoordinateTransformationFactory();

var ict = ctf.CreateFromCoordinateSystems(cs2, cs1);
var ict2 = ctf.CreateFromCoordinateSystems(cs1, cs2);
double[] transformedCoord4326 = ict.MathTransform.Transform(Coord32661);
double[] transformedCoord32661 = ict2.MathTransform.Transform(Coord4326);

Assert.AreEqual(Coord4326[0], transformedCoord4326[0], 0.01);
Assert.AreEqual(Coord4326[1], transformedCoord4326[1], 0.01);
Assert.AreEqual(Coord32661[0], transformedCoord32661[0], 1);
Assert.AreEqual(Coord32661[1], transformedCoord32661[1], 1);
}

[Test]
public void TestAustralianAntarcticPolarStereographicProjection()
{
//test data from http://epsg.io/transform
double[] Coord4326 = new double[] { 15.00, -73.00 };
double[] Coord3032 = new double[] { 4476201.247377692, 7066975.373300694 };

string wkt4326 = "" +
"GEOGCS[\"WGS 84\"," +
"DATUM[\"WGS_1984\"," +
"SPHEROID[\"WGS 84\",6378137,298.257223563," +
"AUTHORITY[\"EPSG\",\"7030\"]]," +
"AUTHORITY[\"EPSG\",\"6326\"]]," +
"PRIMEM[\"Greenwich\",0," +
"AUTHORITY[\"EPSG\",\"8901\"]]," +
"UNIT[\"degree\",0.01745329251994328," +
"AUTHORITY[\"EPSG\",\"9122\"]]," +
"AUTHORITY[\"EPSG\",\"4326\"]]";

string wkt3032 = "" +
"PROJCS[\"WGS 84 / Australian Antarctic Polar Stereographic\"," +
"GEOGCS[\"WGS 84\"," +
"DATUM[\"WGS_1984\"," +
"SPHEROID[\"WGS 84\",6378137,298.257223563," +
"AUTHORITY[\"EPSG\",\"7030\"]]," +
"AUTHORITY[\"EPSG\",\"6326\"]]," +
"PRIMEM[\"Greenwich\",0," +
"AUTHORITY[\"EPSG\",\"8901\"]]," +
"UNIT[\"degree\",0.0174532925199433," +
"AUTHORITY[\"EPSG\",\"9122\"]]," +
"AUTHORITY[\"EPSG\",\"4326\"]]," +
"PROJECTION[\"Polar_Stereographic\"]," +
"PARAMETER[\"latitude_of_origin\",-71]," +
"PARAMETER[\"central_meridian\",70]," +
"PARAMETER[\"false_easting\",6000000]," +
"PARAMETER[\"false_northing\",6000000]," +
"UNIT[\"metre\",1," +
"AUTHORITY[\"EPSG\",\"9001\"]]," +
"AUTHORITY[\"EPSG\",\"3032\"]]";

var cs1 = CoordinateSystemFactory.CreateFromWkt(wkt4326);
var cs2 = CoordinateSystemFactory.CreateFromWkt(wkt3032);
var ctf = new CoordinateTransformationFactory();

var ict = ctf.CreateFromCoordinateSystems(cs2, cs1);
var ict2 = ctf.CreateFromCoordinateSystems(cs1, cs2);
double[] transformedCoord4326 = ict.MathTransform.Transform(Coord3032);
double[] transformedCoord3032 = ict2.MathTransform.Transform(Coord4326);

Assert.AreEqual(Coord4326[0], transformedCoord4326[0], 0.01);
Assert.AreEqual(Coord4326[1], transformedCoord4326[1], 0.01);
Assert.AreEqual(Coord3032[0], transformedCoord3032[0], 1);
Assert.AreEqual(Coord3032[1], transformedCoord3032[1], 1);
}

[Test]
public void TestUnitTransforms()
{
Expand Down

0 comments on commit 246879e

Please sign in to comment.