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 a65df89e7f Add Robinson projection. 
https://issues.apache.org/jira/browse/SIS-599
a65df89e7f is described below

commit a65df89e7f226c5bf21479cb4a072f4386f66dca
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Thu May 2 19:15:20 2024 +0200

    Add Robinson projection.
    https://issues.apache.org/jira/browse/SIS-599
---
 ...g.opengis.referencing.operation.OperationMethod |   1 +
 .../main/module-info.java                          |   1 +
 .../operation/projection/Initializer.java          |   7 +-
 .../operation/projection/Mollweide.java            |  11 -
 .../operation/projection/ProjectionVariant.java    |  19 +-
 .../referencing/operation/projection/Robinson.java | 264 +++++++++++++++++++++
 .../operation/projection/Sinusoidal.java           |   5 -
 .../referencing/operation/provider/Mollweide.java  |   2 +-
 .../provider/{Mollweide.java => Robinson.java}     |  32 ++-
 .../operation/transform/ContextualParameters.java  |  17 +-
 .../operation/projection/RobinsonTest.java         | 118 +++++++++
 .../operation/provider/ProvidersTest.java          |   1 +
 12 files changed, 434 insertions(+), 44 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.referencing/main/META-INF/services/org.opengis.referencing.operation.OperationMethod
 
b/endorsed/src/org.apache.sis.referencing/main/META-INF/services/org.opengis.referencing.operation.OperationMethod
index 9609787f4e..ae50990dda 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/META-INF/services/org.opengis.referencing.operation.OperationMethod
+++ 
b/endorsed/src/org.apache.sis.referencing/main/META-INF/services/org.opengis.referencing.operation.OperationMethod
@@ -66,6 +66,7 @@ org.apache.sis.referencing.operation.provider.Sinusoidal
 org.apache.sis.referencing.operation.provider.PseudoSinusoidal
 org.apache.sis.referencing.operation.provider.Polyconic
 org.apache.sis.referencing.operation.provider.Mollweide
+org.apache.sis.referencing.operation.provider.Robinson
 org.apache.sis.referencing.operation.provider.SouthPoleRotation
 org.apache.sis.referencing.operation.provider.NorthPoleRotation
 org.apache.sis.referencing.operation.provider.NTv2
diff --git a/endorsed/src/org.apache.sis.referencing/main/module-info.java 
b/endorsed/src/org.apache.sis.referencing/main/module-info.java
index d79c190aac..c19d16bee7 100644
--- a/endorsed/src/org.apache.sis.referencing/main/module-info.java
+++ b/endorsed/src/org.apache.sis.referencing/main/module-info.java
@@ -133,6 +133,7 @@ module org.apache.sis.referencing {
              org.apache.sis.referencing.operation.provider.PseudoSinusoidal,
              org.apache.sis.referencing.operation.provider.Polyconic,
              org.apache.sis.referencing.operation.provider.Mollweide,
+             org.apache.sis.referencing.operation.provider.Robinson,
              org.apache.sis.referencing.operation.provider.SouthPoleRotation,
              org.apache.sis.referencing.operation.provider.NorthPoleRotation,
              org.apache.sis.referencing.operation.provider.NTv2,
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Initializer.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Initializer.java
index 2492127180..6b4535f28c 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Initializer.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Initializer.java
@@ -195,7 +195,12 @@ final class Initializer {
          * Set meridian rotation, scale factor, false easting and false 
northing parameter values
          * in the (de)normalization matrices.
          */
-        context.normalizeGeographicInputs(λ0);
+        if (variant == null || variant.useRadians()) {
+            context.normalizeGeographicInputs(λ0);
+        } else if (λ0 != 0) {
+            context.getMatrix(ContextualParameters.MatrixRole.NORMALIZATION)
+                    .convertBefore(0, null, DoubleDouble.of(-λ0, true));
+        }
         final MatrixSIS denormalize = 
context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION);
         denormalize.convertAfter(0, k, DoubleDouble.of(fe, true));
         denormalize.convertAfter(1, k, DoubleDouble.of(fn, true));
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Mollweide.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Mollweide.java
index 16de7b69f7..845db2afba 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Mollweide.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Mollweide.java
@@ -17,7 +17,6 @@
 package org.apache.sis.referencing.operation.projection;
 
 import java.util.EnumMap;
-import java.util.regex.Pattern;
 import static java.lang.Math.*;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.referencing.operation.Matrix;
@@ -64,16 +63,6 @@ public class Mollweide extends NormalizedProjection {
         /** The spherical case. */
         SPHERICAL;
 
-        /** The expected name pattern of an operation method for this variant. 
*/
-        @Override public Pattern getOperationNamePattern() {
-            return null;
-        }
-
-        /** EPSG identifier of an operation method for this variant. */
-        @Override public String getIdentifier() {
-            return null;
-        }
-
         /** Requests the use of authalic radius. */
         @Override public boolean useAuthalicRadius() {
             return true;
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/ProjectionVariant.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/ProjectionVariant.java
index 76be73266b..61474e2059 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/ProjectionVariant.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/ProjectionVariant.java
@@ -32,7 +32,9 @@ interface ProjectionVariant {
      *
      * @return the operation name pattern for this variant, or {@code null} if 
none.
      */
-    Pattern getOperationNamePattern();
+    default Pattern getOperationNamePattern() {
+        return null;
+    }
 
     /**
      * Returns the EPSG identifier to compare against the operation method.
@@ -40,7 +42,9 @@ interface ProjectionVariant {
      *
      * @return EPSG identifier for this variant, or {@code null} if none.
      */
-    String getIdentifier();
+    default String getIdentifier() {
+        return null;
+    }
 
     /**
      * Whether this variant is a spherical variant using authalic radius.
@@ -58,4 +62,15 @@ interface ProjectionVariant {
     default boolean useAuthalicRadius() {
         return false;
     }
+
+    /**
+     * Whether this variant uses longitude and latitude values in radians.
+     * This is the case of almost all map projections.
+     * A value of {@code false} will cause the map projection to work in 
degrees instead.
+     *
+     * @return whether this variant uses longitude and latitude values in 
radians.
+     */
+    default boolean useRadians() {
+        return true;
+    }
 }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Robinson.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Robinson.java
new file mode 100644
index 0000000000..4d51ebd276
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Robinson.java
@@ -0,0 +1,264 @@
+/*
+ * 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 static java.lang.Math.*;
+import org.opengis.parameter.ParameterDescriptor;
+import org.opengis.referencing.operation.Matrix;
+import org.opengis.referencing.operation.OperationMethod;
+import org.apache.sis.parameter.Parameters;
+import org.apache.sis.math.Fraction;
+import org.apache.sis.util.Workaround;
+import org.apache.sis.util.privy.DoubleDouble;
+import org.apache.sis.referencing.internal.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 static org.apache.sis.referencing.operation.provider.Robinson.*;
+
+
+/**
+ * <cite>Robinson</cite> projection.
+ * This projection is different than other projections in that it computes
+ * by interpolations of tabulated values instead of analytic function.
+ * The table is indexed by latitude at a constant interval.
+ *
+ * <p>While the current implementation supports only the Robinson projection,
+ * it could be generalized to any projection using interpolations in a similar 
way.
+ * For example, the "Natural Earth" projection was initially defined by 
interpolations.
+ * See {@link Variant} for a note about how to generalize.</p>
+ *
+ * <h2>References</h2>
+ * <p>Snyder, J. P. (1990). <u>The Robinson projection: A computation 
algorithm.</u>
+ * Cartography and Geographic Information Systems, 17 (4), p. 301-305.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ *
+ * @see <a href="https://en.wikipedia.org/wiki/Robinson_projection";>Robinson 
projection on Wikipedia</a>
+ */
+public class Robinson extends NormalizedProjection {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -2998244461334786203L;
+
+    /**
+     * The projection variants supported by the enclosing class.
+     *
+     * <h2>Future evolution</h2>
+     * If there is a need to support other interpolated projection than 
Robinson, then the {@link #TABLE},
+     * {@link #LAST_INDEX} and {@link #LATITUDE_INCREMENT} constants should 
move in this enumeration.
+     */
+    private enum Variant implements ProjectionVariant {
+        /** The Robinson projection. */
+        ROBINSON;
+
+        /** Requests the use of authalic radius. */
+        @Override public boolean useAuthalicRadius() {
+            return true;
+        }
+
+        /** Requests the use of degrees. */
+        @Override public boolean useRadians() {
+            return false;
+        }
+    }
+
+    /**
+     * 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, Variant.ROBINSON);
+    }
+
+    /**
+     * Increment in degrees between two rows of the interpolation table.
+     *
+     * @see #TABLE
+     */
+    private static final int LATITUDE_INCREMENT = 5;
+
+    /**
+     * Conversion factor from latitude in degrees to index in the 
interpolation table.
+     */
+    private static final Fraction TO_INDEX = new Fraction(1, 
LATITUDE_INCREMENT);
+
+    /**
+     * Multiplication factors of <var>x</var> and <var>y</var> axes after 
projection.
+     */
+    private static final Fraction XF = new Fraction( 8487, 10000),      // 
0.8487
+                                  YF = new Fraction(13523, 10000);      // 
1.3523
+
+    /**
+     * Creates a Robinson projection from the given parameters.
+     *
+     * @param method      description of the projection parameters.
+     * @param parameters  the parameter values of the projection to create.
+     */
+    public Robinson(final OperationMethod method, final Parameters parameters) 
{
+        super(initializer(method, parameters), null);
+        final MatrixSIS normalize   = 
context.getMatrix(ContextualParameters.MatrixRole.NORMALIZATION);
+        final MatrixSIS denormalize = 
context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION);
+        normalize  .convertAfter (0, DoubleDouble.DEGREES_TO_RADIANS, null);
+        normalize  .convertAfter (1, TO_INDEX, null);
+        denormalize.convertBefore(0, XF, null);
+        denormalize.convertBefore(1, YF, null);
+    }
+
+    /**
+     * Interpolation table from 5°S to 90°N inclusive with a step of 5° of 
latitude.
+     * The first column (XLR) is the ratio of the length of the parallel to 
the length of the equator.
+     * The second column (PR) is proportional to the distance of from the 
equator to the parallel
+     * divided by the equator length.
+     */
+    private static final double[] TABLE = {
+        // XLR   PR
+        0.9986, -0.0620,  // -5°
+        1.0000,  0.0000,  //  0°
+        0.9986,  0.0620,  //  5°
+        0.9954,  0.1240,  // 10°
+        0.9900,  0.1860,  // 15°
+        0.9822,  0.2480,  // 20°
+        0.9730,  0.3100,  // 25°
+        0.9600,  0.3720,  // 30°
+        0.9427,  0.4340,  // 35°
+        0.9216,  0.4958,  // 40°
+        0.8962,  0.5571,  // 45°
+        0.8679,  0.6176,  // 50°
+        0.8350,  0.6769,  // 55°
+        0.7986,  0.7346,  // 60°
+        0.7597,  0.7903,  // 65°
+        0.7186,  0.8435,  // 70°
+        0.6732,  0.8936,  // 75°
+        0.6213,  0.9394,  // 80°
+        0.5722,  0.9761,  // 85°
+        0.5322,  1.0000   // 90°
+    };
+
+    /**
+     * Highest valid value for the index of the latitude.
+     * Assertion: {@code (LAST_INDEX << 1) + 5 == TABLE.length - 1}.
+     */
+    private static final int LAST_INDEX = 17;
+
+    /**
+     * Projects the specified (Λ,φ) coordinates and stores the 
(<var>x</var>,<var>y</var>) result in {@code dstPts}.
+     * The units of measurement are implementation-specific (see super-class 
javadoc).
+     * The results must be multiplied by the denormalization matrix before to 
get linear distances.
+     *
+     * @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 cannot be converted.
+     */
+    @Override
+    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 φm = srcPts[srcOff+1];     // In multiple of 
`LATITUDE_INCREMENT`.
+        final double φa = abs(φm);
+        int i = Math.min((int) φa, LAST_INDEX);
+        final double p  = φa - i;
+        double tb, t0, t1;
+
+        tb = TABLE[i <<= 1];    // Value of XLR for the range of latitudes 
before current range.
+        t0 = TABLE[i+2];        // Value of XLR for the lower bound of current 
range of latitudes.
+        t1 = TABLE[i+4];        // Value of XLR for the upper bound of current 
range of latitudes.
+        final double xp1 = t1 - tb;
+        final double xp2 = p*(t1 - 2*t0 + tb);
+        final double xr  = t0 + p*(xp1 + xp2)/2;
+
+        tb = TABLE[i+1];        // Value of PR for the range of latitudes 
before current range.
+        t0 = TABLE[i+3];        // Value of PR for the lower bound of current 
range of latitudes.
+        t1 = TABLE[i+5];        // Value of PR for the upper bound of current 
range of latitudes.
+        final double yp1 = t1 - tb;
+        final double yp2 = p*(t1 - 2*t0 + tb);
+        final double ya  = t0 + p*(yp1 + yp2)/2;
+
+        if (dstPts != null) {
+            dstPts[dstOff  ] = xr * λ;
+            dstPts[dstOff+1] = copySign(ya, φm);
+        }
+        if (!derivate) return null;
+        return new Matrix2(xr, (xp1/2 + xp2)*abs(λ),
+                            0, (yp1/2 + yp2));
+    }
+
+    /**
+     * Converts the specified (<var>x</var>,<var>y</var>) coordinates
+     * and stores the result in {@code dstPts} (angles in radians).
+     */
+    @Override
+    protected void inverseTransform(final double[] srcPts, final int srcOff,
+                                    final double[] dstPts, final int dstOff)
+            throws ProjectionException
+    {
+        final double x  = srcPts[srcOff  ];
+        final double y  = srcPts[srcOff+1];
+        final double ya = abs(y);
+        int i = Math.min((int) (ya*(90/LATITUDE_INCREMENT)), LAST_INDEX);    
// First estimation.
+        double p;
+        do {
+            int ti = (i << 1) | 1;
+            double ym = TABLE[ti  ];
+            double y0 = TABLE[ti+2];
+            double y1 = TABLE[ti+4];
+            double u  = y1 - ym;
+            double t  = 2*(ya - y0) / u;
+            double c  = t*(y1 - 2*y0 + ym) / u;
+            p = t*(1 - c*(1 - 2*c));
+        } while (p < 0 && --i >= 0);      // Recompute if the first estimation 
was too high.
+        i = max(i, 0);
+        /*
+         * Above loop computed an estimated position for the latitude band 
that contains φ.
+         * Refine the result for the actual latitude value (not only the band 
containing it).
+         */
+        int nbIter = MAXIMUM_ITERATIONS;
+        double φm = p + i;                                  // In multiple of 
`LATITUDE_INCREMENT`.
+        do {
+            i <<= 1;
+            double tb, t0, t1;
+            tb = TABLE[i+1];
+            t0 = TABLE[i+3];
+            t1 = TABLE[i+5];
+            final double yp1 = t1 - tb;                     // Same formulas 
as the forward case.
+            final double yp2 = p*(t1 - 2*t0 + tb);
+            final double err = t0 + p*(yp1 + yp2)/2 - ya;   // Difference 
between interpolated y and desired y.
+            final double dy  = (yp1/2 + yp2);               // Derivative 
∂y/∂φ (last term in the Jacobian matrix).
+            φm -= err / dy;                                 // Convert the 
error in y to an error in φ.
+            if (!(abs(err) > ITERATION_TOLERANCE)) {        // Use `!` for 
accepting NaN.
+                tb = TABLE[i+0];
+                t0 = TABLE[i+2];
+                t1 = TABLE[i+4];
+                dstPts[dstOff] = x / (t0 + p*(t1 - tb + p*(t1 - 2*t0 + tb))/2);
+                dstPts[dstOff+1] = copySign(φm, y);
+                return;
+            }
+            i = Math.min((int) φm, LAST_INDEX);
+            p = φm - i;
+        } while (--nbIter >= 0);
+        throw new 
ProjectionException(Resources.format(Resources.Keys.NoConvergence));
+    }
+}
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Sinusoidal.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Sinusoidal.java
index 00123d060a..7dccde4ed5 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Sinusoidal.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Sinusoidal.java
@@ -73,11 +73,6 @@ public class Sinusoidal extends MeridianArcBased {
         @Override public Pattern getOperationNamePattern() {
             return operationName;
         }
-
-        /** EPSG identifier of an operation method for this variant. */
-        @Override public String getIdentifier() {
-            return null;
-        }
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Mollweide.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Mollweide.java
index 742899dc80..9214a12e32 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Mollweide.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Mollweide.java
@@ -42,7 +42,7 @@ public final class Mollweide extends MapProjection {
     private static final long serialVersionUID = -6434031854504431260L;
 
     /**
-     * The operation parameter descriptor for the <cite>Longitude of natural 
origin</cite> (λ₀) parameter value.
+     * The operation parameter descriptor for the <cite>Longitude of 
projection centre</cite> (λ₀) parameter value.
      * Valid values range is [-180 … 180]° and default value is 0°.
      *
      * <!-- Generated by ParameterNameTableGenerator -->
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Mollweide.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Robinson.java
similarity index 81%
copy from 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Mollweide.java
copy to 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Robinson.java
index 742899dc80..94c694f99d 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Mollweide.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Robinson.java
@@ -20,29 +20,27 @@ import jakarta.xml.bind.annotation.XmlTransient;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterDescriptorGroup;
 import org.apache.sis.parameter.Parameters;
+import org.apache.sis.util.privy.Constants;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.referencing.operation.projection.NormalizedProjection;
 
 
 /**
- * The provider for <q>Mollweide</q> (also known as <q>Homalographic</q>) 
projection.
- * As of version 9.4 of EPSG geodetic dataset, there is no known EPSG code for 
this projection.
+ * The provider for <q>Robinson</q> projection.
  *
- * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @see <a 
href="https://mathworld.wolfram.com/MollweideProjection.html";>Mathworld 
formulas</a>
- * @see <a href="https://gdal.org/proj_list/mollweide.html";>GeoTIFF parameters 
for Mollweide</a>
+ * @see <a href="https://gdal.org/proj_list/robinson.html";>GeoTIFF parameters 
for Robinson</a>
  */
 @XmlTransient
-public final class Mollweide extends MapProjection {
+public final class Robinson extends MapProjection {
     /**
      * For cross-version compatibility.
      */
-    private static final long serialVersionUID = -6434031854504431260L;
+    private static final long serialVersionUID = -4027297503846506501L;
 
     /**
-     * The operation parameter descriptor for the <cite>Longitude of natural 
origin</cite> (λ₀) parameter value.
+     * The operation parameter descriptor for the <cite>Longitude of 
projection centre</cite> (λ₀) parameter value.
      * Valid values range is [-180 … 180]° and default value is 0°.
      *
      * <!-- Generated by ParameterNameTableGenerator -->
@@ -92,12 +90,12 @@ public final class Mollweide extends MapProjection {
      */
     private static final ParameterDescriptorGroup PARAMETERS;
     static {
-        PARAMETERS = builder().setCodeSpace(Citations.ESRI, "ESRI")
-                .addName("Mollweide")
-                .addName(null, "Homalographic")
-                .addName(null, "Homolographic")
-                .addName(null, "Elliptical")
-                .addName(null, "Babinet")
+        PARAMETERS = builder().setCodeSpace(Citations.OGC, Constants.OGC)
+                .addName(                   "Robinson")
+                .addName(Citations.ESRI,    "Robinson")
+                .addName(Citations.GEOTIFF, "CT_Robinson")
+                .addName(Citations.PROJ4,   "robin")
+                .addIdentifier(Citations.GEOTIFF, "23")
                 .createGroupForMapProjection(
                         CENTRAL_MERIDIAN,
                         FALSE_EASTING,
@@ -107,17 +105,17 @@ public final class Mollweide extends MapProjection {
     /**
      * Constructs a new provider.
      */
-    public Mollweide() {
+    public Robinson() {
         super(PARAMETERS);
     }
 
     /**
-     * {@inheritDoc}
+     * Creates a map projection on an ellipsoid having a semi-major axis 
length of 1.
      *
      * @return the map projection created from the given parameter values.
      */
     @Override
     protected NormalizedProjection createProjection(final Parameters 
parameters) {
-        return new 
org.apache.sis.referencing.operation.projection.Mollweide(this, parameters);
+        return new 
org.apache.sis.referencing.operation.projection.Robinson(this, parameters);
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/ContextualParameters.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/ContextualParameters.java
index 2e29db0d64..ff10c7f010 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/ContextualParameters.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/ContextualParameters.java
@@ -282,15 +282,15 @@ public class ContextualParameters extends Parameters 
implements Serializable {
             values = forward.values;
         } else {
             final List<GeneralParameterDescriptor> descriptors = 
desc.descriptors();
-            final ParameterValue<?>[] values = new 
ParameterValue<?>[descriptors.size()];
+            final var copy = new ParameterValue<?>[descriptors.size()];
             int count = 0;
-            for (int i=0; i < values.length; i++) {
-                final ContextualParameter<?> p = new 
ContextualParameter<>((ParameterDescriptor<?>) descriptors.get(i));
+            for (int i=0; i < copy.length; i++) {
+                final var p = new 
ContextualParameter<>((ParameterDescriptor<?>) descriptors.get(i));
                 if (mapper.test(forward, p)) {
-                    values[count++] = p;
+                    copy[count++] = p;
                 }
             }
-            this.values = ArraysExt.resize(values, count);
+            values = ArraysExt.resize(copy, count);
         }
         isFrozen = true;
     }
@@ -402,8 +402,9 @@ public class ContextualParameters extends Parameters 
implements Serializable {
      * @since 0.7
      */
     public final MatrixSIS getMatrix(MatrixRole role) {
-        final Matrix fallback;
+        @SuppressWarnings("LocalVariableHidesMemberVariable")
         final ContextualParameters inverse;
+        final Matrix fallback;
         synchronized (this) {
             switch (role) {
                 default:                      throw new AssertionError(role);
@@ -455,6 +456,7 @@ public class ContextualParameters extends Parameters 
implements Serializable {
         if (λ0 != 0) {
             offset = DoubleDouble.of(-λ0, true).multiply(toRadians);
         }
+        @SuppressWarnings("LocalVariableHidesMemberVariable")
         final MatrixSIS normalize = (MatrixSIS) this.normalize;         // 
Must be the same instance, not a copy.
         normalize.convertBefore(0, toRadians, offset);
         normalize.convertBefore(1, toRadians, null);
@@ -479,8 +481,9 @@ public class ContextualParameters extends Parameters 
implements Serializable {
      */
     public synchronized MatrixSIS denormalizeGeographicOutputs(final double 
λ0) {
         ensureModifiable();
+        @SuppressWarnings("LocalVariableHidesMemberVariable")
+        final var denormalize = (MatrixSIS) this.denormalize;           // 
Must be the same instance, not a copy.
         final DoubleDouble toDegrees = DoubleDouble.RADIANS_TO_DEGREES;
-        final MatrixSIS denormalize = (MatrixSIS) this.denormalize;         // 
Must be the same instance, not a copy.
         denormalize.convertAfter(0, toDegrees, (λ0 != 0) ? λ0 : null);
         denormalize.convertAfter(1, toDegrees, null);
         return denormalize;
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/RobinsonTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/RobinsonTest.java
new file mode 100644
index 0000000000..f0a84c4592
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/RobinsonTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.parameter.Parameters;
+import org.apache.sis.geometry.DirectPosition2D;
+import org.apache.sis.referencing.privy.Formulas;
+import org.apache.sis.referencing.operation.provider.MapProjection;
+
+// Test dependencies
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+
+/**
+ * Tests the {@link Robinson} projection.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+public final class RobinsonTest extends MapProjectionTestCase {
+    /**
+     * Creates a new test case.
+     */
+    public RobinsonTest() {
+        final double delta = (100.0 / 60) / 1852;      // Approximately 100 
metres.
+        derivativeDeltas = new double[] {delta, delta};
+    }
+
+    /**
+     * Returns the provider for the "Robinson" projection.
+     */
+    private static MapProjection provider() {
+        return new org.apache.sis.referencing.operation.provider.Robinson();
+    }
+
+    /**
+     * Tests the first point given in Snyder example which does not involve 
interpolation.
+     * Tests also a few simple points at equator and on pole.
+     *
+     * @throws FactoryException if an error occurred while creating the map 
projection.
+     * @throws TransformException if an error occurred while projecting a 
coordinate.
+     */
+    @Test
+    public void testSimplePoints() throws FactoryException, TransformException 
{
+        final MapProjection provider = provider();
+        final Parameters pg = 
Parameters.castOrWrap(provider.getParameters().createValue());
+        pg.parameter("semi-major").setValue(1);
+        pg.parameter("semi-minor").setValue(1);
+        transform = provider.createMathTransform(null, pg);
+        tolerance = Formulas.ANGULAR_TOLERANCE;
+
+        final double[] expected = {
+            0.740630, 0,            // 0°N.
+            0.711005, 0.503055,     // 30°N, result from Snyder.
+            0.591467, 0.993400,     // 60°N.
+            0.394164, 1.352300,     // 90°N.
+        };
+        for (int i=0; i <= 3; i++) {
+            double λ = 50;
+            double φ = i * 30;
+            final var p = new DirectPosition2D(λ, φ);
+            assertSame(p, transform.transform(p, p));
+            assertEquals(expected[i*2    ], p.x, 0.000001);
+            assertEquals(expected[i*2 + 1], p.y, 0.000001);
+
+            assertSame(p, transform.inverse().transform(p, p));
+            assertEquals(λ, p.x, Formulas.ANGULAR_TOLERANCE);
+            assertEquals(φ, p.y, Formulas.ANGULAR_TOLERANCE);
+
+            verifyDerivative(λ, φ);
+        }
+    }
+
+    /**
+     * Tests the second point given in Snyder example which involves 
interpolation.
+     *
+     * @throws FactoryException if an error occurred while creating the map 
projection.
+     * @throws TransformException if an error occurred while projecting a 
coordinate.
+     */
+    @Test
+    public void testInterpolation() throws FactoryException, 
TransformException {
+        final MapProjection provider = provider();
+        final Parameters pg = 
Parameters.castOrWrap(provider.getParameters().createValue());
+        pg.parameter("semi-major").setValue(6370997);
+        pg.parameter("semi-minor").setValue(6370997);
+        transform = provider.createMathTransform(null, pg);
+        tolerance = Formulas.LINEAR_TOLERANCE;
+
+        final double λ = -102;
+        final double φ = -47;
+        final var p = new DirectPosition2D(λ, φ);
+        assertSame(p, transform.transform(p, p));
+        assertEquals(-8521076, p.x, 5);
+        assertEquals(-5009012, p.y, 5);
+
+        assertSame(p, transform.inverse().transform(p, p));
+        assertEquals(λ, p.x, Formulas.ANGULAR_TOLERANCE);
+        assertEquals(φ, p.y, Formulas.ANGULAR_TOLERANCE);
+
+        verifyDerivative(λ, φ);
+    }
+}
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ProvidersTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ProvidersTest.java
index 72e3e25d60..54746ba1ed 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ProvidersTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ProvidersTest.java
@@ -113,6 +113,7 @@ public final class ProvidersTest extends TestCase {
             PseudoSinusoidal.class,
             Polyconic.class,
             Mollweide.class,
+            Robinson.class,
             SouthPoleRotation.class,
             NorthPoleRotation.class,
             NTv2.class,


Reply via email to