All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.geotoolkit.referencing.operation.projection.Stereographic Maven / Gradle / Ivy

/*
 *    Geotoolkit.org - An Open Source Java GIS Toolkit
 *    http://www.geotoolkit.org
 *
 *    (C) 1999-2012, Open Source Geospatial Foundation (OSGeo)
 *    (C) 2009-2012, Geomatys
 *
 *    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;
 *    version 2.1 of the License.
 *
 *    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.
 *
 *    This package contains formulas from the PROJ package of USGS.
 *    USGS's work is fully acknowledged here. This derived work has
 *    been relicensed under LGPL with Frank Warmerdam's permission.
 */
package org.geotoolkit.referencing.operation.projection;

import net.jcip.annotations.Immutable;

import org.opengis.parameter.ParameterValueGroup;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.operation.MathTransform2D;

import org.geotoolkit.resources.Errors;
import org.geotoolkit.util.ComparisonMode;
import org.geotoolkit.referencing.operation.matrix.Matrix2;

import static java.lang.Math.*;
import static org.geotoolkit.internal.InternalUtilities.epsilonEqual;
import static org.geotoolkit.referencing.operation.provider.ObliqueStereographic.PARAMETERS;


/**
 * Stereographic projection. See the
 * Stereographic projection on
 * MathWorld for an overview. See any of the following providers for a list of programmatic
 * parameters:
 * 

*

    *
  • {@link org.geotoolkit.referencing.operation.provider.Stereographic}
  • *
  • {@link org.geotoolkit.referencing.operation.provider.PolarStereographic}
  • *
  • {@link org.geotoolkit.referencing.operation.provider.PolarStereographic.North}
  • *
  • {@link org.geotoolkit.referencing.operation.provider.PolarStereographic.South}
  • *
  • {@link org.geotoolkit.referencing.operation.provider.PolarStereographic.VariantB}
  • *
* * {@section Description} * * The directions starting from the central point are true, but the areas and the lengths become * increasingly deformed as one moves away from the center. This projection is used to represent * polar areas. It can be adapted for other areas having a circular form. *

* This implementation, and its subclasses, provides transforms for six cases of the * stereographic projection: *

*

    *
  • {@code "Oblique_Stereographic"} (EPSG code 9809), alias {@code "Double_Stereographic"} * in ESRI software
  • *
  • {@code "Stereographic"} in ESRI software (NOT EPSG code 9809)
  • *
  • {@code "Polar_Stereographic"} (EPSG code 9810, uses a series calculation for the * inverse)
  • *
  • {@code "Polar_Stereographic (variant B)"} (EPSG code 9829, uses a series calculation * for the inverse)
  • *
  • {@code "Stereographic_North_Pole"} in ESRI software (uses iteration for the inverse)
  • *
  • {@code "Stereographic_South_Pole"} in ESRI software (uses iteration for the inverse)
  • *
*

* Both the {@code "Oblique_Stereographic"} and {@code "Stereographic"} projections are "double" * projections involving two parts: 1) a conformal transformation of the geographic coordinates * to a sphere and 2) a spherical Stereographic projection. The EPSG considers both methods to * be valid, but considers them to be a different coordinate operation methods. *

* The {@code "Stereographic"} case uses the USGS equations of Snyder. This employs a simplified * conversion to the conformal sphere that computes the conformal latitude of each point on the * sphere. *

* The {@code "Oblique_Stereographic"} case uses equations from the EPSG. This uses a more * generalized form of the conversion to the conformal sphere; using only a single conformal * sphere at the origin point. Since this is a "double" projection, it is sometimes called * the "Double Stereographic". The {@code "Oblique_Stereographic"} is used in New Brunswick * (Canada) and the Netherlands. *

* The {@code "Stereographic"} and {@code "Double_Stereographic"} names are used in ESRI's * ArcGIS 8.x product. The {@code "Oblique_Stereographic"} name is the EPSG name for the * later only. * * {@note Tests points calculated with ArcGIS's "Double_Stereographic" are not always * equal to points calculated with the "Oblique_Stereographic". However, where * there are differences, two different implementations of these equations (EPSG guidence * note 7 and libproj) calculate the same values as we do. Until these * differences are resolved, please be careful when using this projection.} * * If a {@link org.geotoolkit.referencing.operation.provider.Stereographic#LATITUDE_OF_ORIGIN * "latitude_of_origin"} parameter is supplied and is not consistent with the projection * classification (for example a latitude different from ±90° for the polar case), * then the oblique or polar case will be automatically inferred from the latitude. In other * words, the latitude of origin has precedence on the projection classification. If omitted, * then the default value is 90°N for {@code "Polar_Stereographic"} and 0° for * {@code "Oblique_Stereographic"}. *

* Polar projections that use the series equations for the inverse calculation will be little bit * faster, but may be a little bit less accurate. If a polar {@code "latitude_of_origin"} is used * for the {@code "Oblique_Stereographic"} or {@code "Stereographic"}, the iterative equations will * be used for inverse polar calculations. *

* The {@code "Polar Stereographic (variant B)"}, {@code "Stereographic_North_Pole"}, * and {@code "Stereographic_South_Pole"} cases include a * {@link org.geotoolkit.referencing.operation.provider.PolarStereographic.VariantB#STANDARD_PARALLEL * "standard_parallel_1"} parameter. This parameter sets the latitude with a scale factor equal to * the supplied scale factor. The {@code "Polar Stereographic (variant A)"} forces its * {@code "latitude_of_origin"} parameter to ±90°, depending on the hemisphere. * * {@section References} *

    *
  • John P. Snyder (Map Projections - A Working Manual,
    * U.S. Geological Survey Professional Paper 1395, 1987)
  • *
  • "Coordinate Conversions and Transformations including Formulas",
    * EPSG Guidance Note Number 7, Version 19.
  • *
  • Gerald Evenden. * "Supplementary PROJ.4 Notes - Oblique Stereographic Alternative"
  • *
  • Krakiwsky, E.J., D.B. Thomson, and R.R. Steeves. 1977.
    * A Manual for Geodetic Coordinate Transformations in the Maritimes.
    * Geodesy and Geomatics Engineering, UNB. Technical Report No. 48.
  • *
  • Thomson, D.B., M.P. Mepham and R.R. Steeves. 1977.
    * The Stereographic Double Projection.
    * Geodesy and Geomatics Engineereng, UNB. Technical Report No. 46.
  • *
* * @author Gerald Evenden (USGS) * @author André Gosselin (MPO) * @author Martin Desruisseaux (MPO, IRD, Geomatys) * @author Rueben Schulz (UBC) * @author Rémi Maréchal (Geomatys) * @version 3.20 * * @see Some Random Stereographic Issues * * @since 1.0 * @module */ @Immutable public class Stereographic extends UnitaryProjection { /** * For compatibility with different versions during deserialization. */ private static final long serialVersionUID = 948619442800459872L; /** * Maximum difference allowed when comparing real numbers. */ static final double EPSILON = 1E-6; /** * The latitude of origin, in radians. */ final double φ0; /** * Constants used for the oblique projections. All those constants are completely * determined by {@link #φ0}. Consequently, there is no need to test them in * {@link #hashCode} or {@link #equals(Object, ComparisonMode)} methods. */ final double sinφ0, cosφ0; /** * Constants computed from the latitude of origin and the excentricity. * It is equal to {@link #φ0} in the spherical and equatorial case. */ private final double χ1; /** * Constants used for the oblique projections. All those constants are completely determined * by {@link #φ0} and {@link #excentricity}. Consequently, there is no need to test them in * {@link #hashCode} or {@link #equals(Object, ComparisonMode)} methods. */ private final double sinχ1, cosχ1; /** * Creates a Stereographic projection from the given parameters. The descriptor argument is * usually {@link org.geotoolkit.referencing.operation.provider.Stereographic#PARAMETERS}, but * is not restricted to. If a different descriptor is supplied, it is user's responsibility * to ensure that it is suitable to a Stereographic projection. * * @param descriptor Typically {@code Stereographic.PARAMETERS}. * @param values The parameter values of the projection to create. * @return The map projection. * * @since 3.00 */ public static MathTransform2D create(final ParameterDescriptorGroup descriptor, final ParameterValueGroup values) { final Parameters parameters = new Parameters(descriptor, values); final double latitudeOfOrigin = toRadians(parameters.latitudeOfOrigin); final Stereographic projection; if (abs(latitudeOfOrigin - PI/2) < ANGLE_TOLERANCE) { projection = PolarStereographic.create(parameters); } else { final boolean isSpherical = parameters.isSpherical(); final boolean isEPSG = parameters.nameMatches(PARAMETERS); if (abs(latitudeOfOrigin) < ANGLE_TOLERANCE) { if (isSpherical) { projection = new EquatorialStereographic.Spherical(parameters); } else if (!isEPSG) { projection = new EquatorialStereographic(parameters); } else { projection = new ObliqueStereographic(parameters); } } else if (isSpherical) { projection = new Stereographic.Spherical(parameters); } else if (!isEPSG) { projection = new Stereographic(parameters); } else { projection = new ObliqueStereographic(parameters); } } return projection.createConcatenatedTransform(); } /** * Constructs an oblique stereographic projection (USGS equations). * * @param parameters The parameters of the projection to be created. */ protected Stereographic(final Parameters parameters) { this(parameters, parameters.latitudeOfOrigin); double k0 = 2*msfn(sinφ0, cosφ0) / cosχ1; // part of (14 - 15) if (excentricity == 0) { k0 = 2; // For fixing rounding errors. } /* * k0 above should be equal to 2 in both the spherical and equatorial cases * (but the simplification happen through different paths). * * At this point, all parameters have been processed. Now process to their * validation and the initialization of (de)normalize affine transforms. */ parameters.validate(); parameters.normalize(false).scale(k0, k0); finish(); } /** * Constructs an oblique stereographic projection. Callers must invoke {@link #finish} * when they have finished their work. * * @param parameters The parameters of the projection to be created. * @param latitudeOfOrigin the latitude of origin, in decimal degrees. */ Stereographic(final Parameters parameters, final double latitudeOfOrigin) { super(parameters); double phi0 = toRadians(latitudeOfOrigin); if (abs(phi0) < ANGLE_TOLERANCE) { // Equatorial phi0 = 0; cosφ0 = 1; sinφ0 = 0; χ1 = 0; cosχ1 = 1; sinχ1 = 0; } else { // Oblique cosφ0 = cos(phi0); sinφ0 = sin(phi0); χ1 = 2 * atan(ssfn(phi0, sinφ0)) - PI/2; cosχ1 = cos(χ1); sinχ1 = sin(χ1); } this.φ0 = phi0; } /** * Converts the specified (λ,φ) coordinate (units in radians) * and stores the result in {@code dstPts} (linear distance on a unit sphere). In addition, * opportunistically computes the projection derivative if {@code derivate} is {@code true}. * * @since 3.20 (derived from 3.00) */ @Override public Matrix transform(final double[] srcPts, final int srcOff, final double[] dstPts, final int dstOff, final boolean derivate) throws ProjectionException { final double λ = rollLongitude(srcPts[srcOff]); final double φ = srcPts[srcOff + 1]; final double sinφ = sin(φ); final double sinλ = sin(λ); final double cosλ = cos(λ); final double ssfn = ssfn(φ, sinφ); if (dstPts != null) { final double χ = 2*atan(ssfn) - PI/2; final double sinχ = sin(χ); final double cosχ = cos(χ); final double cosχ_cosλ = cosχ * cosλ; final double A = 1 + sinχ1*sinχ + cosχ1*cosχ_cosλ; dstPts[dstOff ] = (cosχ * sinλ) / A; dstPts[dstOff+1] = (cosχ1 * sinχ - sinχ1 * cosχ_cosλ) / A; } /* * The multiplication by (k0 / cosχ1) is performed by the "denormalize" affine transform. */ if (!derivate) { return null; } // // End of map projection. Now compute the derivative. // final double cosφ = cos(φ); final double sd = ssfn - 1/ssfn; final double s2p = 1 + ssfn*ssfn; final double dχφ = 2*dssfn_dφ(φ, sinφ, cosφ) * ssfn; final double A = s2p/ssfn + sd*sinχ1 + 2*cosλ*cosχ1; final double dAλ = -2*cosχ1*sinλ / A; // The "/A" is actually not part of the derivative. final double dAφ = (2*sinχ1 - sd*cosλ*cosχ1) / (s2p*A); // Same as above comment. final double d = (cosχ1*sd - 2*cosλ*sinχ1); return new Matrix2( 2*(cosλ - sinλ*dAλ) / A, -sinλ*dχφ * (sd/s2p + 2*dAφ) / A, (2*sinλ*sinχ1 - dAλ*d) / A, dχφ * ((2*cosχ1 + sinχ1*cosλ*sd)/s2p - dAφ*d) / A); } /** * Transforms the specified (x,y) coordinates * and stores the result in {@code dstPts} (angles in radians). */ @Override protected void inverseTransform(final double[] srcPts, final int srcOff, final double[] dstPts, final int dstOff) throws ProjectionException { /* * (x,y) is multiplied by (cosχ1 / k0), so ρ below is multiplied by the same factor * compared to Proj4 code. This allow a few simplifications in the formulas. For example * in the computation of ce: atan2(ρ*cosχ1, k0) * simplifies to: atan(ρ). */ final double x = srcPts[srcOff ]; final double y = srcPts[srcOff+1]; final double ρ = hypot(x, y); final double ce = 2 * atan(ρ); final double cosce = cos(ce); final double since = sin(ce); final double χ = (ρ < EPSILON) ? χ1 : asin(cosce*sinχ1 + (y*since*cosχ1 / ρ)); final double tp = tan(PI/4 + 0.5*χ); // parts of (21-36) used to calculate longitude final double t = x*since; final double ct = ρ*cosχ1*cosce - y*sinχ1*since; // Compute latitude using iterative technique (3-4) final double halfe = 0.5*excentricity; double φ = χ; for (int i=MAXIMUM_ITERATIONS;;) { final double esinφ = excentricity * sin(φ); final double next = 2*atan(tp*pow((1+esinφ)/(1-esinφ), halfe)) - PI/2; if (abs(φ - (φ=next)) < ITERATION_TOLERANCE) { break; } if (--i < 0) { throw new ProjectionException(Errors.Keys.NO_CONVERGENCE); } } dstPts[dstOff ] = unrollLongitude(atan2(t, ct)); dstPts[dstOff+1] = φ; } /** * Provides the transform equations for the spherical case of the Stereographic projection. * * @author Gerald Evenden (USGS) * @author André Gosselin (MPO) * @author Martin Desruisseaux (MPO, IRD) * @author Rueben Schulz (UBC) * @version 3.00 * * @since 2.1 * @module */ @Immutable static final class Spherical extends Stereographic { /** * For compatibility with different versions during deserialization. */ private static final long serialVersionUID = -8558594307755820783L; /** * Constructs a new map projection from the supplied parameters. * * @param parameters The parameters of the projection to be created. */ protected Spherical(final Parameters parameters) { super(parameters); parameters.ensureSpherical(); } /** * Returns {@code true} since this class uses spherical formulas. */ @Override final boolean isSpherical() { return true; } /** * {@inheritDoc} */ @Override public Matrix transform(final double[] srcPts, final int srcOff, final double[] dstPts, final int dstOff, final boolean derivate) throws ProjectionException { final double λ = rollLongitude(srcPts[srcOff]); final double φ = srcPts[srcOff + 1]; final double sinφ = sin(φ); final double cosφ = cos(φ); final double sinλ = sin(λ); final double cosλ = cos(λ); final double c0cφ = cosφ0*cosφ; final double s0sφ = sinφ0*sinφ; final double F = 1 + c0cφ*cosλ + s0sφ; // (21-4) final double x = cosφ * sinλ / F; // (21-2) final double y = (cosφ0 * sinφ - sinφ0 * cosφ * cosλ) / F; // (21-3) Matrix derivative = null; if (derivate) { final double c0sφ = cosφ0*sinφ; final double s0cφ = sinφ0*cosφ; final double dFdλ = (c0cφ*sinλ) / F; // Actually (∂F/∂λ)/-F final double dFdφ = (c0sφ*cosλ - s0cφ) / F; // Actually (∂F/∂φ)/-F final double dcsφ = c0sφ - s0cφ*cosλ; derivative = new Matrix2( cosφ*(dFdλ*sinλ + cosλ) / F, sinλ*(dFdφ*cosφ - sinφ) / F, (dFdλ*dcsφ + (s0cφ*sinλ)) / F, (dFdφ*dcsφ + (s0sφ*cosλ + c0cφ)) / F); } // Following part is common to all spherical projections: verify, store and return. assert Assertions.checkDerivative(derivative, super.transform(srcPts, srcOff, dstPts, dstOff, derivate)) && Assertions.checkTransform(dstPts, dstOff, x, y); // dstPts = result from ellipsoidal formulas. if (dstPts != null) { dstPts[dstOff ] = x; dstPts[dstOff+1] = y; } return derivative; } /** * {@inheritDoc} */ @Override protected void inverseTransform(final double[] srcPts, final int srcOff, final double[] dstPts, final int dstOff) throws ProjectionException { final double x = srcPts[srcOff ]; final double y = srcPts[srcOff+1]; final double ρ = hypot(x, y); double λ, φ; if (abs(ρ) < EPSILON) { φ = φ0; λ = 0.0; } else { final double c = 2 * atan(ρ); final double cosc = cos(c); final double sinc = sin(c); final double ct = ρ*cosφ0*cosc - y*sinφ0*sinc; // (20-15) final double t = x*sinc; // (20-15) φ = asin(cosc*sinφ0 + y*sinc*cosφ0/ρ); // (20-14) λ = atan2(t, ct); } λ = unrollLongitude(λ); assert checkInverseTransform(srcPts, srcOff, dstPts, dstOff, λ, φ); dstPts[dstOff] = λ; dstPts[dstOff+1] = φ; } /** * Computes using ellipsoidal formulas and compare with the * result from spherical formulas. Used in assertions only. */ private boolean checkInverseTransform(final double[] srcPts, final int srcOff, final double[] dstPts, final int dstOff, final double λ, final double φ) throws ProjectionException { super.inverseTransform(srcPts, srcOff, dstPts, dstOff); return Assertions.checkInverseTransform(dstPts, dstOff, λ, φ); } } /** * Compares the given object with this transform for equivalence. */ @Override public boolean equals(final Object object, final ComparisonMode mode) { if (super.equals(object, mode)) { final Stereographic that = (Stereographic) object; return epsilonEqual(this.φ0, that.φ0, mode); // All other fields are derived from the latitude of origin. } return false; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy