sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1724531 [7/13] - in /sis/trunk: ./ application/sis-console/src/main/artifact/bin/ application/sis-console/src/main/artifact/log/ application/sis-console/src/main/java/org/apache/sis/console/ core/sis-build-helper/src/main/java/org/apache/s...
Date Wed, 13 Jan 2016 23:59:41 GMT
Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MolodenskyTransform.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MolodenskyTransform.java?rev=1724531&r1=1724530&r2=1724531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MolodenskyTransform.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MolodenskyTransform.java [UTF-8] Wed Jan 13 23:59:38 2016
@@ -17,28 +17,17 @@
 package org.apache.sis.referencing.operation.transform;
 
 import java.util.Arrays;
-import java.io.Serializable;
 import javax.measure.unit.Unit;
-import javax.measure.quantity.Length;
-import javax.measure.converter.UnitConverter;
 import org.opengis.util.FactoryException;
-import org.opengis.geometry.DirectPosition;
-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.MathTransform;
 import org.opengis.referencing.operation.MathTransformFactory;
 import org.opengis.referencing.operation.TransformException;
-import org.apache.sis.referencing.operation.matrix.Matrices;
-import org.apache.sis.referencing.datum.DefaultEllipsoid;
 import org.apache.sis.internal.referencing.provider.Molodensky;
 import org.apache.sis.internal.referencing.provider.AbridgedMolodensky;
-import org.apache.sis.internal.referencing.provider.MapProjection;
-import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.parameter.Parameters;
-import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.Debug;
 
 import static java.lang.Math.*;
@@ -79,6 +68,16 @@ import static java.lang.Math.*;
  *       axes (usually metres).</li>
  * </ul>
  *
+ * <div class="section">Comparison of Molodensky and geocentric translation</div>
+ * Compared to the <cite>"Geocentric translation (geographic domain)"</cite> method,
+ * the Molodensky method has errors usually within a few centimetres.
+ * The Abridged Molodensky method has more noticeable errors, of a few tenths of centimetres.
+ *
+ * <p>Another difference between Molodensky and geocentric translation methods is their behavior when crossing the
+ * anti-meridian. If a datum shift causes a longitude to cross the anti-meridian (e.g. 179.999° become 180.001°),
+ * the Molodensky method will keep 180.001° as-is while the geocentric translation method will wrap the longitude
+ * to -179.999°. Such wrap-around behavior may or may not be desired, depending on the applications.</p>
+ *
  * @author  Rueben Schulz (UBC)
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Rémi Maréchal (Geomatys)
@@ -86,7 +85,7 @@ import static java.lang.Math.*;
  * @version 0.7
  * @module
  */
-public class MolodenskyTransform extends AbstractMathTransform implements Serializable {
+public class MolodenskyTransform extends MolodenskyFormula {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -102,107 +101,19 @@ public class MolodenskyTransform extends
     private static ParameterDescriptorGroup DESCRIPTOR;
 
     /**
-     * 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 Δφ.
-     */
-    private static final double ANGULAR_SCALE = 1.00000000000391744;
-
-    /**
-     * {@code true} if the source coordinates have a height.
-     */
-    private final boolean isSource3D;
-
-    /**
-     * {@code true} if the target coordinates have a height.
-     */
-    private final boolean isTarget3D;
-
-    /**
-     * {@code true} for the abridged formula, or {@code false} for the complete one.
-     */
-    private 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;
-
-    /**
-     * Semi-major axis length (<var>a</var>) of the source ellipsoid.
-     */
-    private 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()
-     */
-    private final double eccentricitySquared;
-
-    /**
-     * Difference in the semi-major axes of the target and source ellipsoids: {@code Δa = target a - source a}.
-     *
-     * @see DefaultEllipsoid#semiMajorAxisDifference(Ellipsoid)
-     */
-    private final double Δa;
-
-    /**
-     * 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>.
-     */
-    private final double Δfmod;
-
-    /**
-     * The parameters used for creating this conversion.
-     * They are used for formatting <cite>Well Known Text</cite> (WKT) and error messages.
-     *
-     * @see #getContextualParameters()
-     */
-    private final ContextualParameters context;
-
-    /**
      * The inverse of this Molodensky transform.
      *
      * @see #inverse()
      */
-    private MolodenskyTransform inverse;
+    private final MolodenskyTransform inverse;
 
     /**
      * Creates a Molodensky transform from the specified parameters.
-     * This {@code MolodenskyTransform} class expects ordinate values if the following order and units:
+     * This {@code MolodenskyTransform} class expects ordinate values in the following order and units:
      * <ol>
      *   <li>longitudes in <strong>radians</strong> relative to the prime meridian (usually Greenwich),</li>
      *   <li>latitudes in <strong>radians</strong>,</li>
-     *   <li>optionally heights above the ellipsoid, in same units than the source ellipsoids axes.</li>
+     *   <li>optionally heights above the ellipsoid, in same units than the source ellipsoid axes.</li>
      * </ol>
      *
      * For converting geographic coordinates in degrees, {@code MolodenskyTransform} instances
@@ -238,65 +149,49 @@ public class MolodenskyTransform extends
                                   final double tX, final double tY, final double tZ,
                                   final boolean isAbridged)
     {
-        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;
-
-        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 EllipsoidToCartesian, but we need to store them in case the user asks for them.
-         * When both EPSG and OGC parameters exist for equivalent information, we use EPSG ones.
-         */
-        final Unit<Length> unit = src.getAxisUnit();
-        final UnitConverter c = target.getAxisUnit().getConverterTo(unit);
-        context = new ContextualParameters(isAbridged ? AbridgedMolodensky.PARAMETERS : Molodensky.PARAMETERS,
-                                           isSource3D ? 4 : 3, isTarget3D ? 4 : 3);
-        setEPSG(context, unit, Δf);
-        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);
-        /*
-         * Prepare two affine transforms to be executed before and after this MolodenskyTransform:
-         *
-         *   - A "normalization" transform for converting degrees to radians,
-         *   - A "denormalization" transform for for converting radians to degrees.
-         */
-        context.normalizeGeographicInputs(0);
-        context.denormalizeGeographicOutputs(0);
+        super(source, isSource3D, target, isTarget3D, tX, tY, tZ, null, isAbridged,
+                isAbridged ? AbridgedMolodensky.PARAMETERS : Molodensky.PARAMETERS);
+        if (!isSource3D && !isTarget3D) {
+            inverse = new MolodenskyTransform2D(this, source, target);
+        } else {
+            inverse = new MolodenskyTransform(this, source, target);
+        }
+    }
+
+    /**
+     * Constructs the inverse of a Molodensky transform.
+     *
+     * @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.
+     */
+    MolodenskyTransform(final MolodenskyTransform inverse, final Ellipsoid source, final Ellipsoid target) {
+        super(inverse, source, target, inverse.context.getDescriptor());
+        this.inverse = inverse;
     }
 
     /**
-     * Sets the "dim" parameter and the EPSG parameters in the given group.
-     * The OGC parameters other than "dim" are not set by this method.
+     * Invoked by constructor and by {@link #getParameterValues()} for setting all parameters other than axis lengths.
      *
-     * @param pg   Where to set the parameters.
-     * @param unit The unit of measurement to declare.
-     * @param Δf   The flattening difference to set.
-     */
-    private void setEPSG(final Parameters pg, final Unit<?> unit, final double Δf) {
-        final int dim = getSourceDimensions();
-        if (dim == getTargetDimensions()) {
-            pg.getOrCreate(Molodensky.DIMENSION).setValue(dim);
+     * @param pg         Where to set the parameters.
+     * @param semiMinor  Ignored.
+     * @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.
+     */
+    @Override
+    final void completeParameters(final Parameters pg, final double semiMinor, final Unit<?> unit, double Δf) {
+        if (Double.isNaN(Δf)) {
+            Δf = context.doubleValue(Molodensky.FLATTENING_DIFFERENCE);
         }
+        super.completeParameters(pg, semiMinor, unit, Δf);
         pg.getOrCreate(Molodensky.TX)                    .setValue(tX, unit);
         pg.getOrCreate(Molodensky.TY)                    .setValue(tY, unit);
         pg.getOrCreate(Molodensky.TZ)                    .setValue(tZ, unit);
         pg.getOrCreate(Molodensky.AXIS_LENGTH_DIFFERENCE).setValue(Δa, unit);
         pg.getOrCreate(Molodensky.FLATTENING_DIFFERENCE) .setValue(Δf, Unit.ONE);
+        if (pg != context) {
+            pg.parameter("abridged").setValue(isAbridged);  // Only in internal parameters.
+        }
     }
 
     /**
@@ -319,7 +214,7 @@ public class MolodenskyTransform extends
      * @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 isAbridged  {@code true} for the abridged formula, or {@code false} for the complete one.
-     * @return The transformation between geographic coordinates.
+     * @return The transformation between geographic coordinates in degrees.
      * @throws FactoryException if an error occurred while creating a transform.
      */
     public static MathTransform createGeodeticTransformation(final MathTransformFactory factory,
@@ -331,79 +226,14 @@ public class MolodenskyTransform extends
         final MolodenskyTransform tr;
         if (!isSource3D && !isTarget3D) {
             tr = new MolodenskyTransform2D(source, target, tX, tY, tZ, isAbridged);
-            tr.inverse = new MolodenskyTransform2D(target, source, -tX, -tY, -tZ, isAbridged);
         } else {
             tr = new MolodenskyTransform(source, isSource3D, target, isTarget3D, tX, tY, tZ, isAbridged);
-            tr.inverse = new MolodenskyTransform(target, isTarget3D, source, isSource3D, -tX, -tY, -tZ, isAbridged);
         }
-        tr.inverse.inverse = tr;
+        tr.inverse.context.completeTransform(factory, null);
         return tr.context.completeTransform(factory, tr);
     }
 
     /**
-     * Returns the unit of measurement of axis lengths. Current implementation arbitrarily
-     * returns the units of {@link #Δa} (this may change in any future SIS version).
-     */
-    private Unit<?> getLinearUnit() {
-        return context.getOrCreate(Molodensky.AXIS_LENGTH_DIFFERENCE).getUnit();
-    }
-
-    /**
-     * Returns the parameters used for creating the complete transformation. Those parameters describe a sequence
-     * of <cite>normalize</cite> → {@code this} → <cite>denormalize</cite> transforms, <strong>not</strong>
-     * including {@linkplain org.apache.sis.referencing.cs.CoordinateSystems#swapAndScaleAxes axis swapping}.
-     * Those parameters are used for formatting <cite>Well Known Text</cite> (WKT) and error messages.
-     *
-     * @return The parameters values for the sequence of
-     *         <cite>normalize</cite> → {@code this} → <cite>denormalize</cite> transforms.
-     */
-    @Override
-    protected ContextualParameters getContextualParameters() {
-        return context;
-    }
-
-    /**
-     * Returns a copy of internal parameter values of this {@code MolodenskyTransform}.
-     * The returned group contains parameter values for the eccentricity and the shift among others.
-     *
-     * <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 copy of the internal parameter values for this transform.
-     */
-    @Debug
-    @Override
-    public ParameterValueGroup getParameterValues() {
-        final Parameters pg = Parameters.castOrWrap(getParameterDescriptors().createValue());
-        final Unit<?> unit = getLinearUnit();
-        setEPSG(pg, unit, context.doubleValue(Molodensky.FLATTENING_DIFFERENCE));
-        pg.getOrCreate(Molodensky.SRC_SEMI_MAJOR).setValue(semiMajor, unit);
-        pg.getOrCreate(MapProjection.ECCENTRICITY).setValue(sqrt(eccentricitySquared));
-        pg.parameter("abridged").setValue(isAbridged);
-        return pg;
-    }
-
-    /**
-     * Returns a description of the internal parameters of this {@code MolodenskyTransform} transform.
-     * The returned group contains parameter descriptors for the number of dimensions and the eccentricity.
-     *
-     * @return A description of the internal parameters.
-     */
-    @Debug
-    @Override
-    public ParameterDescriptorGroup getParameterDescriptors() {
-        synchronized (MolodenskyTransform.class) {
-            if (DESCRIPTOR == null) {
-                DESCRIPTOR = Molodensky.internal();
-            }
-            return DESCRIPTOR;
-        }
-    }
-
-    /**
      * Returns {@code true} if this transform is the identity one.
      * Molodensky transform is considered identity (minus rounding errors) if:
      *
@@ -422,49 +252,6 @@ public class MolodenskyTransform 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;
-    }
-
-    /**
-     * Computes the derivative at the given location.
-     * This method relaxes a little bit the {@code MathTransform} contract by accepting two- or three-dimensional
-     * points even if the number of dimensions does not match the {@link #getSourceDimensions()} or
-     * {@link #getTargetDimensions()} values.
-     *
-     * @param  point The coordinate point where to evaluate the derivative.
-     * @return The derivative at the specified point (never {@code null}).
-     * @throws TransformException if the derivative can not be evaluated at the specified point.
-     */
-    @Override
-    public Matrix derivative(final DirectPosition point) throws TransformException {
-        final int dim = point.getDimension();
-        final boolean withHeight;
-        final double h;
-        switch (dim) {
-            default: throw mismatchedDimension("point", getSourceDimensions(), dim);
-            case 3:  withHeight = true;  h = point.getOrdinate(2); break;
-            case 2:  withHeight = false; h = 0; break;
-        }
-        return transform(point.getOrdinate(0), point.getOrdinate(1), h, withHeight, null, 0, withHeight, true);
-    }
-
-    /**
      * Transforms the (λ,φ) or (λ,φ,<var>h</var>) coordinates between two geographic CRS,
      * and optionally returns the derivative at that location.
      *
@@ -478,133 +265,7 @@ public class MolodenskyTransform extends
                             final boolean derivate) throws TransformException
     {
         return transform(srcPts[srcOff], srcPts[srcOff+1], isSource3D ? srcPts[srcOff+2] : 0,
-                         isSource3D, dstPts, dstOff, isTarget3D, derivate);
-    }
-
-    /**
-     * Implementation of {@link #transform(double[], int, double[], int, boolean)} with possibility
-     * to override whether the source and target coordinates are two- or three-dimensional.
-     *
-     * @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.
-     *                 May be {@code null} if only the derivative matrix is desired.
-     * @param dstOff   The offset to the location of the transformed point that is stored in the destination array.
-     * @param derivate {@code true} for computing the derivative, or {@code false} if not needed.
-     * @throws TransformException if a point can not be transformed.
-     */
-    private Matrix transform(final double λ, final double φ, final double h, final boolean isSource3D,
-                             final double[] dstPts, int dstOff, final boolean isTarget3D,
-                             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]
-        }
-        final double spcλ = tY*sinλ + tX*cosλ;                      // "spc" stands for "sin plus cos"
-        final double cmsλ = tY*cosλ - tX*sinλ;                      // "cms" stands for "cos minus sin"
-        final double cmsφ = (tZ + t*sinφ)*cosφ - spcλ*sinφ;
-        final double scaleX = ANGULAR_SCALE / (ν*cosφ);
-        final double scaleY = ANGULAR_SCALE / ρ;
-        if (dstPts != null) {
-            dstPts[dstOff++] = λ + (cmsλ * scaleX);
-            dstPts[dstOff++] = φ + (cmsφ * scaleY);
-            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;
+                         dstPts, dstOff, tX, tY, tZ, null, derivate);
     }
 
     /**
@@ -650,9 +311,9 @@ public class MolodenskyTransform extends
             }
         }
         /*
-         * The code in the following loop is basically a copy-and-paste of the code in the above
-         * transform(λ, φ, h, isSource3D, dstPts, dstOff, isTarget3D, false) method, but without the
-         * code for computing the derivative matrix.
+         * The code in the following loop is basically a copy-and-paste of the code in the
+         * MolodenskyFormula.transform(λ, φ, h, …) method, but without derivative matrix
+         * computation and without support for interpolation of (tX,tY,tZ) values in a grid.
          */
         while (--numPts >= 0) {
             final double λ     = srcPts[srcOff++];
@@ -671,17 +332,18 @@ public class MolodenskyTransform extends
             if (!isAbridged) {
                 ρ += h;
                 ν += h;
-                t = t*(0.5/νden + 0.5/ρden) + Δa*eccentricitySquared/νden;
+                t = t*(0.5/νden + 0.5/ρden)                 // = Δf⋅[ν⋅(b/a) + ρ⋅(a/b)]     (without the +h in ν and ρ)
+                        + Δa*eccentricitySquared/νden;      // = Δa⋅[ℯ²⋅ν/a]
             }
             final double spcλ = tY*sinλ + tX*cosλ;
             dstPts[dstOff++] = λ + ANGULAR_SCALE * (tY*cosλ - tX*sinλ) / (ν*cosφ);
             dstPts[dstOff++] = φ + ANGULAR_SCALE * ((t*cosφ - spcλ)*sinφ + tZ*cosφ) / ρ;
             if (isTarget3D) {
-                t = Δfmod * sin2φ;
+                t = Δfmod * sin2φ;                          // A term in the calculation of Δh
                 double d = Δa;
                 if (!isAbridged) {
-                    t /= νden;
-                    d *= νden;
+                    t /= νden;                              // = Δf⋅(b/a)⋅ν⋅sin²φ
+                    d *= νden;                              // = Δa⋅(a/ν)
                 }
                 dstPts[dstOff++] = h + spcλ*cosφ + tZ*sinφ + t - d;
             }
@@ -717,46 +379,25 @@ public class MolodenskyTransform extends
     }
 
     /**
-     * {@inheritDoc}
+     * Returns a description of the internal parameters of this {@code MolodenskyTransform} transform.
+     * The returned group contains parameter descriptors for the number of dimensions and the eccentricity.
      *
-     * @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;
-    }
-
-    /**
-     * Compares the specified object with this math transform for equality.
+     * <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 {@inheritDoc}
+     * @return A description of the internal parameters.
      */
+    @Debug
     @Override
-    public boolean equals(final Object object, final ComparisonMode mode) {
-        if (object == this) {
-            // Slight optimization
-            return true;
-        }
-        if (super.equals(object, mode)) {
-            final MolodenskyTransform that = (MolodenskyTransform) object;
-            return isSource3D == that.isSource3D
-                && isTarget3D == that.isTarget3D
-                && isAbridged == that.isAbridged
-                && Numerics.equals(tX,                  that.tX)
-                && Numerics.equals(tY,                  that.tY)
-                && Numerics.equals(tZ,                  that.tZ)
-                && Numerics.equals(Δa,                  that.Δa)
-                && Numerics.equals(Δfmod,               that.Δfmod)
-                && Numerics.equals(semiMajor,           that.semiMajor)
-                && Numerics.equals(eccentricitySquared, that.eccentricitySquared);
+    public ParameterDescriptorGroup getParameterDescriptors() {
+        synchronized (MolodenskyTransform.class) {
+            if (DESCRIPTOR == null) {
+                DESCRIPTOR = Molodensky.internal();
+            }
+            return DESCRIPTOR;
         }
-        return false;
     }
 }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MolodenskyTransform2D.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MolodenskyTransform2D.java?rev=1724531&r1=1724530&r2=1724531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MolodenskyTransform2D.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MolodenskyTransform2D.java [UTF-8] Wed Jan 13 23:59:38 2016
@@ -50,6 +50,17 @@ final class MolodenskyTransform2D extend
     }
 
     /**
+     * Constructs the inverse of a 2D transform.
+     *
+     * @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.
+     */
+    MolodenskyTransform2D(final MolodenskyTransform inverse, final Ellipsoid source, final Ellipsoid target) {
+        super(inverse, source, target);
+    }
+
+    /**
      * Computes the derivative at the given position point.
      */
     @Override

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ProjectiveTransform.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ProjectiveTransform.java?rev=1724531&r1=1724530&r2=1724531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ProjectiveTransform.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ProjectiveTransform.java [UTF-8] Wed Jan 13 23:59:38 2016
@@ -205,8 +205,8 @@ class ProjectiveTransform extends Abstra
      * equal to <code>{@link Matrix#getNumCol}-1</code>. For example, for square matrix of size 4×4, coordinate
      * points are three-dimensional and stored in the arrays starting at the specified offset ({@code srcOff}) in
      * the order
-     * <code>[x<sub>0</sub>, y<sub>0</sub>, z<sub>0</sub>,
-     *        x<sub>1</sub>, y<sub>1</sub>, z<sub>1</sub>...,
+     * <code>[x₀, y₀, z₀,
+     *        x₁, y₁, z₁...,
      *        x<sub>n</sub>, y<sub>n</sub>, z<sub>n</sub>]</code>.
      *
      * @param srcPts The array containing the source point coordinates.
@@ -276,8 +276,8 @@ class ProjectiveTransform extends Abstra
      * equal to <code>{@link Matrix#getNumCol()} - 1</code>. For example, for square matrix of size 4×4, coordinate
      * points are three-dimensional and stored in the arrays starting at the specified offset ({@code srcOff})
      * in the order
-     * <code>[x<sub>0</sub>, y<sub>0</sub>, z<sub>0</sub>,
-     *        x<sub>1</sub>, y<sub>1</sub>, z<sub>1</sub>...,
+     * <code>[x₀, y₀, z₀,
+     *        x₁, y₁, z₁...,
      *        x<sub>n</sub>, y<sub>n</sub>, z<sub>n</sub>]</code>.
      *
      * @param srcPts The array containing the source point coordinates.

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ProjectiveTransform2D.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ProjectiveTransform2D.java?rev=1724531&r1=1724530&r2=1724531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ProjectiveTransform2D.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ProjectiveTransform2D.java [UTF-8] Wed Jan 13 23:59:38 2016
@@ -33,10 +33,10 @@ import org.opengis.referencing.operation
  * @author  Jan Jezek (UWB)
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.5
- * @version 0.5
+ * @version 0.7
  * @module
  */
-final class ProjectiveTransform2D extends ProjectiveTransform implements MathTransform2D {
+final class ProjectiveTransform2D extends ProjectiveTransform implements MathTransform2D, LinearTransform {
     /**
      * For cross-version compatibility.
      */
@@ -87,9 +87,15 @@ final class ProjectiveTransform2D extend
 
     /**
      * Creates the inverse transform of this object.
+     * The inverse shall be linear and two-dimensional.
      */
     @Override
-    public MathTransform2D inverse() throws NoninvertibleTransformException {
-        return (MathTransform2D) super.inverse();
+    public ProjectiveTransform2D inverse() throws NoninvertibleTransformException {
+        final LinearTransform inv = super.inverse();
+        if (inv instanceof ProjectiveTransform2D) {
+            return (ProjectiveTransform2D) inv;
+        } else {
+            return new ProjectiveTransform2D(inv.getMatrix());
+        }
     }
 }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ScaleTransform.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ScaleTransform.java?rev=1724531&r1=1724530&r2=1724531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ScaleTransform.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ScaleTransform.java [UTF-8] Wed Jan 13 23:59:38 2016
@@ -184,8 +184,8 @@ final class ScaleTransform extends Abstr
      * equal to <code>{@link Matrix#getNumCol}-1</code>. For example, for square matrix of size 4×4, coordinate
      * points are three-dimensional and stored in the arrays starting at the specified offset ({@code srcOff}) in
      * the order
-     * <code>[x<sub>0</sub>, y<sub>0</sub>, z<sub>0</sub>,
-     *        x<sub>1</sub>, y<sub>1</sub>, z<sub>1</sub>...,
+     * <code>[x₀, y₀, z₀,
+     *        x₁, y₁, z₁...,
      *        x<sub>n</sub>, y<sub>n</sub>, z<sub>n</sub>]</code>.
      *
      * @param srcPts The array containing the source point coordinates.
@@ -218,8 +218,8 @@ final class ScaleTransform extends Abstr
      * equal to <code>{@link Matrix#getNumCol()} - 1</code>. For example, for square matrix of size 4×4, coordinate
      * points are three-dimensional and stored in the arrays starting at the specified offset ({@code srcOff})
      * in the order
-     * <code>[x<sub>0</sub>, y<sub>0</sub>, z<sub>0</sub>,
-     *        x<sub>1</sub>, y<sub>1</sub>, z<sub>1</sub>...,
+     * <code>[x₀, y₀, z₀,
+     *        x₁, y₁, z₁...,
      *        x<sub>n</sub>, y<sub>n</sub>, z<sub>n</sub>]</code>.
      *
      * @param srcPts The array containing the source point coordinates.

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/formulas.html
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/formulas.html?rev=1724531&r1=1724530&r2=1724531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/formulas.html [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/formulas.html [UTF-8] Wed Jan 13 23:59:38 2016
@@ -169,7 +169,7 @@
 
 
 
-    <h2>DeormalizeGeographic</h2>
+    <h2>DenormalizeGeographic</h2>
     <math display="block" alttext="MathML capable browser required">
       <mfenced open="[" close="]">
         <mtable>

Modified: sis/trunk/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod?rev=1724531&r1=1724530&r2=1724531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod [UTF-8] Wed Jan 13 23:59:38 2016
@@ -3,7 +3,8 @@
 org.apache.sis.internal.referencing.provider.Affine
 org.apache.sis.internal.referencing.provider.Geographic3Dto2D
 org.apache.sis.internal.referencing.provider.GeographicOffsets
-org.apache.sis.internal.referencing.provider.GeographicOffsets3D
+org.apache.sis.internal.referencing.provider.GeographicOffsets2D
+org.apache.sis.internal.referencing.provider.VerticalOffset
 org.apache.sis.internal.referencing.provider.LongitudeRotation
 org.apache.sis.internal.referencing.provider.GeocentricTranslation
 org.apache.sis.internal.referencing.provider.PositionVector7Param
@@ -38,3 +39,6 @@ org.apache.sis.internal.referencing.prov
 org.apache.sis.internal.referencing.provider.PolarStereographicNorth
 org.apache.sis.internal.referencing.provider.PolarStereographicSouth
 org.apache.sis.internal.referencing.provider.ObliqueStereographic
+org.apache.sis.internal.referencing.provider.NTv2
+org.apache.sis.internal.referencing.provider.NADCON
+org.apache.sis.internal.referencing.provider.FranceGeocentricInterpolation

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/FormulasTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/FormulasTest.java?rev=1724531&r1=1724530&r2=1724531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/FormulasTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/FormulasTest.java [UTF-8] Wed Jan 13 23:59:38 2016
@@ -17,6 +17,7 @@
 package org.apache.sis.internal.referencing;
 
 import org.apache.sis.internal.metadata.ReferencingServices;
+import org.apache.sis.measure.Longitude;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
@@ -28,15 +29,24 @@ import static org.junit.Assert.*;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.4
- * @version 0.5
+ * @version 0.7
  * @module
  */
 public final strictfp class FormulasTest extends TestCase {
     /**
+     * Verifies the {@link Formulas#LONGITUDE_MAX} constant.
+     */
+    @Test
+    public void verifyLongitudeMax() {
+        assertTrue(Formulas.LONGITUDE_MAX > Longitude.MAX_VALUE);
+        assertTrue(StrictMath.ulp(Formulas.LONGITUDE_MAX) <= Formulas.ANGULAR_TOLERANCE);
+    }
+
+    /**
      * Verifies the {@link Formulas#JULIAN_YEAR_LENGTH} constant.
      */
     @Test
-    public void testConstants() {
+    public void verifyJulianYearLength() {
         assertEquals(StrictMath.round(365.25 * 24 * 60 * 60 * 1000), Formulas.JULIAN_YEAR_LENGTH);
     }
 

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/AllProvidersTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/AllProvidersTest.java?rev=1724531&r1=1724530&r2=1724531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/AllProvidersTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/AllProvidersTest.java [UTF-8] Wed Jan 13 23:59:38 2016
@@ -53,7 +53,8 @@ public final strictfp class AllProviders
         return new Class<?>[] {
             Affine.class,
             GeographicOffsets.class,
-            GeographicOffsets3D.class,
+            GeographicOffsets2D.class,
+            VerticalOffset.class,
             LongitudeRotation.class,
             CoordinateFrameRotation.class,
             CoordinateFrameRotation2D.class,
@@ -88,7 +89,10 @@ public final strictfp class AllProviders
             PolarStereographicC.class,
             PolarStereographicNorth.class,
             PolarStereographicSouth.class,
-            ObliqueStereographic.class
+            ObliqueStereographic.class,
+            NTv2.class,
+            NADCON.class,
+            FranceGeocentricInterpolation.class
         };
     }
 

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/GeocentricTranslationTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/GeocentricTranslationTest.java?rev=1724531&r1=1724530&r2=1724531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/GeocentricTranslationTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/GeocentricTranslationTest.java [UTF-8] Wed Jan 13 23:59:38 2016
@@ -21,12 +21,15 @@ import org.opengis.parameter.ParameterVa
 import org.opengis.referencing.datum.Ellipsoid;
 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.system.DefaultFactories;
 import org.apache.sis.internal.referencing.Formulas;
 import org.apache.sis.parameter.Parameters;
 import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.referencing.operation.matrix.Matrix4;
 import org.apache.sis.referencing.operation.transform.CoordinateDomain;
+import org.apache.sis.referencing.operation.transform.EllipsoidToCentricTransform;
 import org.apache.sis.referencing.operation.transform.LinearTransform;
 import org.apache.sis.referencing.operation.transform.MathTransformTestCase;
 import org.apache.sis.test.DependsOnMethod;
@@ -34,7 +37,7 @@ import org.apache.sis.test.DependsOn;
 import org.junit.Test;
 
 import static java.lang.StrictMath.toRadians;
-import static org.junit.Assert.*;
+import static org.opengis.test.Assert.*;
 
 
 /**
@@ -54,6 +57,12 @@ import static org.junit.Assert.*;
 })
 public final strictfp class GeocentricTranslationTest extends MathTransformTestCase {
     /**
+     * Geocentric translation parameters for transforming a point in the North Sea from WGS84 to ED50.
+     * They are the parameters to use for transformation of the point given by {@link #samplePoint(int)}.
+     */
+    public static final double TX = 84.87, TY = 96.49, TZ = 116.95;
+
+    /**
      * Returns the sample point for a step in the example given by the EPSG guidance note.
      *
      * <blockquote><b>Source:</b>
@@ -137,6 +146,41 @@ public final strictfp class GeocentricTr
     }
 
     /**
+     * Creates a datum shift operation without using the provider. This method is equivalent to the
+     * <cite>"Geocentric translations (geog3D domain)"</cite> method (EPSG:1035), but the transform
+     * steps are created without the use of any {@link ParameterValueGroup}. This way to create the
+     * datum shift is provided for better separation of aspects being tested.
+     *
+     * @param factory  The factory to use for creating the transforms.
+     * @param source   The source ellipsoid.
+     * @param target   The target ellipsoid.
+     * @param tX       Geocentric translation on the X axis.
+     * @param tY       Geocentric translation on the Y axis.
+     * @param tZ       Geocentric translation on the Z axis.
+     * @return Transform performing the datum shift.
+     * @throws FactoryException if an error occurred while creating the transform.
+     * @throws NoninvertibleTransformException if an error occurred while creating the transform.
+     *
+     * @see #testGeographicDomain()
+     */
+    public static MathTransform createDatumShiftForGeographic3D(
+            final MathTransformFactory factory,
+            final Ellipsoid source, final Ellipsoid target,
+            final double tX, final double tY, final double tZ)
+            throws FactoryException, NoninvertibleTransformException
+    {
+        final Matrix4 translation = new Matrix4();
+        translation.m03 = tX;
+        translation.m13 = tY;
+        translation.m23 = tZ;
+        final MathTransform step1 = EllipsoidToCentricTransform.createGeodeticConversion(factory, source, true);
+        final MathTransform step3 = EllipsoidToCentricTransform.createGeodeticConversion(factory, target, true).inverse();
+        final MathTransform step2 = factory.createAffineTransform(translation);
+        return factory.createConcatenatedTransform(step1,
+               factory.createConcatenatedTransform(step2, step3));
+    }
+
+    /**
      * Creates a "Geographic 2D to 3D → Geocentric → Affine → Geographic → Geographic 3D to 2D" chain
      * using EPSG or OGC standard operation methods and parameters. This is used for integration tests.
      *
@@ -148,17 +192,21 @@ public final strictfp class GeocentricTr
         final Parameters values = Parameters.castOrWrap(factory.getDefaultParameters("Geocentric translations (geog2D domain)"));
         setTranslation(values);
         setEllipsoids(values, CommonCRS.WGS84.ellipsoid(), CommonCRS.ED50.ellipsoid());
-        return Geographic3Dto2DTest.createDatumShiftForGeographic2D(factory,
-                new GeocentricTranslation().createMathTransform(factory, values), values);
+        final MathTransform gt = new GeocentricTranslation().createMathTransform(factory, values);
+        assertFalse("isIdentity", gt.isIdentity());
+        assertEquals("sourceDimensions", 3, gt.getSourceDimensions());
+        assertEquals("targetDimensions", 3, gt.getTargetDimensions());
+        assertInstanceOf("Geocentric translation", LinearTransform.class, gt);
+        return Geographic3Dto2DTest.createDatumShiftForGeographic2D(factory, gt, values);
     }
 
     /**
      * Sets the translation parameters in the given parameter value group.
      */
     private static void setTranslation(final ParameterValueGroup values) {
-        values.parameter("X-axis translation").setValue( 84.87);
-        values.parameter("Y-axis translation").setValue( 96.49);
-        values.parameter("Z-axis translation").setValue(116.95);
+        values.parameter("X-axis translation").setValue(TX);
+        values.parameter("Y-axis translation").setValue(TY);
+        values.parameter("Z-axis translation").setValue(TZ);
     }
 
     /**

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/Geographic3Dto2DTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/Geographic3Dto2DTest.java?rev=1724531&r1=1724530&r2=1724531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/Geographic3Dto2DTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/Geographic3Dto2DTest.java [UTF-8] Wed Jan 13 23:59:38 2016
@@ -83,6 +83,8 @@ public final strictfp class Geographic3D
     static MathTransform createDatumShiftForGeographic2D(final MathTransformFactory factory,
             final MathTransform affine, final Parameters pv) throws FactoryException
     {
+        assertEquals("sourceDimensions", 3, affine.getSourceDimensions());
+        assertEquals("targetDimensions", 3, affine.getTargetDimensions());
         /*
          * Create a "Geographic to Geocentric" conversion with ellipsoid axis length units converted to metres
          * (the unit implied by SRC_SEMI_MAJOR) because it is the unit of Bursa-Wolf parameters that we created above.
@@ -91,12 +93,19 @@ public final strictfp class Geographic3D
         step.getOrCreate(MapProjection.SEMI_MAJOR).setValue(pv.doubleValue(GeocentricAffineBetweenGeographic.SRC_SEMI_MAJOR));
         step.getOrCreate(MapProjection.SEMI_MINOR).setValue(pv.doubleValue(GeocentricAffineBetweenGeographic.SRC_SEMI_MINOR));
         MathTransform toGeocentric = factory.createParameterizedTransform(step);
+        assertEquals("sourceDimensions", 3, toGeocentric.getSourceDimensions());
+        assertEquals("targetDimensions", 3, toGeocentric.getTargetDimensions());
+
         final MathTransform reduce = factory.createParameterizedTransform(factory.getDefaultParameters("Geographic3D to 2D conversion"));
+        assertEquals("sourceDimensions", 3, reduce.getSourceDimensions());
+        assertEquals("targetDimensions", 2, reduce.getTargetDimensions());
         try {
             toGeocentric = factory.createConcatenatedTransform(reduce.inverse(), toGeocentric);
         } catch (NoninvertibleTransformException e) {
             throw new FactoryException(e);
         }
+        assertEquals("sourceDimensions", 2, toGeocentric.getSourceDimensions());
+        assertEquals("targetDimensions", 3, toGeocentric.getTargetDimensions());
         /*
          * Create a "Geocentric to Geographic" conversion with ellipsoid axis length units converted to metres
          * because this is the unit of the Geocentric CRS used above.
@@ -105,7 +114,12 @@ public final strictfp class Geographic3D
         step.getOrCreate(MapProjection.SEMI_MAJOR).setValue(pv.doubleValue(GeocentricAffineBetweenGeographic.TGT_SEMI_MAJOR));
         step.getOrCreate(MapProjection.SEMI_MINOR).setValue(pv.doubleValue(GeocentricAffineBetweenGeographic.TGT_SEMI_MINOR));
         MathTransform toGeographic = factory.createParameterizedTransform(step);
+        assertEquals("sourceDimensions", 3, toGeographic.getSourceDimensions());
+        assertEquals("targetDimensions", 3, toGeographic.getTargetDimensions());
+
         toGeographic = factory.createConcatenatedTransform(toGeographic, reduce);
+        assertEquals("sourceDimensions", 3, toGeographic.getSourceDimensions());
+        assertEquals("targetDimensions", 2, toGeographic.getTargetDimensions());
         /*
          * The  Geocentric → Affine → Geographic  chain.
          */

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/GeographicOffsetsTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/GeographicOffsetsTest.java?rev=1724531&r1=1724530&r2=1724531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/GeographicOffsetsTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/GeographicOffsetsTest.java [UTF-8] Wed Jan 13 23:59:38 2016
@@ -16,17 +16,24 @@
  */
 package org.apache.sis.internal.referencing.provider;
 
+import javax.measure.unit.NonSI;
 import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterValueGroup;
+import org.opengis.referencing.operation.MathTransformFactory;
 import org.opengis.referencing.operation.TransformException;
-import org.opengis.test.referencing.TransformTestCase;
 import org.apache.sis.internal.referencing.Formulas;
+import org.apache.sis.internal.system.DefaultFactories;
+import org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory;
+
+// Test dependencies
+import org.opengis.test.referencing.TransformTestCase;
+import org.apache.sis.referencing.cs.HardCodedCS;
 import org.apache.sis.test.DependsOn;
 import org.junit.Test;
 
 
 /**
- * Tests the {@link GeographicOffsets} and {@link GeographicOffsets3D} classes.
+ * Tests the {@link GeographicOffsets}, {@link GeographicOffsets2D} and {@link VerticalOffset} classes.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.7
@@ -36,26 +43,26 @@ import org.junit.Test;
 @DependsOn(AffineTest.class)
 public final strictfp class GeographicOffsetsTest extends TransformTestCase {
     /**
-     * Tests {@code GeographicOffsets.createMathTransform(…)}.
+     * Tests {@code GeographicOffsets2D.createMathTransform(…)}.
      * This test uses the sample point given in §2.4.4.3 of EPSG guide (April 2015).
      *
      * @throws FactoryException should never happen.
      * @throws TransformException should never happen.
      */
     @Test
-    public void testCreateMathTransform2D() throws FactoryException, TransformException {
+    public void testGeographicOffsets2D() throws FactoryException, TransformException {
         testCreateMathTransform(new GeographicOffsets());
     }
 
     /**
-     * Tests {@code GeographicOffsets3D.createMathTransform(…)}.
+     * Tests {@code GeographicOffsets.createMathTransform(…)}.
      *
      * @throws FactoryException should never happen.
      * @throws TransformException should never happen.
      */
     @Test
-    public void testCreateMathTransform3D() throws FactoryException, TransformException {
-        testCreateMathTransform(new GeographicOffsets3D());
+    public void testGeographicOffsets3D() throws FactoryException, TransformException {
+        testCreateMathTransform(new GeographicOffsets2D());
     }
 
     /**
@@ -77,4 +84,63 @@ public final strictfp class GeographicOf
         target[0] = 23 + (48 + 16.515 /60) /60;     // 23°48′16.515″E
         verifyTransform(source, target);
     }
+
+    /**
+     * Tests {@code VerticalOffset.createMathTransform(…)}.
+     * This test uses the sample point given in §2.4.2.1 of EPSG guide (April 2015)
+     * for the <cite>"KOC CD height to KOC WD depth (ft) (1)"</cite> transformation (EPSG:5453).
+     *
+     * <p><b>IMPORTANT:</b> since the source and target axis directions are opposite, the input coordinate
+     * need to be multiplied by -1 <strong>before</strong> the operation is applied. This order is required
+     * for consistency with the sign of <cite>"Vertical Offset"</cite> parameter value.</p>
+     *
+     * @throws FactoryException should never happen.
+     * @throws TransformException should never happen.
+     */
+    @Test
+    public void testVerticalOffset() throws FactoryException, TransformException {
+        final VerticalOffset provider = new VerticalOffset();
+        final ParameterValueGroup pv = provider.getParameters().createValue();
+        pv.parameter("Vertical Offset").setValue(15.55, NonSI.FOOT);
+        transform = provider.createMathTransform(null, pv);
+        tolerance = Formulas.LINEAR_TOLERANCE;
+        final double[] source = new double[transform.getSourceDimensions()];
+        final double[] target = new double[transform.getTargetDimensions()];
+        source[0] = -2.55;              // 2.55 metres up, sign reversed in order to match target axis direction.
+        target[0] =  7.18 * 0.3048;     // 7.18 feet down.
+        verifyTransform(source, target);
+    }
+
+    /**
+     * Tests {@code VerticalOffset.createMathTransform(…)} indirectly, through a call to the math transform factory
+     * with the source and target coordinate systems specified. The intend of this test is to verify that the change
+     * of axis direction is properly handled, given source CRS axis direction up and target CRS axis direction down.
+     *
+     * @throws FactoryException if an error occurred while creating the transform.
+     * @throws TransformException should never happen.
+     */
+    @Test
+    public void testCreateWithContext() throws FactoryException, TransformException {
+        final DefaultMathTransformFactory factory = DefaultFactories.forBuildin(
+                MathTransformFactory.class, DefaultMathTransformFactory.class);
+        final ParameterValueGroup pv = factory.getDefaultParameters("Vertical Offset");
+        pv.parameter("Vertical Offset").setValue(15.55, NonSI.FOOT);
+        /*
+         * Now create the MathTransform. But at the difference of the above testVerticalOffset() method,
+         * we supply information about axis directions. The operation parameter shall have the same sign
+         * than in the EPSG database (which is positive), and the source and target ordinates shall have
+         * the same sign than in the EPSG example (positive too). However we do not test unit conversion
+         * in this method (EPSG sample point uses feet units), only axis direction.
+         */
+        final DefaultMathTransformFactory.Context context = new DefaultMathTransformFactory.Context();
+        context.setSource(HardCodedCS.GRAVITY_RELATED_HEIGHT);  // Direction up, in metres.
+        context.setTarget(HardCodedCS.DEPTH);                   // Direction down, in metres.
+        transform = factory.createParameterizedTransform(pv, context);
+        tolerance = Formulas.LINEAR_TOLERANCE;
+        final double[] source = new double[transform.getSourceDimensions()];
+        final double[] target = new double[transform.getTargetDimensions()];
+        source[0] = 2.55;              // 2.55 metres up, same sign than in EPSG example (positive).
+        target[0] = 7.18 * 0.3048;     // 7.18 feet down.
+        verifyTransform(source, target);
+    }
 }

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/MapProjectionTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/MapProjectionTest.java?rev=1724531&r1=1724530&r2=1724531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/MapProjectionTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/MapProjectionTest.java [UTF-8] Wed Jan 13 23:59:38 2016
@@ -64,12 +64,12 @@ public final strictfp class MapProjectio
         assertParamEquals(null,                                   SEMI_MAJOR,          true,  it.next());
         assertParamEquals(null,                                   SEMI_MINOR,          true,  it.next());
         assertParamEquals("Latitude of 1st standard parallel",    STANDARD_PARALLEL_1, true,  it.next());
-        assertParamEquals("Latitude of natural origin",          "latitude_of_origin", false, it.next());
+        assertParamEquals("Latitude of natural origin",           LATITUDE_OF_ORIGIN,  false, it.next());
         assertParamEquals("Longitude of natural origin",          CENTRAL_MERIDIAN,    true,  it.next());
         assertParamEquals("False easting",                        FALSE_EASTING,       true,  it.next());
         assertParamEquals("False northing",                       FALSE_NORTHING,      true,  it.next());
         assertFalse(it.hasNext());
-        assertIsForcedToZero((ParameterDescriptor<?>) Equirectangular.PARAMETERS.descriptor("latitude_of_origin"));
+        assertIsForcedToZero((ParameterDescriptor<?>) Equirectangular.PARAMETERS.descriptor(LATITUDE_OF_ORIGIN));
     }
 
     /**
@@ -82,13 +82,13 @@ public final strictfp class MapProjectio
         assertParamEquals("Mercator (variant A)",          "Mercator_1SP",       true, Mercator1SP.PARAMETERS);
         assertParamEquals(null,                             SEMI_MAJOR,          true, it.next());
         assertParamEquals(null,                             SEMI_MINOR,          true, it.next());
-        assertParamEquals("Latitude of natural origin",    "latitude_of_origin", true, it.next());
+        assertParamEquals("Latitude of natural origin",     LATITUDE_OF_ORIGIN,  true, it.next());
         assertParamEquals("Longitude of natural origin",    CENTRAL_MERIDIAN,    true, it.next());
         assertParamEquals("Scale factor at natural origin", SCALE_FACTOR,        true, it.next());
         assertParamEquals("False easting",                  FALSE_EASTING,       true, it.next());
         assertParamEquals("False northing",                 FALSE_NORTHING,      true, it.next());
         assertFalse(it.hasNext());
-        assertIsForcedToZero((ParameterDescriptor<?>) Mercator1SP.PARAMETERS.descriptor("latitude_of_origin"));
+        assertIsForcedToZero((ParameterDescriptor<?>) Mercator1SP.PARAMETERS.descriptor(LATITUDE_OF_ORIGIN));
     }
 
     /**
@@ -102,13 +102,13 @@ public final strictfp class MapProjectio
         assertParamEquals(null,                                SEMI_MAJOR,           true,  it.next());
         assertParamEquals(null,                                SEMI_MINOR,           true,  it.next());
         assertParamEquals("Latitude of 1st standard parallel", STANDARD_PARALLEL_1,  true,  it.next());
-        assertParamEquals("Latitude of natural origin",        "latitude_of_origin",  false, it.next());
+        assertParamEquals("Latitude of natural origin",        LATITUDE_OF_ORIGIN,   false, it.next());
         assertParamEquals("Longitude of natural origin",       CENTRAL_MERIDIAN,     true,  it.next());
         assertParamEquals(null,                                SCALE_FACTOR,         false, it.next());
         assertParamEquals("False easting",                     FALSE_EASTING,        true,  it.next());
         assertParamEquals("False northing",                    FALSE_NORTHING,       true,  it.next());
         assertFalse(it.hasNext());
-        assertIsForcedToZero((ParameterDescriptor<?>) Mercator1SP.PARAMETERS.descriptor("latitude_of_origin"));
+        assertIsForcedToZero((ParameterDescriptor<?>) Mercator1SP.PARAMETERS.descriptor(LATITUDE_OF_ORIGIN));
     }
 
     /**

Copied: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NADCONTest.java (from r1724528, sis/branches/JDK6/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NADCONTest.java)
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NADCONTest.java?p2=sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NADCONTest.java&p1=sis/branches/JDK6/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NADCONTest.java&r1=1724528&r2=1724531&rev=1724531&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NADCONTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NADCONTest.java [UTF-8] Wed Jan 13 23:59:38 2016
@@ -32,7 +32,7 @@ import org.apache.sis.geometry.Envelopes
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 // Branch-dependent imports
 import org.apache.sis.internal.jdk7.Path;

Copied: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NTv2Test.java (from r1724528, sis/branches/JDK6/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NTv2Test.java)
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NTv2Test.java?p2=sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NTv2Test.java&p1=sis/branches/JDK6/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NTv2Test.java&r1=1724528&r2=1724531&rev=1724531&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NTv2Test.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NTv2Test.java [UTF-8] Wed Jan 13 23:59:38 2016
@@ -28,7 +28,7 @@ import org.apache.sis.referencing.operat
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 // Branch-dependent imports
 import org.apache.sis.internal.jdk7.Path;

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java?rev=1724531&r1=1724530&r2=1724531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java [UTF-8] Wed Jan 13 23:59:38 2016
@@ -1004,12 +1004,12 @@ public final strictfp class GeodeticObje
                                " • The text contains unknown elements:\n" +
                                "    ‣ “Intruder” in PRIMEM, GEOGCS.\n" +
                                "    ‣ “Ext1” in SPHEROID.\n" +
-                               "    ‣ “Ext2” in SPHEROID.\n", warnings.toString(Locale.US));
+                               "    ‣ “Ext2” in SPHEROID.", warnings.toString(Locale.US));
 
         assertMultilinesEquals("La lecture de « WGS 84 » a été faite, mais en ignorant certains éléments.\n" +
                                " • Le texte contient des éléments inconnus :\n" +
                                "    ‣ « Intruder » dans PRIMEM, GEOGCS.\n" +
                                "    ‣ « Ext1 » dans SPHEROID.\n" +
-                               "    ‣ « Ext2 » dans SPHEROID.\n", warnings.toString(Locale.FRANCE));
+                               "    ‣ « Ext2 » dans SPHEROID.", warnings.toString(Locale.FRANCE));
     }
 }

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterValueGroupTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterValueGroupTest.java?rev=1724531&r1=1724530&r2=1724531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterValueGroupTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterValueGroupTest.java [UTF-8] Wed Jan 13 23:59:38 2016
@@ -19,6 +19,7 @@ package org.apache.sis.parameter;
 import java.util.List;
 import java.util.Arrays;
 import java.util.ArrayList;
+import java.util.Collections;
 import org.opengis.parameter.GeneralParameterDescriptor;
 import org.opengis.parameter.GeneralParameterValue;
 import org.opengis.parameter.ParameterDescriptor;
@@ -27,6 +28,7 @@ import org.opengis.parameter.ParameterVa
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.InvalidParameterNameException;
 import org.opengis.parameter.InvalidParameterCardinalityException;
+import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
@@ -43,7 +45,7 @@ import static org.opengis.referencing.Id
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @since   0.4
- * @version 0.6
+ * @version 0.7
  * @module
  */
 @DependsOn({
@@ -415,6 +417,26 @@ public final strictfp class DefaultParam
     }
 
     /**
+     * Tests {@link #equals(Object, ComparisonMode)}.
+     *
+     * @since 0.7
+     */
+    @Test
+    @DependsOnMethod("testEqualsAndHashCode")
+    public void testEqualsIgnoreMetadata() {
+        final DefaultParameterValueGroup g1 = createGroup(10);
+        final DefaultParameterValueGroup g2 = new DefaultParameterValueGroup(g1.getDescriptor());
+        final List<GeneralParameterValue> values = new ArrayList<GeneralParameterValue>(g1.values());
+        Collections.swap(values, 2, 3);
+        g2.values().addAll(values);
+
+        assertFalse("STRICT",          g1.equals(g2, ComparisonMode.STRICT));
+        assertFalse("BY_CONTRACT",     g1.equals(g2, ComparisonMode.BY_CONTRACT));
+        assertTrue ("IGNORE_METADATA", g1.equals(g2, ComparisonMode.IGNORE_METADATA));
+        assertTrue ("APPROXIMATIVE",   g1.equals(g2, ComparisonMode.APPROXIMATIVE));
+    }
+
+    /**
      * Tests {@link DefaultParameterValueGroup} serialization.
      */
     @Test

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/CommonCRSTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/CommonCRSTest.java?rev=1724531&r1=1724530&r2=1724531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/CommonCRSTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/CommonCRSTest.java [UTF-8] Wed Jan 13 23:59:38 2016
@@ -17,13 +17,14 @@
 package org.apache.sis.referencing;
 
 import java.util.Date;
-import org.opengis.test.Validators;
+import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.crs.SingleCRS;
 import org.opengis.referencing.crs.TemporalCRS;
 import org.opengis.referencing.crs.VerticalCRS;
 import org.opengis.referencing.crs.GeographicCRS;
 import org.opengis.referencing.crs.GeocentricCRS;
+import org.opengis.referencing.crs.ProjectedCRS;
 import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.cs.EllipsoidalCS;
@@ -33,6 +34,9 @@ import org.opengis.referencing.datum.Ver
 import org.apache.sis.internal.metadata.AxisNames;
 import org.apache.sis.internal.metadata.VerticalDatumTypes;
 import org.apache.sis.internal.util.Constants;
+
+// Test dependencies
+import org.opengis.test.Validators;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
@@ -47,7 +51,7 @@ import static org.apache.sis.test.TestUt
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @since   0.4
- * @version 0.5
+ * @version 0.7
  * @module
  */
 @DependsOn({
@@ -143,11 +147,12 @@ public final strictfp class CommonCRSTes
             final VerticalDatumType datumType;
             final String axisName, datumName;
             switch (e) {
-                case BAROMETRIC:     axisName = "Barometric altitude";            datumName = "Constant pressure surface"; datumType = VerticalDatumType. BAROMETRIC;    break;
-                case MEAN_SEA_LEVEL: axisName = AxisNames.GRAVITY_RELATED_HEIGHT; datumName = "Mean Sea Level";            datumType = VerticalDatumType. GEOIDAL;       break;
-                case DEPTH:          axisName = AxisNames.DEPTH;                  datumName = "Mean Sea Level";            datumType = VerticalDatumType. GEOIDAL;       break;
-                case ELLIPSOIDAL:    axisName = AxisNames.ELLIPSOIDAL_HEIGHT;     datumName = "Ellipsoid";                 datumType = VerticalDatumTypes.ELLIPSOIDAL;   break;
-                case OTHER_SURFACE:  axisName = "Height";                         datumName = "Other surface";             datumType = VerticalDatumType. OTHER_SURFACE; break;
+                case NAVD88:         axisName = AxisNames.GRAVITY_RELATED_HEIGHT; datumName = "North American Vertical Datum 1988"; datumType = VerticalDatumType. GEOIDAL;       break;
+                case BAROMETRIC:     axisName = "Barometric altitude";            datumName = "Constant pressure surface";          datumType = VerticalDatumType. BAROMETRIC;    break;
+                case MEAN_SEA_LEVEL: axisName = AxisNames.GRAVITY_RELATED_HEIGHT; datumName = "Mean Sea Level";                     datumType = VerticalDatumType. GEOIDAL;       break;
+                case DEPTH:          axisName = AxisNames.DEPTH;                  datumName = "Mean Sea Level";                     datumType = VerticalDatumType. GEOIDAL;       break;
+                case ELLIPSOIDAL:    axisName = AxisNames.ELLIPSOIDAL_HEIGHT;     datumName = "Ellipsoid";                          datumType = VerticalDatumTypes.ELLIPSOIDAL;   break;
+                case OTHER_SURFACE:  axisName = "Height";                         datumName = "Other surface";                      datumType = VerticalDatumType. OTHER_SURFACE; break;
                 default: throw new AssertionError(e);
             }
             final String        name  = e.name();
@@ -202,6 +207,23 @@ public final strictfp class CommonCRSTes
     }
 
     /**
+     * Tests {@link CommonCRS#UTM(double, double)}.
+     *
+     * @since 0.7
+     */
+    @Test
+    @DependsOnMethod("testGeographic")
+    public void testUTM() {
+        final ProjectedCRS crs = CommonCRS.WGS72.UTM(-45, -122);
+        assertEquals("name", "WGS 72 / UTM zone 10S", crs.getName().getCode());
+        final ParameterValueGroup pg = crs.getConversionFromBase().getParameterValues();
+        assertEquals(Constants.LATITUDE_OF_ORIGIN, -123, pg.parameter(Constants.CENTRAL_MERIDIAN).doubleValue(), STRICT);
+        assertEquals(Constants.FALSE_NORTHING, 10000000, pg.parameter(Constants.FALSE_NORTHING).doubleValue(),   STRICT);
+        assertSame("Expected a cached instance.", crs, CommonCRS.WGS72.UTM(-45, -122));
+        assertNotSame("Expected a new instance.", crs, CommonCRS.WGS72.UTM(+45, -122));
+    }
+
+    /**
      * Tests {@link CommonCRS#forCode(String, String, FactoryException)}.
      *
      * @throws FactoryException If a CRS can not be constructed.

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/StandardDefinitionsTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/StandardDefinitionsTest.java?rev=1724531&r1=1724530&r2=1724531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/StandardDefinitionsTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/StandardDefinitionsTest.java [UTF-8] Wed Jan 13 23:59:38 2016
@@ -16,12 +16,22 @@
  */
 package org.apache.sis.referencing;
 
+import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.referencing.crs.GeographicCRS;
+import org.opengis.referencing.crs.ProjectedCRS;
+import org.opengis.referencing.crs.VerticalCRS;
+import org.opengis.referencing.cs.AxisDirection;
+import org.opengis.referencing.cs.CartesianCS;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.cs.EllipsoidalCS;
 import org.opengis.referencing.datum.Ellipsoid;
 import org.opengis.referencing.datum.PrimeMeridian;
 import org.opengis.referencing.datum.GeodeticDatum;
+import org.opengis.referencing.datum.VerticalDatum;
+import org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.internal.util.Constants;
+
+// Test dependencies
 import org.apache.sis.test.mock.GeodeticDatumMock;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
@@ -29,6 +39,7 @@ import org.apache.sis.test.TestCase;
 import org.opengis.test.Validators;
 import org.apache.sis.referencing.crs.HardCodedCRS;
 import org.apache.sis.referencing.cs.HardCodedAxes;
+import org.apache.sis.referencing.cs.HardCodedCS;
 import org.apache.sis.referencing.datum.HardCodedDatum;
 import org.junit.Test;
 
@@ -40,14 +51,30 @@ import static org.junit.Assert.*;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.4
- * @version 0.4
+ * @version 0.7
  * @module
  */
 @DependsOn({
-    org.apache.sis.referencing.crs.DefaultGeographicCRSTest.class
+    org.apache.sis.referencing.crs.DefaultGeographicCRSTest.class,
+    org.apache.sis.internal.referencing.provider.TransverseMercatorTest.class
 })
 public final strictfp class StandardDefinitionsTest extends TestCase {
     /**
+     * Tests {@link StandardDefinitions#createUTM(int, GeographicCRS, double, boolean, CartesianCS)}.
+     *
+     * @since 0.7
+     */
+    @Test
+    @DependsOnMethod("testCreateGeographicCRS")
+    public void testCreateUTM() {
+        final ProjectedCRS crs = StandardDefinitions.createUTM(32610, HardCodedCRS.WGS84, 15, -122, HardCodedCS.PROJECTED);
+        assertEquals("name", "WGS 84 / UTM zone 10N", crs.getName().getCode());
+        final ParameterValueGroup pg = crs.getConversionFromBase().getParameterValues();
+        assertEquals(Constants.LATITUDE_OF_ORIGIN, -123, pg.parameter(Constants.CENTRAL_MERIDIAN).doubleValue(), STRICT);
+        assertEquals(Constants.FALSE_NORTHING,        0, pg.parameter(Constants.FALSE_NORTHING).doubleValue(),   STRICT);
+    }
+
+    /**
      * Compares the values created by {@code StandardDefinitions} against hard-coded constants.
      * This method tests the following methods:
      *
@@ -64,7 +91,7 @@ public final strictfp class StandardDefi
      */
     @Test
     @DependsOnMethod("testCreateAxis")
-    public void testCreateGographicCRS() {
+    public void testCreateGeographicCRS() {
         final PrimeMeridian pm = StandardDefinitions.primeMeridian();
         final EllipsoidalCS cs = (EllipsoidalCS) StandardDefinitions.createCoordinateSystem((short) 6422);
         for (final CommonCRS e : CommonCRS.values()) {
@@ -123,10 +150,12 @@ public final strictfp class StandardDefi
      */
     @Test
     public void testCreateAxis() {
-        for (final short code : new short[] {106, 107, 110, 114, 113}) {
+        for (final short code : new short[] {1, 2, 106, 107, 110, 114, 113}) {
             final CoordinateSystemAxis actual = StandardDefinitions.createAxis(code);
             Validators.validate(actual);
             switch (code) {
+                case   1: compare(HardCodedAxes.EASTING,                actual); break;
+                case   2: compare(HardCodedAxes.NORTHING,               actual); break;
                 case 106: compare(HardCodedAxes.GEODETIC_LATITUDE,      actual); break;
                 case 107: compare(HardCodedAxes.GEODETIC_LONGITUDE,     actual); break;
                 case 110: compare(HardCodedAxes.ELLIPSOIDAL_HEIGHT,     actual); break;
@@ -149,4 +178,37 @@ public final strictfp class StandardDefi
         assertEquals("maximumValue", expected.getMaximumValue(),   actual.getMaximumValue(), STRICT);
         assertEquals("rangeMeaning", expected.getRangeMeaning(),   actual.getRangeMeaning());
     }
+
+    /**
+     * Tests the creation of vertical CRS.
+     *
+     * @since 0.7
+     */
+    @Test
+    @DependsOnMethod("testCreateAxis")
+    public void testCreateVerticalCRS() {
+        VerticalDatum datum;
+        VerticalCRS crs;
+
+        datum = StandardDefinitions.createVerticalDatum(CommonCRS.Vertical.NAVD88.datum);
+        crs = StandardDefinitions.createVerticalCRS(CommonCRS.Vertical.NAVD88.crs, datum);
+        assertEquals("name", "NAVD88 height", crs.getName().getCode());
+        assertEquals("identifier", "5703", IdentifiedObjects.getIdentifier(crs, Citations.EPSG).getCode());
+        assertEquals("identifier",   "88", IdentifiedObjects.getIdentifier(crs, Citations.OGC ).getCode());
+        assertEquals("direction", AxisDirection.UP, crs.getCoordinateSystem().getAxis(0).getDirection());
+
+        datum = StandardDefinitions.createVerticalDatum(CommonCRS.Vertical.MEAN_SEA_LEVEL.datum);
+        crs = StandardDefinitions.createVerticalCRS(CommonCRS.Vertical.MEAN_SEA_LEVEL.crs, datum);
+        assertEquals("name", "MSL height", crs.getName().getCode());
+        assertEquals("identifier", "5714", IdentifiedObjects.getIdentifier(crs, Citations.EPSG).getCode());
+        assertNull  ("identifier", IdentifiedObjects.getIdentifier(crs, Citations.OGC));
+        assertEquals("direction", AxisDirection.UP, crs.getCoordinateSystem().getAxis(0).getDirection());
+
+        datum = StandardDefinitions.createVerticalDatum(CommonCRS.Vertical.DEPTH.datum);
+        crs = StandardDefinitions.createVerticalCRS(CommonCRS.Vertical.DEPTH.crs, datum);
+        assertEquals("name", "MSL depth", crs.getName().getCode());
+        assertEquals("identifier", "5715", IdentifiedObjects.getIdentifier(crs, Citations.EPSG).getCode());
+        assertNull  ("identifier", IdentifiedObjects.getIdentifier(crs, Citations.OGC));
+        assertEquals("direction", AxisDirection.DOWN, crs.getCoordinateSystem().getAxis(0).getDirection());
+    }
 }

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultDerivedCRSTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultDerivedCRSTest.java?rev=1724531&r1=1724530&r2=1724531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultDerivedCRSTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultDerivedCRSTest.java [UTF-8] Wed Jan 13 23:59:38 2016
@@ -116,7 +116,7 @@ public final strictfp class DefaultDeriv
     @Test
     public void testConstruction() {
         final DefaultDerivedCRS crs = createLongitudeRotation();
-        Validators.validate(crs);
+//      Validators.validate(crs);
 
         assertEquals("name",    "Back to Greenwich",                crs.getName().getCode());
         assertEquals("baseCRS", "NTF (Paris)",                      crs.getBaseCRS().getName().getCode());

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultProjectedCRSTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultProjectedCRSTest.java?rev=1724531&r1=1724530&r2=1724531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultProjectedCRSTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultProjectedCRSTest.java [UTF-8] Wed Jan 13 23:59:38 2016
@@ -26,14 +26,19 @@ import org.opengis.referencing.crs.Geogr
 import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.operation.Projection;
 import org.opengis.parameter.ParameterValueGroup;
-import org.opengis.test.Validators;
 import org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.referencing.cs.HardCodedCS;
-import org.apache.sis.referencing.GeodeticObjectBuilder;
+import org.apache.sis.internal.referencing.GeodeticObjectBuilder;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.internal.system.Loggers;
 import org.apache.sis.io.wkt.Convention;
 import org.apache.sis.util.logging.Logging;
+import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.util.LenientComparable;
+
+// Test dependencies
+import org.opengis.test.Validators;
 import org.apache.sis.test.LoggingWatcher;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
@@ -49,7 +54,7 @@ import static org.apache.sis.test.Refere
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.6
- * @version 0.6
+ * @version 0.7
  * @module
  */
 @DependsOn({
@@ -455,4 +460,23 @@ public final strictfp class DefaultProje
         assertMarshalEqualsFile(XML_FILE, crs, STRICT, new String[] {"gml:name"},
                 new String[] {"xmlns:*", "xsi:schemaLocation", "gml:id"});
     }
+
+    /**
+     * Tests {@link DefaultProjectedCRS#equals(Object, ComparisonMode)}.
+     * In particular, we want to test the ability to ignore axis order of the base CRS in "ignore metadata" mode.
+     *
+     * @throws FactoryException if the CRS creation failed.
+     *
+     * @since 0.7
+     */
+    @Test
+    public void testEquals() throws FactoryException {
+        final ProjectedCRS standard   = create(CommonCRS.WGS84.geographic());
+        final ProjectedCRS normalized = create(CommonCRS.WGS84.normalizedGeographic());
+        assertFalse("STRICT",          ((LenientComparable) standard).equals(normalized, ComparisonMode.STRICT));
+        assertFalse("BY_CONTRACT",     ((LenientComparable) standard).equals(normalized, ComparisonMode.BY_CONTRACT));
+        assertTrue ("IGNORE_METADATA", ((LenientComparable) standard).equals(normalized, ComparisonMode.IGNORE_METADATA));
+        assertTrue ("APPROXIMATIVE",   ((LenientComparable) standard).equals(normalized, ComparisonMode.APPROXIMATIVE));
+        assertTrue ("ALLOW_VARIANT",   ((LenientComparable) standard).equals(normalized, ComparisonMode.ALLOW_VARIANT));
+    }
 }




Mime
View raw message