sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1718273 [1/2] - in /sis/branches/JDK8: core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/ core/sis-referencing/src/main/java/org/apache/sis/...
Date Mon, 07 Dec 2015 09:56:50 GMT
Author: desruisseaux
Date: Mon Dec  7 09:56:50 2015
New Revision: 1718273

URL: http://svn.apache.org/viewvc?rev=1718273&view=rev
Log:
Add InterpolatedTransform class and support for NTv2 datum shift grids.

Added:
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/NTv2.java   (with props)
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedTransform.java   (with props)
    sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NTv2Test.java   (with props)
    sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/InterpolatedTransformTest.java   (with props)
    sis/branches/JDK8/core/sis-referencing/src/test/resources/org/apache/sis/internal/referencing/provider/NTF_R93-extract.gsb   (with props)
Modified:
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridCompressed.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java
    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/AffineTransforms2D.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/GeneralMatrix.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrix1.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrix2.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrix3.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrix4.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/MolodenskyFormula.java
    sis/branches/JDK8/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod
    sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/AllProvidersTest.java
    sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolationTest.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
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.properties
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages_fr.properties
    sis/branches/JDK8/ide-project/NetBeans/build.xml

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridCompressed.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridCompressed.java?rev=1718273&r1=1718272&r2=1718273&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridCompressed.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridCompressed.java [UTF-8] Mon Dec  7 09:56:50 2015
@@ -128,6 +128,18 @@ final class DatumShiftGridCompressed<C e
     }
 
     /**
+     * Suggests a precision for the translation values in this grid.
+     *
+     * @return A precision for the translation values in this grid.
+     */
+    @Override
+    public double getCellPrecision() {
+        // 5* is for converting 0.1 × 10⁻ⁿ to 0.5 × 10⁻ⁿ
+        // where n is the number of significant digits.
+        return Math.min(super.getCellPrecision(), 5*scale);
+    }
+
+    /**
      * Returns direct references (not cloned) to the data arrays.
      */
     @Override

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java?rev=1718273&r1=1718272&r2=1718273&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java [UTF-8] Mon Dec  7 09:56:50 2015
@@ -20,10 +20,13 @@ import java.util.Arrays;
 import java.lang.reflect.Array;
 import javax.measure.unit.Unit;
 import javax.measure.quantity.Quantity;
+import org.opengis.parameter.ParameterDescriptorGroup;
+import org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.apache.sis.math.DecimalFunctions;
 import org.apache.sis.util.collection.Cache;
 import org.apache.sis.referencing.datum.DatumShiftGrid;
 import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
+import org.apache.sis.referencing.operation.transform.LinearTransform;
 
 // Branch-specific imports
 import java.nio.file.Path;
@@ -73,6 +76,11 @@ public abstract class DatumShiftGridFile
     };
 
     /**
+     * The parameter descriptor of the provider that created this grid.
+     */
+    public final ParameterDescriptorGroup descriptor;
+
+    /**
      * The file from which the grid has been loaded. This is not used directly by this class
      * (except for {@link #equals(Object)} and {@link #hashCode()}), but can be used by math
      * transform for setting the parameter values.
@@ -85,6 +93,16 @@ public abstract class DatumShiftGridFile
     final int nx;
 
     /**
+     * The best translation accuracy that we can expect from this file.
+     *
+     * <p>This field is initialized to {@link Double#NaN}. It is loader responsibility
+     * to assign a value to this field after {@code DatumShiftGridFile} construction.</p>
+     *
+     * @see #getCellPrecision()
+     */
+    double accuracy;
+
+    /**
      * Creates a new datum shift grid for the given grid geometry.
      * The actual offset values need to be provided by subclasses.
      *
@@ -101,12 +119,15 @@ public abstract class DatumShiftGridFile
                        final double x0, final double y0,
                        final double Δx, final double Δy,
                        final int    nx, final int    ny,
-                       final Path file)
+                       final ParameterDescriptorGroup descriptor,
+                       final Path file) throws NoninvertibleTransformException
     {
-        super(coordinateUnit, new AffineTransform2D(1/Δx, 0, 0, 1/Δy, -x0/Δx, -y0/Δy),
+        super(coordinateUnit, (LinearTransform) new AffineTransform2D(Δx, 0, 0, Δy, x0, y0).inverse(),
                 new int[] {nx, ny}, isCellValueRatio, translationUnit);
-        this.file = file;
-        this.nx   = nx;
+        this.descriptor = descriptor;
+        this.file       = file;
+        this.nx         = nx;
+        this.accuracy   = Double.NaN;
     }
 
     /**
@@ -116,8 +137,21 @@ public abstract class DatumShiftGridFile
      */
     DatumShiftGridFile(final DatumShiftGridFile<C,T> other) {
         super(other);
-        this.file = other.file;
-        this.nx   = other.nx;
+        descriptor = other.descriptor;
+        file       = other.file;
+        nx         = other.nx;
+        accuracy   = other.accuracy;
+    }
+
+    /**
+     * Suggests a precision for the translation values in this grid.
+     * The default implementation returns a value smaller than the accuracy.
+     *
+     * @return A precision for the translation values in this grid.
+     */
+    @Override
+    public double getCellPrecision() {
+        return accuracy / 10;   // Division by 10 is arbitrary.
     }
 
     /**
@@ -232,9 +266,10 @@ public abstract class DatumShiftGridFile
               final double x0, final double y0,
               final double Δx, final double Δy,
               final int    nx, final int    ny,
-              final Path file, final int dim)
+              final ParameterDescriptorGroup descriptor,
+              final Path file, final int dim) throws NoninvertibleTransformException
         {
-            super(coordinateUnit, translationUnit, isCellValueRatio, x0, y0, Δx, Δy, nx, ny, file);
+            super(coordinateUnit, translationUnit, isCellValueRatio, x0, y0, Δx, Δy, nx, ny, descriptor, file);
             offsets = new float[dim][];
             final int size = Math.multiplyExact(nx, ny);
             for (int i=0; i<dim; i++) {

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=1718273&r1=1718272&r2=1718273&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] Mon Dec  7 09:56:50 2015
@@ -117,6 +117,18 @@ public final class FranceGeocentricInter
     static final double PRECISION = 0.0001;
 
     /**
+     * Accuracies of offset values. Accuracies in GR3D files are defined by standard-deviations rounded
+     * to integer values. The mapping given in NTG_88 document is:
+     *
+     *   01 =  5 cm,
+     *   02 = 10 cm,
+     *   03 = 20 cm,
+     *   04 = 50 cm and
+     *   99 &gt; 1 m.
+     */
+    private static final double[] ACCURACY = {0.05, 0.1, 0.2, 0.5, 1};
+
+    /**
      * The keyword expected at the beginning of every lines in the header.
      */
     private static final String HEADER = "GR3D";
@@ -278,7 +290,7 @@ public final class FranceGeocentricInter
      * (which is the direction that use the interpolation grid directly without iteration),
      * then inverts the transform.
      *
-     * @param  factory Ignored (can be null).
+     * @param  factory The factory to use if this constructor needs to create other math transforms.
      * @param  values The group of parameter values.
      * @return The created math transform.
      * @throws ParameterNotFoundException if a required parameter was not found.
@@ -332,7 +344,7 @@ public final class FranceGeocentricInter
                     try (final BufferedReader in = Files.newBufferedReader(file)) {
                         final DatumShiftGridFile.Float<Angle,Length> g = load(in, file);
                         grid = DatumShiftGridCompressed.compress(g, averages, scale);
-                    } catch (IOException | RuntimeException e) {
+                    } catch (IOException | NoninvertibleTransformException | RuntimeException e) {
                         // NumberFormatException, ArithmeticException, NoSuchElementException, possibly other.
                         throw new FactoryException(Errors.format(Errors.Keys.CanNotParseFile_2, HEADER, file), e);
                     }
@@ -357,7 +369,7 @@ public final class FranceGeocentricInter
      * @throws ArithmeticException if the width or the height exceed the integer capacity.
      */
     static DatumShiftGridFile.Float<Angle,Length> load(final BufferedReader in, final Path file)
-            throws IOException, FactoryException
+            throws IOException, FactoryException, NoninvertibleTransformException
     {
         DatumShiftGridFile.Float<Angle,Length> grid = null;
         double x0 = 0;
@@ -414,7 +426,7 @@ public final class FranceGeocentricInter
                             ny = Math.toIntExact(Math.round((yf - y0) / Δy + 1));
                             grid = new DatumShiftGridFile.Float<>(
                                     NonSI.DEGREE_ANGLE, SI.METRE, false,
-                                    x0, y0, Δx, Δy, nx, ny, file, 3);
+                                    x0, y0, Δx, Δy, nx, ny, PARAMETERS, file, 3);
                         }
                         break;
                     }
@@ -474,6 +486,11 @@ public final class FranceGeocentricInter
             tX[p] = -parseFloat(t.nextToken());  // See javadoc for the reason why we reverse the sign.
             tY[p] = -parseFloat(t.nextToken());
             tZ[p] = -parseFloat(t.nextToken());
+            final double accuracy = ACCURACY[Math.min(ACCURACY.length-1,
+                    Math.max(0, Integer.parseInt(t.nextToken()) - 1))];
+            if (!(accuracy >= grid.accuracy)) {   // Use '!' for replacing the initial NaN.
+                grid.accuracy = accuracy;
+            }
         } while ((line = in.readLine()) != null);
         return grid;
     }

Added: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/NTv2.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/NTv2.java?rev=1718273&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/NTv2.java (added)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/NTv2.java [UTF-8] Mon Dec  7 09:56:50 2015
@@ -0,0 +1,485 @@
+/*
+ * 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 java.util.Map;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.ByteOrder;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+import javax.xml.bind.annotation.XmlTransient;
+import javax.measure.unit.Unit;
+import javax.measure.unit.NonSI;
+import javax.measure.quantity.Angle;
+import org.opengis.util.FactoryException;
+import org.opengis.parameter.ParameterValueGroup;
+import org.opengis.parameter.ParameterDescriptor;
+import org.opengis.parameter.ParameterDescriptorGroup;
+import org.opengis.parameter.ParameterNotFoundException;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.MathTransformFactory;
+import org.opengis.referencing.operation.Transformation;
+import org.opengis.referencing.operation.NoninvertibleTransformException;
+import org.apache.sis.referencing.operation.transform.InterpolatedTransform;
+import org.apache.sis.internal.system.Loggers;
+import org.apache.sis.parameter.ParameterBuilder;
+import org.apache.sis.parameter.Parameters;
+import org.apache.sis.util.collection.Cache;
+import org.apache.sis.util.logging.Logging;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.resources.Messages;
+
+// Branch-dependent imports
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.charset.StandardCharsets;
+
+
+/**
+ * The provider for <cite>"National Transformation version 2"</cite> (EPSG:9615).
+ * This transform requires data that are not bundled by default with Apache SIS.
+ *
+ * @author  Simon Reynard (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.7
+ * @version 0.7
+ * @module
+ */
+@XmlTransient
+public final class NTv2 extends AbstractProvider {
+    /**
+     * Serial number for inter-operability with different versions.
+     */
+    private static final long serialVersionUID = -4027618007780159180L;
+
+    /**
+     * The operation parameter descriptor for the <cite>"Latitude and longitude difference file"</cite> parameter value.
+     * The file extension is typically {@code ".gsb"}. There is no default value.
+     */
+    private static final ParameterDescriptor<Path> FILE;
+
+    /**
+     * The group of all parameters expected by this coordinate operation.
+     */
+    public static final ParameterDescriptorGroup PARAMETERS;
+    static {
+        final ParameterBuilder builder = builder();
+        FILE = builder
+                .addIdentifier("8656")
+                .addName("Latitude and longitude difference file")
+                .create(Path.class, null);
+        PARAMETERS = builder
+                .addIdentifier("9615")
+                .addName("NTv2")
+                .createGroup(FILE);
+    }
+
+    /**
+     * Creates a new provider.
+     */
+    public NTv2() {
+        super(2, 2, PARAMETERS);
+    }
+
+    /**
+     * Returns the base interface of the {@code CoordinateOperation} instances that use this method.
+     *
+     * @return Fixed to {@link Transformation}.
+     */
+    @Override
+    public Class<Transformation> getOperationType() {
+        return Transformation.class;
+    }
+
+    /**
+     * Creates a transform from the specified group of parameter values.
+     *
+     * @param  factory The factory to use if this constructor needs to create other math transforms.
+     * @param  values The group of parameter values.
+     * @return The created math transform.
+     * @throws ParameterNotFoundException if a required parameter was not found.
+     * @throws FactoryException if an error occurred while loading the grid.
+     */
+    @Override
+    public MathTransform createMathTransform(final MathTransformFactory factory, final ParameterValueGroup values)
+            throws ParameterNotFoundException, FactoryException
+    {
+        final Parameters pg = Parameters.castOrWrap(values);
+        return InterpolatedTransform.createGeodeticTransformation(factory, getOrLoad(pg.getValue(FILE)));
+    }
+
+    /**
+     * Returns the grid of the given name. This method returns the cached instance if it still exists,
+     * or load the grid otherwise.
+     *
+     * @param file Name of the datum shift grid file to load.
+     */
+    static DatumShiftGridFile<Angle,Angle> getOrLoad(final Path file) throws FactoryException {
+        DatumShiftGridFile<?,?> grid = DatumShiftGridFile.CACHE.peek(file);
+        if (grid == null) {
+            final Cache.Handler<DatumShiftGridFile<?,?>> handler = DatumShiftGridFile.CACHE.lock(file);
+            try {
+                grid = handler.peek();
+                if (grid == null) {
+                    try (final ReadableByteChannel in = Files.newByteChannel(file)) {
+                        final Loader loader = new Loader(in, file);
+                        grid = loader.readGrid();
+                        loader.reportWarnings();
+                    } catch (IOException | NoninvertibleTransformException | RuntimeException e) {
+                        throw new FactoryException(Errors.format(Errors.Keys.CanNotParseFile_2, "NTv2", file), e);
+                    }
+                    grid = grid.useSharedData();
+                }
+            } finally {
+                handler.putAndUnlock(grid);
+            }
+        }
+        return grid.castTo(Angle.class, Angle.class);
+    }
+
+
+
+
+    /**
+     * Loaders of NTv2 data. Instances of this class exist only at loading time.
+     *
+     * @author  Simon Reynard (Geomatys)
+     * @author  Martin Desruisseaux (Geomatys)
+     * @since   0.7
+     * @version 0.7
+     * @module
+     */
+    private static final class Loader {
+        /**
+         * Size of a record. This value applies to both the header records and the data records.
+         * In the case of header records, this is the size of the key plus the size of the value.
+         */
+        private static final int RECORD_LENGTH = 16;
+
+        /**
+         * Maximum number of characters of a key in a header record.
+         */
+        private static final int KEY_LENGTH = 8;
+
+        /**
+         * Type of data allowed in header records.
+         */
+        private static final int STRING_TYPE = 0, INTEGER_TYPE = 1, DOUBLE_TYPE = 2;
+
+        /**
+         * Some known keywords that may appear in NTv2 header records.
+         */
+        private static final Map<String,Integer> TYPES;
+        static {
+            final Map<String,Integer> types = new HashMap<>(32);
+            final Integer string  = STRING_TYPE;    // Autoboxing
+            final Integer integer = INTEGER_TYPE;
+            final Integer real    = DOUBLE_TYPE;
+            types.put("NUM_OREC", integer);         // Number of records in the header - usually 11
+            types.put("NUM_SREC", integer);         // Number of records in the header of sub-grids - usually 11
+            types.put("NUM_FILE", integer);         // Number of sub-grids
+            types.put("GS_TYPE",  string);          // Units: "SECONDS", "MINUTES" or "DEGREES"
+            types.put("VERSION",  string);          // Grid version
+            types.put("SYSTEM_F", string);          // Source CRS
+            types.put("SYSTEM_T", string);          // Target CRS
+            types.put("MAJOR_F",  real);            // Semi-major axis of source ellipsoid (in metres)
+            types.put("MINOR_F",  real);            // Semi-minor axis of source ellipsoid (in metres)
+            types.put("MAJOR_T",  real);            // Semi-major axis of target ellipsoid (in metres)
+            types.put("MINOR_T",  real);            // Semi-minor axis of target ellipsoid (in metres)
+            types.put("SUB_NAME", string);          // Sub-grid identifier
+            types.put("PARENT",   string);          // Parent grid
+            types.put("CREATED",  string);          // Creation time
+            types.put("UPDATED",  string);          // Update time
+            types.put("S_LAT",    real);            // Southmost φ value
+            types.put("N_LAT",    real);            // Northmost φ value
+            types.put("E_LONG",   real);            // Eastmost λ value - west is positive, east is negative
+            types.put("W_LONG",   real);            // Westmost λ value - west is positive, east is negative
+            types.put("LAT_INC",  real);            // Increment on φ axis
+            types.put("LONG_INC", real);            // Increment on λ axis - positive toward west
+            types.put("GS_COUNT", integer);         // Number of sub-grid records following
+            TYPES = types;
+        }
+
+        /**
+         * The file to load, used only if we have errors to report.
+         */
+        private final Path file;
+
+        /**
+         * The channel opened on the file.
+         */
+        private final ReadableByteChannel channel;
+
+        /**
+         * The buffer to use for transferring data from the channel.
+         */
+        private final ByteBuffer buffer;
+
+        /**
+         * The header content. Keys are strings like {@code "VERSION"}, {@code "SYSTEM_F"},
+         * <var>etc.</var>. Values are {@link String}, {@link Integer} or {@link Double}.
+         * If some keys are unrecognized, they will be put in this map with the {@code null} value
+         * and the {@link #hasUnrecognized} field will be set to {@code true}.
+         */
+        private final Map<String,Object> header;
+
+        /**
+         * {@code true} if the {@code header} map contains at least one key associated to a null value.
+         */
+        private boolean hasUnrecognized;
+
+        /**
+         * Number of grids remaining in the file. This value is set in the constructor,
+         * then decremented at every call to {@link #readGrid()}.
+         */
+        private int remainingGrids;
+
+        /**
+         * Creates a new reader for the given channel.
+         * This constructor parses the header immediately, but does not read any grid.
+         *
+         * @param  channel Where to read data from.
+         * @param  file Path to the longitude and latitude difference file. Used only for error reporting.
+         * @throws FactoryException if a data record can not be parsed.
+         */
+        Loader(final ReadableByteChannel channel, final Path file) throws IOException, FactoryException {
+            this.file    = file;
+            this.channel = channel;
+            this.header  = new LinkedHashMap<>();
+            this.buffer  = ByteBuffer.allocate(4096);
+            channel.read(buffer);
+            buffer.flip();
+            ensureBufferContains(RECORD_LENGTH);
+            if (isLittleEndian(buffer.getInt(KEY_LENGTH))) {
+                buffer.order(ByteOrder.LITTLE_ENDIAN);
+            }
+            /*
+             * Read the overview header. It is normally made of the first 11 records documented in TYPES map:
+             * NUM_OREC, NUM_SREC, NUM_FILE, GS_TYPE, VERSION, SYSTEM_F, SYSTEM_T, MAJOR_F, MINOR_F, MAJOR_T,
+             * MINOR_T.
+             */
+            readHeader(11, "NUM_OREC");
+            remainingGrids = (Integer) get("NUM_FILE");
+            if (remainingGrids < 1) {
+                throw new FactoryException(Errors.format(Errors.Keys.UnexpectedValueInElement_2, "NUM_FILE", remainingGrids));
+            }
+        }
+
+        /**
+         * Reads the next grid, starting at the current position. A NTv2 file can have many grids.
+         * This can be used for grids having different resolutions depending on the geographic area.
+         * The first grid can cover a large area with a coarse resolution, and next grids cover smaller
+         * areas overlapping the first grid but with finer resolution.
+         *
+         * Current SIS implementation does not yet handle the above-cited hierarchy of grids.
+         * For now we just take the first one.
+         *
+         * <p>NTv2 grids contain also information about shifts accuracy. This is not yet handled by SIS.</p>
+         */
+        final DatumShiftGridFile<Angle,Angle> readGrid() throws IOException, FactoryException, NoninvertibleTransformException {
+            if (--remainingGrids < 0) {
+                throw new FactoryException(Errors.format(Errors.Keys.CanNotRead_1, file));
+            }
+            final Object[] overviewKeys = header.keySet().toArray();
+            readHeader((Integer) get("NUM_SREC"), "NUM_SREC");
+            /*
+             * Extract the geographic bounding box and cell size. While different units are allowed,
+             * in practice we usually have seconds of angle. This units has the advantage of allowing
+             * all floating-point values to be integers.
+             *
+             * Note that the longitude values in NTv2 files are positive WEST.
+             */
+            final Unit<Angle> unit;
+            final double precision;
+            final String name = (String) get("GS_TYPE");
+            if (name.equalsIgnoreCase("SECONDS")) {         // Most common value
+                unit = NonSI.SECOND_ANGLE;
+                precision = 1;
+            } else if (name.equalsIgnoreCase("MINUTES")) {
+                unit = NonSI.MINUTE_ANGLE;
+                precision = 0.001;                          // Used only as a hint; will not hurt if wrong.
+            } else if (name.equalsIgnoreCase("DEGREES")) {
+                unit = NonSI.DEGREE_ANGLE;
+                precision = 0.00001;                        // Used only as a hint; will not hurt if wrong.
+            } else {
+                throw new FactoryException(Errors.format(Errors.Keys.UnexpectedValueInElement_2, "GS_TYPE", name));
+            }
+            final double  ymin     = (Double)  get("S_LAT");
+            final double  ymax     = (Double)  get("N_LAT");
+            final double  xmin     = (Double)  get("E_LONG");    // Sign reversed compared to usual convention.
+            final double  xmax     = (Double)  get("W_LONG");    // Idem.
+            final double  dy       = (Double)  get("LAT_INC");
+            final double  dx       = (Double)  get("LONG_INC");  // Positive toward west.
+            final Integer declared = (Integer) header.get("GS_COUNT");
+            final int     width    = Math.toIntExact(Math.round((xmax - xmin) / dx + 1));
+            final int     height   = Math.toIntExact(Math.round((ymax - ymin) / dy + 1));
+            final int     count    = Math.multiplyExact(width, height);
+            if (declared != null && count != declared) {
+                throw new FactoryException(Errors.format(Errors.Keys.UnexpectedValueInElement_2, "GS_COUNT", declared));
+            }
+            /*
+             * Construct the grid with the sign of longitude values reversed, in order to have longitude values
+             * increasing toward East. We set isCellValueRatio = true (by the arguments given to the constructor)
+             * because this is required by our InterpolatedTransform implementation. This setting implies that we
+             * divide translation values by dx or dy at reading time. Note that this free us from reversing the
+             * sign of longitude translations; instead, this reversal will be handled by grid.coordinateToGrid
+             * MathTransform and its inverse.
+             */
+            final DatumShiftGridFile.Float<Angle,Angle> grid = new DatumShiftGridFile.Float<>(
+                    unit, unit, true, -xmin, ymin, -dx, dy, width, height, PARAMETERS, file, 2);
+            @SuppressWarnings("MismatchedReadAndWriteOfArray") final float[] tx = grid.offsets[0];
+            @SuppressWarnings("MismatchedReadAndWriteOfArray") final float[] ty = grid.offsets[1];
+            for (int i=0; i<count; i++) {
+                ensureBufferContains(4 * Float.BYTES);
+                ty[i] = (float) (buffer.getFloat() / dy);   // Division by dx and dy because isCellValueRatio = true.
+                tx[i] = (float) (buffer.getFloat() / dx);
+                final double accuracy = Math.min(buffer.getFloat() / dy, buffer.getFloat() / dx);
+                if (accuracy > 0 && !(accuracy >= grid.accuracy)) {   // Use '!' for replacing the initial NaN.
+                    grid.accuracy = accuracy;
+                }
+            }
+            header.keySet().retainAll(Arrays.asList(overviewKeys));   // Keep only overview records.
+            return DatumShiftGridCompressed.compress(grid, null, precision / Math.max(dx, dy));
+        }
+
+        /**
+         * Returns {@code true} if the given value seems to be stored in little endian order.
+         */
+        private static boolean isLittleEndian(final int n) {
+            return Integer.compareUnsigned(n, Integer.reverseBytes(n)) > 0;
+        }
+
+        /**
+         * Makes sure that the buffer contains at least <var>n</var> remaining bytes.
+         * It is caller's responsibility to ensure that the given number of bytes is
+         * not greater than the {@linkplain ByteBuffer#capacity() buffer capacity}.
+         *
+         * @param  n The minimal number of bytes needed in the {@linkplain #buffer}.
+         * @throws EOFException If the channel has reached the end of stream.
+         * @throws IOException If an other kind of error occurred while reading.
+         */
+        private void ensureBufferContains(int n) throws IOException {
+            assert n >= 0 && n <= buffer.capacity() : n;
+            n -= buffer.remaining();
+            if (n > 0) {
+                buffer.compact();
+                do {
+                    final int c = channel.read(buffer);
+                    if (c < 0) {
+                        throw new EOFException(Errors.format(Errors.Keys.UnexpectedEndOfFile_1, file));
+                    }
+                    n -= c;
+                } while (n > 0);
+                buffer.flip();
+            }
+        }
+
+        /**
+         * Reads a string at the given position in the buffer.
+         */
+        private String readString(final int position, int length) {
+            final byte[] array = buffer.array();
+            while (length > position && array[position + length - 1] <= ' ') length--;
+            return new String(array, position, length, StandardCharsets.US_ASCII).trim();
+        }
+
+        /**
+         * Reads all records found in the header, starting from the current buffer position.
+         * It may be the overview header (in which case we expect {@code NUM_OREC} records)
+         * or a sub-grid header (in which case we expect {@code NUM_SREC} records).
+         *
+         * @param numRecords Default number of expected records (usually 11).
+         * @param numkey Key of the record giving the number of records: {@code "NUM_OREC"} or {@code "NUM_SREC"}.
+         */
+        private void readHeader(int numRecords, final String numkey) throws IOException, FactoryException {
+            int position = buffer.position();
+            for (int i=0; i < numRecords; i++) {
+                ensureBufferContains(RECORD_LENGTH);
+                final String key = readString(position, KEY_LENGTH).toUpperCase(Locale.US);
+                position += KEY_LENGTH;
+                final Integer type = TYPES.get(key);
+                final Comparable<?> value;
+                if (type == null) {
+                    value = null;
+                    hasUnrecognized = true;
+                } else switch (type) {
+                    case STRING_TYPE: {
+                        value = readString(position, RECORD_LENGTH - KEY_LENGTH);
+                        break;
+                    }
+                    case INTEGER_TYPE: {
+                        final int n = buffer.getInt(position);
+                        if (key.equals(numkey)) {
+                            numRecords = n;
+                        }
+                        value = n;
+                        break;
+                    }
+                    case DOUBLE_TYPE: {
+                        value = buffer.getDouble(position);
+                        break;
+                    }
+                    default: throw new AssertionError(type);
+                }
+                final Object old = header.put(key, value);
+                if (old != null && !old.equals(value)) {
+                    throw new FactoryException(Errors.format(Errors.Keys.DuplicatedElement_1, key));
+                }
+                buffer.position(position += RECORD_LENGTH - KEY_LENGTH);
+            }
+        }
+
+        /**
+         * Returns the value for the given key, or thrown an exception if the value is not found.
+         */
+        private Object get(final String key) throws FactoryException {
+            final Object value = header.get(key);
+            if (value != null) {
+                return value;
+            }
+            throw new FactoryException(Errors.format(Errors.Keys.PropertyNotFound_2, file, key));
+        }
+
+        /**
+         * If we had any warnings during the loading process, report them now.
+         */
+        void reportWarnings() {
+            if (hasUnrecognized) {
+                final StringBuilder keywords = new StringBuilder();
+                for (final Map.Entry<String,Object> entry : header.entrySet()) {
+                    if (entry.getValue() == null) {
+                        if (keywords.length() != 0) {
+                            keywords.append(", ");
+                        }
+                        keywords.append(entry.getKey());
+                    }
+                }
+                final LogRecord record = Messages.getResources(null).getLogRecord(Level.WARNING,
+                        Messages.Keys.UnknownKeywordInRecord_2, file, keywords.toString());
+                record.setLoggerName(Loggers.COORDINATE_OPERATION);
+                Logging.log(NTv2.class, "createMathTransform", record);
+            }
+        }
+    }
+}

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

Propchange: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/NTv2.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=1718273&r1=1718272&r2=1718273&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] Mon Dec  7 09:56:50 2015
@@ -35,6 +35,7 @@ import org.apache.sis.geometry.Envelopes
 import org.apache.sis.internal.util.DoubleDouble;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.measure.Units;
 
 // Branch-dependent imports
 import java.util.Objects;
@@ -101,6 +102,24 @@ import java.util.Objects;
  *
  * Implementations of this class shall be immutable and thread-safe.
  *
+ * <div class="section">Number of dimensions</div>
+ * Input coordinates and translation vectors can have any number of dimensions. However in the current implementation,
+ * only the two first dimensions are used for interpolating the translation vectors. This restriction appears in the
+ * following method signatures:
+ *
+ * <ul>
+ *   <li>{@link #interpolateAtNormalized(double, double, double[])}
+ *       where the two first {@code double} values are (<var>x</var>,<var>y</var>) normalized ordinates.</li>
+ *   <li>{@link #interpolateInCell(double, double, double[])}
+ *       where the two first {@code double} values are (<var>x</var>,<var>y</var>) grid indices.</li>
+ *   <li>{@link #getCellValue(int, int, int)}
+ *       where the two last {@code int} values are (<var>x</var>,<var>y</var>) grid indices.</li>
+ * </ul>
+ *
+ * Note that the above restriction does not prevent {@code DatumShiftGrid} to interpolate translation vectors
+ * in more than two dimensions. See the above <cite>datum shift by geocentric translations</cite> use case for
+ * an example.
+ *
  * <div class="section">Serialization</div>
  * Serialized objects of this class are not guaranteed to be compatible with future Apache SIS releases.
  * Serialization support is appropriate for short term storage or RMI between applications running the
@@ -231,7 +250,7 @@ public abstract class DatumShiftGrid<C e
             final Matrix m = coordinateToGrid.getMatrix();
             if (Matrices.isAffine(m)) {
                 final int n = m.getNumCol() - 1;
-                final double toUnit = c.convert(1);
+                final double toUnit = Units.derivative(c, 0);
                 switch (m.getNumRow()) {
                     default: y0 = m.getElement(1,n); scaleY = diagonal(m, 1, n) * toUnit;   // Fall through
                     case 1:  x0 = m.getElement(0,n); scaleX = diagonal(m, 0, n) * toUnit;
@@ -277,6 +296,16 @@ public abstract class DatumShiftGrid<C e
     }
 
     /**
+     * Returns the number of cells along each axis in the grid. The length of this array is equal to
+     * {@code coordinateToGrid} target dimensions.
+     *
+     * @return The number of cells along each axis in the grid.
+     */
+    public int[] getGridSize() {
+        return gridSize.clone();
+    }
+
+    /**
      * Returns the domain of validity of input coordinates that can be specified to the
      * {@link #interpolateAt interpolateAt(…)} method. Coordinates outside that domain of
      * validity will still be accepted, but the extrapolated results may be very wrong.
@@ -308,16 +337,6 @@ public abstract class DatumShiftGrid<C e
     }
 
     /**
-     * Returns the number of cells along each axis in the grid. The length of this array is equal to
-     * {@code coordinateToGrid} target dimensions.
-     *
-     * @return The number of cells along each axis in the grid.
-     */
-    public int[] getGridSize() {
-        return gridSize.clone();
-    }
-
-    /**
      * Conversion from the "real world" coordinates to grid indices including fractional parts.
      * The input points given to the {@code MathTransform} shall be in the unit of measurement
      * given by {@link #getCoordinateUnit()}.
@@ -489,7 +508,32 @@ public abstract class DatumShiftGrid<C e
     }
 
     /**
-     * Returns the translation stored at the given grid indices for the given dimension.
+     * Returns the derivative at the given grid indices.
+     *
+     * <div class="section">Default implementation</div>
+     * The current implementation assumes that the derivative is constant everywhere in the cell
+     * at the given indices. It does not yet take in account the fractional part of {@code gridX}
+     * and {@code gridY}, because empirical tests suggest that the accuracy of such interpolation
+     * is uncertain.
+     *
+     * @param  gridX First grid ordinate of the point for which to get the translation.
+     * @param  gridY Second grid ordinate of the point for which to get the translation.
+     * @return The derivative at the given location.
+     */
+    public Matrix derivativeInCell(final double gridX, final double gridY) {
+        final int ix = Math.max(0, Math.min(gridSize[0] - 2, (int) gridX));
+        final int iy = Math.max(0, Math.min(gridSize[1] - 2, (int) gridY));
+        final Matrix derivative = Matrices.createDiagonal(getTranslationDimensions(), gridSize.length);
+        for (int j=derivative.getNumRow(); --j>=0;) {
+            final double orig = getCellValue(j, iy, ix);
+            derivative.setElement(j, 0, derivative.getElement(j, 0) + (getCellValue(j, iy+1, ix) - orig));
+            derivative.setElement(j, 1, derivative.getElement(j, 1) + (getCellValue(j, iy, ix+1) - orig));
+        }
+        return derivative;
+    }
+
+    /**
+     * Returns the translation stored at the given two-dimensional grid indices for the given dimension.
      * The returned value is considered representative of the value in the center of the grid cell.
      * The output unit of measurement depends on the {@link #isCellValueRatio()} boolean:
      *
@@ -540,6 +584,28 @@ public abstract class DatumShiftGrid<C e
     }
 
     /**
+     * Returns an estimation of cell value precision (not to be confused with accuracy).
+     * This information can be determined in different ways:
+     *
+     * <ul>
+     *   <li>If the data are read from an ASCII file with a fixed number of digits, then a suggested value is half
+     *       the precision of the last digit (i.e. 0.5 × 10⁻ⁿ where <var>n</var> is the number of digits after the
+     *       comma).</li>
+     *   <li>If there is no indication about precision, then this method should return a value smaller than the
+     *       best accuracy found in the grid. Accuracy are often specified on a cell-by-cell basis in grid files.</li>
+     * </ul>
+     *
+     * The output unit of measurement is the same than the one documented in {@link #getCellValue}.
+     * In particular if {@link #isCellValueRatio()} returns {@code true}, then the accuracy is in
+     * units of grid cell size.
+     *
+     * <p>This information is used for determining a tolerance threshold in iterative calculation.</p>
+     *
+     * @return An estimation of cell value precision.
+     */
+    public abstract double getCellPrecision();
+
+    /**
      * Returns {@code true} if the translation values in the cells are divided by the cell size.
      * If {@code true}, then the values returned by {@link #getCellValue getCellValue(…)},
      * {@link #getCellMean getCellMean(…)} and {@link #interpolateInCell interpolateInCell(…)} methods

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/AffineTransforms2D.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/AffineTransforms2D.java?rev=1718273&r1=1718272&r2=1718273&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/AffineTransforms2D.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/AffineTransforms2D.java [UTF-8] Mon Dec  7 09:56:50 2015
@@ -65,7 +65,7 @@ public final class AffineTransforms2D ex
         if (matrix == null || matrix instanceof AffineTransform) {
             return (AffineTransform) matrix;
         }
-        MatrixSIS.ensureSizeMatch(3, matrix);
+        MatrixSIS.ensureSizeMatch(3, 3, matrix);
         if (!Matrices.isAffine(matrix)) {
             throw new IllegalStateException(Errors.format(Errors.Keys.NotAnAffineTransform));
         }

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/GeneralMatrix.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/GeneralMatrix.java?rev=1718273&r1=1718272&r2=1718273&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/GeneralMatrix.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/GeneralMatrix.java [UTF-8] Mon Dec  7 09:56:50 2015
@@ -172,27 +172,6 @@ class GeneralMatrix extends MatrixSIS im
     }
 
     /**
-     * Copies the elements of the given matrix in the given array.
-     * This method ignores the error terms, if any.
-     *
-     * @param matrix   The matrix to copy.
-     * @param numRow   The number of rows to copy (usually {@code matrix.getNumRow()}).
-     * @param numCol   The number of columns to copy (usually {@code matrix.getNumCol()}).
-     * @param elements Where to copy the elements.
-     */
-    private static void getElements(final Matrix matrix, final int numRow, final int numCol, final double[] elements) {
-        if (matrix instanceof MatrixSIS) {
-            ((MatrixSIS) matrix).getElements(elements);
-        } else {
-            for (int k=0,j=0; j<numRow; j++) {
-                for (int i=0; i<numCol; i++) {
-                    elements[k++] = matrix.getElement(j, i);
-                }
-            }
-        }
-    }
-
-    /**
      * Infers all {@link DoubleDouble#error} with a default values inferred from {@link DoubleDouble#value}.
      * For example if a matrix element is exactly 3.141592653589793, there is good chances that the user's
      * intend was to specify the {@link Math#PI} value, in which case this method will infer that we would

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrix1.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrix1.java?rev=1718273&r1=1718272&r2=1718273&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrix1.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrix1.java [UTF-8] Mon Dec  7 09:56:50 2015
@@ -117,7 +117,7 @@ public final class Matrix1 extends Matri
         if (matrix == null || matrix instanceof Matrix1) {
             return (Matrix1) matrix;
         }
-        ensureSizeMatch(SIZE, matrix);
+        ensureSizeMatch(SIZE, SIZE, matrix);
         return new Matrix1(matrix);
     }
 

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrix2.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrix2.java?rev=1718273&r1=1718272&r2=1718273&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrix2.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrix2.java [UTF-8] Mon Dec  7 09:56:50 2015
@@ -127,7 +127,7 @@ public final class Matrix2 extends Matri
         if (matrix == null || matrix instanceof Matrix2) {
             return (Matrix2) matrix;
         }
-        ensureSizeMatch(SIZE, matrix);
+        ensureSizeMatch(SIZE, SIZE, matrix);
         return new Matrix2(matrix);
     }
 

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrix3.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrix3.java?rev=1718273&r1=1718272&r2=1718273&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrix3.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrix3.java [UTF-8] Mon Dec  7 09:56:50 2015
@@ -139,7 +139,7 @@ public final class Matrix3 extends Matri
         if (matrix == null || matrix instanceof Matrix3) {
             return (Matrix3) matrix;
         }
-        ensureSizeMatch(SIZE, matrix);
+        ensureSizeMatch(SIZE, SIZE, matrix);
         return new Matrix3(matrix);
     }
 

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrix4.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrix4.java?rev=1718273&r1=1718272&r2=1718273&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrix4.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrix4.java [UTF-8] Mon Dec  7 09:56:50 2015
@@ -157,7 +157,7 @@ public final class Matrix4 extends Matri
         if (matrix == null || matrix instanceof Matrix4) {
             return (Matrix4) matrix;
         }
-        ensureSizeMatch(SIZE, matrix);
+        ensureSizeMatch(SIZE, SIZE, matrix);
         return new Matrix4(matrix);
     }
 

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=1718273&r1=1718272&r2=1718273&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] Mon Dec  7 09:56:50 2015
@@ -87,16 +87,17 @@ public abstract class MatrixSIS implemen
     }
 
     /**
-     * Ensures that the given matrix is a square matrix having the given dimension.
+     * Ensures that the given matrix has the given dimension.
      * This is a convenience method for subclasses.
      */
-    static void ensureSizeMatch(final int size, final Matrix matrix) {
-        final int numRow = matrix.getNumRow();
-        final int numCol = matrix.getNumCol();
-        if (numRow != size || numCol != size) {
-            final Integer n = size;
+    static void ensureSizeMatch(final int numRow, final int numCol, final Matrix matrix)
+            throws MismatchedMatrixSizeException
+    {
+        final int othRow = matrix.getNumRow();
+        final int othCol = matrix.getNumCol();
+        if (numRow != othRow || numCol != othCol) {
             throw new MismatchedMatrixSizeException(Errors.format(
-                    Errors.Keys.MismatchedMatrixSize_4, n, n, numRow, numCol));
+                    Errors.Keys.MismatchedMatrixSize_4, numRow, numCol, othRow, othCol));
         }
     }
 
@@ -214,6 +215,27 @@ public abstract class MatrixSIS implemen
     }
 
     /**
+     * Copies the elements of the given matrix in the given array.
+     * This method ignores the error terms, if any.
+     *
+     * @param matrix   The matrix to copy.
+     * @param numRow   {@code matrix.getNumRow()}.
+     * @param numCol   {@code matrix.getNumCol()}.
+     * @param elements Where to copy the elements.
+     */
+    static void getElements(final Matrix matrix, final int numRow, final int numCol, final double[] elements) {
+        if (matrix instanceof MatrixSIS) {
+            ((MatrixSIS) matrix).getElements(elements);
+        } else {
+            for (int k=0,j=0; j<numRow; j++) {
+                for (int i=0; i<numCol; i++) {
+                    elements[k++] = matrix.getElement(j, i);
+                }
+            }
+        }
+    }
+
+    /**
      * Sets all matrix elements from a flat, row-major (column indices vary fastest) array.
      * The array length shall be <code>{@linkplain #getNumRow()} * {@linkplain #getNumCol()}</code>.
      *
@@ -226,6 +248,25 @@ public abstract class MatrixSIS implemen
     public abstract void setElements(final double[] elements);
 
     /**
+     * Sets this matrix to the values of another matrix.
+     * The given matrix must have the same size.
+     *
+     * @param matrix  The matrix to copy.
+     * @throws MismatchedMatrixSizeException if the given matrix has a different size than this matrix.
+     *
+     * @since 0.7
+     */
+    public void setMatrix(final Matrix matrix) throws MismatchedMatrixSizeException {
+        ArgumentChecks.ensureNonNull("matrix", matrix);
+        final int numRow = getNumRow();
+        final int numCol = getNumCol();
+        ensureSizeMatch(numRow, numCol, matrix);
+        final double[] elements = new double[numRow * numCol];
+        getElements(matrix, numRow, numCol, elements);
+        setElements(elements);
+    }
+
+    /**
      * Returns {@code true} if this matrix uses extended precision.
      */
     boolean isExtendedPrecision() {

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=1718273&r1=1718272&r2=1718273&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] Mon Dec  7 09:56:50 2015
@@ -210,7 +210,7 @@ public class InterpolatedGeocentricTrans
     }
 
     /**
-     * Creates a transformation between two from geographic CRS. This factory method combines the
+     * Creates a transformation between two geographic CRS. This factory method combines the
      * {@code InterpolatedGeocentricTransform} instance with the steps needed for converting values between
      * degrees to radians. The transform works with input and output coordinates in the following units:
      *
@@ -472,7 +472,8 @@ public class InterpolatedGeocentricTrans
 
     /**
      * Returns a description of the internal parameters of this {@code InterpolatedGeocentricTransform} transform.
-     * The returned group contains parameter descriptors for the number of dimensions and the eccentricity.
+     * 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}

Added: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedTransform.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedTransform.java?rev=1718273&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedTransform.java (added)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedTransform.java [UTF-8] Mon Dec  7 09:56:50 2015
@@ -0,0 +1,594 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.referencing.operation.transform;
+
+import java.util.Arrays;
+import java.io.Serializable;
+import javax.measure.unit.Unit;
+import javax.measure.unit.NonSI;
+import javax.measure.quantity.Quantity;
+import javax.measure.converter.UnitConverter;
+import javax.measure.converter.LinearConverter;
+import org.opengis.util.FactoryException;
+import org.opengis.parameter.ParameterValueGroup;
+import org.opengis.parameter.ParameterDescriptor;
+import org.opengis.parameter.GeneralParameterDescriptor;
+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.datum.DatumShiftGrid;
+import org.apache.sis.referencing.operation.matrix.Matrices;
+import org.apache.sis.referencing.operation.matrix.MatrixSIS;
+import org.apache.sis.referencing.operation.matrix.NoninvertibleMatrixException;
+import org.apache.sis.measure.Units;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.internal.referencing.Formulas;
+import org.apache.sis.internal.referencing.DirectPositionView;
+import org.apache.sis.internal.referencing.provider.NTv2;
+import org.apache.sis.internal.referencing.provider.DatumShiftGridFile;
+
+// Branch-specific imports
+import java.util.Objects;
+import java.nio.file.Path;
+
+
+/**
+ * Transforms between two CRS by performing translations interpolated from a grid file.
+ * The source and target coordinate reference systems are typically, but not necessarily,
+ * two-dimensional {@linkplain org.apache.sis.referencing.crs.DefaultGeographicCRS geographic CRS}.
+ * The actual number of dimensions is determined by {@link DatumShiftGrid#getTranslationDimensions()}.
+ *
+ * <div class="note"><b>Example:</b>
+ * this transform is used for example with NADCON and NTv2 datum shift grids.</div>
+ *
+ * <div class="section">Input and output coordinates</div>
+ * First, <cite>"real world"</cite> input coordinates (<var>x</var>,<var>y</var>) are converted to
+ * <cite>grid</cite> coordinates (<var>gridX</var>, <var>gridY</var>), which are zero-based indices
+ * in the two-dimensional grid. This conversion is applied by an affine transform <em>before</em>
+ * to be passed to the {@code transform} methods of this {@code InterpolatedTransform} class.
+ *
+ * <p>Translation vectors are stored in the datum shift grid at the specified grid indices.
+ * If the grid indices are non-integer values, then the translations are interpolated using a bilinear interpolation.
+ * If the grid indices are outside the grid domain ([0 … <var>width</var>-2] × [0 … <var>height</var>-2]
+ * where <var>width</var> and <var>height</var> are the number of columns and rows in the grid),
+ * then the translations are extrapolated. The translation is then added to the input coordinates.</p>
+ *
+ * <p>The input and output coordinates can have any number of dimensions, provided that they are the same
+ * than the number of {@linkplain DatumShiftGrid#getTranslationDimensions() translation dimensions}.
+ * However current implementation uses only the two first dimensions for interpolations in the grid.</p>
+ *
+ * @author  Rémi Eve (IRD)
+ * @author  Martin Desruisseaux (IRD, Geomatys)
+ * @author  Simon Reynard (Geomatys)
+ * @author  Rueben Schulz (UBC)
+ * @since   0.7
+ * @version 0.7
+ * @module
+ */
+public class InterpolatedTransform extends AbstractMathTransform implements Serializable {
+    /**
+     * Serial number for inter-operability with different versions.
+     */
+    private static final long serialVersionUID = -8962688502524486475L;
+
+    /**
+     * Number of dimensions used for interpolating in the datum shift grid.
+     * This is not necessarily the same than the number of dimensions of interpolated values.
+     * In current SIS implementation, this number of dimensions is fixed by the {@link DatumShiftGrid} API.
+     */
+    private static final int GRID_DIMENSION = 2;
+
+    /**
+     * The grid of datum shifts from source datum to target datum.
+     */
+    protected final DatumShiftGrid<?,?> grid;
+
+    /**
+     * The value of {@link DatumShiftGrid#getTranslationDimensions()}, stored for efficiency.
+     */
+    private final int dimension;
+
+    /**
+     * 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 interpolated transform.
+     *
+     * @see #inverse()
+     */
+    private final InterpolatedTransform.Inverse inverse;
+
+    /**
+     * Creates a transform for the given interpolation grid.
+     * This {@code InterpolatedTransform} class works with ordinate values in <em>units of grid cell</em>
+     * For example input coordinate (4,5) is the position of the center of the cell at grid index (4,5).
+     * The output units are the same than the input units.
+     *
+     * <p>For converting geodetic coordinates, {@code InterpolatedTransform} instances need to be concatenated
+     * with the following affine transforms:
+     *
+     * <ul>
+     *   <li><cite>Normalization</cite> before {@code InterpolatedTransform}
+     *     for converting the geodetic coordinates into grid coordinates.</li>
+     *   <li><cite>Denormalization</cite> after {@code InterpolatedTransform}
+     *     for converting grid coordinates into geodetic coordinates.</li>
+     * </ul>
+     *
+     * After {@code InterpolatedTransform} construction,
+     * the full conversion chain including the above affine transforms can be created by
+     * <code>{@linkplain #getContextualParameters()}.{@linkplain ContextualParameters#completeTransform
+     * completeTransform}(factory, this)}</code>.
+     *
+     * @param  <T> Dimension of the coordinate and the translation unit.
+     * @param  grid The grid of datum shifts from source to target datum.
+     * @throws NoninvertibleMatrixException if the conversion from geodetic coordinates
+     *         to grid indices can not be inverted.
+     *
+     * @see #createGeodeticTransformation(MathTransformFactory, DatumShiftGrid)
+     */
+    protected <T extends Quantity> InterpolatedTransform(final DatumShiftGrid<T,T> grid)
+            throws NoninvertibleMatrixException
+    {
+        ArgumentChecks.ensureNonNull("grid", grid);
+        if (!grid.isCellValueRatio()) {
+            throw new IllegalArgumentException(Errors.format(
+                    Errors.Keys.IllegalParameterValue_2, "isCellValueRatio", Boolean.FALSE));
+        }
+        final Unit<T> unit = grid.getTranslationUnit();
+        if (unit != grid.getCoordinateUnit()) {
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalUnitFor_2, "translation", unit));
+        }
+        this.grid = grid;
+        dimension = grid.getTranslationDimensions();
+        /*
+         * Create the contextual parameters using the descriptor of the provider that created the datum shift grid.
+         * If that provider is unknown, default (for now) to NTv2. This default may change in any future SIS version.
+         */
+        if (grid instanceof DatumShiftGridFile<?,?>) {
+            final DatumShiftGridFile<?,?> gridFile = (DatumShiftGridFile<?,?>) grid;
+            context = new ContextualParameters(gridFile.descriptor, dimension + 1, dimension + 1);
+            for (final GeneralParameterDescriptor gd : gridFile.descriptor.descriptors()) {
+                if (gd instanceof ParameterDescriptor<?>) {
+                    final ParameterDescriptor<?> d = (ParameterDescriptor<?>) gd;
+                    if (Path.class.isAssignableFrom(d.getValueClass())) {
+                        context.getOrCreate(d).setValue(gridFile.file);
+                        break;
+                    }
+                }
+            }
+        } else {
+            context = new ContextualParameters(NTv2.PARAMETERS, dimension + 1, dimension + 1);
+        }
+        /*
+         * Set the normalization matrix to the conversion from grid coordinates (e.g. seconds of angle)
+         * to grid indices. This will allow us to invoke DatumShiftGrid.interpolateAtCell(x, y, vector)
+         * directly in the transform(…) methods.
+         */
+        final MatrixSIS normalize = context.getMatrix(ContextualParameters.MatrixRole.NORMALIZATION);
+        normalize.setMatrix(grid.getCoordinateToGrid().getMatrix());
+        /*
+         * NADCON and NTv2 datum shift grids expect geographic coordinates in seconds of angle, while
+         * MathTransform instances created by DefaultMathTransformFactory.createParameterized(…) must
+         * expect coordinates in standardized units (degrees of angle, metres, seconds, etc.).
+         * We concatenate the unit conversion with above "coordinate to grid" conversion.
+         */
+        @SuppressWarnings("unchecked")
+        final Unit<T> normalized = Units.isAngular(unit) ? (Unit<T>) NonSI.DEGREE_ANGLE : unit.toSI();
+        if (!unit.equals(normalized)) {
+            final UnitConverter converter = normalized.getConverterTo(unit);
+            if (!(converter instanceof LinearConverter)) {
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.NonLinearUnitConversion_2, normalized, unit));
+            }
+            final Double offset = converter.convert(0);
+            final Double scale  = Units.derivative(converter, 0);
+            for (int j=0; j<dimension; j++) {
+                normalize.convertBefore(j, scale, offset);
+            }
+        }
+        /*
+         * Denormalization is the inverse of all above conversions.
+         */
+        context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION).setMatrix(normalize.inverse());
+        inverse = new Inverse();
+    }
+
+    /**
+     * Creates a transformation between two geodetic CRS. This factory method combines the
+     * {@code InterpolatedTransform} instance with the steps needed for converting values between
+     * geodetic and grid coordinates.
+     *
+     * <div class="section">Unit of measurement</div>
+     * The unit of measurement is determined by {@link DatumShiftGrid#getCoordinateUnit()}:
+     * <ul>
+     *   <li>If the datum shift unit {@linkplain Units#isAngular(Unit) is angular}, then the transform
+     *       will work with input and output coordinates in degrees of angle.</li>
+     *   <li>If the datum shift unit {@linkplain Units#isLinear(Unit) is linear}, then the transform
+     *       will work with input and output coordinates in metres.</li>
+     *   <li>If the datum shift unit {@linkplain Units#isTemporal(Unit) is temporal}, then the transform
+     *       will work with input and output coordinates in seconds.</li>
+     *   <li>Generally for all units other than angular, the transform will work with input and output
+     *       coordinates in the unit given by {@link Unit#toSI()}.</li>
+     * </ul>
+     *
+     * @param <T>      Dimension of the coordinate and the translation unit.
+     * @param factory  The factory to use for creating the transform.
+     * @param grid     The grid of datum shifts from source to target datum.
+     *                 The {@link DatumShiftGrid#interpolateInCell DatumShiftGrid.interpolateInCell(…)}
+     *                 method shall compute translations from <em>source</em> to <em>target</em> as
+     *                 {@linkplain DatumShiftGrid#isCellValueRatio() ratio of offsets divided by cell sizes}.
+     * @return The transformation between geodetic coordinates.
+     * @throws FactoryException if an error occurred while creating a transform.
+     */
+    public static <T extends Quantity> MathTransform createGeodeticTransformation(
+            final MathTransformFactory factory, final DatumShiftGrid<T,T> grid) throws FactoryException
+    {
+        ArgumentChecks.ensureNonNull("grid", grid);
+        final InterpolatedTransform tr;
+        try {
+            tr = new InterpolatedTransform(grid);
+        } catch (NoninvertibleMatrixException e) {
+            throw new FactoryException(e.getLocalizedMessage(), e);
+        }
+        return tr.context.completeTransform(factory, tr);
+    }
+
+    /**
+     * Returns the number of input dimensions.
+     * This fixed to {@link DatumShiftGrid#getTranslationDimensions()}.
+     *
+     * @return The dimension of input points.
+     */
+    @Override
+    public final int getSourceDimensions() {
+        return dimension;
+    }
+
+    /**
+     * Returns the number of target dimensions.
+     * This fixed to {@link DatumShiftGrid#getTranslationDimensions()}.
+     *
+     * @return The dimension of output points.
+     */
+    @Override
+    public final int getTargetDimensions() {
+        return dimension;
+    }
+
+    /**
+     * Returns the parameter values of this transform.
+     *
+     * @return The parameter values for this transform.
+     */
+    @Override
+    public ParameterValueGroup getParameterValues() {
+        return context;
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * Applies the datum shift on a coordinate and optionally returns the derivative at that location.
+     *
+     * @return {@inheritDoc}
+     * @throws TransformException if the point can not be transformed or
+     *         if a problem occurred while calculating the derivative.
+     */
+    @Override
+    public Matrix transform(final double[] srcPts, final int srcOff,
+                            final double[] dstPts, final int dstOff,
+                            final boolean derivate) throws TransformException
+    {
+        final double x = srcPts[srcOff  ];
+        final double y = srcPts[srcOff+1];
+        if (dstPts != null) {
+            final double[] vector = new double[dimension];
+            grid.interpolateInCell(x, y, vector);
+            if (dimension > GRID_DIMENSION) {
+                System.arraycopy(srcPts, srcOff + GRID_DIMENSION,
+                                 dstPts, dstOff + GRID_DIMENSION,
+                                      dimension - GRID_DIMENSION);
+                /*
+                 * We can not use srcPts[srcOff + i] = dstPts[dstOff + i] + offset[i]
+                 * because the arrays may overlap. The contract said that this method
+                 * must behave as if all input ordinate values have been read before
+                 * we write outputs, which is the reason for System.arraycopy(…) call.
+                 */
+                int i = dimension;
+                do dstPts[dstOff + --i] += vector[i];
+                while (i > GRID_DIMENSION);
+            }
+            dstPts[dstOff+1] = y + vector[1];
+            dstPts[dstOff  ] = x + vector[0];      // Shall not be done before above loop.
+        }
+        if (!derivate) {
+            return null;
+        }
+        return grid.derivativeInCell(x, y);
+    }
+
+    /**
+     * Transforms an arbitrary amount of coordinates.
+     *
+     * @throws TransformException if a point can not be transformed.
+     */
+    @Override
+    public void transform(double[] srcPts, int srcOff, final double[] dstPts, int dstOff, int numPts)
+            throws TransformException
+    {
+        int inc = dimension;
+        if (srcPts == dstPts) {
+            switch (IterationStrategy.suggest(srcOff, inc, dstOff, inc, numPts)) {
+                case ASCENDING: {
+                    break;
+                }
+                case DESCENDING: {
+                    srcOff += (numPts-1) * inc;
+                    dstOff += (numPts-1) * inc;
+                    inc = -inc;
+                    break;
+                }
+                default: {  // BUFFER_SOURCE, but also a reasonable default for any case.
+                    srcPts = Arrays.copyOfRange(srcPts, srcOff, srcOff + numPts*inc);
+                    srcOff = 0;
+                    break;
+                }
+            }
+        }
+        final double[] vector = new double[dimension];
+        while (--numPts >= 0) {
+            final double x = srcPts[srcOff  ];
+            final double y = srcPts[srcOff+1];
+            grid.interpolateInCell(x, y, vector);
+            if (dimension > GRID_DIMENSION) {
+                System.arraycopy(srcPts, srcOff + GRID_DIMENSION,
+                                 dstPts, dstOff + GRID_DIMENSION,
+                                      dimension - GRID_DIMENSION);
+                /*
+                 * We can not use srcPts[srcOff + i] = dstPts[dstOff + i] + offset[i]
+                 * because the arrays may overlap. The contract said that this method
+                 * must behave as if all input ordinate values have been read before
+                 * we write outputs, which is the reason for System.arraycopy(…) call.
+                 */
+                int i = dimension;
+                do dstPts[dstOff + --i] += vector[i];
+                while (i > GRID_DIMENSION);
+            }
+            dstPts[dstOff+1] = y + vector[1];
+            dstPts[dstOff  ] = x + vector[0];      // Shall not be done before above loop.
+            dstOff += inc;
+            srcOff += inc;
+        }
+    }
+
+    /*
+     * 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.
+     *
+     * @return A transform from the target ellipsoid to the source ellipsoid of this transform.
+     */
+    @Override
+    public MathTransform inverse() {
+        return inverse;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return {@inheritDoc}
+     */
+    @Override
+    protected int computeHashCode() {
+        return super.computeHashCode() + Objects.hashCode(grid);
+    }
+
+    /**
+     * Compares the specified object with this math transform for equality.
+     *
+     * @return {@inheritDoc}
+     */
+    @Override
+    public boolean equals(final Object object, final ComparisonMode mode) {
+        if (object == this) {
+            // Slight optimization
+            return true;
+        }
+        return super.equals(object, mode) && Objects.equals(grid, ((InterpolatedTransform) object).grid);
+    }
+
+    /**
+     * Transforms target coordinates to source coordinates. This is done by iteratively finding a target coordinate
+     * that shifts to the input coordinate. The input coordinate is used as the first approximation.
+     *
+     * @author  Rueben Schulz (UBC)
+     * @author  Martin Desruisseaux (IRD, Geomatys)
+     * @version 0.7
+     * @since   0.7
+     * @module
+     */
+    private final class Inverse extends AbstractMathTransform.Inverse {
+        /**
+         * Serial number for inter-operability with different versions.
+         */
+        private static final long serialVersionUID = -6779719408779847014L;
+
+        /**
+         * Difference allowed in iterative computations, in units of grid cell size.
+         */
+        private final double tolerance;
+
+        /**
+         * Creates an inverse transform.
+         */
+        Inverse() {
+            tolerance = grid.getCellPrecision();
+            if (!(tolerance > 0)) {         // Use ! for catching NaN.
+                throw new IllegalArgumentException(Errors.format(
+                        Errors.Keys.ValueNotGreaterThanZero_2, "grid.cellPrecision", tolerance));
+            }
+        }
+
+        /**
+         * Transforms a single coordinate in a list of ordinal values,
+         * and optionally returns the derivative at that location.
+         *
+         * @throws TransformException If there is no convergence.
+         */
+        @Override
+        public Matrix transform(final double[] srcPts, final int srcOff, double[] dstPts, int dstOff,
+                                final boolean derivate) throws TransformException
+        {
+            if (dstPts == null) {
+                dstPts = new double[dimension];
+                dstOff = 0;
+            }
+            double xi, yi;
+            final double x = xi = srcPts[srcOff  ];
+            final double y = yi = srcPts[srcOff+1];
+            final double[] vector = new double[dimension];
+            int it = Formulas.MAXIMUM_ITERATIONS;
+            do {
+                grid.interpolateInCell(xi, yi, vector);
+                final double ox = xi;
+                final double oy = yi;
+                xi = x - vector[0];
+                yi = y - vector[1];
+                if (!(Math.abs(xi - ox) > tolerance ||    // Use '!' for catching NaN.
+                      Math.abs(yi - oy) > tolerance))
+                {
+                    if (dimension > GRID_DIMENSION) {
+                        System.arraycopy(srcPts, srcOff + GRID_DIMENSION,
+                                         dstPts, dstOff + GRID_DIMENSION,
+                                              dimension - GRID_DIMENSION);
+                        /*
+                         * We can not use srcPts[srcOff + i] = dstPts[dstOff + i] + offset[i]
+                         * because the arrays may overlap. The contract said that this method
+                         * must behave as if all input ordinate values have been read before
+                         * we write outputs, which is the reason for System.arraycopy(…) call.
+                         */
+                        int i = dimension;
+                        do dstPts[dstOff + --i] += vector[i];
+                        while (i > GRID_DIMENSION);
+                    }
+                    dstPts[dstOff  ] = xi;      // Shall not be done before above loop.
+                    dstPts[dstOff+1] = yi;
+                    if (derivate) {
+                        return Matrices.inverse(InterpolatedTransform.this.derivative(
+                                new DirectPositionView(dstPts, dstOff, dimension)));
+                    }
+                    return null;
+                }
+            } while (--it >= 0);
+            throw new TransformException(Errors.format(Errors.Keys.NoConvergence));
+        }
+
+        /**
+         * Transforms an arbitrary amount of coordinates.
+         *
+         * @throws TransformException if a point can not be transformed.
+         */
+        @Override
+        public void transform(double[] srcPts, int srcOff, final double[] dstPts, int dstOff, int numPts)
+                throws TransformException
+        {
+            int inc = dimension;
+            if (srcPts == dstPts) {
+                switch (IterationStrategy.suggest(srcOff, inc, dstOff, inc, numPts)) {
+                    case ASCENDING: {
+                        break;
+                    }
+                    case DESCENDING: {
+                        srcOff += (numPts-1) * inc;
+                        dstOff += (numPts-1) * inc;
+                        inc = -inc;
+                        break;
+                    }
+                    default: {  // BUFFER_SOURCE, but also a reasonable default for any case.
+                        srcPts = Arrays.copyOfRange(srcPts, srcOff, srcOff + numPts*inc);
+                        srcOff = 0;
+                        break;
+                    }
+                }
+            }
+            final double[] vector = new double[dimension];
+nextPoint:  while (--numPts >= 0) {
+                double xi, yi;
+                final double x = xi = srcPts[srcOff  ];
+                final double y = yi = srcPts[srcOff+1];
+                int it = Formulas.MAXIMUM_ITERATIONS;
+                do {
+                    grid.interpolateInCell(xi, yi, vector);
+                    final double ox = xi;
+                    final double oy = yi;
+                    xi = x - vector[0];
+                    yi = y - vector[1];
+                    if (!(Math.abs(xi - ox) > tolerance ||    // Use '!' for catching NaN.
+                          Math.abs(yi - oy) > tolerance))
+                    {
+                        if (dimension > GRID_DIMENSION) {
+                            System.arraycopy(srcPts, srcOff + GRID_DIMENSION,
+                                             dstPts, dstOff + GRID_DIMENSION,
+                                                  dimension - GRID_DIMENSION);
+                            /*
+                             * We can not use srcPts[srcOff + i] = dstPts[dstOff + i] + offset[i]
+                             * because the arrays may overlap. The contract said that this method
+                             * must behave as if all input ordinate values have been read before
+                             * we write outputs, which is the reason for System.arraycopy(…) call.
+                             */
+                            int i = dimension;
+                            do dstPts[dstOff + --i] += vector[i];
+                            while (i > GRID_DIMENSION);
+                        }
+                        dstPts[dstOff  ] = xi;      // Shall not be done before above loop.
+                        dstPts[dstOff+1] = yi;
+                        dstOff += inc;
+                        srcOff += inc;
+                        continue nextPoint;
+                    }
+                } while (--it >= 0);
+                throw new TransformException(Errors.format(Errors.Keys.NoConvergence));
+            }
+        }
+    }
+}

Propchange: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedTransform.java
------------------------------------------------------------------------------
    svn:eol-style = native

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

Modified: 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/MolodenskyFormula.java?rev=1718273&r1=1718272&r2=1718273&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/MolodenskyFormula.java [UTF-8] Mon Dec  7 09:56:50 2015
@@ -236,7 +236,8 @@ abstract class MolodenskyFormula extends
 
     /**
      * Returns a copy of internal parameter values of this transform.
-     * The returned group contains parameter values for the eccentricity and the shift among others.
+     * 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}




Mime
View raw message