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

commit d6502364fe43513d836c23c2016844d2ca315531
Author: Matthieu_Bastianelli <matthieu.bastiane...@geomatys.com>
AuthorDate: Tue Jul 23 09:31:22 2019 +0200

    feat (sis-referencing & internal) : implement cylindrical and conic 
satellite-tracking projections from Snyder's 'Map Projections - a working 
Manual'. Don't compute derivatives yet. Very few tests.
---
 .../referencing/provider/SatelliteTracking.java    | 200 +++++++++++
 .../projection/ConicSatelliteTracking.java         | 398 +++++++++++++++++++++
 .../projection/CylindricalSatelliteTracking.java   | 197 ++++++++++
 .../projection/ConicSatelliteTrackingTest.java     | 127 +++++++
 .../CylindricalSatelliteTrackingTest.java          | 117 ++++++
 5 files changed, 1039 insertions(+)

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
new file mode 100644
index 0000000..3e9382d
--- /dev/null
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/SatelliteTracking.java
@@ -0,0 +1,200 @@
+/*
+ * 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.internal.referencing.provider;
+
+import javax.xml.bind.annotation.XmlTransient;
+import static 
org.apache.sis.internal.referencing.provider.AbstractProvider.createZeroConstant;
+import static 
org.apache.sis.internal.referencing.provider.Mercator1SP.LATITUDE_OF_ORIGIN;
+import org.apache.sis.measure.Units;
+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.ArgumentChecks;
+import org.opengis.parameter.ParameterDescriptor;
+import org.opengis.parameter.ParameterDescriptorGroup;
+import org.opengis.parameter.ParameterNotFoundException;
+
+/**
+ * The provider for <cite>Satellite-Tracking projections"</cite>.
+ *
+ * <cite>
+ * - All groundtracks for satellites orbiting the Earth with the same orbital
+ * parameters are shown as straight lines on the map.
+ *
+ * - Cylindrical {@link CylindricalSatelliteTracking} or conical
+ * {@link ConicSatelliteTracking} form available.
+ *
+ * - Neither conformal nor equal-area.
+ *
+ * - All meridians are equally spaced straight lines, parallel on cylindrical
+ * form and converging to a common point on conical form.
+ *
+ * - All parallels are straight and parallel on cylindrical form and are
+ * concentric circular arcs on conical form. Parallels are unequally spaced.
+ *
+ * - 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.
+ *
+ * Developed 1977 by Snyder
+ * </cite>
+ *
+ * <cite> These formulas are confined to circular orbits and the SPHERICAL
+ * Earth.</cite>
+ *
+ * <cite>The ascending and descending groundtracks meet at the northern an
+ * southern tracking limits, lats. 80.9°N and S for landsat 1, 2 and 3. The map
+ * Projection does not extend closer to the poles.</cite>
+ *
+ * This projection method has no associated EPSG code.
+ *
+ * Earth radius is normalized. Its value is 1 and is'nt an input parameter.
+ *
+ * 
=============================================================================
+ * REMARK : The parameters associated with the satellite (and its orbit) could
+ * be aggregate in class of the kind : Satellite or SatelliteOrbit.
+ * 
=============================================================================
+ *
+ * @see <cite>Map Projections - A Working Manual</cite> By John P. Snyder
+ * @author Matthieu Bastianelli (Geomatys)
+ * @version 1.0
+ */
+@XmlTransient
+public class SatelliteTracking extends MapProjection {
+
+    /**
+     * Name of this projection.
+     */
+    public static final String NAME = "Satellite-Tracking";
+
+    /**
+     * The operation parameter descriptor for the <cite>Longitude of projection
+     * center</cite> (λ₀) parameter value. Valid values range is [-180 … 180]°
+     * and default value is 0°.
+     */
+    public static final ParameterDescriptor<Double> CENTRAL_MERIDIAN = 
ESRI.CENTRAL_MERIDIAN;
+
+    /**
+     * The operation parameter descriptor for the <cite>Latitude of 1st 
standard
+     * parallel</cite>. For conical satellite-tracking projection : first
+     * parallel of conformality with true scale.
+     *
+     * Valid values range is [-90 … 90]° and default value is the value given 
to
+     * the {@link #LATITUDE_OF_FALSE_ORIGIN} parameter.
+     */
+    public static final ParameterDescriptor<Double> STANDARD_PARALLEL_1 = 
LambertConformal2SP.STANDARD_PARALLEL_1;
+
+    /**
+     * The operation parameter descriptor for the <cite>second parallel of
+     * conformality but without true scale</cite> parameter value for conic
+     * projection. Valid values range is [-90 … 90]° and default value is the
+     * opposite value given to the {@link #STANDARD_PARALLEL_1} parameter.
+     */
+    public static final ParameterDescriptor<Double> STANDARD_PARALLEL_2 = 
LambertConformal2SP.STANDARD_PARALLEL_2;
+
+    /**
+     * Latitude Crossing the central meridian at the desired origin of
+     * rectangular coordinates.
+     */
+    public static final ParameterDescriptor<Double> LATITUDE_OF_ORIGIN = 
TransverseMercator.LATITUDE_OF_ORIGIN;;
+
+    /**
+     * The operation parameter descriptor for the <cite> Angle of inclination
+     * between the plane of the Earth's Equator and the plane of the satellite
+     * orbit</cite> parameter value. <cite> It's measured counterclockwise from
+     * the Equatorto the orbit plane at the ascending node (99.092° for Landsat
+     * 1, 2, 3</cite>
+     */
+    public static final ParameterDescriptor<Double> 
SATELLITE_ORBIT_INCLINATION;
+
+    /**
+     * The operation parameter descriptor for the <cite> time required for
+     * revolution of the satellite</cite> parameter value.
+     */
+    public static final ParameterDescriptor<Double> SATELLITE_ORBITAL_PERIOD;
+
+    /**
+     * The operation parameter descriptor for the <cite> length of earth's
+     * rotation with respect to the precessed ascending node</cite> parameter
+     * value. The ascending node is the point on the satellite orbit at which
+     * the satellite crosses the Earth's equatorial plane in a northerly
+     * direction.
+     */
+    public static final ParameterDescriptor<Double> ASCENDING_NODE_PERIOD; 
//Constant for landsat but is it the case for every satellite with circular 
orbital?
+
+    /**
+     * The group of all parameters expected by this coordinate operation.
+     */
+    static final ParameterDescriptorGroup PARAMETERS;
+
+    static {
+        final ParameterBuilder builder = new ParameterBuilder();
+        builder.setCodeSpace(null, null)
+                .setRequired(true);
+
+        SATELLITE_ORBIT_INCLINATION = builder
+                .addName("satellite_orbit_inclination")
+                .setDescription("Angle of inclination between the plane of the 
Earth's Equator and the plane of the satellite orbit")
+                .create(0, Units.RADIAN);
+
+        SATELLITE_ORBITAL_PERIOD = builder
+                .addName("satellite_orbital_period")
+                .setDescription("Time required for revolution of the 
satellite")
+                .createStrictlyPositive(103.267, Units.MINUTE); //Or hours? 
Seconds?
+
+        ASCENDING_NODE_PERIOD = builder
+                .addName("ascending_node_period")
+                .setDescription("Length of Earth's rotation with respect to 
the precessed ascending node")
+                .createStrictlyPositive(98.884, Units.MINUTE);
+                
+        PARAMETERS = builder.addName(NAME)
+                .createGroupForMapProjection(CENTRAL_MERIDIAN,
+                        STANDARD_PARALLEL_1, STANDARD_PARALLEL_2,
+                        SATELLITE_ORBIT_INCLINATION, SATELLITE_ORBITAL_PERIOD,
+                        ASCENDING_NODE_PERIOD, LATITUDE_OF_ORIGIN);
+
+    }
+
+    /**
+     * Constructs a new provider.
+     */
+    public SatelliteTracking() {
+        super(PARAMETERS);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return the map projection created from the given parameter values.
+     */
+    @Override
+    protected NormalizedProjection createProjection(final Parameters 
parameters) throws ParameterNotFoundException {
+        ArgumentChecks.ensureNonNull("Parameters", parameters);
+
+        if (parameters.getValue(STANDARD_PARALLEL_2) == 
-parameters.getValue(STANDARD_PARALLEL_1)) { 
+            return new 
org.apache.sis.referencing.operation.projection.CylindricalSatelliteTracking(this,
 parameters);
+        } else {
+            return new 
org.apache.sis.referencing.operation.projection.ConicSatelliteTracking(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
new file mode 100644
index 0000000..3c8d556
--- /dev/null
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConicSatelliteTracking.java
@@ -0,0 +1,398 @@
+/*
+ * 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 static java.lang.Double.NaN;
+import java.util.EnumMap;
+import org.apache.sis.parameter.Parameters;
+import org.apache.sis.util.Workaround;
+import org.opengis.parameter.ParameterDescriptor;
+import org.opengis.referencing.operation.Matrix;
+import org.opengis.referencing.operation.OperationMethod;
+
+import static java.lang.Math.*;
+import java.util.function.DoubleUnaryOperator;
+import org.apache.sis.internal.referencing.Resources;
+import static org.apache.sis.internal.referencing.provider.SatelliteTracking.*;
+import org.apache.sis.referencing.operation.matrix.MatrixSIS;
+import org.apache.sis.referencing.operation.transform.ContextualParameters;
+
+/**
+ * <cite>Cylindrical Satellite-Tracking projections</cite>.
+ * 
+ * <cite>
+ * - All groundtracks for satellites orbiting the Earth with the same orbital
+ * parameters are shown as straight lines on the map.
+ *
+ * - Cylindrical {@link CylindricalSatelliteTracking}
+ * or conical {@link ConicSatelliteTracking} form available. 
+ *
+ * - Neither conformal nor equal-area.
+ *
+ * - All meridians are equally spaced straight lines, parallel on cylindrical
+ * form and converging to a common point on conical form.
+ *
+ * - All parallels are straight and parallel on cylindrical form and are
+ * concentric circular arcs on conical form. Parallels are unequally spaced.
+ *
+ * - 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.
+ *
+ * Developed 1977 by Snyder
+ * </cite>
+ *
+ * <cite> These formulas are confined to circular orbits and the SPHERICAL
+ * Earth.</cite>
+ *
+ * <cite>The ascending and descending groundtracks meet at the northern an
+ * southern tracking limits, lats. 80.9°N and S for landsat 1, 2 and 3. The map
+ * Projection does not extend closer to the poles.</cite>
+ *
+ * This projection method has no associated EPSG code.
+ *
+ * Earth radius is normalized. Its value is 1 and is'nt an input parameter.
+ *
+ * 
=============================================================================
+ * REMARK : The parameters associated with the satellite (and its orbit) could
+ * be aggregate in class of the kind : Satellite or SatelliteOrbit.
+ * 
=============================================================================
+ *
+ * @see <cite>Map Projections - A Working Manual</cite> By John P. Snyder
+ * @author Matthieu Bastianelli (Geomatys)
+ * @version 1.0
+ */
+public class ConicSatelliteTracking extends NormalizedProjection{
+    
+    /**
+     * {@code SATELLITE_ORBIT_INCLINATION}
+     */
+    final double i;
+    
+    /**
+     * Coefficients for both cylindrical and conic Satellite-Tracking 
Projection.
+     */
+    final double cos_i, sin_i, cos2_i, cos_φ1, p2_on_p1;
+    
+//    /**
+//     * Radius of the circle radius of the circle to which groundtracks
+//     * are tangent on the map.
+//     */
+//    private final double ρs;
+    
+    /**
+     * Projection Cone's constant.
+     */
+    private final double n;
+    /**
+     * Approximation of the limiting lattitude to which the {@code L 
coefficient}
+     * is associated with a particular case {@code L equals -s0/n}.
+     */
+    private final double lattitudeLimit;
+    /**
+     * Boolean attribute indicating if the projection cone's constant is 
positive.
+     * */
+    private final boolean positiveN;
+    /**
+     * Coefficients for the Conic Satellite-Tracking Projection.
+     */
+    private final double cos_φ1xsin_F1, s0, ρ0;
+
+    /**
+     * 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);
+    }
+
+    /**
+     * 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 ConicSatelliteTracking(final OperationMethod method, final 
Parameters parameters) {
+        this(initializer(method, parameters));
+    }
+
+    /**
+     * Constructor for ConicSatelliteTracking.
+     * 
+     * Calculation are based on <cite>28 .SATTELITE-TRACKING PROJECTIONS 
</cite> 
+     * in <cite> Map Projections - A Working Manual</cite> By John P. Snyder.
+     * 
+     * @param initializer 
+     */
+    ConicSatelliteTracking(final Initializer initializer) {
+        super(initializer);
+
+        
//======================================================================
+        // Common for both cylindrical and conic sattelite tracking 
projections :
+        
//======================================================================
+        i        = 
toRadians(initializer.getAndStore(SATELLITE_ORBIT_INCLINATION)); // Radian 
input value.
+        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 double F0 = atan( (p2_on_p1*cos2_φ0 - cos_i)/sqrt(cos2_φ0 - 
cos2_i) );
+//        final double F1 = atan( (p2_on_p1*cos2_φ1 - cos_i)/sqrt(cos2_φ1 - 
cos2_i) );
+//        final double F2 = atan( (p2_on_p1*cos2_φ2 - cos_i)/sqrt(cos2_φ2 - 
cos2_i) );
+//        final double dλ0 = -asin(sin_φ0/sin_i);
+//        final double dλ1 = -asin(sin_φ1/sin_i);
+//        final double dλ2 = -asin(sin_φ2/sin_i);   
+//        final double λt0 = atan( tan(dλ0) * cos_i );
+//        final double λt1 = atan( tan(dλ1) * cos_i );
+//        final double λt2 = atan( tan(dλ2) * cos_i );
+            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);
+            /*
+         *Inclination of the groundtrack to the meridian at latitude φ1
+             */
+            final double F1 = computeFn.applyAsDouble(cos2_φ1);
+
+            cos_φ1xsin_F1 = cos_φ1 * sin(F1);
+
+            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 L0 = λt0 - p2_on_p1 * dλ0;
+            final double L1 = λt1 - p2_on_p1 * dλ1;
+
+            if (φ1 == PI / 2 - i) { //tracking limit
+                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 λt2 = computeλtn.applyAsDouble(dλ2);
+                final double L2 = λt2 - p2_on_p1 * dλ2;
+                final double F2 = computeFn.applyAsDouble(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 
+            }
+            s0 = F1 - n * L1;
+            ρ0 = cos_φ1xsin_F1 / (n * sin(n * L0 + s0)); // *R in eq.28-12 in 
Snyder 
+
+            //======================== Unsure 
======================================
+            // Aim to assess the limit lattitude associated with -s0/n L-value.
+            //
+            lattitudeLimit = lattitudeFromNewtonMethod(-s0 / n);
+            positiveN = (n >= 0);
+            
//======================================================================
+
+//            //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);
+//        final MatrixSIS denormalize = 
context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION);
+        normalize  .convertAfter (0, n, null);  //For conic tracking
+//        denormalize.convertBefore(0, 1/PI,     null);
+//        denormalize.convertBefore(1, , null);
+        }else{
+            n = lattitudeLimit = cos_φ1xsin_F1 = s0 = ρ0 = NaN;
+            positiveN = false;
+        }
+    }
+
+    /**
+     * 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( ((positiveN)&&(φ<=lattitudeLimit)) || 
((!positiveN)&&(φ>=lattitudeLimit)) ){
+            throw new 
ProjectionException(Resources.format(Resources.Keys.CanNotTransformCoordinates_2));
+        }
+        
+        final double sin_φ  = sin(φ);
+        final double dλ     = -asin(sin_φ / sin_i);
+        final double λt     = atan(tan(dλ) * cos_i);
+        final double L      = λt - p2_on_p1 * dλ;
+        
+        /*
+         * As {@code lattitudeLimit} is an approximation we repeat the test 
here.
+         */
+        if( ((positiveN)&&(L<=-s0/n)) || ((!positiveN)&&(L>=-s0/n)) ){
+            //TODO if usefull, could we add :
+            // lattitudeLimit = φ;
+            throw new 
ProjectionException(Resources.format(Resources.Keys.CanNotTransformCoordinates_2));
+        }
+        
+        final double ρ      = cos_φ1xsin_F1/(n*sin(n*L+s0)); 
+        final double θ      = λ;      // extracted n
+        
+        final double sinθ = sin(θ);
+        final double cosθ = cos(θ);
+        if (dstPts != null) {
+            dstPts[dstOff    ] = ρ * sinθ;   // x
+            dstPts[dstOff + 1] = ρ0 - ρ*cosθ;  // y       //TODO : extract ρ0 
when ensuring : λ = λ - λ0;
+        }
+        
+         if (!derivate) {
+            return null;
+        }
+
+        //=========================TO Resolve =================================
+//        final double dx_dλ = ρ*n*cosθ; 
+//        final double dx_dφ =?;
+//
+//        final double dy_dλ =  ρ*n*sinθ;
+//        final double dy_dφ = ?;
+        
//======================================================================
+        
+        throw new UnsupportedOperationException("Not supported yet."); //To 
change body of generated methods, choose Tools | Templates.
+        
+        //Additionally we can compute the 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] - ρ0;
+        
+        //TODO : extract - ρ0 : MatrixSIS convertBefore and convertAfter??
+        
+        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 ρ = positiveN? hypot(x,y) : -hypot(x,y);
+        final double θ = atan(x/(-y) ); //undefined if x=y=0
+        final double L = (asin(cos_φ1xsin_F1/(ρ*n)) -s0)/n; //undefined if 
x=y=0  //eq.28-26 in Snyder with R=1
+        
+        //TODO ensure that λ0 will be added. ;  In eq. Snyder 28-23 : λ0 + θ/n
+        dstPts[dstOff  ] =  θ ;//λ 
+        dstPts[dstOff+1] = lattitudeFromNewtonMethod(L); //φ
+    }
+    
+    protected final double lattitudeFromNewtonMethod(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) || dλ-dλn >= (0.1*PI/180)){ //TODO : check the 
condition. It is considered here that convergence is reached when improvement 
is lower than 0.1°.
+            if(count >= maxIteration){
+                throw new 
RuntimeException(Resources.format(Resources.Keys.NoConvergence));
+            }
+            dλn = dλ;
+
+//            λ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++;
+        }
+//        λt = L + p2_on_p1 * dλ;
+        final double sin_dλ = sin(dλ);
+        return -asin(sin_dλ * sin_i); 
+    }
+    
+    
+    public double getLattitudeLimit(){
+        return lattitudeLimit;
+    }
+    
+//    /**
+//     * Radius of the circle radius of the circle to which groundtracks
+//     * are tangent on the map.
+//     * 
+//     * @return radius ρs.
+//     */
+//    public double getRadiusOfTangencyCircle(){
+//        return ρs;
+//    }
+}
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
new file mode 100644
index 0000000..70ea6fe
--- /dev/null
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/CylindricalSatelliteTracking.java
@@ -0,0 +1,197 @@
+/*
+ * 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.apache.sis.parameter.Parameters;
+import org.opengis.referencing.operation.OperationMethod;
+
+import static java.lang.Math.*;
+import org.apache.sis.internal.referencing.Resources;
+import org.apache.sis.referencing.operation.matrix.MatrixSIS;
+import org.apache.sis.referencing.operation.transform.ContextualParameters;
+
+/**
+ * <cite>Cylindrical Satellite-Tracking projections</cite>. 
+ * 
+ * <cite>
+ * - All groundtracks
+ * for satellites orbiting the Earth with the same orbital parameters are shown
+ * as straight lines on the map.
+ *
+ * - Cylindrical {@link CylindricalSatelliteTracking}
+ * or conical {@link ConicSatelliteTracking} form available. 
+ *
+ * - Neither conformal nor equal-area.
+ *
+ * - All meridians are equally spaced straight lines, parallel on cylindrical
+ * form and converging to a common point on conical form.
+ *
+ * - All parallels are straight and parallel on cylindrical form and are
+ * concentric circular arcs on conical form. Parallels are unequally spaced.
+ *
+ * - 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.
+ *
+ * Developed 1977 by Snyder
+ * </cite>
+ *
+ * <cite> These formulas are confined to circular orbits and the SPHERICAL
+ * Earth.</cite>
+ *
+ * <cite>The ascending and descending groundtracks meet at the northern an
+ * southern tracking limits, lats. 80.9°N and S for landsat 1, 2 and 3. The map
+ * Projection does not extend closer to the poles.</cite>
+ *
+ * This projection method has no associated EPSG code.
+ *
+ * Earth radius is normalized. Its value is 1 and is'nt an input parameter.
+ *
+ * 
=============================================================================
+ * REMARK : The parameters associated with the satellite (and its orbit) could
+ * be aggregate in class of the kind : Satellite or SatelliteOrbit.
+ * 
=============================================================================
+ *
+ * @see <cite>Map Projections - A Working Manual</cite> By John P. Snyder
+ * @author Matthieu Bastianelli (Geomatys)
+ * @version 1.0
+ */
+public class CylindricalSatelliteTracking extends ConicSatelliteTracking {
+
+    
+    /**
+     * F1' in Snyder : tangente of the angle on both the globe and on the map 
between 
+     * the groundtrack and the meridian at latitude φ1. 
+     */
+    final double dF1;
+    
+    /**
+     * 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;
+        dF1 = (p2_on_p1 * cos2_φ1 - cos_i) / sqrt(cos2_φ1 - cos2_i);
+        
+        final MatrixSIS normalize   = 
context.getMatrix(ContextualParameters.MatrixRole.NORMALIZATION);
+//        final MatrixSIS denormalize = 
context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION);
+
+        normalize  .convertAfter (0, cos_φ1, null);  //For conic tracking
+        
+//        denormalize.convertBefore(0, 1/PI,     null);
+//        denormalize.convertBefore(1, , 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];
+
+        // TODO : check the condition and the thrown exception.
+        if (abs(φ) > PI / 2 - abs(PI / 2 - i)) {  // Exceed tracking limit 
+            throw new 
ProjectionException(Resources.format(Resources.Keys.CanNotTransformCoordinates_2));
+        }
+
+//        final double cos_φ  = cos(φ);
+//        final double cos2_φ = cos_φ * cos_φ;
+        final double sin_φ  = sin(φ);
+
+        final double dλ     = -asin(sin_φ / sin_i);
+        final double λt     = atan(tan(dλ) * cos_i);
+        final double L      = λt - p2_on_p1 * dλ;
+
+        if (dstPts != null) {
+            dstPts[dstOff    ] = λ;   // In eq. Snyder 28-5 : R(λ - λ0) cos_φ1
+            dstPts[dstOff + 1] = L * cos_φ1 / dF1;  // 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 / dF1;    // Meridian eq. Snyder 28-8
+        ===================================================================== 
*/
+        if (!derivate) {
+            return null;
+        }
+
+//        final double dx_dλ = cos_φ1; //*R
+//        final double dx_dφ = 0;
+//
+//        final double dy_dλ = 0;
+//        final double dy_dφ =
+                
+        throw new UnsupportedOperationException("Not supported yet."); //To 
change body of generated methods, choose Tools | Templates.
+    }
+
+    /**
+     * 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];
+        
+        final double L   = y * dF1 / cos_φ1; // In eq. Snyder 28-19 : L = y * 
dF1 / R . cos_φ1
+        
+        dstPts[dstOff  ] =  x;
+        dstPts[dstOff+1] = lattitudeFromNewtonMethod(L); //φ
+    }
+
+}
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
new file mode 100644
index 0000000..28d83df
--- /dev/null
+++ 
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ConicSatelliteTrackingTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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 static java.lang.Double.NaN;
+import java.util.Collections;
+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.DependsOn;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+import org.opengis.parameter.ParameterValueGroup;
+import org.opengis.referencing.operation.TransformException;
+import org.opengis.util.FactoryException;
+
+/**
+ * Tests coordiantes computed by applying a conic satellite-tracking projection
+ * with {@link ConicSatelliteTracking}.
+ *
+ * @author Matthieu Bastianelli (Geomatys)
+ * @version 1.0
+ * @since 1.0
+ * @module
+ */
+@DependsOn(ConformalProjectionTest.class)
+public 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 : lattitude_of_origin : latitude Crossing the central meridian
+     * at the desired origin of rectangular coordinates (null or NaN for
+     * cylindrical satellite tracking projection.)
+     * @return
+     */
+    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);
+        values.parameter("ascending_node_period").setValue(ascendingNodeT);
+        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.
+     *
+     * @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       //lattitude_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
+                });
+    }
+
+}
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
new file mode 100644
index 0000000..0a86ba6
--- /dev/null
+++ 
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/CylindricalSatelliteTrackingTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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 static java.lang.Double.NaN;
+import org.apache.sis.internal.referencing.Formulas;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+import org.opengis.referencing.operation.TransformException;
+import org.opengis.util.FactoryException;
+
+/**
+ * Tests coordiantes computed by applying a cylindrical satellite-tracking 
projection
+ * with {@link CylindricalSatelliteTracking}.
+ *
+ * @author Matthieu Bastianelli (Geomatys)
+ * @version 1.0
+ * @since 1.0
+ * @module
+ */
+public 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.
+     * @param φ2 : second parallel of conformality, without true scale.
+     * @param φ0 : lattitude_of_origin : latitude Crossing the central meridian
+     * at the desired origin of rectangular coordinates (null or NaN for
+     * cylindrical satellite tracking projection.)
+     * @return
+     */
+    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.
+     *
+     * @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
+                });
+        
+        createProjection(
+                99.092,  //satellite_orbit_inclination
+                103.267, //satellite_orbital_period
+                1440.0,  //ascending_node_period
+                0,       //central_meridian
+                0        //standard_parallel_1
+        );
+        
+        tolerance = Formulas.LINEAR_TOLERANCE; //Don't pass with the former 
tolerance.
+        
+        final double xConverterFactor=0.017453;
+        verifyTransform(
+                new double[]{ // (λ,φ) coordinates in degrees to project.
+                      0,  0,
+                     10,  0, 
+                    -10, 10
+                },
+                new double[]{ // Expected (x,y) results in metres.
+                    0, 0,
+                    xConverterFactor *  10,  0,
+                    xConverterFactor * -10,  0.17579,
+                });
+    }
+    
+}

Reply via email to