sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1719607 [1/2] - in /sis/branches/JDK8/core/sis-referencing/src: main/java/org/apache/sis/internal/referencing/provider/ main/java/org/apache/sis/referencing/datum/ main/java/org/apache/sis/referencing/operation/matrix/ main/java/org/apache...
Date Sat, 12 Dec 2015 00:51:26 GMT
Author: desruisseaux
Date: Sat Dec 12 00:51:25 2015
New Revision: 1719607

URL: http://svn.apache.org/viewvc?rev=1719607&view=rev
Log:
Rename InterpolatedGeocentricTransform as InterpolatedMolodenskyTransform,
and rewrite InterpolatedGeocentricTransform on top of real geographic/geocentric conversions.

Added:
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MolodenskyInterpolation.java   (with props)
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DatumShiftTransform.java
      - copied, changed from r1719489, sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MolodenskyFormula.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedMolodenskyTransform.java
      - copied, changed from r1719083, sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransform.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedMolodenskyTransform2D.java
      - copied, changed from r1719083, sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransform2D.java
    sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/InterpolatedMolodenskyTransformTest.java
      - copied, changed from r1719083, sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransformTest.java
Modified:
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolation.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DatumShiftGrid.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/MatrixSIS.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransform.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransform2D.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MolodenskyFormula.java
    sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransformTest.java
    sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolation.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolation.java?rev=1719607&r1=1719606&r2=1719607&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolation.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolation.java [UTF-8] Sat Dec 12 00:51:25 2015
@@ -92,7 +92,7 @@ import java.nio.file.Files;
  * @module
  */
 @XmlTransient
-public final class FranceGeocentricInterpolation extends AbstractProvider {
+public class FranceGeocentricInterpolation extends AbstractProvider {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -177,33 +177,37 @@ public final class FranceGeocentricInter
      *   <li>Bit 1: dimension of source coordinates (0 for 2D, 1 for 3D).</li>
      *   <li>Bit 0: dimension of target coordinates (0 for 2D, 1 for 3D).</li>
      * </ul>
+     *
+     * This array is initialized at construction time and shall not be modified after.
      */
-    private final FranceGeocentricInterpolation[] redimensioned;
+    final FranceGeocentricInterpolation[] redimensioned;
 
     /**
      * Constructs a provider.
      */
     @SuppressWarnings("ThisEscapedInObjectConstruction")
     public FranceGeocentricInterpolation() {
-        super(2, 2, PARAMETERS);
-        redimensioned = new FranceGeocentricInterpolation[4];
+        this(2, 2, PARAMETERS, new FranceGeocentricInterpolation[4]);
         redimensioned[0] = this;
-        redimensioned[1] = new FranceGeocentricInterpolation(2, 3, redimensioned);
-        redimensioned[2] = new FranceGeocentricInterpolation(3, 2, redimensioned);
-        redimensioned[3] = new FranceGeocentricInterpolation(3, 3, redimensioned);
+        redimensioned[1] = new FranceGeocentricInterpolation(2, 3, PARAMETERS, redimensioned);
+        redimensioned[2] = new FranceGeocentricInterpolation(3, 2, PARAMETERS, redimensioned);
+        redimensioned[3] = new FranceGeocentricInterpolation(3, 3, PARAMETERS, redimensioned);
     }
 
     /**
-     * Constructs a provider for the given dimensions.
+     * Constructs a provider for the given number of dimensions.
      *
      * @param sourceDimensions Number of dimensions in the source CRS of this operation method.
      * @param targetDimensions Number of dimensions in the target CRS of this operation method.
+     * @param parameters       The set of parameters (never {@code null}).
      * @param redimensioned    Providers for all combinations between 2D and 3D cases.
      */
-    private FranceGeocentricInterpolation(final int sourceDimensions, final int targetDimensions,
-            final FranceGeocentricInterpolation[] redimensioned)
+    FranceGeocentricInterpolation(final int sourceDimensions,
+                                  final int targetDimensions,
+                                  final ParameterDescriptorGroup parameters,
+                                  final FranceGeocentricInterpolation[] redimensioned)
     {
-        super(sourceDimensions, targetDimensions, PARAMETERS);
+        super(sourceDimensions, targetDimensions, parameters);
         this.redimensioned = redimensioned;
     }
 
@@ -307,16 +311,18 @@ public final class FranceGeocentricInter
         if (dim != null) switch (dim) {
             case 2:  break;
             case 3:  withHeights = true; break;
-            default: throw new InvalidParameterValueException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "dim", dim), "dim", dim);
+            default: throw new InvalidParameterValueException(Errors.format(
+                            Errors.Keys.IllegalArgumentValue_2, "dim", dim), "dim", dim);
         }
         final Path file = pg.getMandatoryValue(FILE);
-        final DatumShiftGridFile<Angle,Length> grid = getOrLoad(file, !isRecognized(file) ? null : new double[] {TX, TY, TZ}, PRECISION);
-        MathTransform tr = InterpolatedGeocentricTransform.createGeodeticTransformation(factory,
+        final DatumShiftGridFile<Angle,Length> grid = getOrLoad(file,
+                isRecognized(file) ? new double[] {TX, TY, TZ} : null, PRECISION);
+        MathTransform tr = createGeodeticTransformation(factory,
                 createEllipsoid(pg, Molodensky.TGT_SEMI_MAJOR,
-                                    Molodensky.TGT_SEMI_MINOR, CommonCRS.ETRS89.ellipsoid()), withHeights, // GRS 1980 ellipsoid
+                                    Molodensky.TGT_SEMI_MINOR, CommonCRS.ETRS89.ellipsoid()),   // GRS 1980 ellipsoid
                 createEllipsoid(pg, Molodensky.SRC_SEMI_MAJOR,
-                                    Molodensky.SRC_SEMI_MINOR, null), withHeights,  // Clarke 1880 (IGN) ellipsoid
-                grid);
+                                    Molodensky.SRC_SEMI_MINOR, null),                           // Clarke 1880 (IGN) ellipsoid
+                withHeights, grid);
         try {
             tr = tr.inverse();
         } catch (NoninvertibleTransformException e) {
@@ -326,6 +332,19 @@ public final class FranceGeocentricInter
     }
 
     /**
+     * Creates the actual math transform. The default implementation delegates to the static method defined in
+     * {@link InterpolatedGeocentricTransform}, but the {@link MolodenskyInterpolation} subclass will rather
+     * delegate to {@link org.apache.sis.referencing.operation.transform.InterpolatedMolodenskyTransform}.
+     */
+    MathTransform createGeodeticTransformation(final MathTransformFactory factory,
+            final Ellipsoid source, final Ellipsoid target, final boolean withHeights,
+            final DatumShiftGridFile<Angle,Length> grid) throws FactoryException
+    {
+        return InterpolatedGeocentricTransform.createGeodeticTransformation(
+                factory, source, withHeights, target, withHeights, grid);
+    }
+
+    /**
      * Returns the grid of the given name. This method returns the cached instance if it still exists,
      * or load the grid otherwise.
      *

Added: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MolodenskyInterpolation.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MolodenskyInterpolation.java?rev=1719607&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MolodenskyInterpolation.java (added)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MolodenskyInterpolation.java [UTF-8] Sat Dec 12 00:51:25 2015
@@ -0,0 +1,96 @@
+/*
+ * 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.measure.quantity.Angle;
+import javax.measure.quantity.Length;
+import javax.xml.bind.annotation.XmlTransient;
+import org.opengis.util.FactoryException;
+import org.opengis.referencing.datum.Ellipsoid;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.MathTransformFactory;
+import org.opengis.parameter.ParameterDescriptorGroup;
+import org.opengis.parameter.ParameterValueGroup;
+import org.apache.sis.internal.util.Constants;
+import org.apache.sis.referencing.operation.transform.InterpolatedMolodenskyTransform;
+
+
+/**
+ * An approximation of geocentric interpolations which uses {@link InterpolatedMolodenskyTransform}
+ * instead than {@link org.apache.sis.referencing.operation.transform.InterpolatedGeocentricTransform}.
+ *
+ * <p>This operation method is not standard, and as of SIS 0.7 not yet registered in the operation methods
+ * provided by {@link org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory}.
+ * This class extends {@code FranceGeocentricInterpolation} for now because the later is currently
+ * the only operation performing interpolation in the geocentric domain.
+ * However this class hierarchy may be revisited in any future SIS version.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.7
+ * @version 0.7
+ * @module
+ */
+@XmlTransient
+public final class MolodenskyInterpolation extends FranceGeocentricInterpolation {
+    /**
+     * Serial number for inter-operability with different versions.
+     */
+    private static final long serialVersionUID = 4265894749866901286L;
+
+    /**
+     * Constructs a provider.
+     */
+    @SuppressWarnings("ThisEscapedInObjectConstruction")
+    public MolodenskyInterpolation() {
+        super(2, 2, builder().setCodeSpace(null, Constants.SIS).addName("Molodensky interpolation")
+                        .createGroupWithSameParameters(PARAMETERS), new MolodenskyInterpolation[4]);
+        final ParameterDescriptorGroup parameters = super.getParameters();
+        redimensioned[0] = this;
+        redimensioned[1] = new MolodenskyInterpolation(2, 3, parameters, redimensioned);
+        redimensioned[2] = new MolodenskyInterpolation(3, 2, parameters, redimensioned);
+        redimensioned[3] = new MolodenskyInterpolation(3, 3, parameters, redimensioned);
+    }
+
+    /**
+     * Constructs a provider for the given number of dimensions.
+     *
+     * @param sourceDimensions Number of dimensions in the source CRS of this operation method.
+     * @param targetDimensions Number of dimensions in the target CRS of this operation method.
+     * @param parameters       The set of parameters (never {@code null}).
+     * @param redimensioned    Providers for all combinations between 2D and 3D cases.
+     */
+    private MolodenskyInterpolation(final int sourceDimensions,
+                                    final int targetDimensions,
+                                    final ParameterDescriptorGroup parameters,
+                                    final FranceGeocentricInterpolation[] redimensioned)
+    {
+        super(sourceDimensions, targetDimensions, parameters, redimensioned);
+    }
+
+    /**
+     * Invoked by {@link #createMathTransform(MathTransformFactory, ParameterValueGroup)}
+     * after all parameters have been processed.
+     */
+    @Override
+    MathTransform createGeodeticTransformation(final MathTransformFactory factory,
+            final Ellipsoid source, final Ellipsoid target, final boolean withHeights,
+            final DatumShiftGridFile<Angle,Length> grid) throws FactoryException
+    {
+        return InterpolatedMolodenskyTransform.createGeodeticTransformation(
+                factory, source, withHeights, target, withHeights, grid);
+    }
+}

Propchange: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MolodenskyInterpolation.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MolodenskyInterpolation.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DatumShiftGrid.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DatumShiftGrid.java?rev=1719607&r1=1719606&r2=1719607&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DatumShiftGrid.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DatumShiftGrid.java [UTF-8] Sat Dec 12 00:51:25 2015
@@ -373,6 +373,30 @@ public abstract class DatumShiftGrid<C e
     }
 
     /**
+     * Converts the given normalized <var>x</var> ordinate to grid index.
+     * "Normalized coordinates" are coordinates in the unit of measurement given by {@link Unit#toSI()}.
+     * For angular coordinates, this is radians. For linear coordinates, this is metres.
+     *
+     * @param x The "real world" ordinate (often longitude in radians) of the point for which to get the translation.
+     * @return The grid index for the given ordinate. May be out of bounds.
+     */
+    public final double normalizedToGridX(final double x) {
+        return x * scaleX + x0;
+    }
+
+    /**
+     * Converts the given normalized <var>x</var> ordinate to grid index.
+     * "Normalized coordinates" are coordinates in the unit of measurement given by {@link Unit#toSI()}.
+     * For angular coordinates, this is radians. For linear coordinates, this is metres.
+     *
+     * @param y The "real world" ordinate (often latitude in radians) of the point for which to get the translation.
+     * @return The grid index for the given ordinate. May be out of bounds.
+     */
+    public final double normalizedToGridY(final double y) {
+        return y * scaleY + y0;
+    }
+
+    /**
      * Returns the number of dimensions of the translation vectors interpolated by this datum shift grid.
      * This number of dimensions is not necessarily equals to the number of source or target dimensions
      * of the "{@linkplain #getCoordinateToGrid() coordinate to grid}" transform.
@@ -438,23 +462,6 @@ public abstract class DatumShiftGrid<C e
     }
 
     /**
-     * Interpolates the translation to apply for the given two-dimensional normalized coordinates.
-     * "Normalized coordinates" are coordinates in the unit of measurement given by {@link Unit#toSI()}.
-     * For angular coordinates, this is radians. For linear coordinates, this is metres.
-     *
-     * <p>The result is stored in the given {@code vector} array, which shall have a length of at least
-     * {@link #getTranslationDimensions()}. The output unit of measurement is the same than the one
-     * documented in {@link #getCellValue}.</p>
-     *
-     * @param x First "real world" ordinate (often longitude in radians) of the point for which to get the translation.
-     * @param y Second "real world" ordinate (often latitude in radians) of the point for which to get the translation.
-     * @param vector A pre-allocated array where to write the translation vector.
-     */
-    public final void interpolateAtNormalized(final double x, final double y, final double[] vector) {
-        interpolateInCell(x * scaleX + x0, y * scaleY + y0, vector);
-    }
-
-    /**
      * Interpolates the translation to apply for the given two-dimensional grid indices. The result is stored in
      * the given {@code vector} array, which shall have a length of at least {@link #getTranslationDimensions()}.
      * The output unit of measurement is the same than the one documented in {@link #getCellValue}.

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/MatrixSIS.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/MatrixSIS.java?rev=1719607&r1=1719606&r2=1719607&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/MatrixSIS.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/MatrixSIS.java [UTF-8] Sat Dec 12 00:51:25 2015
@@ -102,11 +102,11 @@ public abstract class MatrixSIS implemen
     }
 
     /**
-     * Ensures that the number of rows of the given matrix matches the given value.
+     * Ensures that the number of rows of a given matrix matches the given value.
      * This is a convenience method for {@link #multiply(Matrix)} implementations.
      *
      * @param expected The expected number of rows.
-     * @param matrix   The matrix to verify.
+     * @param actual   The actual number of rows in the matrix to verify.
      * @param numCol   The number of columns to report in case of errors. This is an arbitrary
      *                 value and have no incidence on the verification performed by this method.
      */

Copied: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DatumShiftTransform.java (from r1719489, sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MolodenskyFormula.java)
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DatumShiftTransform.java?p2=sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DatumShiftTransform.java&p1=sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MolodenskyFormula.java&r1=1719489&r2=1719607&rev=1719607&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MolodenskyFormula.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DatumShiftTransform.java [UTF-8] Sat Dec 12 00:51:25 2015
@@ -20,130 +20,37 @@ import java.io.Serializable;
 import javax.measure.unit.Unit;
 import javax.measure.quantity.Length;
 import javax.measure.converter.UnitConverter;
+import org.opengis.referencing.datum.Ellipsoid;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterDescriptorGroup;
-import org.opengis.referencing.datum.Ellipsoid;
-import org.opengis.referencing.operation.Matrix;
-import org.opengis.referencing.operation.TransformException;
-import org.apache.sis.referencing.operation.matrix.Matrices;
-import org.apache.sis.referencing.datum.DefaultEllipsoid;
+import org.opengis.geometry.MismatchedDimensionException;
 import org.apache.sis.referencing.datum.DatumShiftGrid;
 import org.apache.sis.internal.referencing.provider.Molodensky;
-import org.apache.sis.internal.util.Numerics;
-import org.apache.sis.parameter.Parameters;
-import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.Debug;
 
-import static java.lang.Math.*;
-
 // Branch-specific imports
 import java.util.Objects;
 
 
 /**
- * Implementation of Molodensky formulas. This class is used by
- *
- * <ul>
- *   <li>The "real" {@link MolodenskyTransform} (see that class for documentation about Molodensky transform).</li>
- *   <li>{@link InterpolatedGeocentricTransform}, which conceptually works on geocentric coordinates but
- *       is implemented in Apache SIS using Molodensky (never abridged) formulas for performance reasons.
- *       However this implementation choice should be hidden to users (except by mention in javadoc).</li>
- * </ul>
+ * Base class of transforms performing datum shifts.
+ * Some implementations are backed by a {@link DatumShiftGrid}.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.7
  * @version 0.7
  * @module
  */
-abstract class MolodenskyFormula extends AbstractMathTransform implements Serializable {
+public abstract class DatumShiftTransform extends AbstractMathTransform implements Serializable {
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = 7684676923384073055L;
-
-    /**
-     * The value of 1/sin(1″) multiplied by the conversion factor from arc-seconds to radians (π/180)/(60⋅60).
-     * This is the final multiplication factor for Δλ and Δφ.
-     */
-    static final double ANGULAR_SCALE = 1.00000000000391744;
-
-    /**
-     * {@code true} if the source coordinates have a height.
-     */
-    final boolean isSource3D;
-
-    /**
-     * {@code true} if the target coordinates have a height.
-     */
-    final boolean isTarget3D;
-
-    /**
-     * {@code true} for the abridged formula, or {@code false} for the complete one.
-     */
-    final boolean isAbridged;
-
-    /**
-     * Shift along the geocentric X axis (toward prime meridian)
-     * in units of the semi-major axis of the source ellipsoid.
-     *
-     * @see org.apache.sis.referencing.datum.BursaWolfParameters#tX
-     */
-    protected final double tX;
-
-    /**
-     * Shift along the geocentric Y axis (toward 90°E)
-     * in units of the semi-major axis of the source ellipsoid.
-     *
-     * @see org.apache.sis.referencing.datum.BursaWolfParameters#tY
-     */
-    protected final double tY;
-
-    /**
-     * Shift along the geocentric Z axis (toward north pole)
-     * in units of the semi-major axis of the source ellipsoid.
-     *
-     * @see org.apache.sis.referencing.datum.BursaWolfParameters#tZ
-     */
-    protected final double tZ;
-
-    /**
-     * Difference in the semi-major axes of the target and source ellipsoids: {@code Δa = target a - source a}.
-     *
-     * @see DefaultEllipsoid#semiMajorAxisDifference(Ellipsoid)
-     */
-    final double Δa;
+    private static final long serialVersionUID = -4492222496475405226L;
 
     /**
-     * Difference between the flattening of the target and source ellipsoids (Δf), opportunistically modified
-     * with additional terms. The value depends on whether this Molodensky transform is abridged or not:
-     *
-     * <ul>
-     *   <li>For Molodensky, this field is set to (b⋅Δf).</li>
-     *   <li>For Abridged Molodensky, this field is set to (a⋅Δf) + (f⋅Δa).</li>
-     * </ul>
-     *
-     * where Δf = <var>target flattening</var> - <var>source flattening</var>.
-     */
-    final double Δfmod;
-
-    /**
-     * Semi-major axis length (<var>a</var>) of the source ellipsoid.
-     */
-    protected final double semiMajor;
-
-    /**
-     * The square of eccentricity of the source ellipsoid.
-     * This can be computed by ℯ² = (a²-b²)/a² where
-     * <var>a</var> is the <cite>semi-major</cite> axis length and
-     * <var>b</var> is the <cite>semi-minor</cite> axis length.
-     *
-     * @see DefaultEllipsoid#getEccentricitySquared()
-     */
-    protected final double eccentricitySquared;
-
-    /**
-     * The parameters used for creating this conversion.
+     * The parameters used for creating this transformation.
      * They are used for formatting <cite>Well Known Text</cite> (WKT) and error messages.
      *
      * @see #getContextualParameters()
@@ -151,152 +58,75 @@ abstract class MolodenskyFormula extends
     final ContextualParameters context;
 
     /**
-     * The grid of datum shifts from source datum to target datum.
-     * This can be non-null only for {@link InterpolatedGeocentricTransform}.
+     * The grid of datum shifts from source datum to target datum, or {@code null} if none.
      */
     final DatumShiftGrid<?,?> grid;
 
     /**
-     * Constructs the inverse of a Molodensky transform.
+     * Creates a datum shift transform. It is caller responsibility to initialize the {@link #context} parameters.
      *
-     * @param inverse     The transform for which to create the inverse.
-     * @param source      The source ellipsoid of the given {@code inverse} transform.
-     * @param target      The target ellipsoid of the given {@code inverse} transform.
      * @param descriptor  The contextual parameter descriptor.
+     * @param grid        Interpolation grid in geocentric coordinates, or {@code null} if none.
      */
-    MolodenskyFormula(final MolodenskyFormula inverse, final Ellipsoid source, final Ellipsoid target,
-            final ParameterDescriptorGroup descriptor)
-    {
-        this(target, inverse.isTarget3D,
-             source, inverse.isSource3D,
-             -inverse.tX, -inverse.tY, -inverse.tZ, inverse.grid, inverse.isAbridged, descriptor);
+    DatumShiftTransform(ParameterDescriptorGroup descriptor, int srcSize, int tgtSize, final DatumShiftGrid<?,?> grid) {
+        context = new ContextualParameters(descriptor, srcSize, tgtSize);
+        this.grid = grid;
     }
 
     /**
-     * Creates a Molodensky transform from the specified parameters.
-     * If a non-null {@code grid} is specified, it is caller's responsibility to verify its validity.
+     * Ensures that the {@link #grid} performs geocentric translations in the given units.
+     * This method is invoked by constructor for validation of given arguments.
      *
-     * @param source      The source ellipsoid.
-     * @param isSource3D  {@code true} if the source coordinates have a height.
-     * @param target      The target ellipsoid.
-     * @param isTarget3D  {@code true} if the target coordinates have a height.
-     * @param tX          The geocentric <var>X</var> translation in same units than the source ellipsoid axes.
-     * @param tY          The geocentric <var>Y</var> translation in same units than the source ellipsoid axes.
-     * @param tZ          The geocentric <var>Z</var> translation in same units than the source ellipsoid axes.
-     * @param grid        Interpolation grid in geocentric coordinates, or {@code null} if none.
-     * @param isAbridged  {@code true} for the abridged formula, or {@code false} for the complete one.
-     * @param descriptor  The contextual parameter descriptor.
+     * <p>This method is defined here in order to ensure a consistent behavior of
+     * {@link InterpolatedGeocentricTransform} with {@link InterpolatedMolodenskyTransform}.</p>
+     *
+     * @param  grid  The grid to validate.
+     * @param  unit  The unit of semi-axis length of the <strong>source</strong> ellipsoid.
+     * @throws IllegalArgumentException if the given grid is not valid.
      */
-    @SuppressWarnings("OverridableMethodCallDuringObjectConstruction")
-    MolodenskyFormula(final Ellipsoid source, final boolean isSource3D,
-                      final Ellipsoid target, final boolean isTarget3D,
-                      final double tX, final double tY, final double tZ,
-                      final DatumShiftGrid<?,?> grid, final boolean isAbridged,
-                      final ParameterDescriptorGroup descriptor)
+    static void ensureGeocentricTranslation(final DatumShiftGrid<?,?> grid, final Unit<Length> unit)
+            throws IllegalArgumentException
     {
-        ArgumentChecks.ensureNonNull("source", source);
-        ArgumentChecks.ensureNonNull("target", target);
-        final DefaultEllipsoid src = DefaultEllipsoid.castOrCopy(source);
-        this.isSource3D = isSource3D;
-        this.isTarget3D = isTarget3D;
-        this.isAbridged = isAbridged;
-        this.semiMajor  = src.getSemiMajorAxis();
-        this.Δa         = src.semiMajorAxisDifference(target);
-        this.tX         = tX;
-        this.tY         = tY;
-        this.tZ         = tZ;
-        this.grid       = grid;
-
-        final double semiMinor = src.getSemiMinorAxis();
-        final double Δf = src.flatteningDifference(target);
-        eccentricitySquared = src.getEccentricitySquared();
-        Δfmod = isAbridged ? (semiMajor * Δf) + (semiMajor - semiMinor) * (Δa / semiMajor)
-                           : (semiMinor * Δf);
-        /*
-         * Copy parameters to the ContextualParameter. Those parameters are not used directly
-         * by MolodenskyTransform, but we need to store them in case the user asks for them.
-         */
-        final Unit<Length> unit = src.getAxisUnit();
+        final int dim = grid.getTranslationDimensions();
+        if (dim != 3) {
+            throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3, "grid", 3, dim));
+        }
+        Object unitLabel = "ratio";
+        if (grid.isCellValueRatio() || (unitLabel = grid.getTranslationUnit()) != unit) {
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalUnitFor_2, "translation", unitLabel));
+        }
+    }
+
+    /**
+     * Sets the semi-axis length in the {@link #context} parameters.
+     * This is a helper method for constructors in some (not all) subclasses.
+     *
+     * @param semiMajor The semi-major axis length of the source ellipsoid.
+     * @param semiMinor The semi-minor axis length of the source ellipsoid.
+     * @param unit      The unit of measurement of source ellipsoid axes.
+     * @param target    The target ellipsoid.
+     */
+    final void setContextParameters(final double semiMajor, final double semiMinor, final Unit<Length> unit, final Ellipsoid target) {
         final UnitConverter c = target.getAxisUnit().getConverterTo(unit);
-        context = new ContextualParameters(descriptor, isSource3D ? 4 : 3, isTarget3D ? 4 : 3);
         context.getOrCreate(Molodensky.SRC_SEMI_MAJOR).setValue(semiMajor, unit);
         context.getOrCreate(Molodensky.SRC_SEMI_MINOR).setValue(semiMinor, unit);
         context.getOrCreate(Molodensky.TGT_SEMI_MAJOR).setValue(c.convert(target.getSemiMajorAxis()), unit);
         context.getOrCreate(Molodensky.TGT_SEMI_MINOR).setValue(c.convert(target.getSemiMinorAxis()), unit);
-        completeParameters(context, semiMinor, unit, Δf);
-        /*
-         * Prepare two affine transforms to be executed before and after the MolodenskyTransform:
-         *
-         *   - A "normalization" transform for converting degrees to radians,
-         *   - A "denormalization" transform for for converting radians to degrees.
-         */
-        context.normalizeGeographicInputs(0);
-        context.denormalizeGeographicOutputs(0);
     }
 
     /**
-     * Returns a copy of internal parameter values of this transform.
-     * The returned group contains parameters for the source ellipsoid semi-axis lengths
-     * and the differences between source and target ellipsoid parameters.
-     *
-     * <div class="note"><b>Note:</b>
-     * this method is mostly for {@linkplain org.apache.sis.io.wkt.Convention#INTERNAL debugging purposes}
+     * Returns the internal parameter values of this transform.
+     * This method is mostly for {@linkplain org.apache.sis.io.wkt.Convention#INTERNAL debugging purposes}
      * since the isolation of non-linear parameters in this class is highly implementation dependent.
      * Most GIS applications will instead be interested in the {@linkplain #getContextualParameters()
-     * contextual parameters}.</div>
+     * contextual parameters}.
      *
-     * @return A copy of the internal parameter values for this transform.
+     * @return The internal parameter values for this transform.
      */
     @Debug
     @Override
     public ParameterValueGroup getParameterValues() {
-        final Unit<?> unit = context.getOrCreate(Molodensky.SRC_SEMI_MAJOR).getUnit();
-        final double semiMinor = context.getOrCreate(Molodensky.SRC_SEMI_MINOR).doubleValue(unit);
-        final Parameters pg = Parameters.castOrWrap(getParameterDescriptors().createValue());
-        pg.getOrCreate(Molodensky.SRC_SEMI_MAJOR).setValue(semiMajor, unit);
-        pg.getOrCreate(Molodensky.SRC_SEMI_MINOR).setValue(semiMinor, unit);
-        completeParameters(pg, semiMinor, unit, Double.NaN);
-        return pg;
-    }
-
-    /**
-     * Sets parameters values in the given group for parameters other than axis lengths.
-     * This method is invoked for both completing contextual parameters ({@code pg == context}) and
-     * for completing internal parameters ({@code pg != context}). When this method is invoked, the
-     * following parameters are already set:
-     *
-     * <ul>
-     *   <li>{@code "src_semi_major"}</li>
-     *   <li>{@code "src_semi_minor"}</li>
-     *   <li>{@code "tgt_semi_major"} (contextual parameters only)</li>
-     *   <li>{@code "tgt_semi_minor"} (contextual parameters only)</li>
-     * </ul>
-     *
-     * The default implementation sets the {@code "dim"} parameter.
-     * Subclasses shall override this method for completing also the following parameters:
-     *
-     * <ul>
-     *   <li><cite>"X-axis translation"</cite> (Molodensky only)</li>
-     *   <li><cite>"Y-axis translation"</cite> (Molodensky only)</li>
-     *   <li><cite>"Z-axis translation"</cite> (Molodensky only)</li>
-     *   <li><cite>"Geocentric translations file"</cite> (Geocentric interpolations only)</li>
-     *   <li><cite>"Semi-major axis length difference"</cite> (Always for Molodensky, internal WKT only for geocentric interpolations)</li>
-     *   <li><cite>"Flattening difference"</cite> (Always for Molodensky, internal WKT only for geocentric interpolations)</li>
-     * </ul>
-     *
-     * @param pg         Where to set the parameters.
-     * @param semiMinor  The semi minor axis length, in unit of {@code unit}.
-     * @param unit       The unit of measurement to declare.
-     * @param Δf         The flattening difference to set, or NaN if this method should fetch that value itself.
-     */
-    void completeParameters(final Parameters pg, final double semiMinor, final Unit<?> unit, final double Δf) {
-        /*
-         * Unconditionally set the "dim" parameters to the number of source dimensions (do not check for consistency
-         * with the number of target dimensions) because source dimensions determine the value of ellipsoidal heights,
-         * which may change the horizontal numerical values. By contrast, the number of target dimensions does not have
-         * any impact on numerical values (it can just causes a drop of the third value).
-         */
-        pg.getOrCreate(Molodensky.DIMENSION).setValue(getSourceDimensions());
+        return context;     // Overridden by some subclasses.
     }
 
     /**
@@ -314,190 +144,13 @@ abstract class MolodenskyFormula extends
     }
 
     /**
-     * Gets the dimension of input points.
-     *
-     * @return The input dimension, which is 2 or 3.
-     */
-    @Override
-    public final int getSourceDimensions() {
-        return isSource3D ? 3 : 2;
-    }
-
-    /**
-     * Gets the dimension of output points.
-     *
-     * @return The output dimension, which is 2 or 3.
-     */
-    @Override
-    public final int getTargetDimensions() {
-        return isTarget3D ? 3 : 2;
-    }
-
-    /**
-     * Implementation of {@link #transform(double[], int, double[], int, boolean)} with possibility
-     * to override some field values. In this method signature, parameters having the same name than
-     * fields have the same value, except in some special circumstances:
-     *
-     * <ul>
-     *   <li>{@code tX}, {@code tY} and {@code tZ} parameters always have the values of {@link #tX}, {@link #tY}
-     *       and {@link #tZ} fields when this method is invoked by {@link MolodenskyTransform}. But those values
-     *       may be slightly different when this method is invoked by {@link InterpolatedGeocentricTransform}.</li>
-     * </ul>
-     *
-     * @param λ           Longitude (radians).
-     * @param φ           Latitude (radians).
-     * @param h           Height above the ellipsoid in unit of semi-major axis.
-     * @param dstPts      The array into which the transformed coordinate is returned, or {@code null}.
-     * @param dstOff      The offset to the location of the transformed point that is stored in the destination array.
-     * @param tX          The {@link #tX} field value (or a slightly different value during geocentric interpolation).
-     * @param tY          The {@link #tY} field value (or a slightly different value during geocentric interpolation).
-     * @param tZ          The {@link #tZ} field value (or a slightly different value during geocentric interpolation).
-     * @param offset      An array of length 3 if this method should use the interpolation grid, or {@code null} otherwise.
-     * @param derivate    {@code true} for computing the derivative, or {@code false} if not needed.
-     * @throws TransformException if a point can not be transformed.
-     */
-    final Matrix transform(final double λ, final double φ, final double h, final double[] dstPts, int dstOff,
-                           double tX, double tY, double tZ, double[] offset, final boolean derivate)
-            throws TransformException
-    {
-        /*
-         * Abridged Molodensky formulas from EPSG guidance note:
-         *
-         *     ν   = a / √(1 - ℯ²⋅sin²φ)                        : radius of curvature in the prime vertical
-         *     ρ   = a⋅(1 – ℯ²) / (1 – ℯ²⋅sin²φ)^(3/2)          : radius of curvature in the meridian
-         *     Δλ″ = (-tX⋅sinλ + tY⋅cosλ) / (ν⋅cosφ⋅sin1″)
-         *     Δφ″ = (-tX⋅sinφ⋅cosλ - tY⋅sinφ⋅sinλ + tZ⋅cosφ + [a⋅Δf + f⋅Δa]⋅sin(2φ)) / (ρ⋅sin1″)
-         *     Δh  = tX⋅cosφ⋅cosλ + tY⋅cosφ⋅sinλ + tZ⋅sinφ + (a⋅Δf + f⋅Δa)⋅sin²φ - Δa
-         *
-         * we set:
-         *
-         *    dfm     = (a⋅Δf + f⋅Δa) in abridged case (b⋅Δf in non-abridged case)
-         *    sin(2φ) = 2⋅sin(φ)⋅cos(φ)
-         */
-        final double sinλ  = sin(λ);
-        final double cosλ  = cos(λ);
-        final double sinφ  = sin(φ);
-        final double cosφ  = cos(φ);
-        final double sin2φ = sinφ * sinφ;
-        final double ν2den = 1 - eccentricitySquared*sin2φ;                 // Square of the denominator of ν
-        final double νden  = sqrt(ν2den);                                   // Denominator of ν
-        final double ρden  = ν2den * νden;                                  // Denominator of ρ
-        double ρ = semiMajor * (1 - eccentricitySquared) / ρden;            // Other notation: Rm = ρ
-        double ν = semiMajor / νden;                                        // Other notation: Rn = ν
-        double t = Δfmod * 2;                                               // A term in the calculation of Δφ
-        if (!isAbridged) {
-            ρ += h;
-            ν += h;
-            t = t*(0.5/νden + 0.5/ρden)                 // = Δf⋅[ν⋅(b/a) + ρ⋅(a/b)]     (without the +h in ν and ρ)
-                    + Δa*eccentricitySquared/νden;      // = Δa⋅[ℯ²⋅ν/a]
-        }
-        double spcλ, cmsλ, cmsφ, scaleX, scaleY;        // Intermediate terms to be reused by the derivative
-        double λt, φt;                                  // The target geographic coordinates
-        do {
-            spcλ = tY*sinλ + tX*cosλ;                   // "spc" stands for "sin plus cos"
-            cmsλ = tY*cosλ - tX*sinλ;                   // "cms" stands for "cos minus sin"
-            cmsφ = (tZ + t*sinφ)*cosφ - spcλ*sinφ;
-            scaleX = ANGULAR_SCALE / (ν*cosφ);
-            scaleY = ANGULAR_SCALE / ρ;
-            λt = λ + (cmsλ * scaleX);
-            φt = φ + (cmsφ * scaleY);
-            if (offset == null) break;
-
-            // Following is executed only in InterpolatedGeocentricTransform case.
-            grid.interpolateAtNormalized(λt, φt, offset);
-            tX = -offset[0];
-            tY = -offset[1];
-            tZ = -offset[2];
-            offset = null;
-        } while (true);
-        if (dstPts != null) {
-            dstPts[dstOff++] = λt;
-            dstPts[dstOff++] = φt;
-            if (isTarget3D) {
-                double t1 = Δfmod * sin2φ;              // A term in the calculation of Δh
-                double t2 = Δa;
-                if (!isAbridged) {
-                    t1 /= νden;                         // = Δf⋅(b/a)⋅ν⋅sin²φ
-                    t2 *= νden;                         // = Δa⋅(a/ν)
-                }
-                dstPts[dstOff++] = h + spcλ*cosφ + tZ*sinφ + t1 - t2;
-            }
-        }
-        if (!derivate) {
-            return null;
-        }
-        /*
-         * At this point the (Abridged) Molodensky transformation is finished.
-         * Code below this point is only for computing the derivative, if requested.
-         * Note: variable names do not necessarily tell all the terms that they contain.
-         */
-        final Matrix matrix   = Matrices.createDiagonal(getTargetDimensions(), getSourceDimensions());
-        final double sinφcosφ = sinφ * cosφ;
-        final double dν       = eccentricitySquared*sinφcosφ / ν2den;
-        final double dν3ρ     = 3*dν * (1 - eccentricitySquared) / ν2den;
-        //    double dXdλ     = spcλ;
-        final double dYdλ     = cmsλ * sinφ;
-        final double dZdλ     = cmsλ * cosφ;
-              double dXdφ     = dYdλ / cosφ;
-              double dYdφ     = -tZ*sinφ - cosφ*spcλ  +  t*(1 - 2*sin2φ);
-              double dZdφ     =  tZ*cosφ - sinφ*spcλ;
-        if (isAbridged) {
-            /*
-             *   Δfmod  =  (a⋅Δf) + (f⋅Δa)
-             *   t      =  2⋅Δfmod
-             *   dXdh   =  0  so no need to set the matrix element.
-             *   dYdh   =  0  so no need to set the matrix element.
-             */
-            dXdφ -= cmsλ * dν;
-            dYdφ -= cmsφ * dν3ρ;
-            dZdφ += t*cosφ*sinφ;
-        } else {
-            /*
-             *   Δfmod  =  b⋅Δf
-             *   t      =  Δf⋅[ν⋅(b/a) + ρ⋅(a/b)]    (real ν and ρ, without + h)
-             *   ν         is actually ν + h
-             *   ρ         is actually ρ + h
-             */
-            final double dρ = dν3ρ * νden * (semiMajor / ρ);    // Reminder: that ρ contains a h term.
-            dXdφ -= dν * cmsλ * semiMajor / (νden*ν);           // Reminder: that ν contains a h term.
-            dYdφ -= dρ * dZdφ - (Δfmod*(dν*2/(1 - eccentricitySquared) + (1 + 1/ν2den)*(dν - dρ))
-                                  + Δa*(dν + 1)*eccentricitySquared) * sinφcosφ / νden;
-            if (isSource3D) {
-                final double dXdh =  cmsλ / ν;
-                final double dYdh = -cmsφ / ρ;
-                matrix.setElement(0, 2, -dXdh * scaleX);
-                matrix.setElement(1, 2, +dYdh * scaleY);
-            }
-            final double t1 = Δfmod * (dν*sin2φ + 2*sinφcosφ);
-            final double t2 = Δa * dν;
-            dZdφ += t1/νden + t2*νden;
-        }
-        matrix.setElement(0, 0, 1 - spcλ * scaleX);
-        matrix.setElement(1, 1, 1 + dYdφ * scaleY);
-        matrix.setElement(0, 1,   + dXdφ * scaleX);
-        matrix.setElement(1, 0,   - dYdλ * scaleY);
-        if (isTarget3D) {
-            matrix.setElement(2, 0, dZdλ);
-            matrix.setElement(2, 1, dZdφ);
-        }
-        return matrix;
-    }
-
-    /**
      * {@inheritDoc}
      *
      * @return {@inheritDoc}
      */
     @Override
     protected int computeHashCode() {
-        int code = super.computeHashCode() + Numerics.hashCode(
-                        Double.doubleToLongBits(Δa)
-                +       Double.doubleToLongBits(Δfmod)
-                + 31 * (Double.doubleToLongBits(tX)
-                + 31 * (Double.doubleToLongBits(tY)
-                + 31 * (Double.doubleToLongBits(tZ)))));
-        if (isAbridged) code = ~code;
-        return code + Objects.hashCode(grid);
+        return super.computeHashCode() + Objects.hashCode(grid);
     }
 
     /**
@@ -507,25 +160,6 @@ abstract class MolodenskyFormula extends
      */
     @Override
     public boolean equals(final Object object, final ComparisonMode mode) {
-        if (object == this) {
-            // Slight optimization
-            return true;
-        }
-        if (super.equals(object, mode)) {
-            final MolodenskyFormula that = (MolodenskyFormula) object;
-            return isSource3D == that.isSource3D
-                && isTarget3D == that.isTarget3D
-                && isAbridged == that.isAbridged
-                && Numerics.epsilonEqual(tX,                  that.tX,                  mode)
-                && Numerics.epsilonEqual(tY,                  that.tY,                  mode)
-                && Numerics.epsilonEqual(tZ,                  that.tZ,                  mode)
-                && Numerics.epsilonEqual(Δa,                  that.Δa,                  mode)
-                && Numerics.epsilonEqual(Δfmod,               that.Δfmod,               mode)
-                && Numerics.epsilonEqual(semiMajor,           that.semiMajor,           mode)
-                && Numerics.epsilonEqual(eccentricitySquared, that.eccentricitySquared, mode)
-                && Objects .equals(grid, that.grid);
-                // No need to compare the contextual parameters since this is done by super-class.
-        }
-        return false;
+        return super.equals(object, mode) && Objects.equals(grid, ((DatumShiftTransform) object).grid);
     }
 }

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransform.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransform.java?rev=1719607&r1=1719606&r2=1719607&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransform.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransform.java [UTF-8] Sat Dec 12 00:51:25 2015
@@ -16,30 +16,28 @@
  */
 package org.apache.sis.referencing.operation.transform;
 
-import java.util.Arrays;
 import javax.measure.unit.Unit;
 import javax.measure.quantity.Angle;
 import javax.measure.quantity.Length;
 import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterDescriptorGroup;
-import org.opengis.geometry.MismatchedDimensionException;
 import org.opengis.referencing.datum.Ellipsoid;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransformFactory;
+import org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.internal.referencing.provider.DatumShiftGridFile;
 import org.apache.sis.internal.referencing.provider.FranceGeocentricInterpolation;
 import org.apache.sis.internal.referencing.provider.Molodensky;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.metadata.iso.citation.Citations;
-import org.apache.sis.parameter.Parameters;
 import org.apache.sis.parameter.ParameterBuilder;
 import org.apache.sis.referencing.datum.DatumShiftGrid;
-import org.apache.sis.util.resources.Errors;
+import org.apache.sis.referencing.operation.matrix.Matrices;
+import org.apache.sis.referencing.operation.matrix.Matrix3;
 import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.Debug;
 
 
 /**
@@ -76,23 +74,23 @@ import org.apache.sis.util.Debug;
  * because the {@code DatumShiftGrid} inputs are geographic coordinates even if the interpolated
  * grid values are in geocentric space.</p></div>
  *
- * <div class="section">Implementation</div>
- * While this transformation is conceptually defined as a translation in geocentric coordinates, the current
- * Apache SIS implementation rather uses the {@linkplain MolodenskyTransform Molodensy} (non-abridged) approximation
- * for performance reasons. Errors are less than 3 centimetres for the France geocentric interpolation.
- * By comparison, the finest accuracy reported in the grid file for France is 5 centimetres.
+ * <div class="section">Performance consideration</div>
+ * {@link InterpolatedMolodenskyTransform} performs the same calculation more efficiently at the cost of
+ * a few centimetres error. Both classes are instantiated in the same way and expect the same inputs.
  *
  * @author  Simon Reynard (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.7
  * @version 0.7
  * @module
+ *
+ * @see InterpolatedMolodenskyTransform
  */
-public class InterpolatedGeocentricTransform extends MolodenskyFormula {
+public class InterpolatedGeocentricTransform extends DatumShiftTransform {
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = -5691721806681489940L;
+    private static final long serialVersionUID = 5503722845441653093L;
 
     /**
      * Parameter descriptor to use with the contextual parameters for the forward transformation.
@@ -128,6 +126,26 @@ public class InterpolatedGeocentricTrans
     }
 
     /**
+     * Semi-major axis length (<var>a</var>) of the source ellipsoid.
+     */
+    final double semiMajor;
+
+    /**
+     * Semi-major axis length of the source ellipsoid divided by semi-major axis length of the target ellipsoid.
+     * Used for converting normalized coordinates between the two geocentric coordinate reference systems.
+     *
+     * <p>This is a dimensionless quantity: the ellipsoid axis lengths must have been converted to the same unit
+     * before to compute this ratio.</p>
+     */
+    final double scale;
+
+    /**
+     * The transform to apply before and after the geocentric translation. Shall be instance of
+     * {@link EllipsoidToCentricTransform} and {@code EllipsoidToCentricTransform.Inverse} respectively.
+     */
+    final AbstractMathTransform ellipsoidToCentric, centricToEllipsoid;
+
+    /**
      * The inverse of this interpolated geocentric transform.
      *
      * @see #inverse()
@@ -142,8 +160,9 @@ public class InterpolatedGeocentricTrans
      * @param target  The target ellipsoid of the given {@code inverse} transform.
      */
     InterpolatedGeocentricTransform(final InterpolatedGeocentricTransform inverse, Ellipsoid source, Ellipsoid target) {
-        super(inverse, source, target, INVERSE);
-        this.inverse = inverse;
+        this(target, inverse.getTargetDimensions() > 2,
+             source, inverse.getSourceDimensions() > 2,
+             inverse.grid, inverse);
     }
 
     /**
@@ -187,26 +206,93 @@ public class InterpolatedGeocentricTrans
                                               final Ellipsoid target, final boolean isTarget3D,
                                               final DatumShiftGrid<Angle,Length> grid)
     {
-        super(source, isSource3D,
-              target, isTarget3D,
-              grid.getCellMean(0),
-              grid.getCellMean(1),
-              grid.getCellMean(2),
-              grid, false, DESCRIPTOR);
-
-        final int dim = grid.getTranslationDimensions();
-        if (dim != 3) {
-            throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3, "grid", 3, dim));
-        }
-        Object unit = "ratio";
-        if (grid.isCellValueRatio() || (unit = grid.getTranslationUnit()) != source.getAxisUnit()) {
-            throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalUnitFor_2, "translation", unit));
+        this(source, isSource3D, target, isTarget3D, grid, null);
+    }
+
+    /**
+     * Constructor for the forward and inverse transformations.
+     * If {@code inverse} is {@code null}, then this constructor creates the forward transformation.
+     * Otherwise this constructor creates the inverse of the given {@code inverse} transformation.
+     */
+    private InterpolatedGeocentricTransform(final Ellipsoid source, final boolean isSource3D,
+                                            final Ellipsoid target, final boolean isTarget3D,
+                                            final DatumShiftGrid<?,?> grid,
+                                            InterpolatedGeocentricTransform inverse)
+    {
+        super((inverse != null) ? INVERSE : DESCRIPTOR, isSource3D ? 4 : 3, isTarget3D ? 4 : 3, grid);
+        ArgumentChecks.ensureNonNull("source", source);
+        ArgumentChecks.ensureNonNull("target", target);
+        final Unit<Length> unit = source.getAxisUnit();
+        ensureGeocentricTranslation(grid, unit);
+        /*
+         * Store the source and target axis lengths in the contextual parameters.
+         * We do not need to store all those information directly in the field of this class,
+         * but we need to have them in ContextualParameters for Well Known Text formatting.
+         */
+        final double semiMinor;
+        semiMajor = source.getSemiMajorAxis();
+        semiMinor = source.getSemiMinorAxis();
+        setContextParameters(semiMajor, semiMinor, unit, target);
+        context.getOrCreate(Molodensky.DIMENSION).setValue(isSource3D ? 3 : 2);
+        if (grid instanceof DatumShiftGridFile<?,?>) {
+            ((DatumShiftGridFile<?,?>) grid).setFileParameters(context);
         }
-        if (isSource3D || isTarget3D) {
-            inverse = new Inverse(this, source, target);
-        } else {
-            inverse = new InterpolatedGeocentricTransform2D.Inverse(this, source, target);
+        /*
+         * The above setContextParameters(…) method converted the axis lengths of target ellipsoid in the same units
+         * than source ellipsoid. Opportunistically fetch that value, so we don't have to convert the values ourselves.
+         */
+        scale = semiMajor / context.doubleValue(Molodensky.TGT_SEMI_MAJOR);
+        /*
+         * Creates the Geographic ↔ Geocentric conversions. Note that the target ellipsoid keeps the unit specified
+         * by the user; we do not convert target axis length. This is okay since our work will be already finished
+         * when the conversion to target units will apply.
+         */
+        if (inverse == null) {
+            ellipsoidToCentric = new EllipsoidToCentricTransform(semiMajor, semiMinor, unit, isSource3D,
+                    EllipsoidToCentricTransform.TargetType.CARTESIAN);
+
+            centricToEllipsoid = (AbstractMathTransform) new EllipsoidToCentricTransform(
+                    target.getSemiMajorAxis(),
+                    target.getSemiMinorAxis(),
+                    target.getAxisUnit(), isTarget3D,
+                    EllipsoidToCentricTransform.TargetType.CARTESIAN).inverse();
+        } else try {
+            ellipsoidToCentric = (AbstractMathTransform) inverse.centricToEllipsoid.inverse();
+            centricToEllipsoid = (AbstractMathTransform) inverse.ellipsoidToCentric.inverse();
+        } catch (NoninvertibleTransformException e) {
+            throw new IllegalArgumentException(e);      // Should never happen.
+        }
+        /*
+         * Usually, this is where we would initialize the normalization and denormalization matrices
+         * to degrees ↔ radians conversions. But in for this class we will rather copy the work done
+         * by EllipsoidToCentricTransform. Especially since the later performs its own adjustment on
+         * height values.
+         */
+        context.getMatrix(ContextualParameters.MatrixRole.NORMALIZATION).setMatrix(
+                ellipsoidToCentric.getContextualParameters().getMatrix(ContextualParameters.MatrixRole.NORMALIZATION));
+        context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION).setMatrix(
+                centricToEllipsoid.getContextualParameters().getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION));
+        /*
+         * Inverse transformation must be created only after everything else has been initialized.
+         */
+        if (inverse == null) {
+            if (isSource3D || isTarget3D) {
+                inverse = new Inverse(this, source, target);
+            } else {
+                inverse = new InterpolatedGeocentricTransform2D.Inverse(this, source, target);
+            }
         }
+        this.inverse = inverse;
+    }
+
+    /**
+     * Delegates to {@link ContextualParameters#completeTransform(MathTransformFactory, MathTransform)}
+     * for this transformation and for its dependencies as well.
+     */
+    private MathTransform completeTransform(final MathTransformFactory factory, final boolean create) throws FactoryException {
+        ellipsoidToCentric.getContextualParameters().completeTransform(factory, null);
+        centricToEllipsoid.getContextualParameters().completeTransform(factory, null);
+        return context.completeTransform(factory, create ? this : null);
     }
 
     /**
@@ -247,29 +333,28 @@ public class InterpolatedGeocentricTrans
         } else {
             tr = new InterpolatedGeocentricTransform2D(source, target, grid);
         }
-        tr.inverse.context.completeTransform(factory, null);
-        return tr.context.completeTransform(factory, tr);
+        tr.inverse.completeTransform(factory, false);
+        return tr.completeTransform(factory, true);
     }
 
     /**
-     * Invoked by constructor and by {@link #getParameterValues()} for setting all parameters other than axis lengths.
+     * Gets the dimension of input points.
      *
-     * @param pg         Where to set the parameters.
-     * @param semiMinor  The semi minor axis length, in unit of {@code unit}.
-     * @param unit       The unit of measurement to declare.
-     * @param Δf         Ignored.
+     * @return The input dimension, which is 2 or 3.
      */
     @Override
-    final void completeParameters(final Parameters pg, final double semiMinor, final Unit<?> unit, double Δf) {
-        super.completeParameters(pg, semiMinor, unit, Δf);
-        if (pg != context) {
-            Δf = Δfmod / semiMinor;
-            pg.getOrCreate(Molodensky.AXIS_LENGTH_DIFFERENCE).setValue(Δa, unit);
-            pg.getOrCreate(Molodensky.FLATTENING_DIFFERENCE) .setValue(Δf, Unit.ONE);
-        }
-        if (grid instanceof DatumShiftGridFile<?,?>) {
-            ((DatumShiftGridFile<?,?>) grid).setFileParameters(pg);
-        }
+    public int getSourceDimensions() {
+        return ellipsoidToCentric.getSourceDimensions();
+    }
+
+    /**
+     * Gets the dimension of output points.
+     *
+     * @return The output dimension, which is 2 or 3.
+     */
+    @Override
+    public int getTargetDimensions() {
+        return centricToEllipsoid.getTargetDimensions();
     }
 
     /**
@@ -285,75 +370,51 @@ public class InterpolatedGeocentricTrans
                             final double[] dstPts, final int dstOff,
                             final boolean derivate) throws TransformException
     {
+        /*
+         * Interpolate the geocentric translation (ΔX,ΔY,ΔZ) for the source coordinate (λ,φ).
+         * The translation that we got is in metres, which we convert into normalized units.
+         */
         final double[] vector = new double[3];
-        final double λ = srcPts[srcOff];
-        final double φ = srcPts[srcOff+1];
-        grid.interpolateAtNormalized(λ, φ, vector);
-        return transform(λ, φ, isSource3D ? srcPts[srcOff+2] : 0,
-                dstPts, dstOff, vector[0], vector[1], vector[2], null, derivate);
-    }
-
-    /**
-     * Transforms the (λ,φ) or (λ,φ,<var>h</var>) coordinates between two geographic CRS.
-     * This method performs the same work than the above
-     * {@link #transform(double[], int, double[], int, boolean) transform(…)} method,
-     * but on an arbitrary amount of coordinates and without computing derivative.
-     *
-     * @throws TransformException if a point can not be transformed.
-     */
-    @Override
-    public void transform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, int numPts) throws TransformException {
-        int srcInc = isSource3D ? 3 : 2;
-        int dstInc = isTarget3D ? 3 : 2;
-        int offFinal = 0;
-        double[] dstFinal  = null;
-        if (srcPts == dstPts) {
-            switch (IterationStrategy.suggest(srcOff, srcInc, dstOff, dstInc, numPts)) {
-                case ASCENDING: {
-                    break;
-                }
-                case DESCENDING: {
-                    srcOff += (numPts-1) * srcInc;  srcInc = -srcInc;
-                    dstOff += (numPts-1) * dstInc;  dstInc = -dstInc;
-                    break;
-                }
-                default: {  // BUFFER_SOURCE, but also a reasonable default for any case.
-                    srcPts = Arrays.copyOfRange(srcPts, srcOff, srcOff + numPts*srcInc);
-                    srcOff = 0;
-                    break;
-                }
-                case BUFFER_TARGET: {
-                    dstFinal = dstPts;
-                    offFinal = dstOff;
-                    dstPts = new double[numPts * dstInc];
-                    dstOff = 0;
-                    break;
-                }
-            }
-        }
-        final double[] offset = new double[3];
-        while (--numPts >= 0) {
-            final double λ = srcPts[srcOff  ];
-            final double φ = srcPts[srcOff+1];
-            grid.interpolateAtNormalized(λ, φ, offset);
-            transform(λ, φ, isSource3D ? srcPts[srcOff+2] : 0,
-                      dstPts, dstOff, offset[0], offset[1], offset[2], null, false);
-            srcOff += srcInc;
-            dstOff += dstInc;
-        }
-        if (dstFinal != null) {
-            System.arraycopy(dstPts, 0, dstFinal, offFinal, dstPts.length);
+        grid.interpolateInCell(grid.normalizedToGridX(srcPts[srcOff]),              // In radians
+                               grid.normalizedToGridY(srcPts[srcOff+1]), vector);
+        final double tX = vector[0] / semiMajor;
+        final double tY = vector[1] / semiMajor;
+        final double tZ = vector[2] / semiMajor;
+        /*
+         * Geographic → Geocentric conversion. Result stored in a temporary array
+         * because we do not check if the target points are two or three dimensional.
+         */
+        final Matrix m1 = ellipsoidToCentric.transform(srcPts, srcOff, vector, 0, derivate);
+        /*
+         * Apply the geocentric translation on the geocentric coordinates. If the coordinates were in metres,
+         * we would have only additions. But since we use normalized units, we also need to convert from the
+         * normalization relative to the source ellipsoid to normalization relative to the target ellipsoid.
+         * This is the purpose of the scale factor.
+         */
+        vector[0] = (vector[0] + tX) * scale;
+        vector[1] = (vector[1] + tY) * scale;
+        vector[2] = (vector[2] + tZ) * scale;
+        /*
+         * Final Geocentric → Geographic conversion, and compute the derivative matrix if the user asked for it.
+         */
+        final Matrix m2 = centricToEllipsoid.transform(vector, 0, dstPts, dstOff, derivate);
+        if (m1 == null || m2 == null) {
+            return null;
         }
+        /*
+         * We could improve a little bit the precision by computing the derivative in the interpolation grid:
+         *
+         *     grid.derivativeInCell(grid.normalizedToGridX(λ), grid.normalizedToGridY(φ));
+         *
+         * But this is a little bit complicated (need to convert to normalized units and divide by the grid
+         * cell size) for a very small difference. For now we neglect that part.
+         */
+        final Matrix3 t = new Matrix3();
+        t.m00 = scale;
+        t.m11 = scale;
+        return Matrices.multiply(m2, Matrices.multiply(t, m1));
     }
 
-    /*
-     * NOTE: we do not bother to override the methods expecting a 'float' array because those methods should
-     *       be rarely invoked. Since there is usually LinearTransforms before and after this transform, the
-     *       conversion between float and double will be handle by those LinearTransforms.   If nevertheless
-     *       this MolodenskyTransform is at the beginning or the end of a transformation chain,  the methods
-     *       inherited from the subclass will work (but may be slightly slower).
-     */
-
     /**
      * Returns the inverse of this interpolated geocentric transform.
      * The source ellipsoid of the returned transform will be the target ellipsoid of this transform, and conversely.
@@ -389,7 +450,12 @@ public class InterpolatedGeocentricTrans
         /**
          * Serial number for inter-operability with different versions.
          */
-        private static final long serialVersionUID = -3520896803296425651L;
+        private static final long serialVersionUID = -3481207454803064521L;
+
+        /**
+         * Initial translation values to use before to improve the accuracy with the interpolation.
+         */
+        private final double tX, tY, tZ;
 
         /**
          * Constructs the inverse of an interpolated geocentric transform.
@@ -400,6 +466,9 @@ public class InterpolatedGeocentricTrans
          */
         Inverse(final InterpolatedGeocentricTransform inverse, final Ellipsoid source, final Ellipsoid target) {
            super(inverse, source, target);
+           tX = grid.getCellMean(0) / semiMajor;
+           tY = grid.getCellMean(1) / semiMajor;
+           tZ = grid.getCellMean(2) / semiMajor;
         }
 
         /**
@@ -415,87 +484,45 @@ public class InterpolatedGeocentricTrans
                                 final double[] dstPts, final int dstOff,
                                 final boolean derivate) throws TransformException
         {
-            return transform(srcPts[srcOff], srcPts[srcOff+1], isSource3D ? srcPts[srcOff+2] : 0,
-                             dstPts, dstOff, tX, tY, tZ, new double[3], derivate);
-        }
-
-        /**
-         * Transforms the (λ,φ) or (λ,φ,<var>h</var>) coordinates between two geographic CRS.
-         * This method performs the same work than the above
-         * {@link #transform(double[], int, double[], int, boolean) transform(…)} method,
-         * but on an arbitrary amount of coordinates and without computing derivative.
-         *
-         * @throws TransformException if a point can not be transformed.
-         */
-        @Override
-        public void transform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, int numPts) throws TransformException {
-            int srcInc = isSource3D ? 3 : 2;
-            int dstInc = isTarget3D ? 3 : 2;
-            int offFinal = 0;
-            double[] dstFinal  = null;
-            if (srcPts == dstPts) {
-                switch (IterationStrategy.suggest(srcOff, srcInc, dstOff, dstInc, numPts)) {
-                    case ASCENDING: {
-                        break;
-                    }
-                    case DESCENDING: {
-                        srcOff += (numPts-1) * srcInc;  srcInc = -srcInc;
-                        dstOff += (numPts-1) * dstInc;  dstInc = -dstInc;
-                        break;
-                    }
-                    default: {  // BUFFER_SOURCE, but also a reasonable default for any case.
-                        srcPts = Arrays.copyOfRange(srcPts, srcOff, srcOff + numPts*srcInc);
-                        srcOff = 0;
-                        break;
-                    }
-                    case BUFFER_TARGET: {
-                        dstFinal = dstPts;
-                        offFinal = dstOff;
-                        dstPts = new double[numPts * dstInc];
-                        dstOff = 0;
-                        break;
-                    }
-                }
-            }
-            final double[] offset = new double[3];
-            while (--numPts >= 0) {
-                transform(srcPts[srcOff], srcPts[srcOff+1], isSource3D ? srcPts[srcOff+2] : 0,
-                          dstPts, dstOff, tX, tY, tZ, offset, false);
-                srcOff += srcInc;
-                dstOff += dstInc;
-            }
-            if (dstFinal != null) {
-                System.arraycopy(dstPts, 0, dstFinal, offFinal, dstPts.length);
+            /*
+             * Geographic → Geocentric conversion. Result stored in a temporary array
+             * because we do not check if the target points are two or three dimensional.
+             */
+            final double[] vector = new double[3];
+            final Matrix m1 = ellipsoidToCentric.transform(srcPts, srcOff, vector, 0, derivate);
+            final double x = vector[0];
+            final double y = vector[1];
+            final double z = vector[2];
+            /*
+             * First approximation of geocentric translation, by using average values or values
+             * specified by the authority (e.g. as in the "France Geocentric Interpolation" method).
+             */
+            vector[0] = x - tX;
+            vector[1] = y - tY;
+            vector[2] = z - tZ;
+            centricToEllipsoid.transform(vector, 0, vector, 0, derivate);
+            /*
+             * We got a (λ,φ) using an approximative geocentric translation. Now interpolate the "real"
+             * geocentric interpolation at that location and get the (λ,φ) again. In theory, we just
+             * iterate until we got the desired precision. But in practice a single interation is enough.
+             */
+            grid.interpolateInCell(grid.normalizedToGridX(vector[0]),
+                                   grid.normalizedToGridY(vector[1]), vector);
+            vector[0] = (x - vector[0] / semiMajor) * scale;
+            vector[1] = (y - vector[1] / semiMajor) * scale;
+            vector[2] = (z - vector[2] / semiMajor) * scale;
+            /*
+             * Final Geocentric → Geographic conversion, and compute the derivative matrix if the user asked for it.
+             * We neglect the derivative provided by grid.derivativeInCell(…).
+             */
+            final Matrix m2 = centricToEllipsoid.transform(vector, 0, dstPts, dstOff, derivate);
+            if (m1 == null || m2 == null) {
+                return null;
             }
+            final Matrix3 t = new Matrix3();
+            t.m00 = scale;
+            t.m11 = scale;
+            return Matrices.multiply(m2, Matrices.multiply(t, m1));
         }
     }
-
-    /**
-     * Returns a description of the internal parameters of this {@code InterpolatedGeocentricTransform} transform.
-     * The returned group contains parameters for the source ellipsoid semi-axis lengths and the differences between
-     * source and target ellipsoid parameters.
-     *
-     * <div class="note"><b>Note:</b>
-     * this method is mostly for {@linkplain org.apache.sis.io.wkt.Convention#INTERNAL debugging purposes}
-     * since the isolation of non-linear parameters in this class is highly implementation dependent.
-     * Most GIS applications will instead be interested in the {@linkplain #getContextualParameters()
-     * contextual parameters}.</div>
-     *
-     * @return A description of the internal parameters.
-     */
-    @Debug
-    @Override
-    public ParameterDescriptorGroup getParameterDescriptors() {
-        final ParameterDescriptor<?>[] param = new ParameterDescriptor<?>[] {
-                Molodensky.DIMENSION,
-                Molodensky.SRC_SEMI_MAJOR,
-                Molodensky.SRC_SEMI_MINOR,
-                Molodensky.AXIS_LENGTH_DIFFERENCE,
-                Molodensky.FLATTENING_DIFFERENCE,
-                FranceGeocentricInterpolation.FILE
-        };
-        return new ParameterBuilder().setRequired(true)
-                .setCodeSpace(Citations.SIS, Constants.SIS)
-                .addName(context.getDescriptor().getName()).createGroup(param);
-    }
 }

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransform2D.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransform2D.java?rev=1719607&r1=1719606&r2=1719607&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransform2D.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransform2D.java [UTF-8] Sat Dec 12 00:51:25 2015
@@ -39,7 +39,7 @@ final class InterpolatedGeocentricTransf
     /**
      * Serial number for compatibility with different versions.
      */
-    private static final long serialVersionUID = 3050077982181110135L;
+    private static final long serialVersionUID = 7182170631400046124L;
 
     /**
      * Constructs a 2D transform.
@@ -92,7 +92,7 @@ final class InterpolatedGeocentricTransf
         /**
          * Serial number for inter-operability with different versions.
          */
-        private static final long serialVersionUID = 3175846640786132902L;
+        private static final long serialVersionUID = 1172500439043982455L;
 
         /**
          * Constructs the inverse of an interpolated geocentric transform.



Mime
View raw message