sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 01/02: Review Mollweide projection implementation and test.
Date Sat, 28 Jul 2018 13:26:25 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

commit 71d417d8d159dd5f51146187b6e583ebc5d446bb
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Sat Jul 28 14:04:58 2018 +0200

    Review Mollweide projection implementation and test.
---
 .../{PolarStereographicSouth.java => ESRI.java}    |  83 ++++-----
 .../internal/referencing/provider/Mollweide.java   |  85 +++++----
 .../provider/PolarStereographicSouth.java          |  35 +---
 .../operation/projection/Mollweide.java            | 163 +++++++++-------
 ...g.opengis.referencing.operation.OperationMethod |   2 +-
 .../operation/projection/MollweideTest.java        | 204 ++++++++++-----------
 6 files changed, 280 insertions(+), 292 deletions(-)

diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicSouth.java
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ESRI.java
similarity index 50%
copy from core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicSouth.java
copy to core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ESRI.java
index 1a36fa5..8469888 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicSouth.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ESRI.java
@@ -16,73 +16,70 @@
  */
 package org.apache.sis.internal.referencing.provider;
 
-import javax.xml.bind.annotation.XmlTransient;
 import org.opengis.parameter.ParameterDescriptor;
-import org.opengis.parameter.ParameterDescriptorGroup;
-import org.apache.sis.metadata.iso.citation.Citations;
-import org.apache.sis.parameter.ParameterBuilder;
 import org.apache.sis.parameter.DefaultParameterDescriptor;
-import org.apache.sis.measure.Latitude;
+import org.apache.sis.parameter.ParameterBuilder;
 import org.apache.sis.measure.MeasurementRange;
-import org.apache.sis.measure.Units;
+import org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.util.Static;
 
 
 /**
- * The provider for <cite>"Stereographic North South"</cite> projection (ESRI).
+ * Constants for projections defined by ESRI but not by EPSG.
  *
- * @author  Rueben Schulz (UBC)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
- * @since   0.6
+ * @version 1.0
+ * @since   1.0
  * @module
  */
-@XmlTransient
-public final class PolarStereographicSouth extends AbstractStereographic {
+final class ESRI extends Static {
     /**
-     * For cross-version compatibility.
+     * The operation parameter descriptor for the <cite>Longitude of natural origin</cite>
(λ₀) parameter value.
+     * Valid values range is [-180 … 180]° and default value is 0°.
      */
-    private static final long serialVersionUID = -6173635411676914083L;
+    static final ParameterDescriptor<Double> CENTRAL_MERIDIAN;
 
     /**
-     * Returns the same parameter than the given one, except that the alias of the ESRI authority
-     * is promoted as the primary name. The old primary name and identifiers (which are usually
the
-     * EPSG ones) are discarded.
-     *
-     * @param  template  the parameter from which to copy the names and identifiers.
-     * @param  builder   an initially clean builder where to add the names.
-     * @return the given {@code builder}, for method call chaining.
+     * The operation parameter descriptor for the <cite>False easting</cite>
(FE) parameter value.
+     * Valid values range is unrestricted and default value is 0 metre.
      */
-    @SuppressWarnings("unchecked")
-    private static ParameterDescriptor<Double> forESRI(final ParameterDescriptor<Double>
template, final ParameterBuilder builder) {
-        return alternativeAuthority(template, Citations.ESRI, builder).createBounded((MeasurementRange<Double>)
-                ((DefaultParameterDescriptor<Double>) template).getValueDomain(), template.getDefaultValue());
-    }
+    static final ParameterDescriptor<Double> FALSE_EASTING;
 
     /**
-     * The group of all parameters expected by this coordinate operation.
+     * The operation parameter descriptor for the <cite>False northing</cite>
(FN) parameter value.
+     * Valid values range is unrestricted and default value is 0 metre.
      */
-    static final ParameterDescriptorGroup PARAMETERS;
+    static final ParameterDescriptor<Double> FALSE_NORTHING;
     static {
-        final ParameterBuilder builder = builder();
-        final ParameterDescriptor<?>[] parameters = {
-            alternativeAuthority(PolarStereographicB.STANDARD_PARALLEL, Citations.ESRI, builder)
-                   .createBounded(Latitude.MIN_VALUE, 0, Latitude.MIN_VALUE, Units.DEGREE),
+        final ParameterBuilder builder = MapProjection.builder();
+        FALSE_EASTING  = asPrimary(Equirectangular.FALSE_EASTING,  builder);
+        FALSE_NORTHING = asPrimary(Equirectangular.FALSE_NORTHING, builder);
 
-            forESRI(PolarStereographicB.LONGITUDE_OF_ORIGIN, builder),
-                    PolarStereographicB.SCALE_FACTOR,                   // Not formally a
parameter of this projection.
-            forESRI(LambertCylindricalEqualArea.FALSE_EASTING, builder),
-            forESRI(LambertCylindricalEqualArea.FALSE_NORTHING, builder)
-        };
+        final ParameterDescriptor<Double> template = Equirectangular.LONGITUDE_OF_ORIGIN;
+        CENTRAL_MERIDIAN = MapProjection.createLongitude(builder
+                .addName(MapProjection.sameNameAs(Citations.ESRI,  template))
+                .addName(MapProjection.sameNameAs(Citations.OGC,   template))
+                .addName(MapProjection.sameNameAs(Citations.PROJ4, template)));
+    }
 
-        PARAMETERS = builder
-                .addName(Citations.ESRI, "Stereographic_South_Pole")
-                .createGroupForMapProjection(parameters);
+    /**
+     * Do not allow instantiation of this class.
+     */
+    private ESRI() {
     }
 
     /**
-     * Constructs a new provider.
+     * Returns the same parameter than the given one, except that the alias of the ESRI authority
+     * is promoted as the primary name. The old primary name and identifiers (which are usually
the
+     * EPSG ones) are discarded.
+     *
+     * @param  template  the parameter from which to copy the names and identifiers.
+     * @param  builder   an initially clean builder where to add the names.
+     * @return the given {@code builder}, for method call chaining.
      */
-    public PolarStereographicSouth() {
-        super(PARAMETERS);
+    @SuppressWarnings("unchecked")
+    static ParameterDescriptor<Double> asPrimary(final ParameterDescriptor<Double>
template, final ParameterBuilder builder) {
+        return MapProjection.alternativeAuthority(template, Citations.ESRI, builder).createBounded((MeasurementRange<Double>)
+                ((DefaultParameterDescriptor<Double>) template).getValueDomain(), template.getDefaultValue());
     }
 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mollweide.java
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mollweide.java
index 72e3571..68861a1 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mollweide.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mollweide.java
@@ -16,87 +16,84 @@
  */
 package org.apache.sis.internal.referencing.provider;
 
-import org.apache.sis.internal.util.Constants;
-import org.apache.sis.parameter.ParameterBuilder;
-import org.apache.sis.parameter.Parameters;
-import org.apache.sis.referencing.operation.projection.NormalizedProjection;
+import javax.xml.bind.annotation.XmlTransient;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterDescriptorGroup;
-import org.opengis.parameter.ParameterNotFoundException;
+import org.apache.sis.parameter.Parameters;
+import org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.referencing.operation.projection.NormalizedProjection;
+
 
 /**
- * The provider for <cite>"Mollweide"</cite> projection.
- * There are no EPSG projection using this operation.
+ * The provider for <cite>"Mollweide"</cite> (also known as <cite>"Homalographic"</cite>)
projection.
+ * As of version 9.4 of EPSG geodetic dataset, there is no known EPSG code for this projection.
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
  *
  * @see <a href="http://mathworld.wolfram.com/MollweideProjection.html">Mathworld formulas</a>
+ * @see <a href="http://geotiff.maptools.org/proj_list/mollweide.html">GeoTIFF parameters
for Mollweide</a>
  *
- * @author Johann Sorel (Geomatys)
- * @version 1.0
  * @since 1.0
  * @module
  */
-public class Mollweide extends MapProjection {
+@XmlTransient
+public final class Mollweide extends MapProjection {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -6434031854504431260L;
 
     /**
-     * The operation parameter descriptor for the {@linkplain
-     * org.apache.sis.internal.util.Constants#centralMeridian
-     * central meridian} parameter value.
-     *
-     * This parameter is <a href="package-summary.html#Obligation">mandatory</a>.
-     * Valid values range is [-180 &hellip; 180]&deg; and default value is 0&deg;.
+     * The operation parameter descriptor for the <cite>Longitude of natural origin</cite>
(λ₀) parameter value.
+     * Valid values range is [-180 … 180]° and default value is 0°.
      */
-    public static final ParameterDescriptor<Double> CENTRAL_MERIDIAN;
+    public static final ParameterDescriptor<Double> CENTRAL_MERIDIAN = ESRI.CENTRAL_MERIDIAN;
 
     /**
-     * The operation parameter descriptor for the {@linkplain
-     * org.apache.sis.internal.util.Constants.Parameters#falseEasting
-     * false easting} parameter value.
-     *
-     * This parameter is <a href="package-summary.html#Obligation">mandatory</a>.
+     * The operation parameter descriptor for the <cite>False easting</cite>
(FE) parameter value.
      * Valid values range is unrestricted and default value is 0 metre.
      */
-    public static final ParameterDescriptor<Double> FALSE_EASTING;
+    public static final ParameterDescriptor<Double> FALSE_EASTING = ESRI.FALSE_EASTING;
 
     /**
-     * The operation parameter descriptor for the {@linkplain
-     * org.apache.sis.internal.util.Constants.Parameters#falseNorthing
-     * false northing} parameter value.
-     *
-     * This parameter is <a href="package-summary.html#Obligation">mandatory</a>.
+     * The operation parameter descriptor for the <cite>False northing</cite>
(FN) parameter value.
      * Valid values range is unrestricted and default value is 0 metre.
      */
-    public static final ParameterDescriptor<Double> FALSE_NORTHING;
+    public static final ParameterDescriptor<Double> FALSE_NORTHING = ESRI.FALSE_NORTHING;
 
     /**
      * The group of all parameters expected by this coordinate operation.
      */
-    static final ParameterDescriptorGroup PARAMETERS;
-    /**
-     * Parameters creation, which must be done before to initialize the {@link #PARAMETERS}
field.
-     * Note that the central Meridian and Latitude of Origin are shared with ObliqueStereographic.
-     */
+    private static final ParameterDescriptorGroup PARAMETERS;
     static {
-        final ParameterBuilder builder = new ParameterBuilder();
-
-        CENTRAL_MERIDIAN = createLongitude(builder.addName(Constants.CENTRAL_MERIDIAN));
-        FALSE_EASTING = createShift(builder.addName(Constants.FALSE_EASTING));
-        FALSE_NORTHING = createShift(builder.addName(Constants.FALSE_NORTHING));
-
-        PARAMETERS = new ParameterBuilder()
+        PARAMETERS = builder().setCodeSpace(Citations.ESRI, "ESRI")
                 .addName("Mollweide")
+                .addName(null, "Homalographic")
+                .addName(null, "Homolographic")
+                .addName(null, "Elliptical")
+                .addName(null, "Babinet")
                 .createGroupForMapProjection(
                         CENTRAL_MERIDIAN,
                         FALSE_EASTING,
                         FALSE_NORTHING);
     }
 
+    /**
+     * Constructs a new provider.
+     */
     public Mollweide() {
         super(PARAMETERS);
     }
 
+    /**
+     * {@inheritDoc}
+     *
+     * @return the map projection created from the given parameter values.
+     */
     @Override
-    protected NormalizedProjection createProjection(Parameters parameters) throws ParameterNotFoundException
{
-        return new org.apache.sis.referencing.operation.projection.Mollweide(this, Parameters.castOrWrap(parameters));
+    protected NormalizedProjection createProjection(final Parameters parameters) {
+        return new org.apache.sis.referencing.operation.projection.Mollweide(this, parameters);
     }
-
 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicSouth.java
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicSouth.java
index 1a36fa5..d06fbbe 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicSouth.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicSouth.java
@@ -21,9 +21,7 @@ import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterDescriptorGroup;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.parameter.ParameterBuilder;
-import org.apache.sis.parameter.DefaultParameterDescriptor;
 import org.apache.sis.measure.Latitude;
-import org.apache.sis.measure.MeasurementRange;
 import org.apache.sis.measure.Units;
 
 
@@ -32,7 +30,7 @@ import org.apache.sis.measure.Units;
  *
  * @author  Rueben Schulz (UBC)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.6
  * @module
  */
@@ -44,34 +42,21 @@ public final class PolarStereographicSouth extends AbstractStereographic
{
     private static final long serialVersionUID = -6173635411676914083L;
 
     /**
-     * Returns the same parameter than the given one, except that the alias of the ESRI authority
-     * is promoted as the primary name. The old primary name and identifiers (which are usually
the
-     * EPSG ones) are discarded.
-     *
-     * @param  template  the parameter from which to copy the names and identifiers.
-     * @param  builder   an initially clean builder where to add the names.
-     * @return the given {@code builder}, for method call chaining.
-     */
-    @SuppressWarnings("unchecked")
-    private static ParameterDescriptor<Double> forESRI(final ParameterDescriptor<Double>
template, final ParameterBuilder builder) {
-        return alternativeAuthority(template, Citations.ESRI, builder).createBounded((MeasurementRange<Double>)
-                ((DefaultParameterDescriptor<Double>) template).getValueDomain(), template.getDefaultValue());
-    }
-
-    /**
      * The group of all parameters expected by this coordinate operation.
      */
     static final ParameterDescriptorGroup PARAMETERS;
     static {
         final ParameterBuilder builder = builder();
-        final ParameterDescriptor<?>[] parameters = {
-            alternativeAuthority(PolarStereographicB.STANDARD_PARALLEL, Citations.ESRI, builder)
-                   .createBounded(Latitude.MIN_VALUE, 0, Latitude.MIN_VALUE, Units.DEGREE),
+        final ParameterDescriptor<Double> standardParallel =
+                alternativeAuthority(PolarStereographicB.STANDARD_PARALLEL, Citations.ESRI,
builder)
+                .createBounded(Latitude.MIN_VALUE, 0, Latitude.MIN_VALUE, Units.DEGREE);
 
-            forESRI(PolarStereographicB.LONGITUDE_OF_ORIGIN, builder),
-                    PolarStereographicB.SCALE_FACTOR,                   // Not formally a
parameter of this projection.
-            forESRI(LambertCylindricalEqualArea.FALSE_EASTING, builder),
-            forESRI(LambertCylindricalEqualArea.FALSE_NORTHING, builder)
+        final ParameterDescriptor<?>[] parameters = {
+            standardParallel,
+            ESRI.asPrimary(PolarStereographicB.LONGITUDE_OF_ORIGIN, builder),
+            PolarStereographicB.SCALE_FACTOR,                       // Not formally a parameter
of this projection.
+            ESRI.FALSE_EASTING,
+            ESRI.FALSE_NORTHING
         };
 
         PARAMETERS = builder
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mollweide.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mollweide.java
index be97193..bf2933a 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mollweide.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mollweide.java
@@ -16,114 +16,139 @@
  */
 package org.apache.sis.referencing.operation.projection;
 
-import static java.lang.Math.*;
-import java.util.HashMap;
-import java.util.Map;
-import org.apache.sis.parameter.Parameters;
-import org.apache.sis.referencing.operation.matrix.MatrixSIS;
-import org.apache.sis.referencing.operation.transform.ContextualParameters;
+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.parameter.Parameters;
+import org.apache.sis.referencing.operation.transform.ContextualParameters;
+import org.apache.sis.referencing.operation.matrix.MatrixSIS;
+import org.apache.sis.internal.referencing.Resources;
+
+import static java.lang.Math.*;
+import static org.apache.sis.math.MathFunctions.SQRT_2;
+import static org.apache.sis.internal.referencing.provider.Mollweide.*;
+
 
 /**
  * <cite>Mollweide</cite> projection.
- * The Mollweide projection do not preserve angles,surfaces
- *
- * TODO : this transform causes issues with large envelopes, we need to have the
- *        information about tranform capability (bijective,subjective,...)
- *        to correctly choose an appropriate common CRS for intersection.
+ * See the <a href="http://mathworld.wolfram.com/MollweideProjection.html">Mollweide
projection on MathWorld</a>
+ * or the <a href="https://en.wikipedia.org/wiki/Mollweide_projection">Mollweide projection
on Wikipedia</a>
+ * for an overview.
  *
- * @see <a href="http://mathworld.wolfram.com/MollweideProjection.html">Mathworld formulas</a>
+ * @todo This projection is not {@link org.apache.sis.math.FunctionProperty#SURJECTIVE surjective}.
+ *       Consequently {@link org.apache.sis.referencing.CRS#suggestCommonTarget CRS.suggestCommonTarget(…)}
+ *       may not work correctly if a CRS uses this projection.
+ *       See <a href="https://issues.apache.org/jira/browse/SIS-427">SIS-427</a>.
  *
- * @author Johann Sorel (Geomatys)
+ * @author  Johann Sorel (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
  * @version 1.0
- * @since 1.0
+ * @since   1.0
  * @module
  */
-public class Mollweide extends NormalizedProjection{
+public class Mollweide extends NormalizedProjection {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 712275000459795291L;
 
-    private static final Map<ParameterRole, ParameterDescriptor<? extends Number>>
ROLES = new HashMap<>();
-    static {
-        ROLES.put(ParameterRole.CENTRAL_MERIDIAN, org.apache.sis.internal.referencing.provider.Mollweide.CENTRAL_MERIDIAN);
-        ROLES.put(ParameterRole.FALSE_EASTING, org.apache.sis.internal.referencing.provider.Mollweide.FALSE_EASTING);
-        ROLES.put(ParameterRole.FALSE_NORTHING, org.apache.sis.internal.referencing.provider.Mollweide.FALSE_NORTHING);
+    /**
+     * 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")
+    private static Initializer initializer(final OperationMethod method, final Parameters
parameters) {
+        final EnumMap<ParameterRole, ParameterDescriptor<Double>> roles = new
EnumMap<>(ParameterRole.class);
+        roles.put(ParameterRole.CENTRAL_MERIDIAN, CENTRAL_MERIDIAN);
+        roles.put(ParameterRole.FALSE_EASTING,    FALSE_EASTING);
+        roles.put(ParameterRole.FALSE_NORTHING,   FALSE_NORTHING);
+        return new Initializer(method, parameters, roles, Initializer.AUTHALIC_RADIUS);
     }
 
-    private static final double SR2 = sqrt(2);
-    private static final double LAMDA_LIMIT = 2*SR2*PI;
-
     /**
-     * Constructs a new map projection from the supplied parameters.
+     * Creates a Mollweide projection from the given parameters.
+     * The {@code method} argument can be the description of one of the following:
      *
-     * @param parameters The parameters of the projection to be created.
+     * <ul>
+     *   <li><cite>"Mollweide"</cite>, also known as
+     *       <cite>"Homalographic"</cite> or <cite>"Homolographic"</cite>.</li>
+     * </ul>
+     *
+     * @param method      description of the projection parameters.
+     * @param parameters  the parameter values of the projection to create.
      */
     public Mollweide(final OperationMethod method, final Parameters parameters) {
-        super(method, parameters, ROLES);
+        super(initializer(method, parameters));
         final MatrixSIS normalize   = context.getMatrix(ContextualParameters.MatrixRole.NORMALIZATION);
         final MatrixSIS denormalize = context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION);
-        normalize.convertBefore(0, 2*SR2, null);
-        denormalize.convertBefore(0, 1/PI, null);
-        denormalize.convertBefore(1, SR2, null);
+        normalize  .convertAfter (0, 2*SQRT_2, null);
+        denormalize.convertBefore(0, 1/PI,     null);
+        denormalize.convertBefore(1, SQRT_2,   null);
     }
 
+    /**
+     * Converts the specified (λ,φ) coordinate and stores the (<var>x</var>,<var>y</var>)
result in {@code dstPts}.
+     * The units of measurement are implementation-specific (see subclass javadoc).
+     *
+     * @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 coordinate can not be converted.
+     */
     @Override
-    public Matrix transform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, boolean
derivate) throws ProjectionException {
-        if (derivate) {
-            //TODO
-            throw new ProjectionException("Derivation not supported");
-        }
-
-        final double λ = srcPts[srcOff]; //longitude
-        final double φ = srcPts[srcOff + 1]; //latitude
-        double primeθ = 2 * asin( (2 * φ) / PI );
-
+    public Matrix transform(final double[] srcPts, final int srcOff,
+                            final double[] dstPts, final int dstOff,
+                            final boolean derivate) throws ProjectionException
+    {
+        final double λ = srcPts[srcOff];
+        final double φ = srcPts[srcOff + 1];
         final double sinφ = sin(φ);
+        double θ = 2 * asin(φ / (PI/2));            // Actually θ′ in Snyder formulas.
         /*
-        if sinφ is 1 or -1 we are on a pole.
-        iteration would produce NaN values.
-        */
+         * If sin(φ) is 1 or -1 we are on a pole.
+         * Iteration would produce NaN values.
+         */
         if (abs(sinφ) != 1) {
-            final double pisinφ = PI * sinφ;
+            final double πsinφ = PI * sinφ;
             int nbIte = MAXIMUM_ITERATIONS;
-            double deltaθ = Double.MAX_VALUE;
+            double Δθ;
             do {
                 if (--nbIte < 0) {
-                    throw new ProjectionException("Operation does not converge");
+                    throw new ProjectionException(Resources.format(Resources.Keys.NoConvergence));
                 }
-                deltaθ = - (primeθ + sin(primeθ) - pisinφ) / (1 + cos(primeθ));
-                primeθ += deltaθ;
-            } while (abs(deltaθ) > ITERATION_TOLERANCE);
+                Δθ = (θ + sin(θ) - πsinφ) / (1 + cos(θ));
+                θ -= Δθ;
+            } while (abs(Δθ) > 2*ITERATION_TOLERANCE);          // *2 because θ′
is twice the desired angle.
         }
-        final double θ = primeθ * 0.5;
-
-        final double x = λ * cos(θ);
-        final double y = sin(θ);
-
-        dstPts[dstOff    ] = x;
-        dstPts[dstOff + 1] = y;
-
+        θ *= 0.5;
+        dstPts[dstOff    ] = cos(θ) * λ;
+        dstPts[dstOff + 1] = sin(θ);
         Matrix matrix = null;
+        if (derivate) {
+            throw new ProjectionException("Derivation not supported");      // TODO
+        }
         return matrix;
     }
 
+    /**
+     * Converts the specified (<var>x</var>,<var>y</var>) coordinates
+     * and stores the result in {@code dstPts} (angles in radians).
+     */
     @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 θ = asin(y);
-
-        final double θθ = 2 * θ;
-        final double φ = asin( (θθ + sin(θθ)) / PI );
+    protected void inverseTransform(final double[] srcPts, final int srcOff,
+                                    final double[] dstPts, final int dstOff)
+    {
+        final double x  = srcPts[srcOff];
+        final double y  = srcPts[srcOff + 1];
+        final double θ  = asin(y);
+        final double tθ = 2 * θ;
+        final double φ  = asin((tθ + sin(tθ)) / PI);
         double λ = x / cos(θ);
-
-        if (abs(λ) > LAMDA_LIMIT) {
+        if (abs(λ) > 2*SQRT_2*PI) {
             λ = Double.NaN;
         }
-
-        dstPts[dstOff] = λ;
+        dstPts[dstOff]   = λ;
         dstPts[dstOff+1] = φ;
     }
-
 }
diff --git a/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod
b/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod
index 09c1df7..c5dff41 100644
--- a/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod
+++ b/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod
@@ -51,8 +51,8 @@ org.apache.sis.internal.referencing.provider.ObliqueMercatorCenter
 org.apache.sis.internal.referencing.provider.ObliqueMercatorTwoPoints
 org.apache.sis.internal.referencing.provider.ObliqueMercatorTwoPointsCenter
 org.apache.sis.internal.referencing.provider.ZonedTransverseMercator
+org.apache.sis.internal.referencing.provider.Mollweide
 org.apache.sis.internal.referencing.provider.NTv2
 org.apache.sis.internal.referencing.provider.NADCON
 org.apache.sis.internal.referencing.provider.FranceGeocentricInterpolation
 org.apache.sis.internal.referencing.provider.Interpolation1D
-org.apache.sis.internal.referencing.provider.Mollweide
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MollweideTest.java
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MollweideTest.java
index 9c7bf32..bab466e 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MollweideTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MollweideTest.java
@@ -16,130 +16,114 @@
  */
 package org.apache.sis.referencing.operation.projection;
 
-import org.apache.sis.internal.system.DefaultFactories;
-import org.apache.sis.internal.util.Constants;
-import org.apache.sis.parameter.Parameters;
-import org.apache.sis.test.DependsOn;
-import org.apache.sis.test.TestCase;
-import static org.junit.Assert.*;
-import org.junit.Test;
-import org.opengis.parameter.ParameterNotFoundException;
-import org.opengis.referencing.operation.MathTransform;
-import org.opengis.referencing.operation.MathTransformFactory;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.util.FactoryException;
+import org.apache.sis.internal.referencing.Formulas;
+import org.apache.sis.test.DependsOnMethod;
+import org.apache.sis.test.DependsOn;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
 
 /**
- * Tests for Mollveide transform.
+ * Tests the {@link Mollweide} projection.
  *
- * @author Johann Sorel (Geomatys)
+ * @author  Johann Sorel (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
  * @version 1.0
  * @since   1.0
  * @module
  */
 @DependsOn(NormalizedProjectionTest.class)
-public class MollweideTest extends TestCase {
-
-    private double tolerance = 0.00001;
-
-    public MollweideTest() {
+public final strictfp class MollweideTest extends MapProjectionTestCase {
+    /**
+     * Creates a new instance of {@link Mollweide} concatenated with the (de)normalization
matrices.
+     * The new instance is stored in the inherited {@link #transform} field.
+     *
+     * @param  ellipse  {@code false} for a sphere, or {@code true} for WGS84 ellipsoid.
+     */
+    private void createProjection(final boolean ellipse) throws FactoryException {
+        createCompleteProjection(new org.apache.sis.internal.referencing.provider.Mollweide(),
+                WGS84_A, ellipse ? WGS84_B : WGS84_A,
+                Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN);
+        tolerance = Formulas.LINEAR_TOLERANCE;  // Not NORMALIZED_TOLERANCE since this is
not a NormalizedProjection.
     }
 
+    /**
+     * 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 TransformException, ParameterNotFoundException, FactoryException
{
-        final org.apache.sis.internal.referencing.provider.Mollweide provider = new org.apache.sis.internal.referencing.provider.Mollweide();
-        final Parameters parameters = Parameters.castOrWrap(provider.getParameters().createValue());
-        parameters.parameter(Constants.CENTRAL_MERIDIAN).setValue(0.0);
-        parameters.parameter(Constants.FALSE_EASTING).setValue(0.0);
-        parameters.parameter(Constants.FALSE_NORTHING).setValue(0.0);
-        parameters.parameter(Constants.SEMI_MAJOR).setValue(6378137);
-        parameters.parameter(Constants.SEMI_MINOR).setValue(6378137);
-
-
-        final MathTransform trs = provider.createMathTransform(DefaultFactories.forClass(MathTransformFactory.class),
parameters);
-        final MathTransform invtrs = trs.inverse();
-
-        final double[] in = new double[2];
-        final double[] out = new double[2];
-
-        // at (0,0) point should be unchanged
-        in[0] = 0.0;
-        in[1] = 0.0;
-        trs.transform(in, 0, out, 0, 1);
-        assertEquals(0.0, out[0], tolerance);
-        assertEquals(0.0, out[1], tolerance);
-
-        // at (0,±90) north/south poles singularity
-        in[0] = 0.0;
-        in[1] = 90;
-        trs.transform(in, 0, out, 0, 1);
-        assertEquals(0.0, out[0], tolerance);
-        assertEquals(9020047.848073645, out[1], tolerance);
-        invtrs.transform(out, 0, in, 0, 1);
-        assertEquals(0.0, in[0], tolerance);
-        assertEquals(90.0, in[1], tolerance);
-
-        in[0] = 0.0;
-        in[1] = -90;
-        trs.transform(in, 0, out, 0, 1);
-        assertEquals(0.0, out[0], tolerance);
-        assertEquals(-9020047.848073645, out[1], tolerance);
-        invtrs.transform(out, 0, in, 0, 1);
-        assertEquals(0.0, in[0], tolerance);
-        assertEquals(-90.0, in[1], tolerance);
-
-        // at (0,~90) point near north pole singularity should be close to ~9.000.000
-        in[0] = 0.0;
-        in[1] = 89;
-        trs.transform(in, 0, out, 0, 1);
-        assertEquals(0.0, out[0], tolerance);
-        assertEquals(8997266.89915323, out[1], tolerance);
-        invtrs.transform(out, 0, in, 0, 1);
-        assertEquals(0.0, in[0], tolerance);
-        assertEquals(89.0, in[1], tolerance);
-
-        //other random points
-        //compared to epsg.io (Bad reference, find something more trustable)
-        //https://epsg.io/transform#s_srs=4326&t_srs=54009&x=-150.0000000&y=-70.0000000
-        in[0] = 12.0;
-        in[1] = 50.0;
-        trs.transform(in, 0, out, 0, 1);
-        assertEquals(912759.82345261, out[0], tolerance);
-        assertEquals(5873471.95621065, out[1], tolerance);
-        invtrs.transform(out, 0, in, 0, 1);
-        assertEquals(12.0, in[0], tolerance);
-        assertEquals(50.0, in[1], tolerance);
-
-
-        in[0] = -150.0;
-        in[1] = -70.0;
-        trs.transform(in, 0, out, 0, 1);
-        assertEquals(-7622861.35718471, out[0], tolerance);
-        assertEquals(-7774469.60789149, out[1], tolerance);
-        invtrs.transform(out, 0, in, 0, 1);
-        assertEquals(-150.0, in[0], tolerance);
-        assertEquals(-70.0, in[1], tolerance);
-
-        in[0] = -179.9999;
-        in[1] = 0.0;
-        trs.transform(in, 0, out, 0, 1);
-        assertEquals(-18040085.67387191, out[0], tolerance);
-        assertEquals(0.0, out[1], tolerance);
-        invtrs.transform(out, 0, in, 0, 1);
-        assertEquals(-179.9999, in[0], tolerance);
-        assertEquals(0.0, in[1], tolerance);
-
-        //outside of validity area, should have NaN with the reverse transform
-        in[0] = -180.0001;
-        in[1] = 0.0;
-        trs.transform(in, 0, out, 0, 1);
-        assertEquals(-1.8040105718422677E7, out[0], tolerance);
-        assertEquals(0.0, out[1], tolerance);
-        invtrs.transform(out, 0, in, 0, 1);
-        assertEquals(Double.NaN, in[0], tolerance);
-        assertEquals(0.0, in[1], tolerance);
-
+    public void testTransform() throws FactoryException, TransformException {
+        createProjection(false);
+        assertTrue(isInverseTransformSupported);
+        verifyTransform(
+            new double[] {          // (λ,φ) coordinates in degrees to project.
+                  0,      0,        // At (0,0) point should be unchanged.
+                  0,    +90,        // At (0,±90) north/south poles singularity.
+                  0,    -90,
+                  0,     89,        // At (0,~90) point near north pole singularity should
be close to ~9000000.
+                 12,     50,        // Other random points.
+               -150,    -70,
+               -179.9999, 0
+            },
+            new double[] {          // Expected (x,y) results in metres.
+                       0.0,           0.0,
+                       0.0,     9020047.848,
+                       0.0,    -9020047.848,
+                       0.0,     8997266.899,
+                  912759.823,   5873471.956,
+                -7622861.357,  -7774469.608,
+               -18040085.674,         0.0
+            });
+    }
 
+    /**
+     * Tests the projection of a few points on a sphere computed from authalic radius.
+     *
+     * @throws FactoryException if an error occurred while creating the map projection.
+     * @throws TransformException if an error occurred while projecting a point.
+     */
+    @Test
+    @DependsOnMethod("testTransform")
+    public void testOnAuthalicRadius() throws FactoryException, TransformException {
+        createProjection(true);
+        assertTrue(isInverseTransformSupported);
+        verifyTransform(
+            new double[] {          // (λ,φ) coordinates in degrees to project.
+                 12,     50,        // Other random points.
+               -150,    -70,
+               -179.9999, 0
+            },
+            new double[] {          // Expected (x,y) results in metres.
+                  911739.492,    5866906.278,
+                -7614340.119,   -7765778.894,
+               -18019919.511,          0.0
+            });
     }
 
+    /**
+     * Tests inverse projection of a point outside domain of validity.
+     *
+     * @throws FactoryException if an error occurred while creating the map projection.
+     * @throws TransformException if an error occurred while projecting a point.
+     */
+    @Test
+    @DependsOnMethod("testTransform")
+    public void testTransformOutsideDomain() throws FactoryException, TransformException
{
+        createProjection(false);
+        final double[] in  = new double[] {-180.0001,     0.0};
+        final double[] out = new double[] {-18040105.718, 0.0};
+        isInverseTransformSupported = false;
+        verifyTransform(in, out);
+
+        // Outside of validity area, should have NaN with the inverse transform.
+        transform = transform.inverse();
+        tolerance = 0;
+        in[0] = Double.NaN;
+        verifyTransform(out, in);
+    }
 }


Mime
View raw message