sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Apply some algebraic simplifications on formulas, then merge ConicSatelliteTracking and CylindricalSatelliteTracking in a single SatelliteTracking class. Those cylindrical and conical forms have a lot in common, which make them easier to manage by `if (isConic)` statements. Reduce the number of fields as a consequence of the merge and algebraic simplification. Adjust the type of exception thrown, or whether an exception is thrown instead of setting values to NaN (policy reminder in documen [...]
Date Wed, 02 Oct 2019 17:33:40 GMT
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 64a587b  Apply some algebraic simplifications on formulas, then merge ConicSatelliteTracking and CylindricalSatelliteTracking in a single SatelliteTracking class. Those cylindrical and conical forms have a lot in common, which make them easier to manage by `if (isConic)` statements. Reduce the number of fields as a consequence of the merge and algebraic simplification. Adjust the type of exception thrown, or whether an exception is thrown instead of setting values to NaN (policy  [...]
64a587b is described below

commit 64a587b61fdcb0f17b06a57c19b54e8b37e667a4
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Wed Oct 2 10:35:36 2019 +0200

    Apply some algebraic simplifications on formulas, then merge ConicSatelliteTracking and CylindricalSatelliteTracking in a single SatelliteTracking class.
    Those cylindrical and conical forms have a lot in common, which make them easier to manage by `if (isConic)` statements.
    Reduce the number of fields as a consequence of the merge and algebraic simplification.
    Adjust the type of exception thrown, or whether an exception is thrown instead of setting values to NaN (policy reminder in documentation).
    Replace the check for latitude limit by a more direct check on the intermediate result which is affected by this limit.
---
 .../referencing/provider/SatelliteTracking.java    |  12 +-
 .../projection/ConicSatelliteTracking.java         | 442 ---------------------
 .../projection/CylindricalSatelliteTracking.java   | 141 -------
 .../operation/projection/SatelliteTracking.java    | 335 ++++++++++++++++
 .../projection/ConicSatelliteTrackingTest.java     | 269 -------------
 .../CylindricalSatelliteTrackingTest.java          | 228 -----------
 .../projection/SatelliteTrackingTest.java          | 369 +++++++++++++++++
 .../sis/test/suite/ReferencingTestSuite.java       |   3 +-
 8 files changed, 709 insertions(+), 1090 deletions(-)

diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/SatelliteTracking.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/SatelliteTracking.java
index dd529b8..e6a3946 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/SatelliteTracking.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/SatelliteTracking.java
@@ -23,8 +23,6 @@ import org.opengis.parameter.ParameterNotFoundException;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.parameter.ParameterBuilder;
 import org.apache.sis.parameter.Parameters;
-import org.apache.sis.referencing.operation.projection.ConicSatelliteTracking;
-import org.apache.sis.referencing.operation.projection.CylindricalSatelliteTracking;
 import org.apache.sis.referencing.operation.projection.NormalizedProjection;
 import org.apache.sis.util.iso.ResourceInternationalString;
 import org.apache.sis.internal.util.Constants;
@@ -33,7 +31,9 @@ import org.apache.sis.measure.Units;
 
 /**
  * The provider for <cite>"Satellite-Tracking"</cite> projections.
- * More details about this projection are documented in the {@link ConicSatelliteTracking} class.
+ * We are not aware of authoritative source for parameter definitions, except the Snyder book.
+ * See {@link org.apache.sis.referencing.operation.projection.SatelliteTracking here for more
+ * details}.
  *
  * @author  Matthieu Bastianelli (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
@@ -140,10 +140,6 @@ public class SatelliteTracking extends MapProjection {
      */
     @Override
     protected NormalizedProjection createProjection(final Parameters parameters) throws ParameterNotFoundException {
-        if (parameters.getValue(STANDARD_PARALLEL_2) == -parameters.getValue(STANDARD_PARALLEL_1)) {
-            return new CylindricalSatelliteTracking(this, parameters);
-        } else {
-            return new ConicSatelliteTracking(this, parameters);
-        }
+        return new org.apache.sis.referencing.operation.projection.SatelliteTracking(this, parameters);
     }
 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConicSatelliteTracking.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConicSatelliteTracking.java
deleted file mode 100644
index 5bd5ff0..0000000
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConicSatelliteTracking.java
+++ /dev/null
@@ -1,442 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.referencing.operation.projection;
-
-import java.util.EnumMap;
-import org.opengis.parameter.ParameterDescriptor;
-import org.opengis.referencing.operation.Matrix;
-import org.opengis.referencing.operation.OperationMethod;
-import org.apache.sis.util.Workaround;
-import org.apache.sis.internal.referencing.Resources;
-import org.apache.sis.referencing.operation.matrix.Matrix2;
-import org.apache.sis.referencing.operation.matrix.MatrixSIS;
-import org.apache.sis.referencing.operation.transform.ContextualParameters;
-import org.apache.sis.parameter.Parameters;
-
-import static java.lang.Math.*;
-import static java.lang.Double.NaN;
-import static org.apache.sis.internal.referencing.provider.SatelliteTracking.*;
-
-
-/**
- * <cite>Satellite-Tracking</cite> projection.
- * This projection has been developed in 1977 by Snyder and has no associated EPSG code.
- * This projection is neither conformal or equal-area, but has the property that ground tracks
- * for satellites orbiting the Earth with the same orbital parameters are shown as straight lines
- * on the map. Other properties are (Snyder 1987):
- *
- * <ul>
- *   <li>All meridians are equally spaced straight lines.
- *       They are parallel on cylindrical form and converging to a common point on conical form.</li>
- *   <li>All parallels are straight but unequally spaced.
- *       They are parallel on cylindrical form and are concentric circular arcs on conical form.</li>
- *   <li>Conformality occurs along two chosen parallels. Scale is correct along one of these parameters
- *       on the conical form and along both on the cylindrical form.</li>
- * </ul>
- *
- * <div class="section">Limitations</div>
- * This map projection supports only circular orbits. The Earth is assumed spherical.
- * Areas close to poles can not be mapped.
- *
- * <div class="section">References</div>
- * John P. Snyder., 1987. <u>Map Projections - A Working Manual</u>
- * chapter 28: <cite>Satellite-tracking projections</cite>.
- *
- * @author  Matthieu Bastianelli (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-public class ConicSatelliteTracking extends NormalizedProjection {
-    /**
-     * For cross-version compatibility.
-     */
-    private static final long serialVersionUID = 859940667477896653L;
-
-    /**
-     * Sines and cosines of inclination between the plane of the Earth's Equator and the plane
-     * of the satellite orbit. The angle variable name is <var>i</var> in Snyder's book.
-     *
-     * @see org.apache.sis.internal.referencing.provider.SatelliteTracking#SATELLITE_ORBIT_INCLINATION
-     */
-    final double cos_i, sin_i, cos2_i;
-
-    /**
-     * Cosine of first standard parallel.
-     *
-     * @see org.apache.sis.internal.referencing.provider.SatelliteTracking#STANDARD_PARALLEL_1
-     */
-    final double cos_φ1;
-
-    /**
-     * Ratio of satellite orbital period (P2) over ascending node period (P1).
-     *
-     * @see org.apache.sis.internal.referencing.provider.SatelliteTracking#SATELLITE_ORBITAL_PERIOD
-     * @see org.apache.sis.internal.referencing.provider.SatelliteTracking#ASCENDING_NODE_PERIOD
-     */
-    final double p2_on_p1;
-
-    /**
-     * Projection Cone's constant.
-     */
-    private final double n;
-
-    /**
-     * Approximation of the minimum latitude at infinite radius.
-     * This is the limiting latitude at which the {@code L} coefficient takes the {@code -s0/n} value.
-     * In such a situation the coefficient ρ computed for transformations is infinite.
-     */
-    private final double latitudeLimit;
-
-    /**
-     * Coefficients for the Conic Satellite-Tracking Projection.
-     * {@code cosφ1_sinF1_n} = cos(φ₁) × sin(F₁) / n.
-     */
-    private final double cosφ1_sinF1_n, s0;
-
-    /**
-     * Work around for RFE #4093999 in Sun's bug database ("Relax constraint on
-     * placement of this()/super() call in constructors").
-     */
-    @Workaround(library = "JDK", version = "1.8")
-    static Initializer initializer(final OperationMethod method, final Parameters parameters) {
-        final EnumMap<NormalizedProjection.ParameterRole, ParameterDescriptor<Double>> roles = new EnumMap<>(NormalizedProjection.ParameterRole.class);
-        roles.put(NormalizedProjection.ParameterRole.CENTRAL_MERIDIAN, CENTRAL_MERIDIAN);
-        return new Initializer(method, parameters, roles, (byte) 0);
-    }
-
-    /**
-     * Creates a Satellite Tracking projection from the given parameters.
-     *
-     * @param method      description of the projection parameters.
-     * @param parameters  the parameter values of the projection to create.
-     */
-    public ConicSatelliteTracking(final OperationMethod method, final Parameters parameters) {
-        this(initializer(method, parameters));
-    }
-
-    /**
-     * Work around for RFE #4093999 in Sun's bug database
-     * ("Relax constraint on placement of this()/super() call in constructors").
-     */
-    ConicSatelliteTracking(final Initializer initializer) {
-        super(initializer);
-
-        //======================================================================
-        // Common for both cylindrical and conic sattelite tracking projections :
-        //======================================================================
-        final double i = toRadians(initializer.getAndStore(SATELLITE_ORBIT_INCLINATION));
-        cos_i    = cos(i);
-        sin_i    = sin(i);
-        cos2_i   = cos_i * cos_i;
-        p2_on_p1 = initializer.getAndStore(SATELLITE_ORBITAL_PERIOD) / initializer.getAndStore(ASCENDING_NODE_PERIOD);
-
-        final double φ1      = toRadians(initializer.getAndStore(STANDARD_PARALLEL_1));  //appropriated use of toRadians??
-        cos_φ1               = cos(φ1);
-        final double cos2_φ1 = cos_φ1 * cos_φ1;
-
-        //======================================================================
-        // For conic projection :
-        //=======================
-        if (!(this instanceof CylindricalSatelliteTracking)) {
-            final double sin_φ1 = sin(φ1);
-            final double φ0 = toRadians(initializer.getAndStore(LATITUDE_OF_ORIGIN));   //appropriated use of toRadians??
-            final double cos_φ0 = cos(φ0);
-            final double cos2_φ0 = cos_φ0 * cos_φ0;
-            final double sin_φ0 = sin(φ0);
-            final double φ2 = toRadians(initializer.getAndStore(STANDARD_PARALLEL_2));   //appropriated use of toRadians??
-
-//            final DoubleUnaryOperator computeFn = (cos2_φn) -> atan((p2_on_p1 * cos2_φn - cos_i) / sqrt(cos2_φn - cos2_i)); // eq.28-9 in Snyder
-//            final double F0 = computeFn.applyAsDouble(cos2_φ0);
-//            final double F1 = computeFn.applyAsDouble(cos2_φ1);
-//            final DoubleUnaryOperator computedλn = (sin_φn) -> -asin(sin_φn / sin_i); // eq.28-2a in Snyder
-//            final double dλ0 = computedλn.applyAsDouble(sin_φ0);
-//            final double dλ1 = computedλn.applyAsDouble(sin_φ1);
-//            final DoubleUnaryOperator computeλtn = (dλn) -> atan(tan(dλn) * cos_i); // eq.28-3a in Snyder
-//            final double λt0 = computeλtn.applyAsDouble(dλ0);
-//            final double λt1 = computeλtn.applyAsDouble(dλ1);
-
-            final double F0 = computeFn(cos2_φ0);
-            /*
-             * Inclination of the groundtrack to the meridian at latitude φ1
-             */
-            final double F1 = computeFn(cos2_φ1);
-
-            final double dλ0 = computedλn(sin_φ0);
-            final double dλ1 = computedλn(sin_φ1);
-
-            final double λt0 = computeλtn(dλ0);
-            final double λt1 = computeλtn(dλ1);
-
-            final double L0 = λt0 - p2_on_p1 * dλ0;
-            final double L1 = λt1 - p2_on_p1 * dλ1;
-
-            if (φ1 == PI - i) { //tracking limit computed as 180 - i from Snyder's manual p.238
-                final double factor = (p2_on_p1 * cos_i - 1);
-                final double factor2 = factor * factor;
-                n = sin_i / (factor2); //eq. 28-18 in Snyder
-            } else if (φ2 != φ1) {
-                final double cos_φ2 = cos(φ2);
-                final double cos2_φ2 = cos_φ2 * cos_φ2;
-                final double sin_φ2 = sin(φ2);
-//                final double dλ2 = computedλn.applyAsDouble(sin_φ2);
-                final double dλ2 = computedλn(sin_φ2);
-                final double λt2 = computeλtn(dλ2);
-                final double L2 = λt2 - p2_on_p1 * dλ2;
-//                final double F2 = computeFn.applyAsDouble(cos2_φ2);
-                final double F2 = computeFn(cos2_φ2);
-                n = (F2 - F1) / (L2 - L1);
-            } else {
-                n = sin_φ1 * (p2_on_p1 * (2 * cos2_i - cos2_φ1) - cos_i) / (p2_on_p1 * cos2_φ1 - cos_i); //eq. 28-17 in Snyder
-            }
-            cosφ1_sinF1_n = cos_φ1 * sin(F1)/n;
-            s0 = F1 - n * L1;
-            final double ρ0 = cosφ1_sinF1_n / sin(n * L0 + s0); // *R in eq.28-12 in Snyder
-
-            //======================== Unsure ======================================
-            // Aim to assess the limit latitude associated with -s0/n L-value.
-            //
-            latitudeLimit = latitudeFromNewtonMethod(-s0 / n);
-            //======================================================================
-
-//            //Additionally we can compute the radius of the circle to which groundtracks
-//            //are tangent on the map :
-//            ρs = cos_φ1xsin_F1 / n; //*R
-            //======================================================================
-            /*
-         * At this point, all parameters have been processed. Now process to their
-         * validation and the initialization of (de)normalize affine transforms.
-             */
-            final MatrixSIS normalize = context.getMatrix(ContextualParameters.MatrixRole.NORMALIZATION);
-            normalize.convertAfter(0, n, null);  //For conic tracking
-
-            final MatrixSIS denormalize = context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION);
-            denormalize.convertBefore(1, 1, ρ0);  //For conic tracking
-        } else {
-            n = latitudeLimit = cosφ1_sinF1_n = s0 = NaN;
-        }
-    }
-
-    /**
-     * 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}.
-     *
-     * <cite> The Yaxis lies along the central meridian λ0, y increasing
-     * northerly, and X axis intersects perpendicularly at LATITUDE_OF_ORIGIN
-     * φ0, x increasing easterly.
-     * </cite>
-     *
-     * @return the matrix of the projection derivative at the given source position,
-     *         or {@code null} if the {@code derivate} argument is {@code false}.
-     * @throws ProjectionException if the coordinates can not be converted.
-     */
-    @Override
-    public Matrix transform(double[] srcPts, int srcOff,
-                            double[] dstPts, int dstOff,
-                            boolean derivate) throws ProjectionException
-    {
-        final double λ = srcPts[srcOff];
-        final double φ = srcPts[srcOff + 1];
-        /*
-         * According to the Snyder (page 236) in those cases cannot or should not be plotted.
-         */
-        if ( ((n >= 0) && (φ<=latitudeLimit)) || ((!(n >= 0)) && (φ>=latitudeLimit)) ){
-            throw new ProjectionException(Resources.format(Resources.Keys.CanNotTransformCoordinates_2));
-        }
-
-        // compute an double array with {L} or {L, dL/dφ} if derivate recquired.
-        final double[] vector_L = computeLanddLdφForDirectTransform(φ, derivate);
-        final double L = vector_L[0];
-        /*
-         * As {@code latitudeLimit} is an approximation we repeat the test here.
-         */
-        if ( ((n >= 0) && (L<=-s0/n)) || ((!(n >= 0)) && (L>=-s0/n)) ){
-            //TODO if usefull, could we add :
-            // latitudeLimit = φ;
-            throw new ProjectionException(Resources.format(Resources.Keys.CanNotTransformCoordinates_2));
-        }
-
-        final double nLandS0     = n*L+s0;
-        final double sin_nLandS0 = sin(nLandS0);
-        final double ρ           = cosφ1_sinF1_n/sin_nLandS0;
-
-        final double sinλ = sin(λ);
-        final double cosλ = cos(λ);
-        if (dstPts != null) {
-            dstPts[dstOff    ] =   ρ * sinλ;   // x
-            dstPts[dstOff + 1] = - ρ * cosλ;   // y
-        }
-
-         if (!derivate) {
-            return null;
-        }
-
-        //=========================To check the resolution =====================
-        final double dρ_dφ = cosφ1_sinF1_n  * (-1 / (sin_nLandS0*sin_nLandS0)) *cos(nLandS0)  * n
-                * vector_L[1]; // dL/dφ
-
-        final double dx_dλ = ρ*cosλ;
-        final double dx_dφ = sinλ * dρ_dφ;
-
-        final double dy_dλ = ρ*sinλ;
-        final double dy_dφ = -cosλ * dρ_dφ;
-        //======================================================================
-
-        return new Matrix2(dx_dλ, dx_dφ,
-                           dy_dλ, dy_dφ);
-//        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
-
-        /* =====================================================================
-        * Uncomputed scale factors :
-        *===========================
-        * k = ρ*n/cos(φ); // /R
-        * h = k*tan(F)/tan(n*L+s0);
-        ===================================================================== */
-    }
-
-    /**
-     * Transforms the specified (<var>x</var>,<var>y</var>) coordinates
-     * and stores the result in {@code dstPts} (angles in radians).
-     *
-     * @throws ProjectionException if the coordinates can not be converted.
-     */
-    @Override
-    protected void inverseTransform(double[] srcPts, int srcOff,
-                                    double[] dstPts, int dstOff)
-                                    throws ProjectionException {
-
-        final double x   = srcPts[srcOff];
-        final double y   = srcPts[srcOff + 1];
-
-        if ((x== 0) && (y == 0)) {
-            //TODO : which values does it imply?
-            throw new UnsupportedOperationException("Not supported yet for those coordinates."); //To change body of generated methods, choose Tools | Templates.
-        }
-        final double ρ = copySign(hypot(x,y), n);
-        final double θ = atan(x/(-y) ); //undefined if x=y=0
-        final double L = (asin(cosφ1_sinF1_n/ρ) -s0)/n; //undefined if x=y=0  //eq.28-26 in Snyder with R=1
-
-        dstPts[dstOff  ] =  θ ;//λ
-        dstPts[dstOff+1] = latitudeFromNewtonMethod(L); //φ
-    }
-
-    /**
-     * Return an approximation of the latitude associated with L coefficient
-     * by applying Newton-Raphson method.
-     *
-     * Eq. 28-24, 28-25 and then 28-22 in Snyder's Manual.
-     *
-     * @param l the L coefficient used in (cylindrical and conic)
-     * satellite-tracking projections.
-     * @return Approximation of the associated latitude.
-     */
-    protected final double latitudeFromNewtonMethod(final double l){
-        double dλ = -PI/2;
-        double dλn = Double.MIN_VALUE;
-        double A, A2, Δdλ;
-
-        int count=0;
-        int maxIteration = 100; //TODO : check the value.
-
-        while ((count==0) || abs(dλ-dλn) >= (0.01*PI/180)){ //TODO : check the condition. It is considered here that convergence is reached when improvement is lower than 0.01°.
-            if (count >= maxIteration){
-                throw new RuntimeException(Resources.format(Resources.Keys.NoConvergence));
-            }
-            dλn = dλ;
-
-            // Alternative calculation with Snyder's eq.  28-20 and 28-21
-//            λt = l + p2_on_p1 * dλ_n;
-//            dλ = atan(tan(λt) / cos_i);
-
-            A = tan(l + p2_on_p1 * dλn) / cos_i;
-            A2=A*A;
-            Δdλ = -(dλn-atan(A)) / (1- (A2 + 1/cos2_i) * (p2_on_p1*cos_i/(A2+1)) );
-            dλ = dλn + Δdλ ;
-
-            count++;
-        }
-        final double sin_dλ = sin(dλ);
-        return -asin(sin_dλ * sin_i);
-    }
-
-    /**
-     * Method to compute the φn coefficient according to equation 28-9
-     * in Snyder's Map Projections manual.
-     *
-     * @param cos2_φn : square of the φn 's cosinus.
-     * @return Fn  coefficient associated with the φn latittude.
-     */
-    private double computeFn(final double cos2_φn) {
-        return atan((p2_on_p1 * cos2_φn - cos_i) / sqrt(cos2_φn - cos2_i)); // eq.28-9 in Snyder
-    }
-    /**
-     * Method to compute the φn coefficient according to equation 28-2a
-     * in Snyder's Map Projections manual.
-     *
-     * @param sin_φn : φn 's sinus.
-     * @return dλn  coefficient associated with the φn latittude.
-     */
-    private double computedλn(final double sin_φn) {
-        return -asin(sin_φn / sin_i); // eq.28-2a in Snyder
-    }
-    /**
-     * Method to compute the φn coefficient according to equation 28-3a
-     * in Snyder's Map Projections manual.
-     *
-     * @param dλn  coefficient associated with the φn latittude.
-     * @return λtn  coefficient associated with the φn latittude.
-     */
-    private double computeλtn(final double dλn) {
-        return atan(tan(dλn) * cos_i); // eq.28-3a in Snyder
-    }
-
-    /**
-     * Method returning the L coefficient used for the direct transformation of
-     * the Satellite-tracking projections and its partial derivate dL_dφ if
-     * queried by a true value of the input parameter derivate.
-     *
-     * This method is used By :
-     *     {@link ConicSatelliteTracking#transform(double[], int, double[], int, boolean) }
-     * and {@link CylindricalSatelliteTracking#transform(double[], int, double[], int, boolean) }
-     *
-     * @param φ : input latitude of the projection's (direct)  method.
-     * @param derivate : boolean value indicating if the partial derivate dL_dφ
-     *                  must be computed.
-     * @return a double array. It contains :
-     *        - if derivate is false :  only the L coefficient value.
-     *        - if derivate is true :  the L coefficient value and the partial
-     *          derivate dL_dφ respectively at the at index 0 and 1 of the
-     *          resulting array.
-     */
-    double[] computeLanddLdφForDirectTransform(final double φ, final boolean derivate){
-        final double sinφ_sini = sin(φ) / sin_i;
-        final double dλ        = -asin(sinφ_sini);
-        final double tan_dλ    = tan(dλ);
-        final double λt        = atan(tan_dλ * cos_i);
-        final double L         = λt - p2_on_p1 * dλ;
-
-        if (derivate){
-            final double dL_dφ = ((cos(φ) / sin_i) *(1/sqrt(1-sinφ_sini*sinφ_sini)))  // first computed term associated with derivative of (-dλ/dφ)
-                               * ( p2_on_p1 - ((1+tan_dλ*tan_dλ)*cos_i/(1+λt*λt) ) );
-            return new double[] {L, dL_dφ};
-        } else {
-            return new double[] {L};
-        }
-    }
-}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/CylindricalSatelliteTracking.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/CylindricalSatelliteTracking.java
deleted file mode 100644
index 0b98e4c..0000000
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/CylindricalSatelliteTracking.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.referencing.operation.projection;
-
-import org.opengis.referencing.operation.Matrix;
-import org.opengis.referencing.operation.OperationMethod;
-import org.apache.sis.parameter.Parameters;
-import org.apache.sis.referencing.operation.matrix.Matrix2;
-import org.apache.sis.referencing.operation.matrix.MatrixSIS;
-import org.apache.sis.referencing.operation.transform.ContextualParameters;
-
-import static java.lang.Math.*;
-
-
-/**
- * Special case of <cite>Satellite-Tracking</cite> projection when the standard parallels are opposite.
- *
- * @author  Matthieu Bastianelli (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-public class CylindricalSatelliteTracking extends ConicSatelliteTracking {
-    /**
-     * For cross-version compatibility.
-     */
-    private static final long serialVersionUID = -5972958777067525602L;
-
-    /**
-     * Create a Cylindrical Satellite Tracking Projection from the given parameters.
-     *
-     * The parameters are described in <cite>Map Projections - A Working
-     * Manual</cite> By John P. Snyder.
-     *
-     * @param method : description of the projection parameters.
-     * @param parameters : the parameter values of the projection to create.
-     */
-    public CylindricalSatelliteTracking(final OperationMethod method, final Parameters parameters) {
-        this(initializer(method, parameters));
-    }
-
-    private CylindricalSatelliteTracking(final Initializer initializer) {
-        super(initializer);
-
-        final double cos2_φ1 = cos_φ1 * cos_φ1;
-        final double cosφ1_dF1 = sqrt(cos2_φ1 - cos2_i) *cos_φ1 / (p2_on_p1 * cos2_φ1 - cos_i);
-
-        final MatrixSIS normalize   = context.getMatrix(ContextualParameters.MatrixRole.NORMALIZATION);
-        normalize  .convertAfter (0, cos_φ1, null);  //For conic tracking
-
-        final MatrixSIS denormalize = context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION);
-        denormalize.convertBefore(1, cosφ1_dF1,     null);
-
-    }
-
-    /**
-     * 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}.
-     *
-     * <cite> The Yaxis lies along the central meridian λ0, y increasing northerly,
-     * and X axis intersects perpendicularly at O_PARALLEL φ0, x increasing easterly.
-     * </cite>
-     *
-     * @return the matrix of the projection derivative at the given source position,
-     *         or {@code null} if the {@code derivate} argument is {@code false}.
-     * @throws ProjectionException if the coordinates can not be converted.
-     */
-    @Override
-    public Matrix transform(double[] srcPts, int srcOff,
-            double[] dstPts, int dstOff,
-            boolean derivate) throws ProjectionException {
-
-        final double λ = srcPts[srcOff];
-        final double φ = srcPts[srcOff + 1];
-
-        // compute an double array with {L} or {L, dL/dφ} if derivate recquired.
-        final double[] vector_L = computeLanddLdφForDirectTransform(φ, derivate);
-
-        if (dstPts != null) {
-            dstPts[dstOff    ] = λ;   // In eq. Snyder 28-5 : R(λ - λ0) cos_φ1
-            dstPts[dstOff + 1] = vector_L[0];   // In eq. Snyder 28-6 : R L cos(φ1)/F'1
-        }
-
-        /* =====================================================================
-        * Uncomputed scale factors :
-        *===========================
-        * F' : tangente of the angle on the globe between the groundtrack and
-        * the meridian at latitude φ
-        * final double dF = (p2_on_p1 * cos2_φ - cos_i) / sqrt(cos2_φ - cos2_i);
-        * k = cos_φ1/cos_φ;   // Parallel eq. Snyder 28-7
-        * h = k* dF / cosφ1_dF1;    // Meridian eq. Snyder 28-8
-        ===================================================================== */
-        if (!derivate) {
-            return null;
-        }
-
-        //=========================To check the resolution =====================
-        final double dx_dλ = 1; //*R
-        final double dx_dφ = 0;
-
-        final double dy_dλ = 0;
-        final double dy_dφ = vector_L[1]; //dL/dφ
-        //======================================================================
-
-        return new Matrix2(dx_dλ, dx_dφ,
-                           dy_dλ, dy_dφ);
-    }
-
-    /**
-     * Transforms the specified (<var>x</var>,<var>y</var>) coordinates
-     * and stores the result in {@code dstPts} (angles in radians).
-     *
-     * @throws ProjectionException if the coordinates can not be converted.
-     */
-    @Override
-    protected void inverseTransform(double[] srcPts, int srcOff,
-                                    double[] dstPts, int dstOff)
-            throws ProjectionException {
-
-        final double x   = srcPts[srcOff];
-        final double y   = srcPts[srcOff + 1]; // In eq. Snyder 28-19 : y = yinit * cosφ1_dF1 / R . cos_φ1
-
-        dstPts[dstOff  ] =  x;
-        dstPts[dstOff+1] = latitudeFromNewtonMethod(y); //φ
-    }
-}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/SatelliteTracking.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/SatelliteTracking.java
new file mode 100644
index 0000000..d678cec
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/SatelliteTracking.java
@@ -0,0 +1,335 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.referencing.operation.projection;
+
+import java.util.EnumMap;
+import org.opengis.parameter.ParameterDescriptor;
+import org.opengis.referencing.operation.Matrix;
+import org.opengis.referencing.operation.OperationMethod;
+import org.apache.sis.internal.referencing.Formulas;
+import org.apache.sis.internal.referencing.Resources;
+import org.apache.sis.referencing.operation.matrix.Matrix2;
+import org.apache.sis.referencing.operation.matrix.MatrixSIS;
+import org.apache.sis.referencing.operation.transform.ContextualParameters;
+import org.apache.sis.parameter.Parameters;
+import org.apache.sis.util.Workaround;
+
+import static java.lang.Math.*;
+import static org.apache.sis.internal.referencing.provider.SatelliteTracking.*;
+
+
+/**
+ * <cite>Satellite-Tracking</cite> projection.
+ * This projection has been developed in 1977 by Snyder and has no associated EPSG code.
+ * This projection is neither conformal or equal-area, but has the property that ground tracks
+ * for satellites orbiting the Earth with the same orbital parameters are shown as straight lines
+ * on the map. Other properties are (Snyder 1987):
+ *
+ * <ul>
+ *   <li>All meridians are equally spaced straight lines.
+ *       They are parallel on cylindrical form and converging to a common point on conical form.</li>
+ *   <li>All parallels are straight but unequally spaced.
+ *       They are parallel on cylindrical form and are concentric circular arcs on conical form.</li>
+ *   <li>Conformality occurs along two chosen parallels. Scale is correct along one of these parameters
+ *       on the conical form and along both on the cylindrical form.</li>
+ * </ul>
+ *
+ * <div class="section">Limitations</div>
+ * This map projection supports only circular orbits. The Earth is assumed spherical.
+ * Areas close to poles can not be mapped.
+ *
+ * <div class="section">References</div>
+ * John P. Snyder., 1987. <u>Map Projections - A Working Manual</u>
+ * chapter 28: <cite>Satellite-tracking projections</cite>.
+ *
+ * @author  Matthieu Bastianelli (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public class SatelliteTracking extends NormalizedProjection {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 859940667477896653L;
+
+    /**
+     * Sines and cosines of inclination between the plane of the Earth's Equator and the plane
+     * of the satellite orbit. The angle variable name is <var>i</var> in Snyder's book.
+     *
+     * @see org.apache.sis.internal.referencing.provider.SatelliteTracking#SATELLITE_ORBIT_INCLINATION
+     */
+    private final double cos_i, sin_i, cos2_i;
+
+    /**
+     * Ratio of satellite orbital period (P₂) over ascending node period (P₁).
+     *
+     * @see org.apache.sis.internal.referencing.provider.SatelliteTracking#SATELLITE_ORBITAL_PERIOD
+     * @see org.apache.sis.internal.referencing.provider.SatelliteTracking#ASCENDING_NODE_PERIOD
+     */
+    private final double p2_on_p1;
+
+    /**
+     * Coefficients for the Conic Satellite-Tracking Projection.
+     * Those values are {@link Double#NaN} in the cylindrical case.
+     */
+    private final double n, s0;
+
+    /**
+     * {@code true} if this projection is conic, or {@code false} if cylindrical or unknown.
+     */
+    private final boolean isConic;
+
+    /**
+     * Work around for RFE #4093999 in Sun's bug database ("Relax constraint on
+     * placement of this()/super() call in constructors").
+     */
+    @Workaround(library = "JDK", version = "1.8")
+    static Initializer initializer(final OperationMethod method, final Parameters parameters) {
+        final EnumMap<NormalizedProjection.ParameterRole, ParameterDescriptor<Double>> roles = new EnumMap<>(NormalizedProjection.ParameterRole.class);
+        roles.put(NormalizedProjection.ParameterRole.CENTRAL_MERIDIAN, CENTRAL_MERIDIAN);
+        roles.put(ParameterRole.LATITUDE_OF_CONFORMAL_SPHERE_RADIUS, LATITUDE_OF_ORIGIN);
+        return new Initializer(method, parameters, roles, (byte) 0);
+    }
+
+    /**
+     * Creates a Satellite Tracking projection from the given parameters.
+     *
+     * @param method      description of the projection parameters.
+     * @param parameters  the parameter values of the projection to create.
+     */
+    public SatelliteTracking(final OperationMethod method, final Parameters parameters) {
+        this(initializer(method, parameters));
+    }
+
+    /**
+     * Work around for RFE #4093999 in Sun's bug database
+     * ("Relax constraint on placement of this()/super() call in constructors").
+     */
+    private SatelliteTracking(final Initializer initializer) {
+        super(initializer);
+        final double φ0 = toRadians(initializer.getAndStore(LATITUDE_OF_ORIGIN));
+        final double φ1 = toRadians(initializer.getAndStore(STANDARD_PARALLEL_1));
+        final double φ2 = toRadians(initializer.getAndStore(STANDARD_PARALLEL_2));
+        isConic = Math.abs(φ2 + φ1) > ANGULAR_TOLERANCE;
+        /*
+         * Common for both cylindrical and conic sattelite tracking projections.
+         * Symbols from Snyder:
+         *
+         *   i  =  angle of inclination between the plane of Earth Equator and the plane of satellite orbit.
+         *   P₁ =  length of Earth's rotation with respect to precessed ascending node.
+         *   P₂ =  time required for revolution of the satellite.
+         *   φ₁ =  standard parallel (North and South in cylindrical case).
+         *   φ₂ =  second standard parallel (conic case only).
+         */
+        final double i = toRadians(initializer.getAndStore(SATELLITE_ORBIT_INCLINATION));
+        cos_i    = cos(i);
+        sin_i    = sin(i);
+        cos2_i   = cos_i * cos_i;
+        p2_on_p1 = initializer.getAndStore(SATELLITE_ORBITAL_PERIOD) / initializer.getAndStore(ASCENDING_NODE_PERIOD);
+        final double cos_φ1  = cos(φ1);
+        final double cos2_φ1 = cos_φ1 * cos_φ1;
+        /*
+         *
+         */
+        double scale;
+        final Double ρ0;
+        if (isConic) {
+            final double sin_φ1 = sin(φ1);
+            /*
+             * Conic projection case.
+             * Inclination of the groundtrack to the meridian at latitude φ1
+             */
+            final double F1  = computeFn(cos2_φ1);
+            final double dλ0 = -asin(sin(φ0) / sin_i);       // eq.28-2a in Snyder
+            final double dλ1 = -asin(sin_φ1  / sin_i);
+            final double λt0 = computeλtn(dλ0);
+            final double λt1 = computeλtn(dλ1);
+            final double L0  = λt0 - p2_on_p1 * dλ0;
+            final double L1  = λt1 - p2_on_p1 * dλ1;
+
+            //tracking limit computed as 180 - i from Snyder's manual p.238
+            if (φ1 == PI - i) {
+                final double factor = (p2_on_p1 * cos_i - 1);
+                n = sin_i / (factor * factor); //eq. 28-18 in Snyder
+            } else if (φ2 != φ1) {
+                final double cos_φ2 = cos(φ2);
+                final double dλ2 = -asin(sin(φ2) / sin_i);
+                final double λt2 = computeλtn(dλ2);
+                final double L2 = λt2 - p2_on_p1 * dλ2;
+                final double F2 = computeFn(cos_φ2 * cos_φ2);
+                n = (F2 - F1) / (L2 - L1);
+            } else {
+                n = sin_φ1 * (p2_on_p1 * (2 * cos2_i - cos2_φ1) - cos_i) / (p2_on_p1 * cos2_φ1 - cos_i); //eq. 28-17 in Snyder
+            }
+            // cos(φ₁) × sin(F₁) / n
+            scale = cos_φ1 * sin(F1) / n;
+            s0 = F1 - n * L1;
+            ρ0 = scale / sin(n * L0 + s0); // *R in eq.28-12 in Snyder
+            scale = -scale;
+        } else {
+            /*
+             * Cylindrical projection case.
+             */
+            n = s0 = Double.NaN;
+            ρ0 = null;
+            scale = sqrt(cos2_φ1 - cos2_i) * cos_φ1 / (p2_on_p1 * cos2_φ1 - cos_i);
+        }
+        /*
+         * At this point, all parameters have been processed. Now process to their
+         * validation and the initialization of (de)normalize affine transforms.
+         */
+        final MatrixSIS normalize = context.getMatrix(ContextualParameters.MatrixRole.NORMALIZATION);
+        normalize.convertAfter(0, isConic ? n : cos_φ1, null);
+
+        final MatrixSIS denormalize = context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION);
+        denormalize.convertBefore(1, scale, ρ0);
+        if (isConic) {
+            denormalize.convertBefore(0, -scale, null);
+        }
+    }
+
+    /**
+     * 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}.
+     *
+     * <cite> The Yaxis lies along the central meridian λ0, y increasing
+     * northerly, and X axis intersects perpendicularly at LATITUDE_OF_ORIGIN
+     * φ0, x increasing easterly.
+     * </cite>
+     *
+     * @return the matrix of the projection derivative at the given source position,
+     *         or {@code null} if the {@code derivate} argument is {@code false}.
+     * @throws ProjectionException if the coordinates can not be converted.
+     */
+    @Override
+    public Matrix transform(double[] srcPts, int srcOff,
+                            double[] dstPts, int dstOff,
+                            boolean derivate) throws ProjectionException
+    {
+        final double φ         = srcPts[srcOff + 1];
+        final double sinφ_sini = sin(φ) / sin_i;
+        final double dλ        = -asin(sinφ_sini);
+        final double tan_dλ    = tan(dλ);
+        final double λt        = atan(tan_dλ * cos_i);
+        double x     = srcPts[srcOff];          // In cylindrical case, x=λ (assuming Earth radius of 1).
+        double y     = λt - p2_on_p1 * dλ;      // In cylindrical case, y=L. Otherwise will be adjusted.
+        double dx_dL = 0;       // Part of dx_dφ
+        double dy_dL = 1;       // Part of dy_dφ
+        if (isConic) {
+            double nλs0 = n*y + s0;
+            if (nλs0 * n < 0) {
+                /*
+                 * if nλs0 does not have the sign than n, the (x,y) values computed below would suddenly
+                 * change their sign. The y values lower or greater (depending of n sign) than -s0/n can
+                 * not be plotted. Snyder suggests to use another projection if cosmetic output is wanted.
+                 * For now, we just set the results to NaN (meaning "no result", which is not the same than
+                 * TransformException which means that a result exists but can not be computed).
+                 */
+                nλs0 = Double.NaN;
+            }
+            final double iρ = sin(nλs0);        // Inverse of ρ.
+            y = cos(x) / iρ;
+            x = sin(x) / iρ;                    // Must be last.
+            if (derivate) {
+                final double dρ_dφ = -n / tan(nλs0);
+                dx_dL = x * dρ_dφ;
+                dy_dL = y * dρ_dφ;
+            }
+        }
+        if (dstPts != null) {
+            dstPts[dstOff    ] = x;
+            dstPts[dstOff + 1] = y;
+        }
+        if (!derivate) {
+            return null;
+        }
+        // first computed term associated with derivative of (-dλ/dφ)
+        final double dL_dφ = ((cos(φ) / sin_i) / sqrt(1 - sinφ_sini*sinφ_sini))
+                           * (p2_on_p1 - ((1 + tan_dλ*tan_dλ)*cos_i/(1 + λt*λt)));
+
+        return new Matrix2(isConic ? +y : 1,    dx_dL * dL_dφ,
+                           isConic ? -x : 0,    dy_dL * dL_dφ);
+    }
+
+    /**
+     * Transforms the specified (<var>x</var>,<var>y</var>) coordinates
+     * and stores the result in {@code dstPts} (angles in radians).
+     *
+     * @throws ProjectionException if the coordinates can not be converted.
+     */
+    @Override
+    protected void inverseTransform(double[] srcPts, int srcOff,
+                                    double[] dstPts, int dstOff)
+                                    throws ProjectionException {
+
+        double x = srcPts[srcOff];
+        double L = srcPts[srcOff + 1];
+        if (isConic) {
+            final double ρ = copySign(hypot(x,L), n);
+            x = atan(x / L);                            // Undefined if x = y = 0.
+            L = (asin(1 / ρ) - s0) / n;                 // Equation 28-26 in Snyder with R=1.
+        }
+        /*
+         * Approximation of the latitude associated with L coefficient by applying Newton-Raphson method.
+         * Equations 28-24, 28-25 and then 28-22 in Snyder's book.
+         */
+        double Δdλ, dλ = -PI/2;
+        int iter = Formulas.MAXIMUM_ITERATIONS;
+        do {
+            if (--iter < 0) {
+                throw new ProjectionException(Resources.format(Resources.Keys.NoConvergence));
+            }
+            final double dλn = dλ;
+
+            // Alternative calculation with Snyder's eq.  28-20 and 28-21
+//            λt = l + p2_on_p1 * dλ_n;
+//            dλ = atan(tan(λt) / cos_i);
+
+            final double A   = tan(L + p2_on_p1 * dλn) / cos_i;
+            final double A2  = A*A;
+            Δdλ = (atan(A) - dλn) / (1 - (A2 + 1/cos2_i) * (p2_on_p1*cos_i/(A2 + 1)));
+            dλ  = dλn + Δdλ;
+        } while (abs(Δdλ) >= ANGULAR_TOLERANCE);
+        dstPts[dstOff  ] = x;
+        dstPts[dstOff+1] = -asin(sin(dλ) * sin_i);
+    }
+
+    /**
+     * Method to compute the φn coefficient according to equation 28-9
+     * in Snyder's Map Projections manual.
+     *
+     * @param cos2_φn : square of the φn 's cosine.
+     * @return Fn  coefficient associated with the φn latitude.
+     */
+    private double computeFn(final double cos2_φn) {
+        return atan((p2_on_p1 * cos2_φn - cos_i) / sqrt(cos2_φn - cos2_i)); // eq.28-9 in Snyder
+    }
+
+    /**
+     * Method to compute the φn coefficient according to equation 28-3a
+     * in Snyder's Map Projections manual.
+     *
+     * @param dλn  coefficient associated with the φn latitude.
+     * @return λtn  coefficient associated with the φn latitude.
+     */
+    private double computeλtn(final double dλn) {
+        return atan(tan(dλn) * cos_i); // eq.28-3a in Snyder
+    }
+}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ConicSatelliteTrackingTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ConicSatelliteTrackingTest.java
deleted file mode 100644
index 077a12b..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ConicSatelliteTrackingTest.java
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.referencing.operation.projection;
-
-import java.util.Collections;
-import org.opengis.util.FactoryException;
-import org.opengis.parameter.ParameterValueGroup;
-import org.opengis.referencing.operation.TransformException;
-import org.apache.sis.internal.referencing.Formulas;
-import org.apache.sis.internal.referencing.NilReferencingObject;
-import org.apache.sis.internal.referencing.provider.SatelliteTracking;
-import org.apache.sis.measure.Units;
-import org.apache.sis.referencing.datum.DefaultEllipsoid;
-import org.apache.sis.referencing.operation.transform.MathTransformFactoryMock;
-import org.apache.sis.test.DependsOnMethod;
-import org.junit.Test;
-
-import static java.lang.StrictMath.sin;
-import static java.lang.StrictMath.toRadians;
-import static org.junit.Assert.assertTrue;
-
-
-/**
- * Tests coordinates computed by applying a conic satellite-tracking projection
- * with {@link ConicSatelliteTracking}.
- *
- * @author  Matthieu Bastianelli (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-public strictfp class ConicSatelliteTrackingTest extends MapProjectionTestCase {
-    /**
-     * Creates a new instance of {@link ConicSatelliteTracking} concatenated
-     * with the (de)normalization matrices. The new instance is stored in the
-     * inherited {@link #transform} field.
-     *
-     * @param i : Angle of inclination between the plane of the Earth's Equator
-     * and the plane of the satellite orbit.
-     * @param orbitalT : Time required for revolution of the satellite.
-     * @param ascendingNodeT : Length of Earth's rotation with respect to the
-     * precessed ascending node.
-     * @param λ0 : central meridian.
-     * @param φ1 : first parallel of conformality, with true scale.
-     * @param φ2 : second parallel of conformality, without true scale.
-     * @param φ0 : latitude_of_origin : latitude Crossing the central meridian
-     * at the desired origin of rectangular coordinates (null or NaN for
-     * cylindrical satellite tracking projection.)
-     */
-    void createProjection(final double i,
-            final double orbitalT, final double ascendingNodeT,
-            final double λ0, final double φ1,
-            final double φ2, final double φ0)
-            throws FactoryException {
-
-        final SatelliteTracking provider = new SatelliteTracking();
-        final ParameterValueGroup values = provider.getParameters().createValue();
-        final DefaultEllipsoid sphere = DefaultEllipsoid.createEllipsoid(
-                Collections.singletonMap(DefaultEllipsoid.NAME_KEY, NilReferencingObject.UNNAMED),
-                1, 1, Units.METRE);
-
-        values.parameter("semi_major").setValue(sphere.getSemiMajorAxis());
-        values.parameter("semi_minor").setValue(sphere.getSemiMinorAxis());
-        values.parameter("satellite_orbit_inclination").setValue(i);
-        values.parameter("satellite_orbital_period").setValue(orbitalT, Units.MINUTE);
-        values.parameter("ascending_node_period").setValue(ascendingNodeT, Units.MINUTE);
-        values.parameter("central_meridian").setValue(λ0);
-        values.parameter("standard_parallel_1").setValue(φ1);
-
-        if (!Double.isNaN(φ2)) {
-            values.parameter("standard_parallel_2").setValue(φ2);
-        } else {
-            values.parameter("standard_parallel_2").setValue(-φ1); //Cylindrical case
-        }
-        if (!Double.isNaN(φ0)) {
-            values.parameter("latitude_of_origin").setValue(φ0);
-        }
-
-        transform = new MathTransformFactoryMock(provider).createParameterizedTransform(values);
-        validate();
-    }
-
-    /**
-     * Tests the projection of a few points on a sphere.
-     *
-     * Test based on the numerical example given by Snyder p. 360 to 363 of
-     * <cite> Map Projections - A working Manual</cite>
-     *
-     * @throws FactoryException if an error occurred while creating the map projection.
-     * @throws TransformException if an error occurred while projecting a point.
-     */
-    @Test
-    public void testTransform() throws FactoryException, TransformException {
-        tolerance = 1E-5;
-        createProjection(
-                99.092, //satellite_orbit_inclination
-                103.267, //satellite_orbital_period
-                1440.0, //ascending_node_period
-                -90, //central_meridian
-                45, //standard_parallel_1
-                70, //standard_parallel_2
-                30 //latitude_of_origin
-        );
-        assertTrue(isInverseTransformSupported);
-        verifyTransform(
-                new double[]{ // (λ,φ) coordinates in degrees to project.
-                    -75, 40
-                },
-                new double[]{ // Expected (x,y) results in metres.
-                    0.2001910, 0.2121685
-                });
-    }
-
-    /**
-     * Tests the projection of a few points on a sphere.
-     *
-     * Test based on the sample coordinates for several of the
-     * Satellite-Tracking Projections shown in table 39 from
-     * <cite> Map Projections - A working Manual</cite>
-     *
-     * As this table only introduce the values for n, F1 and ρ coefficients, the
-     * test was realized by checking these coefficients in debugger mode and by
-     * extracting the computed results of the projection. Thus this method
-     * should be used as a non-regression test.
-     *
-     * @throws FactoryException if an error occurred while creating the map projection.
-     * @throws TransformException if an error occurred while projecting a point.
-     */
-    @Test
-    public void testSampleCoordinates() throws FactoryException, TransformException {
-
-        //Following tests don't pass with the former tolerance.
-        tolerance = Formulas.LINEAR_TOLERANCE;
-
-        //----------------------------------------------------------------------
-        // φ1 = 30° ; φ2 = 60°
-        //---------------------
-        createProjection(
-                99.092, //satellite_orbit_inclination
-                103.267, //satellite_orbital_period
-                1440.0, //ascending_node_period
-                -90, //central_meridian
-                30, //standard_parallel_1
-                60, //standard_parallel_2
-                0 //latitude_of_origin
-        );
-
-        double n = 0.49073;
-
-        verifyTransform(
-                new double[]{ // (λ,φ) coordinates in degrees to project.
-                       0, -10,
-                       0, 0,
-                       0, 10,
-                       0, 70,
-                    -120, 80.908  //Tracking limit
-                },
-                new double[]{ // Expected (x,y) results in metres.
-                    2.67991 * sin(n * (toRadians(   0 - -90))),    0.46093,
-                    2.38332 * sin(n * (toRadians(   0 - -90))),    0.67369,
-                    2.14662 * sin(n * (toRadians(   0 - -90))),    0.84348,
-                    0.98470 * sin(n * (toRadians(   0 - -90))),    1.67697,
-                    0.50439 * sin(n * (toRadians(-120 - -90))),    1.89549 //Tracking limit
-                });
-
-        //----------------------------------------------------------------------
-        // φ1 = 45° ; φ2 = 70°
-        //---------------------
-        createProjection(
-                99.092, //satellite_orbit_inclination
-                103.267, //satellite_orbital_period
-                1440.0, //ascending_node_period
-                -90, //central_meridian
-                45, //standard_parallel_1
-                70, //standard_parallel_2
-                0 //latitude_of_origin
-        );
-
-        n = 0.69478;
-
-        verifyTransform(
-                new double[]{ // (λ,φ) coordinates in degrees to project.
-                    0, -10,
-                    0, 0,
-                    0, 10,
-                    0, 70,
-                    -120, 80.908 //Tracking limit
-                },
-                new double[]{ // Expected (x,y) results in metres.
-                    2.92503 * sin(n * (toRadians(   0 - -90))), 0.90110,
-                    2.25035 * sin(n * (toRadians(   0 - -90))), 1.21232,
-                    1.82978 * sin(n * (toRadians(   0 - -90))), 1.40632,
-                    0.57297 * sin(n * (toRadians(   0 - -90))), 1.98605,
-                    0.28663 * sin(n * (toRadians(-120 - -90))), 1.982485 //Tracking limit
-                });
-        //----------------------------------------------------------------------
-        // φ1 = 45° ; φ2 = 70°
-        //---------------------
-        createProjection(
-                99.092, //satellite_orbit_inclination
-                103.267, //satellite_orbital_period
-                1440.0, //ascending_node_period
-                -90, //central_meridian
-                45, //standard_parallel_1
-                80.908, //standard_parallel_2
-                0 //latitude_of_origin
-        );
-
-        n = 0.88475;
-
-        verifyTransform(
-                new double[]{ // (λ,φ) coordinates in degrees to project.
-                       0, -10,
-                       0,  0,
-                       0, 10,
-                       0, 70,
-                    -120, 80.908 //Tracking limit
-                },
-                new double[]{ // Expected (x,y) results in metres.
-                    4.79153 * sin(n * (toRadians(   0 - -90))), 1.80001,
-                    2.66270 * sin(n * (toRadians(   0 - -90))), 2.18329,
-                    1.84527 * sin(n * (toRadians(   0 - -90))), 2.33046,
-                    0.40484 * sin(n * (toRadians(   0 - -90))), 2.58980,
-                    0.21642 * sin(n * (toRadians(-120 - -90))), 2.46908 //Tracking limit
-                });
-    }
-
-    /**
-     * Tests the derivatives at a few points on a sphere. This method compares the derivatives computed
-     * by the projection with an estimation of derivatives computed by the finite differences method.
-     *
-     * @throws FactoryException if an error occurred while creating the map projection.
-     * @throws TransformException if an error occurred while projecting a point.
-     */
-    @Test
-    @DependsOnMethod("testInverseDerivative")
-    public void testDerivativeOnSphere() throws FactoryException, TransformException {
-        createProjection(
-                99.092, //satellite_orbit_inclination
-                103.267, //satellite_orbital_period
-                1440.0, //ascending_node_period
-                -90, //central_meridian
-                45, //standard_parallel_1
-                70, //standard_parallel_2
-                30 //latitude_of_origin
-        );
-        final double delta = (1.0 / 60) / 1852;                 // Approximately 1 metre.
-        derivativeDeltas = new double[] {delta, delta};
-        tolerance = Formulas.LINEAR_TOLERANCE/100 ;
-        verifyDerivative( -75, 40);
-        verifyDerivative(-100,  3);
-        verifyDerivative( -56, 50);
-        verifyDerivative( -20, 47);
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/CylindricalSatelliteTrackingTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/CylindricalSatelliteTrackingTest.java
deleted file mode 100644
index 5221dcb..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/CylindricalSatelliteTrackingTest.java
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.referencing.operation.projection;
-
-import org.opengis.util.FactoryException;
-import org.opengis.referencing.operation.TransformException;
-import org.apache.sis.internal.referencing.Formulas;
-import org.apache.sis.test.DependsOnMethod;
-import org.junit.Test;
-
-import static java.lang.Double.NaN;
-import static org.junit.Assert.assertTrue;
-
-
-/**
- * Tests coordinates computed by applying a cylindrical satellite-tracking projection
- * with {@link CylindricalSatelliteTracking}.
- *
- * @author  Matthieu Bastianelli (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-public final strictfp class CylindricalSatelliteTrackingTest extends ConicSatelliteTrackingTest {
-    /**
-     * Creates a new instance of {@link ConicSatelliteTracking} concatenated
-     * with the (de)normalization matrices. The new instance is stored in the
-     * inherited {@link #transform} field.
-     *
-     * @param i : Angle of inclination between the plane of the Earth's Equator
-     * and the plane of the satellite orbit.
-     * @param orbitalT : Time required for revolution of the satellite.
-     * @param ascendingNodeT : Length of Earth's rotation with respect to the
-     * precessed ascending node.
-     * @param λ0 : central meridian.
-     * @param φ1 : first parallel of conformality, with true scale.
-     * at the desired origin of rectangular coordinates (null or NaN for
-     * cylindrical satellite tracking projection.)
-     */
-    private void createProjection(final double i,
-            final double orbitalT, final double ascendingNodeT,
-            final double λ0,       final double φ1)
-            throws FactoryException
-    {
-        super.createProjection(i, orbitalT, ascendingNodeT, λ0, φ1, NaN, NaN);
-    }
-
-    /**
-     * Tests the projection of a few points on a sphere.
-     *
-     * Test based on the numerical example given by Snyder p. 360 to 363 of
-     * <cite> Map Projections - A working Manual</cite>
-     *
-     * @throws FactoryException if an error occurred while creating the map projection.
-     * @throws TransformException if an error occurred while projecting a point.
-     */
-    @Test
-    @Override
-    public void testTransform() throws FactoryException, TransformException {
-        tolerance = 1E-5;
-        createProjection(
-                99.092,  //satellite_orbit_inclination
-                103.267, //satellite_orbital_period
-                1440.0,  //ascending_node_period
-                -90,     //central_meridian
-                30       //standard_parallel_1
-        );
-        assertTrue(isInverseTransformSupported);
-        verifyTransform(
-                new double[]{ // (λ,φ) coordinates in degrees to project.
-                    -75, 40
-                },
-                new double[]{ // Expected (x,y) results in metres.
-                    0.2267249, 0.6459071
-                });
-    }
-
-    /**
-     * Tests the projection of a few points on a sphere.
-     *
-     * Test based on the sample coordinates for several of the
-     * Satellite-Tracking Projections shown in table 38 from
-     * <cite> Map Projections - A working Manual</cite>
-     *
-     * @throws FactoryException if an error occurred while creating the map projection.
-     * @throws TransformException if an error occurred while projecting a point.
-     */
-    @Test
-    public void testSampleCoordinatesCylindricalProjection()
-            throws FactoryException, TransformException {
-
-        //Following tests don't pass with the former tolerance.
-        tolerance = Formulas.LINEAR_TOLERANCE;
-
-        //----------------------------------------------------------------------
-        // φ1 = 0°
-        //---------
-        createProjection(
-                99.092,  //satellite_orbit_inclination
-                103.267, //satellite_orbital_period
-                1440.0,  //ascending_node_period
-                0,       //central_meridian
-                0        //standard_parallel_1
-        );
-
-        double xConverterFactor=0.017453;
-        verifyTransform(
-                new double[]{ // (λ,φ) coordinates in degrees to project.
-                      0,  0,
-                     10,  0,
-                    -10, 10,
-                     60, 40,
-                     80, 70,
-                    -120, 80.908  //Tracking limit
-                },
-                new double[]{ // Expected (x,y) results in metres.
-                    0, 0,
-                    xConverterFactor *   10,  0,
-                    xConverterFactor *  -10,  0.17579,
-                    xConverterFactor *   60,  0.79741,
-                    xConverterFactor *   80,  2.34465,
-                    xConverterFactor * -120,  7.23571 //Tracking limit
-                });
-
-
-        //----------------------------------------------------------------------
-        // φ1 = +- 30°
-        //------------
-        createProjection(
-                99.092,  //satellite_orbit_inclination
-                103.267, //satellite_orbital_period
-                1440.0,  //ascending_node_period
-                0,       //central_meridian
-                -30        //standard_parallel_1
-        );
-
-        xConverterFactor=0.015115;
-        verifyTransform(
-                new double[]{ // (λ,φ) coordinates in degrees to project.
-                      0,  0,
-                     10,  0,
-                    -10, 10,
-                     60, 40,
-                     80, 70,
-                    -120, 80.908  //Tracking limit
-                },
-                new double[]{ // Expected (x,y) results in metres.
-                    0, 0,
-                    xConverterFactor *   10,  0,
-                    xConverterFactor *  -10,  0.14239,
-                    xConverterFactor *   60,  0.64591,
-                    xConverterFactor *   80,  1.89918,
-                    xConverterFactor * -120,  5.86095 //Tracking limit
-                });
-
-        //----------------------------------------------------------------------
-        // φ1 = +- 45°
-        //------------
-        createProjection(
-                99.092,  //satellite_orbit_inclination
-                103.267, //satellite_orbital_period
-                1440.0,  //ascending_node_period
-                0,       //central_meridian
-                45        //standard_parallel_1
-        );
-
-        xConverterFactor=0.012341;
-        verifyTransform(
-                new double[]{ // (λ,φ) coordinates in degrees to project.
-                      0,  0,
-                     10,  0,
-                    -10, 10,
-                     60, 40,
-                     80, 70,
-                    -120, 80.908  //Tracking limit
-                },
-                new double[]{ // Expected (x,y) results in metres.
-                    0, 0,
-                    xConverterFactor *   10,  0,
-                    xConverterFactor *  -10,  0.10281,
-                    xConverterFactor *   60,  0.46636,
-                    xConverterFactor *   80,  1.37124,
-                    xConverterFactor * -120,  4.23171 //Tracking limit
-                });
-
-
-    }
-     /**
-     * Tests the derivatives at a few points on a sphere. This method compares the derivatives computed
-     * by the projection with an estimation of derivatives computed by the finite differences method.
-     *
-     * @throws FactoryException if an error occurred while creating the map projection.
-     * @throws TransformException if an error occurred while projecting a point.
-     */
-    @Test
-    @Override
-    @DependsOnMethod("testInverseDerivative")
-    public void testDerivativeOnSphere() throws FactoryException, TransformException {
-        createProjection(
-                99.092,  //satellite_orbit_inclination
-                103.267, //satellite_orbital_period
-                1440.0,  //ascending_node_period
-                -90,     //central_meridian
-                30       //standard_parallel_1
-        );
-        final double delta = (1.0 / 60) / 1852;                 // Approximately 1 metre.
-        derivativeDeltas = new double[] {delta, delta};
-        tolerance = Formulas.LINEAR_TOLERANCE / 100;
-        verifyDerivative(-75, 40);
-        verifyDerivative(-100,  3);
-        verifyDerivative( -56, 50);
-        verifyDerivative( -20, 47);
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/SatelliteTrackingTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/SatelliteTrackingTest.java
new file mode 100644
index 0000000..38d550b
--- /dev/null
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/SatelliteTrackingTest.java
@@ -0,0 +1,369 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.referencing.operation.projection;
+
+import java.util.Collections;
+import org.opengis.util.FactoryException;
+import org.opengis.parameter.ParameterValueGroup;
+import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.internal.referencing.Formulas;
+import org.apache.sis.internal.referencing.NilReferencingObject;
+import org.apache.sis.internal.referencing.provider.SatelliteTracking;
+import org.apache.sis.measure.Units;
+import org.apache.sis.referencing.datum.DefaultEllipsoid;
+import org.apache.sis.referencing.operation.transform.MathTransformFactoryMock;
+import org.junit.Test;
+
+import static java.lang.StrictMath.sin;
+import static java.lang.StrictMath.toRadians;
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * Tests coordinates computed by applying a satellite-tracking projection with {@link SatelliteTracking}.
+ *
+ * @author  Matthieu Bastianelli (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public final strictfp class SatelliteTrackingTest extends MapProjectionTestCase {
+    /**
+     * Creates a new instance of {@link SatelliteTracking} concatenated with the (de)normalization matrices.
+     * The new instance is stored in the inherited {@link #transform} field.
+     * This methods uses projection parameters for Landsat 3 satellite, namely:
+     *
+     * <table class="sis">
+     *   <caption>Hard-coded projection parameters</caption>
+     *   <tr><th>Symbol</th> <th>Value</th>       <th>Meaning</th></tr>
+     *   <tr><td>i</td>  <td>99.092°</td>         <td>Angle of inclination between the plane of the Earth's Equator and the plane of the satellite orbit.</td></tr>
+     *   <tr><td>P1</td> <td>1440 minutes</td>    <td>Length of Earth's rotation with respect to the precessed ascending node.</td></tr>
+     *   <tr><td>P2</td> <td>103.267 minutes</td> <td>Time required for revolution of the satellite.</td></tr>
+     * </table>
+     *
+     * The Earth radius is set to 1.
+     *
+     * @param λ0  central meridian.
+     * @param φ0  latitude crossing the central meridian at the desired origin of rectangular coordinates.
+     * @param φ1  first parallel of conformality (with true scale).
+     * @param φ2  second parallel of conformality (without true scale), or -φ1 for a cylindrical projection.
+     */
+    private void createForLandsat(final double λ0, final double φ0, final double φ1, final double φ2)
+            throws FactoryException
+    {
+        final SatelliteTracking provider = new SatelliteTracking();
+        final ParameterValueGroup values = provider.getParameters().createValue();
+        final DefaultEllipsoid sphere = DefaultEllipsoid.createEllipsoid(
+                Collections.singletonMap(DefaultEllipsoid.NAME_KEY, NilReferencingObject.UNNAMED), 1, 1, Units.METRE);
+
+        values.parameter("semi_major")                 .setValue(sphere.getSemiMajorAxis());
+        values.parameter("semi_minor")                 .setValue(sphere.getSemiMinorAxis());
+        values.parameter("central_meridian")           .setValue(λ0);
+        values.parameter("latitude_of_origin")         .setValue(φ0);
+        values.parameter("standard_parallel_1")        .setValue(φ1);
+        values.parameter("standard_parallel_2")        .setValue(φ2);
+        values.parameter("satellite_orbit_inclination").setValue(  99.092);
+        values.parameter("satellite_orbital_period")   .setValue( 103.267, Units.MINUTE);
+        values.parameter("ascending_node_period")      .setValue(1440.0,   Units.MINUTE);
+        transform = new MathTransformFactoryMock(provider).createParameterizedTransform(values);
+        validate();
+    }
+
+    /**
+     * Tests the projection of a few points on a sphere.
+     *
+     * Test based on the numerical example given by Snyder p. 360 to 363 of
+     * <cite> Map Projections - A working Manual</cite>
+     *
+     * @throws FactoryException if an error occurred while creating the map projection.
+     * @throws TransformException if an error occurred while projecting a point.
+     */
+    @Test
+    public void testCylindricalTransform() throws FactoryException, TransformException {
+        tolerance = 1E-7;   // Number of digits in the output values provided by Snyder.
+        tolerance = 1E-5;   // TODO
+        createForLandsat(-90, 0, 30, -30);
+        assertTrue(isInverseTransformSupported);
+        verifyTransform(
+                new double[] {              // (λ,φ) coordinates in degrees to project.
+                    -75, 40
+                },
+                new double[] {              // Expected (x,y) results on a unit sphere.
+                    0.2267249,  0.6459071
+                });
+    }
+
+    /**
+     * Tests the projection of a few points on a sphere.
+     * Test based on the numerical example given by Snyder pages 360 to 363 of
+     * <cite>Map Projections - A working Manual</cite>.
+     *
+     * @throws FactoryException if an error occurred while creating the map projection.
+     * @throws TransformException if an error occurred while projecting a point.
+     */
+    @Test
+    public void testConicTransform() throws FactoryException, TransformException {
+        tolerance = 1E-7;   // Number of digits in the output values provided by Snyder.
+        tolerance = 1E-5;   // TODO
+        createForLandsat(-90, 30, 45, 70);
+        assertTrue(isInverseTransformSupported);
+        verifyTransform(
+                new double[] {              // (λ,φ) coordinates in degrees to project.
+                    -75, 40
+                },
+                new double[] {              // Expected (x,y) results on a unit sphere.
+                    0.2001910,  0.2121685
+                });
+        /*
+         * Expected intermediate values (can be checked in a debugger):
+         *
+         *    F₀ = 13.9686735°
+         *    F₁ = 15.7111447°
+         *    F₂ = 28.7497148°
+         */
+    }
+
+    /**
+     * Compares the projection of a few points against expected values computed from intermediate values
+     * published by Snyder. As Snyder tables in chapter 28 introduce only the values for <var>n</var>,
+     * <var>F₁</var> and <var>ρ</var> coefficients, the test was realized by checking these coefficients
+     * in debugger and by extracting the computed results of the projection.
+     *
+     * @param  xScale       scale to apply on <var>x</var> values.
+     * @param  coordinates  the points to transform.
+     * @param  internal     the expected intermediate transformation results.
+     * @throws TransformException if the transformation failed.
+     */
+    private void verifyCylindricInternal(final double xScale, final double[] coordinates, final double[] internal)
+            throws TransformException
+    {
+        for (int i=0; i<internal.length; i += 2) {
+            internal[i] *= xScale;
+        }
+        verifyTransform(coordinates, internal);
+    }
+
+    /**
+     * Compares the projection of a few points against expected values computed from intermediate values
+     * published by Snyder. As Snyder tables in chapter 28 introduce only the values for <var>n</var>,
+     * <var>F₁</var> and <var>ρ</var> coefficients, the test was realized by checking these coefficients
+     * in debugger and by extracting the computed results of the projection.
+     *
+     * @param  λ0           central meridian.
+     * @param  n            cone factor <var>n</var>.
+     * @param  coordinates  the points to transform.
+     * @param  internal     the expected intermediate transformation results.
+     * @throws TransformException if the transformation failed.
+     */
+    private void verifyConicInternal(final double λ0, final double n, final double[] coordinates, final double[] internal)
+            throws TransformException
+    {
+        for (int i=0; i<internal.length; i += 2) {
+            internal[i] *= sin(n * toRadians(coordinates[i] - λ0));
+        }
+        verifyTransform(coordinates, internal);
+    }
+
+    /**
+     * Tests the projection of a few points on a sphere.
+     * Test based on the sample coordinates for several of the Satellite-Tracking projections
+     * shown in table 38 of <cite>Map Projections - A working Manual</cite>.
+     *
+     * @throws FactoryException if an error occurred while creating the map projection.
+     * @throws TransformException if an error occurred while projecting a point.
+     */
+    @Test
+    public void testCylindricalInternal() throws FactoryException, TransformException {
+        tolerance = NormalizedProjection.ANGULAR_TOLERANCE;
+        tolerance = Formulas.LINEAR_TOLERANCE;                  // TODO
+        /*
+         * φ₁ = 0°
+         */
+        createForLandsat(0, 0, 0, 0);
+        verifyCylindricInternal(0.017453,
+                new double[] {                  // (λ,φ) coordinates in degrees to project.
+                       0,   0,
+                      10,   0,
+                     -10,  10,
+                      60,  40,
+                      80,  70,
+                    -120,  80.908               // Tracking limit.
+                },
+                new double[] {                  // Expected (x,y) results on a unit sphere.
+                       0,   0,
+                      10,   0,
+                     -10,   0.17579,
+                      60,   0.79741,
+                      80,   2.34465,
+                    -120,   7.23571             // Projection of tracking limit.
+                });
+        /*
+         * φ₁ = -30°
+         */
+        createForLandsat(0, 0, -30, 30);
+        verifyCylindricInternal(0.015115,
+                new double[] {
+                       0,   0,
+                      10,   0,
+                     -10,  10,
+                      60,  40,
+                      80,  70,
+                    -120,  80.908
+                },
+                new double[] {
+                       0,  0,
+                      10,  0,
+                     -10,  0.14239,
+                      60,  0.64591,
+                      80,  1.89918,
+                    -120,  5.86095
+                });
+        /*
+         * φ₁ = 45°
+         */
+        createForLandsat(0, 0, 45, -45);
+        verifyCylindricInternal(0.012341,
+                new double[] {
+                       0,   0,
+                      10,   0,
+                     -10,  10,
+                      60,  40,
+                      80,  70,
+                    -120,  80.908
+                },
+                new double[] {
+                       0, 0,
+                      10,  0,
+                     -10,  0.10281,
+                      60,  0.46636,
+                      80,  1.37124,
+                    -120,  4.23171
+                });
+    }
+
+    /**
+     * Tests the projection of a few points on a sphere.
+     * Test based on the sample coordinates for several of the Satellite-Tracking projections
+     * shown in table 39 of <cite>Map Projections - A working Manual</cite>, page 238.
+     *
+     * @throws FactoryException if an error occurred while creating the map projection.
+     * @throws TransformException if an error occurred while projecting a point.
+     */
+    @Test
+    public void testConicInternal() throws FactoryException, TransformException {
+        tolerance = NormalizedProjection.ANGULAR_TOLERANCE;
+        tolerance = Formulas.LINEAR_TOLERANCE;                  // TODO
+        /*
+         * φ₁ = 30° ; φ₂ = 60°
+         */
+        createForLandsat(-90, 0, 30, 60);
+        verifyConicInternal(-90, 0.49073,
+                new double[] {                  // (λ,φ) coordinates in degrees to project.
+                       0, -10,
+                       0,   0,
+                       0,  10,
+                       0,  70,
+                    -120,  80.908               // Tracking limit.
+                },
+                new double[] {                  // Expected (x,y) results on a unit sphere.
+                    2.67991,  0.46093,
+                    2.38332,  0.67369,
+                    2.14662,  0.84348,
+                    0.98470,  1.67697,
+                    0.50439,  1.89549           // Projection of tracking limit.
+                });
+        /*
+         * φ₁ = 45° ; φ₂ = 70°
+         */
+        createForLandsat(-90, 0, 45, 70);
+        verifyConicInternal(-90, 0.69478,
+                new double[] {
+                    0, -10,
+                    0,   0,
+                    0,  10,
+                    0,  70,
+                 -120,  80.908                  // Tracking limit.
+                },
+                new double[] {
+                    2.92503,  0.90110,
+                    2.25035,  1.21232,
+                    1.82978,  1.40632,
+                    0.57297,  1.98605,
+                    0.28663,  1.982485          // Projection of tracking limit.
+                });
+        /*
+         * φ₁ = 45° ; φ₂ = 80.908° (the tracking limit).
+         */
+        createForLandsat(-90, 0, 45, 80.908);
+        verifyConicInternal(-90, 0.88475,
+                new double[] {
+                       0, -10,
+                       0,   0,
+                       0,  10,
+                       0,  70,
+                    -120,  80.908
+                },
+                new double[] {
+                    4.79153,  1.80001,
+                    2.66270,  2.18329,
+                    1.84527,  2.33046,
+                    0.40484,  2.58980,
+                    0.21642,  2.46908
+                });
+    }
+
+    /**
+     * Tests the derivatives at a few points for cylindrical case. This method compares the derivatives computed
+     * by the projection with an estimation of derivatives computed by the finite differences method.
+     *
+     * @throws FactoryException if an error occurred while creating the map projection.
+     * @throws TransformException if an error occurred while projecting a point.
+     */
+    @Test
+    public void testCylindricalDerivative() throws FactoryException, TransformException {
+        createForLandsat(-90, 0, 30, -30);
+        final double delta = (1.0 / 60) / 1852;                 // Approximately 1 metre.
+        derivativeDeltas = new double[] {delta, delta};
+        tolerance = NormalizedProjection.ANGULAR_TOLERANCE;
+        tolerance = Formulas.LINEAR_TOLERANCE / 100;            // TODO
+        verifyDerivative( -75, 40);
+        verifyDerivative(-100,  3);
+        verifyDerivative( -56, 50);
+        verifyDerivative( -20, 47);
+    }
+
+    /**
+     * Tests the derivatives at a few points for conic case. This method compares the derivatives computed
+     * by the projection with an estimation of derivatives computed by the finite differences method.
+     *
+     * @throws FactoryException if an error occurred while creating the map projection.
+     * @throws TransformException if an error occurred while projecting a point.
+     */
+    @Test
+    public void testConicDerivative() throws FactoryException, TransformException {
+        createForLandsat(-90, 30, 45, 70);
+        final double delta = (1.0 / 60) / 1852;                 // Approximately 1 metre.
+        derivativeDeltas = new double[] {delta, delta};
+        tolerance = NormalizedProjection.ANGULAR_TOLERANCE;
+        tolerance = Formulas.LINEAR_TOLERANCE/100 ;             // TODO
+        verifyDerivative( -75, 40);
+        verifyDerivative(-100,  3);
+        verifyDerivative( -56, 50);
+        verifyDerivative( -20, 47);
+    }
+}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java b/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
index 388b5a8..8020457 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
@@ -191,8 +191,7 @@ import org.junit.BeforeClass;
     org.apache.sis.referencing.operation.projection.SinusoidalTest.class,
     org.apache.sis.referencing.operation.projection.PolyconicTest.class,
     org.apache.sis.referencing.operation.projection.MollweideTest.class,
-    org.apache.sis.referencing.operation.projection.ConicSatelliteTrackingTest.class,
-    org.apache.sis.referencing.operation.projection.CylindricalSatelliteTrackingTest.class,
+    org.apache.sis.referencing.operation.projection.SatelliteTrackingTest.class,
 
     // Coordinate operation and derived Coordinate Reference Systems (cyclic dependency).
     org.apache.sis.referencing.operation.DefaultTransformationTest.class,


Mime
View raw message