We did not used this code for the port to Apache SIS. Instead, we rewrote the projection using the formulas given in the EPSG guide.
Command line:
svn diff --extensions "--unified --ignore-space-change --ignore-all-space --ignore-eol-style" -r9358:9359 https://svn.osgeo.org/geotools/trunk/modules/library/referencing/src/main/java/org/geotools/referencing/operation/projection/PolarStereographic.java
Revision 9358 | Revision 9359 |
---|---|
/* * Geotools - OpenSource mapping toolkit * (C) 2003, 2004, Geotools Project Managment Committee (PMC) * (C) 2001, Institut de Recherche pour le Développement * (C) 1999, Fisheries and Oceans Canada * * 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 * * * This package contains formulas from the PROJ package of USGS. * USGS's work is fully acknowledged here. */ /* * Some parts Copyright (c) 2000, Frank Warmerdam * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ package org.geotools.referencing.operation.projection; // J2SE dependencies and extensions import java.util.Collection; import java.awt.geom.Point2D; // OpenGIS dependencies import org.opengis.parameter.ParameterValueGroup; import org.opengis.parameter.ParameterNotFoundException; // Geotools dependencies import org.geotools.resources.cts.ResourceKeys; import org.geotools.resources.cts.Resources; /** * The polar case of the {@linkplain Stereographic stereographic} projection. * This default implementation uses USGS equation (i.e. iteration) for computing * the {@linkplain #inverseTransformNormalized inverse transform}. * * @version $Id$ * @author André Gosselin * @author Martin Desruisseaux * @author Rueben Schulz */ public class StereographicPolar extends Stereographic { /** * A constant used in the transformations. * This is <strong>not</strong> equal to the {@link #scaleFactor}. */ private final double k0; /** * Latitude of true scale, in radians (a.k.a. standard_parallel_1). */ protected double latitudeTrueScale; /** * <code>true</code> if this projection is for the south pole, or <code>false</code> * if it is for the north pole. */ protected final boolean southPole; /** * Construct a polar stereographic projection. * * @param parameters The group of parameter values. * @param expected The expected parameter descriptors. * @param latitudeOfOrigin The latitude of origin in radians. If not Double.NaN * it overrides the latitude of origin parameter. * @param stereoType The type of stereographic projection (used for * creating wkt). * @return The created math transform. * @throws ParameterNotFoundException if a required parameter was not found. */ protected StereographicPolar(final ParameterValueGroup parameters, final Collection expected, final double latitudeOfOrigin, final short stereoType) throws ParameterNotFoundException { super(parameters, expected); this.stereoType = stereoType; //over-ride the latitude of origin parameter if (!Double.isNaN(latitudeOfOrigin)) { this.latitudeOfOrigin = latitudeOfOrigin; } southPole = (this.latitudeOfOrigin < 0); this.latitudeOfOrigin = (southPole) ? -(Math.PI/2) : +(Math.PI/2); //get standard_parallel_1 parameter value latitudeTrueScale = doubleValue(expected, Provider_Polar_B.LATITUDE_TRUE_SCALE, parameters); if (Double.isNaN(latitudeTrueScale)) { latitudeTrueScale = latitudeOfOrigin; } ensureLatitudeInRange(Provider_Polar_B.LATITUDE_TRUE_SCALE, latitudeTrueScale, true); if (Math.abs(Math.abs(latitudeTrueScale)-(Math.PI/2)) >= EPS) { final double latTrueScale = (southPole) ? -latitudeTrueScale : latitudeTrueScale; final double t = Math.sin(latTrueScale); k0 = msfn(t ,Math.cos(latTrueScale)) / tsfn(latTrueScale, t); //derives from (21-32 and 21-33) } else { // True scale at pole (part of (21-33)) k0 = 2.0 / Math.sqrt(Math.pow(1+excentricity, 1+excentricity)* Math.pow(1-excentricity, 1-excentricity)); } } /** * Transforms the specified (<var>x</var>,<var>y</var>) coordinate (units in radians) * and stores the result in <code>ptDst</code> (linear distance on a unit sphere). */ protected Point2D transformNormalized(double x, double y, Point2D ptDst) throws ProjectionException { final double sinlat = Math.sin(y); final double coslon = Math.cos(x); final double sinlon = Math.sin(x); if (southPole) { final double rho = k0 * tsfn(-y, -sinlat); x = rho * sinlon; y = rho * coslon; } else { final double rho = k0 * tsfn(y, sinlat); x = rho * sinlon; y = -rho * coslon; } if (ptDst != null) { ptDst.setLocation(x,y); return ptDst; } return new Point2D.Double(x,y); } /** * Transforms the specified (<var>x</var>,<var>y</var>) coordinate (units in radians) * and stores the result in <code>ptDst</code> (linear distance on a unit sphere). */ protected Point2D inverseTransformNormalized(double x, double y, Point2D ptDst) throws ProjectionException { final double rho = Math.sqrt(x*x + y*y); if (southPole) { y = -y; } /* * Compute latitude using iterative technique. */ final double t = rho/k0; final double halfe = excentricity/2.0; double phi0 = 0; for (int i=MAX_ITER;;) { final double esinphi = excentricity * Math.sin(phi0); final double phi = (Math.PI/2) - 2.0*Math.atan(t*Math.pow((1-esinphi)/(1+esinphi), halfe)); if (Math.abs(phi-phi0) < TOL) { x = (Math.abs(rho)<EPS) ? 0.0 : Math.atan2(x, -y); y = (southPole) ? -phi : phi; break; } phi0 = phi; if (--i < 0) { throw new ProjectionException(Resources.format(ResourceKeys.ERROR_NO_CONVERGENCE)); } } if (ptDst != null) { ptDst.setLocation(x,y); return ptDst; } return new Point2D.Double(x,y); } /** * {@inheritDoc} */ public ParameterValueGroup getParameterValues() { final ParameterValueGroup values = super.getParameterValues(); switch (stereoType) { case POLAR_B: case POLAR_NORTH: case POLAR_SOUTH: { final Collection expected = getParameterDescriptors().descriptors(); set(expected, Provider_Polar_B.LATITUDE_TRUE_SCALE, values, latitudeTrueScale); } } return values; } /** * Returns a hash value for this map projection. */ public int hashCode() { final long code = Double.doubleToLongBits(k0); return ((int)code ^ (int)(code >>> 32)) + 37*super.hashCode(); } /** * Compares the specified object with this map projection for equality. */ public boolean equals(final Object object) { if (object == this) { // Slight optimization return true; } if (super.equals(object)) { final StereographicPolar that = (StereographicPolar) object; return this.southPole == that.southPole && equals(this.k0, that.k0) && equals(this.latitudeTrueScale, that.latitudeTrueScale); } return false; } /** * Provides the transform equations for the spherical case of the polar * stereographic projection. * * @version $Id$ * @author Martin Desruisseaux * @author Rueben Schulz */ static final class Spherical extends StereographicPolar { /** * A constant used in the transformations. This constant hides the <code>k0</code> * constant from the ellipsoidal case. The spherical and ellipsoidal <code>k0</code> * are not computed in the same way, and we preserve the ellipsoidal <code>k0</code> * in {@link Stereographic} in order to allow assertions to work. */ private final double k0; /** * Construct a spherical stereographic projection. * * @param parameters The group of parameter values. * @param expected The expected parameter descriptors. * @param latitudeOfOrigin The latitude of origin in radians. If not Double.NaN * it overrides the latitude of origin parameter. * @param stereoType The type of stereographic projection (used for * creating wkt). * @return The created math transform. * @throws ParameterNotFoundException if a required parameter was not found. */ protected Spherical(final ParameterValueGroup parameters, final Collection expected, final double latitudeOfOrigin, final short stereoType) throws ParameterNotFoundException { super(parameters, expected, latitudeOfOrigin, stereoType); assert isSpherical; if (Math.abs(Math.abs(latitudeTrueScale) - (Math.PI/2)) >= EPS) { if (southPole) { k0 = 1 - Math.sin(latitudeTrueScale); //derived from (21-11) } else { k0 = 1 + Math.sin(latitudeTrueScale); //derived from (21-7) } } else { k0 = 2; } } /** * Transforms the specified (<var>x</var>,<var>y</var>) coordinate (units in radians) * and stores the result in <code>ptDst</code> (linear distance on a unit sphere). */ protected Point2D transformNormalized(double x, double y, Point2D ptDst) throws ProjectionException { //Compute using ellipsoidal formulas, for comparaison later. assert (ptDst = super.transformNormalized(x, y, ptDst)) != null; final double coslat = Math.cos(y); final double sinlat = Math.sin(y); final double coslon = Math.cos(x); final double sinlon = Math.sin(x); if (southPole) { if (Math.abs(1-sinlat) < EPS) { throw new ProjectionException(Resources.format( ResourceKeys.ERROR_VALUE_TEND_TOWARD_INFINITY)); } // (21-12) final double f = k0 * coslat / (1-sinlat); // == tan (pi/4 + phi/2) x = f * sinlon; // (21-9) y = f * coslon; // (21-10) } else { if (Math.abs(1+sinlat) < EPS) { throw new ProjectionException(Resources.format( ResourceKeys.ERROR_VALUE_TEND_TOWARD_INFINITY)); } // (21-8) final double f = k0 * coslat / (1+sinlat); // == tan (pi/4 - phi/2) x = f * sinlon; // (21-5) y = -f * coslon; // (21-6) } assert Math.abs(ptDst.getX()-x) <= EPS*globalScale : x; assert Math.abs(ptDst.getY()-y) <= EPS*globalScale : y; if (ptDst != null) { ptDst.setLocation(x,y); return ptDst; } return new Point2D.Double(x,y); } /** * Transforms the specified (<var>x</var>,<var>y</var>) coordinate (units in radians) * and stores the result in <code>ptDst</code> (linear distance on a unit sphere). */ protected Point2D inverseTransformNormalized(double x, double y, Point2D ptDst) throws ProjectionException { // Compute using ellipsoidal formulas, for comparaison later. assert (ptDst = super.inverseTransformNormalized(x, y, ptDst)) != null; final double rho = Math.sqrt(x*x + y*y); if (!southPole) { y = -y; } // (20-17) call atan2(x,y) to properly deal with y==0 x = (Math.abs(x)<EPS && Math.abs(y)<EPS) ? 0.0 : Math.atan2(x, y); if (Math.abs(rho) < EPS) { y = latitudeOfOrigin; } else { final double c = 2.0 * Math.atan(rho/k0); final double cosc = Math.cos(c); y = (southPole) ? Math.asin(-cosc) : Math.asin(cosc); // (20-14) with phi1=90 } assert Math.abs(ptDst.getX()-x) <= EPS : x; assert Math.abs(ptDst.getY()-y) <= EPS : y; if (ptDst != null) { ptDst.setLocation(x,y); return ptDst; } return new Point2D.Double(x,y); } } /** * Overides {@link PolarStereographic} to use the a series for the * {@link #inverseTransformNormalized inverseTransformNormalized(...)} * method. This is the equation specified by the EPSG. Allows for a * <code>"latitude_true_scale"<code> parameter to be used, but this * parameter is not listed by the EPSG and is not given as a parameter * by the provider. * <br><br> * Compared to the default {@link PolarStereographic} implementation, the series * implementation is a little bit faster at the expense of a little bit less * accuracy. The default {@link PolarStereographic} implementation implementation * is an derivated work of Proj4, and is therefor better tested. * * @version $Id$ * @author Rueben Schulz */ static final class Series extends StereographicPolar { /** * Constants used for the inverse polar series */ private final double A, B; /** * Constants used for the inverse polar series */ private double C, D; /** * A constant used in the transformations. This constant hides the <code>k0</code> * constant from the USGS case. The EPSG and USGS <code>k0</code> are not computed * in the same way, and we preserve the USGS <code>k0</code> in order to allow * assertions to work. */ private final double k0; /** * Construct a polar stereographic projection (seires inverse equations). * * @param parameters The group of parameter values. * @param expected The expected parameter descriptors. * @param latitudeOfOrigin The latitude of origin in radians. If not Double.NaN * it overrides the latitude of origin parameter. * @param stereoType The type of stereographic projection (used for * creating wkt). * @return The created math transform. * @throws ParameterNotFoundException if a required parameter was not found. */ protected Series(final ParameterValueGroup parameters, final Collection expected, final double latitudeOfOrigin, final short stereoType) throws ParameterNotFoundException { super(parameters, expected, latitudeOfOrigin, stereoType); //See Snyde P. 19, "Computation of Series" final double e4 = excentricitySquared*excentricitySquared; final double e6 = excentricitySquared*excentricitySquared*excentricitySquared; final double e8 = excentricitySquared*excentricitySquared*excentricitySquared*excentricitySquared; C = 7.0*e6/120.0 + 81.0*e8/1120.0; D = 4279.0*e8/161280.0; A = excentricitySquared/2.0 + 5.0*e4/24.0 + e6/12.0 + 13.0*e8/360.0 - C; B = 2.0*(7.0*e4/48.0 + 29.0*e6/240.0 + 811.0*e8/11520.0) - 4.0*D; C *= 4.0; D *= 8.0; if (Math.abs(Math.abs(latitudeTrueScale)-(Math.PI/2)) >= EPS) { final double latTrueScale = (southPole) ? -latitudeTrueScale : latitudeTrueScale; final double t = Math.sin(latTrueScale); k0 = msfn(t, Math.cos(latTrueScale)) * Math.sqrt(Math.pow(1+excentricity, 1+excentricity)* Math.pow(1-excentricity, 1-excentricity)) / (2.0*tsfn(latTrueScale, t)); } else { k0 = 1.0; } } /** * Transforms the specified (<var>x</var>,<var>y</var>) coordinate * and stores the result in <code>ptDst</code>. */ protected Point2D inverseTransformNormalized(double x, double y, Point2D ptDst) throws ProjectionException { // Compute using itteration formulas, for comparaison later. assert (ptDst = super.inverseTransformNormalized(x, y, ptDst)) != null; final double rho = Math.sqrt(x*x + y*y); if (southPole) { y = -y; } // The series form final double t = (rho/k0) * Math.sqrt(Math.pow(1+excentricity, 1+excentricity)* Math.pow(1-excentricity, 1-excentricity)) / 2; final double chi = Math.PI/2 - 2*Math.atan(t); x = (Math.abs(rho)<EPS) ? 0.0 : Math.atan2(x, -y); //See Snyde P. 19, "Computation of Series" final double sin2chi = Math.sin(2.0*chi); final double cos2chi = Math.cos(2.0*chi); y = chi + sin2chi*(A + cos2chi*(B + cos2chi*(C + D*cos2chi))); y = (southPole) ? -y : y; assert Math.abs(ptDst.getX()-x) <= EPS : x; assert Math.abs(ptDst.getY()-y) <= EPS : y; if (ptDst != null) { ptDst.setLocation(x,y); return ptDst; } return new Point2D.Double(x,y); } } } |