sis-commits mailing list archives

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

URL: http://svn.apache.org/viewvc?rev=1718271&view=rev
Log:
Redesign DatumShiftGrid API in an attempt to make it safer:
- Unit of measurement are explicitely declared.
- Input units are no longer required to be radians.
- Separated the interpolation method in two methods:
  1) one expecting "real world" coordinate (only a convenience method which delegate to the method below),
  2) one expecting a coordinate in grid units. This is the method really used by InterpolatedGeocentricTransform.

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/Matrices.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractLinearTransform.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/LinearTransform.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform1D.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MolodenskyFormula.java
    sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/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-utility/src/main/java/org/apache/sis/measure/Units.java

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=1718271&r1=1718270&r2=1718271&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:52:59 2015
@@ -17,6 +17,7 @@
 package org.apache.sis.internal.referencing.provider;
 
 import java.util.Arrays;
+import javax.measure.quantity.Quantity;
 import org.apache.sis.math.DecimalFunctions;
 
 
@@ -26,18 +27,28 @@ import org.apache.sis.math.DecimalFuncti
  * increase the precision in the common case where the shifts are specified with no more than
  * 5 digits in base 10 in ASCII files.
  *
+ * @param <C> Dimension of the coordinate unit (usually {@link javax.measure.quantity.Angle}).
+ * @param <T> Dimension of the translation unit (usually {@link javax.measure.quantity.Angle}
+ *            or {@link javax.measure.quantity.Length}).
+ *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.7
  * @version 0.7
  * @module
  */
-final class DatumShiftGridCompressed extends DatumShiftGridFile {
+final class DatumShiftGridCompressed<C extends Quantity, T extends Quantity> extends DatumShiftGridFile<C,T> {
     /**
      * Serial number for inter-operability with different versions.
      */
     private static final long serialVersionUID = 4847888093457104917L;
 
     /**
+     * Maximal grid index along the <var>y</var> axis.
+     * This is the number of grid cells minus 2.
+     */
+    private final int ymax;
+
+    /**
      * An "average" value for the offset in each dimension.
      */
     private final double[] averages;
@@ -56,10 +67,11 @@ final class DatumShiftGridCompressed ext
     /**
      * Creates a new datum shift grid for the same geometry than the given grid but different data.
      */
-    private DatumShiftGridCompressed(final DatumShiftGridFile grid, final double[] averages,
+    private DatumShiftGridCompressed(final DatumShiftGridFile<C,T> grid, final double[] averages,
             final short[][] data, final double scale)
     {
         super(grid);
+        this.ymax     = getGridSize()[1] - 2;
         this.averages = averages;
         this.data     = data;
         this.scale    = scale;
@@ -74,7 +86,9 @@ final class DatumShiftGridCompressed ext
      * @param  scale     The factor by which to multiply each compressed value before to add to the average value.
      * @return The grid to use (may or may not be compressed).
      */
-    static DatumShiftGridFile compress(final DatumShiftGridFile.Float grid, double[] averages, final double scale) {
+    static <C extends Quantity, T extends Quantity> DatumShiftGridFile<C,T> compress(
+            final DatumShiftGridFile.Float<C,T> grid, double[] averages, final double scale)
+    {
         final short[][] data = new short[grid.offsets.length][];
         final boolean computeAverages = (averages == null);
         if (computeAverages) {
@@ -83,7 +97,7 @@ final class DatumShiftGridCompressed ext
         for (int dim = 0; dim < data.length; dim++) {
             final double average;
             if (computeAverages) {
-                average = Math.rint(grid.getAverageOffset(dim) / scale);
+                average = Math.rint(grid.getCellMean(dim) / scale);
                 averages[dim] = average * scale;
             } else {
                 average = averages[dim] / scale;
@@ -102,15 +116,15 @@ final class DatumShiftGridCompressed ext
             }
             data[dim] = compressed;
         }
-        return new DatumShiftGridCompressed(grid, averages, data, scale);
+        return new DatumShiftGridCompressed<>(grid, averages, data, scale);
     }
 
     /**
      * Returns a new grid with the same geometry than this grid but different data arrays.
      */
     @Override
-    final DatumShiftGridFile setData(final Object[] other) {
-        return new DatumShiftGridCompressed(this, averages, (short[][]) other, scale);
+    final DatumShiftGridFile<C,T> setData(final Object[] other) {
+        return new DatumShiftGridCompressed<>(this, averages, (short[][]) other, scale);
     }
 
     /**
@@ -126,7 +140,7 @@ final class DatumShiftGridCompressed ext
      * Returns the number of shift dimensions.
      */
     @Override
-    public final int getShiftDimensions() {
+    public final int getTranslationDimensions() {
         return data.length;
     }
 
@@ -137,7 +151,7 @@ final class DatumShiftGridCompressed ext
      * @return A value close to the average for the given dimension.
      */
     @Override
-    public double getAverageOffset(final int dim) {
+    public double getCellMean(final int dim) {
         return averages[dim];
     }
 
@@ -145,7 +159,7 @@ final class DatumShiftGridCompressed ext
      * Returns the cell value at the given grid index.
      */
     @Override
-    protected double getCellValue(final int dim, final int gridX, final int gridY) {
+    public double getCellValue(final int dim, final int gridX, final int gridY) {
         return data[dim][gridX + gridY*nx] * scale + averages[dim];
     }
 
@@ -154,20 +168,32 @@ final class DatumShiftGridCompressed ext
      * reduce the number of arithmetic operations for efficiency reasons.
      */
     @Override
-    public void offsetAt(double x, double y, double[] offsets) {
-        final int gridX = Math.max(0, Math.min(nx - 2, (int) Math.floor(x = (x - x0) * scaleX)));
-        final int gridY = Math.max(0, Math.min(ny - 2, (int) Math.floor(y = (y - y0) * scaleY)));
-        x -= gridX;
-        y -= gridY;
-        final int p0 = nx*gridY + gridX;
+    public void interpolateInCell(double gridX, double gridY, double[] vector) {
+        int ix = (int) gridX;  gridX -= ix;
+        int iy = (int) gridY;  gridY -= iy;
+        if (ix < 0) {
+            ix = 0;
+            gridX = -1;
+        } else if (ix >= nx - 1) {
+            ix = nx - 2;
+            gridX = +1;
+        }
+        if (iy < 0) {
+            iy = 0;
+            gridY = -1;
+        } else if (iy > ymax) {   // Subtraction of 2 already done by the constructor.
+            iy = ymax;
+            gridY = +1;
+        }
+        final int p0 = nx*iy + ix;
         final int p1 = nx + p0;
         for (int dim = 0; dim < data.length; dim++) {
             final short[] values = data[dim];
             double r0 = values[p0];
             double r1 = values[p1];
-            r0 +=  x * (values[p0+1] - r0);
-            r1 +=  x * (values[p1+1] - r1);
-            offsets[dim] = (y * (r1 - r0) + r0) * scale + averages[dim];
+            r0 +=  gridX * (values[p0+1] - r0);
+            r1 +=  gridX * (values[p1+1] - r1);
+            vector[dim] = (gridY * (r1 - r0) + r0) * scale + averages[dim];
         }
     }
 
@@ -181,7 +207,7 @@ final class DatumShiftGridCompressed ext
     @Override
     public boolean equals(final Object other) {
         if (super.equals(other)) {
-            final DatumShiftGridCompressed that = (DatumShiftGridCompressed) other;
+            final DatumShiftGridCompressed<?,?> that = (DatumShiftGridCompressed<?,?>) other;
             return Double.doubleToLongBits(scale) == Double.doubleToLongBits(that.scale)
                    && Arrays.equals(averages, that.averages);
         }

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=1718271&r1=1718270&r2=1718271&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:52:59 2015
@@ -18,9 +18,12 @@ package org.apache.sis.internal.referenc
 
 import java.util.Arrays;
 import java.lang.reflect.Array;
+import javax.measure.unit.Unit;
+import javax.measure.quantity.Quantity;
 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;
 
 // Branch-specific imports
 import java.nio.file.Path;
@@ -39,12 +42,16 @@ import java.nio.file.Path;
  *       from {@code float} to {@code double} performed by the {@link #getCellValue(int, int, int)} method.</li>
  * </ul>
  *
+ * @param <C> Dimension of the coordinate unit (usually {@link javax.measure.quantity.Angle}).
+ * @param <T> Dimension of the translation unit (usually {@link javax.measure.quantity.Angle}
+ *            or {@link javax.measure.quantity.Length}).
+ *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.7
  * @version 0.7
  * @module
  */
-public abstract class DatumShiftGridFile extends DatumShiftGrid {
+public abstract class DatumShiftGridFile<C extends Quantity, T extends Quantity> extends DatumShiftGrid<C,T> {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -55,8 +62,8 @@ public abstract class DatumShiftGridFile
      * data exceed 32768 (about 128 kilobytes if the values use the {@code float} type). in which case
      * the oldest grids will be replaced by weak references.
      */
-    static final Cache<Path, DatumShiftGridFile> CACHE = new Cache<Path, DatumShiftGridFile>(4, 32*1024, true) {
-        @Override protected int cost(final DatumShiftGridFile grid) {
+    static final Cache<Path, DatumShiftGridFile<?,?>> CACHE = new Cache<Path, DatumShiftGridFile<?,?>>(4, 32*1024, true) {
+        @Override protected int cost(final DatumShiftGridFile<?,?> grid) {
             int p = 1;
             for (final Object array : grid.getData()) {
                 p *= Array.getLength(array);
@@ -73,9 +80,13 @@ public abstract class DatumShiftGridFile
     public final Path file;
 
     /**
+     * Number of grid cells along the <var>x</var> axis.
+     */
+    final int nx;
+
+    /**
      * Creates a new datum shift grid for the given grid geometry.
      * The actual offset values need to be provided by subclasses.
-     * All {@code double} values given to this constructor will be converted from degrees to radians.
      *
      * @param x0  Longitude in degrees of the center of the cell at grid index (0,0).
      * @param y0  Latitude in degrees of the center of the cell at grid index (0,0).
@@ -84,17 +95,18 @@ public abstract class DatumShiftGridFile
      * @param nx  Number of cells along the <var>x</var> axis in the grid.
      * @param ny  Number of cells along the <var>y</var> axis in the grid.
      */
-    DatumShiftGridFile(final double x0, final double y0,
+    DatumShiftGridFile(final Unit<C> coordinateUnit,
+                       final Unit<T> translationUnit,
+                       final boolean isCellValueRatio,
+                       final double x0, final double y0,
                        final double Δx, final double Δy,
                        final int    nx, final int    ny,
                        final Path file)
     {
-        super(Math.toRadians(x0),
-              Math.toRadians(y0),
-              Math.toRadians(Δx),
-              Math.toRadians(Δy),
-              nx, ny);
+        super(coordinateUnit, new AffineTransform2D(1/Δx, 0, 0, 1/Δy, -x0/Δx, -y0/Δy),
+                new int[] {nx, ny}, isCellValueRatio, translationUnit);
         this.file = file;
+        this.nx   = nx;
     }
 
     /**
@@ -102,18 +114,19 @@ public abstract class DatumShiftGridFile
      *
      * @param other The other datum shift grid from which to copy the grid geometry.
      */
-    DatumShiftGridFile(final DatumShiftGridFile other) {
+    DatumShiftGridFile(final DatumShiftGridFile<C,T> other) {
         super(other);
         this.file = other.file;
+        this.nx   = other.nx;
     }
 
     /**
      * If a grid exists in the cache for the same data, returns a new grid sharing the same data arrays.
      * Otherwise returns {@code this}.
      */
-    final DatumShiftGridFile useSharedData() {
+    final DatumShiftGridFile<C,T> useSharedData() {
         final Object[] data = getData();
-        for (final DatumShiftGridFile grid : CACHE.values()) {
+        for (final DatumShiftGridFile<?,?> grid : CACHE.values()) {
             final Object[] other = grid.getData();
             if (Arrays.deepEquals(data, other)) {
                 return setData(other);
@@ -129,7 +142,7 @@ public abstract class DatumShiftGridFile
      * reference the same grid (e.g. symbolic link, lower case versus upper case in a case-insensitive file
      * system).
      */
-    abstract DatumShiftGridFile setData(Object[] other);
+    abstract DatumShiftGridFile<C,T> setData(Object[] other);
 
     /**
      * Returns the data for each shift dimensions.
@@ -137,6 +150,18 @@ public abstract class DatumShiftGridFile
     abstract Object[] getData();
 
     /**
+     * Returns {@code this} casted to the given type, after verification that those types are valid.
+     */
+    @SuppressWarnings("unchecked")
+    final <NC extends Quantity, NT extends Quantity> DatumShiftGridFile<NC,NT> castTo(
+            final Class<NC> coordinateType, final Class<NT> translationType)
+    {
+        super.getCoordinateUnit() .asType(coordinateType);
+        super.getTranslationUnit().asType(translationType);
+        return (DatumShiftGridFile<NC,NT>) this;
+    }
+
+    /**
      * Returns {@code true} if the given object is a grid containing the same data than this grid.
      *
      * @param  other The other object to compare with this datum shift grid.
@@ -149,7 +174,7 @@ public abstract class DatumShiftGridFile
             return true;
         }
         if (super.equals(other)) {
-            final DatumShiftGridFile that = (DatumShiftGridFile) other;
+            final DatumShiftGridFile<?,?> that = (DatumShiftGridFile<?,?>) other;
             return file.equals(that.file) && Arrays.deepEquals(getData(), that.getData());
         }
         return false;
@@ -186,7 +211,7 @@ public abstract class DatumShiftGridFile
      * @version 0.7
      * @module
      */
-    static final class Float extends DatumShiftGridFile {
+    static final class Float<C extends Quantity, T extends Quantity> extends DatumShiftGridFile<C,T> {
         /**
          * Serial number for inter-operability with different versions.
          */
@@ -201,12 +226,15 @@ public abstract class DatumShiftGridFile
          * Creates a new datum shift grid with the given grid geometry, filename and number of shift dimensions.
          * All {@code double} values given to this constructor will be converted from degrees to radians.
          */
-        Float(final double x0, final double y0,
+        Float(final Unit<C> coordinateUnit,
+              final Unit<T> translationUnit,
+              final boolean isCellValueRatio,
+              final double x0, final double y0,
               final double Δx, final double Δy,
               final int    nx, final int    ny,
               final Path file, final int dim)
         {
-            super(x0, y0, Δx, Δy, nx, ny, file);
+            super(coordinateUnit, translationUnit, isCellValueRatio, x0, y0, Δx, Δy, nx, ny, file);
             offsets = new float[dim][];
             final int size = Math.multiplyExact(nx, ny);
             for (int i=0; i<dim; i++) {
@@ -217,7 +245,7 @@ public abstract class DatumShiftGridFile
         /**
          * Creates a new grid of the same geometry than the given grid but using a different data array.
          */
-        private Float(final DatumShiftGridFile grid, final float[][] offsets) {
+        private Float(final DatumShiftGridFile<C,T> grid, final float[][] offsets) {
             super(grid);
             this.offsets = offsets;
         }
@@ -226,8 +254,8 @@ public abstract class DatumShiftGridFile
          * Returns a new grid with the same geometry than this grid but different data arrays.
          */
         @Override
-        final DatumShiftGridFile setData(final Object[] other) {
-            return new Float(this, (float[][]) other);
+        final DatumShiftGridFile<C,T> setData(final Object[] other) {
+            return new Float<>(this, (float[][]) other);
         }
 
         /**
@@ -243,7 +271,7 @@ public abstract class DatumShiftGridFile
          * Returns the number of shift dimension.
          */
         @Override
-        public final int getShiftDimensions() {
+        public final int getTranslationDimensions() {
             return offsets.length;
         }
 
@@ -259,7 +287,7 @@ public abstract class DatumShiftGridFile
          * @return The offset at the given dimension in the grid cell at the given index.
          */
         @Override
-        protected final double getCellValue(final int dim, final int gridX, final int gridY) {
+        public final double getCellValue(final int dim, final int gridX, final int gridY) {
             return DecimalFunctions.floatToDouble(offsets[dim][gridX + gridY*nx]);
         }
     }

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=1718271&r1=1718270&r2=1718271&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:52:59 2015
@@ -27,6 +27,9 @@ import java.io.EOFException;
 import java.io.IOException;
 import javax.xml.bind.annotation.XmlTransient;
 import javax.measure.unit.SI;
+import javax.measure.unit.NonSI;
+import javax.measure.quantity.Angle;
+import javax.measure.quantity.Length;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterDescriptorGroup;
@@ -294,7 +297,7 @@ public final class FranceGeocentricInter
             default: throw new InvalidParameterValueException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "dim", dim), "dim", dim);
         }
         final Path file = pg.getValue(FILE);
-        final DatumShiftGridFile grid = getOrLoad(file, !isRecognized(file) ? null : new double[] {TX, TY, TZ}, PRECISION);
+        final DatumShiftGridFile<Angle,Length> grid = getOrLoad(file, !isRecognized(file) ? null : new double[] {TX, TY, TZ}, PRECISION);
         MathTransform tr = InterpolatedGeocentricTransform.createGeodeticTransformation(factory,
                 createEllipsoid(pg, Molodensky.TGT_SEMI_MAJOR,
                                     Molodensky.TGT_SEMI_MINOR, CommonCRS.ETRS89.ellipsoid()), withHeights, // GRS 1980 ellipsoid
@@ -317,17 +320,17 @@ public final class FranceGeocentricInter
      * @param  averages  An "average" value for the offset in each dimension, or {@code null} if unknown.
      * @param  scale     The factor by which to multiply each compressed value before to add to the average value.
      */
-    static DatumShiftGridFile getOrLoad(final Path file, final double[] averages, final double scale)
+    static DatumShiftGridFile<Angle,Length> getOrLoad(final Path file, final double[] averages, final double scale)
             throws FactoryException
     {
-        DatumShiftGridFile grid = DatumShiftGridFile.CACHE.peek(file);
+        DatumShiftGridFile<?,?> grid = DatumShiftGridFile.CACHE.peek(file);
         if (grid == null) {
-            final Cache.Handler<DatumShiftGridFile> handler = DatumShiftGridFile.CACHE.lock(file);
+            final Cache.Handler<DatumShiftGridFile<?,?>> handler = DatumShiftGridFile.CACHE.lock(file);
             try {
                 grid = handler.peek();
                 if (grid == null) {
                     try (final BufferedReader in = Files.newBufferedReader(file)) {
-                        final DatumShiftGridFile.Float g = load(in, file);
+                        final DatumShiftGridFile.Float<Angle,Length> g = load(in, file);
                         grid = DatumShiftGridCompressed.compress(g, averages, scale);
                     } catch (IOException | RuntimeException e) {
                         // NumberFormatException, ArithmeticException, NoSuchElementException, possibly other.
@@ -339,7 +342,7 @@ public final class FranceGeocentricInter
                 handler.putAndUnlock(grid);
             }
         }
-        return grid;
+        return grid.castTo(Angle.class, Length.class);
     }
 
     /**
@@ -353,10 +356,10 @@ public final class FranceGeocentricInter
      * @throws FactoryException if an problem is found with the file content.
      * @throws ArithmeticException if the width or the height exceed the integer capacity.
      */
-    static DatumShiftGridFile.Float load(final BufferedReader in, final Path file)
+    static DatumShiftGridFile.Float<Angle,Length> load(final BufferedReader in, final Path file)
             throws IOException, FactoryException
     {
-        DatumShiftGridFile.Float grid = null;
+        DatumShiftGridFile.Float<Angle,Length> grid = null;
         double x0 = 0;
         double xf = 0;
         double y0 = 0;
@@ -409,7 +412,9 @@ public final class FranceGeocentricInter
                             Δy = gridGeometry[5];
                             nx = Math.toIntExact(Math.round((xf - x0) / Δx + 1));
                             ny = Math.toIntExact(Math.round((yf - y0) / Δy + 1));
-                            grid = new DatumShiftGridFile.Float(x0, y0, Δx, Δy, nx, ny, file, 3);
+                            grid = new DatumShiftGridFile.Float<>(
+                                    NonSI.DEGREE_ANGLE, SI.METRE, false,
+                                    x0, y0, Δx, Δy, nx, ny, file, 3);
                         }
                         break;
                     }

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=1718271&r1=1718270&r2=1718271&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:52:59 2015
@@ -16,13 +16,29 @@
  */
 package org.apache.sis.referencing.datum;
 
+import java.util.Arrays;
+import java.io.IOException;
 import java.io.Serializable;
+import java.io.ObjectInputStream;
+import javax.measure.unit.Unit;
+import javax.measure.quantity.Quantity;
+import javax.measure.converter.UnitConverter;
+import javax.measure.converter.LinearConverter;
 import org.opengis.geometry.Envelope;
-import org.apache.sis.geometry.Envelope2D;
+import org.opengis.geometry.MismatchedDimensionException;
+import org.opengis.referencing.operation.Matrix;
+import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.referencing.operation.matrix.Matrices;
+import org.apache.sis.referencing.operation.transform.LinearTransform;
+import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.geometry.Envelopes;
 import org.apache.sis.internal.util.DoubleDouble;
-import org.apache.sis.internal.util.Numerics;
+import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
 
+// Branch-dependent imports
+import java.util.Objects;
+
 
 /**
  * Small but non-constant translations to apply on coordinates for datum shifts or other transformation process.
@@ -30,29 +46,44 @@ import org.apache.sis.util.ArgumentCheck
  * like NTv2, NADCON or RGF93. But this class could also be used for other kind of transformations,
  * provided that the shifts are <strong>small</strong> (otherwise algorithms may not converge).
  *
- * <p>{@linkplain DefaultGeodeticDatum Geodetic datum} changes impact directly two kinds of coordinates:
- * geographic and geocentric. Translations given by {@code DatumShiftGrid} instances are often, but not always,
+ * <p>{@linkplain DefaultGeodeticDatum Geodetic datum} changes can be implemented by translations in geographic
+ * or geocentric coordinates. Translations given by {@code DatumShiftGrid} instances are often, but not always,
  * applied directly on geographic coordinates (<var>λ</var>,<var>φ</var>). But some algorithms rather apply the
  * translations in geocentric coordinates (<var>X</var>,<var>Y</var>,<var>Z</var>). This {@code DatumShiftGrid}
  * class can describe both cases, but will be used with different {@code MathTransform} implementations.</p>
  *
+ * <p>Steps for calculation of a translation vector:</p>
+ * <ol>
+ *   <li>Coordinates are given in some "real world" unit.
+ *       The expected unit is given by {@link #getCoordinateUnit()}.</li>
+ *   <li>Above coordinates are converted to grid indices including fractional parts.
+ *       That conversion is given by {@link #getCoordinateToGrid()}.</li>
+ *   <li>Translation vectors are interpolated at the position of above grid indices.
+ *       That interpolation is done by {@link #interpolateInCell interpolateInCell(…)}.</li>
+ *   <li>If the above translations were given as a ratio of the real translation divided by the size of grid cells, apply
+ *       the inverse of the conversion given at step 2. This information is given by {@link #isCellValueRatio()}.</li>
+ *   <li>The resulting translation vectors are in the unit given by {@link #getTranslationUnit()}.</li>
+ * </ol>
+ *
+ * The {@link #interpolateAt interpolateAt(…)} method performs all those steps.
+ * But that method is provided only for convenience; it is not used by Apache SIS.
+ * For performance reasons SIS {@code MathTransform} implementations perform all the above-cited steps themselves,
+ * and apply the interpolated translations on coordinate values in their own step between above steps 3 and 4.
+ *
  * <div class="note"><b>Use cases:</b>
- * NADCON, NTv2 and other grids are used in the contexts described below. But be aware of units of measurement!
- * In particular, input geographic coordinates (λ,φ) need to be in <strong>radians</strong> for use with SIS
- * {@link org.apache.sis.referencing.operation.transform.InterpolatedGeocentricTransform} implementation.
  * <ul>
  *   <li><p><b>Datum shift by geographic translations</b><br>
- *   NADCON and NTv2 grids are defined with longitude (λ) and latitude (φ) inputs in angular <em>degrees</em>
- *   and give (<var>Δλ</var>, <var>Δφ</var>) translations in angular <em>seconds</em>.
- *   However Apache SIS needs all those values to be converted to <strong>radians</strong>.
+ *   NADCON and NTv2 grids are defined with longitude (<var>λ</var>) and latitude (<var>φ</var>) inputs in angular
+ *   <em>degrees</em> and give (<var>Δλ</var>, <var>Δφ</var>) translations in angular <em>seconds</em>.
+ *   However SIS stores the translation values in units of grid cell rather than angular seconds.
  *   The translations will be applied by {@link org.apache.sis.referencing.operation.transform.InterpolatedTransform}
  *   directly on the given (<var>λ</var>,<var>φ</var>) coordinates.
  *   </p></li>
  *
  *   <li><p><b>Datum shift by geocentric translations</b><br>
- *   France interpolation grid is defined with longitude (λ) and latitude (φ) inputs in angular <em>degrees</em>
- *   and gives (<var>ΔX</var>, <var>ΔY</var>, <var>ΔZ</var>) geocentric translations in <em>metres</em>.
- *   Those offsets will not be added directly to the given (<var>λ</var>,<var>φ</var>) coordinates since there is
+ *   France interpolation grid is defined with longitude (<var>λ</var>) and latitude (<var>φ</var>) inputs in angular
+ *   <em>degrees</em> and gives (<var>ΔX</var>, <var>ΔY</var>, <var>ΔZ</var>) geocentric translations in <em>metres</em>.
+ *   Those translations will not be added directly to the given (<var>λ</var>,<var>φ</var>) coordinates since there is
  *   a geographic/geocentric conversion in the middle
  *   (see {@link org.apache.sis.referencing.operation.transform.InterpolatedGeocentricTransform}).
  *   </p></li>
@@ -68,119 +99,419 @@ import org.apache.sis.util.ArgumentCheck
  *   </p></li>
  * </ul></div>
  *
- * <p>Implementations of this class shall be immutable and thread-safe.</p>
+ * Implementations of this class shall be immutable and thread-safe.
+ *
+ * <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
+ * same version of Apache SIS. But for long term storage, an established datum shift grid format like
+ * NTv2 should be preferred.
+ *
+ * @param <C> Dimension of the coordinate unit (usually {@link javax.measure.quantity.Angle}).
+ * @param <T> Dimension of the translation unit (usually {@link javax.measure.quantity.Angle}
+ *            or {@link javax.measure.quantity.Length}).
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.7
  * @version 0.7
  * @module
  */
-public abstract class DatumShiftGrid implements Serializable {
+public abstract class DatumShiftGrid<C extends Quantity, T extends Quantity> implements Serializable {
     /**
      * Serial number for inter-operability with different versions.
      */
     private static final long serialVersionUID = 8405276545243175808L;
 
     /**
-     * Coordinate of the center of the cell at grid index (0,0).
-     * This is usually a geographic coordinate (<var>λ₀</var>,<var>φ₀</var>) in <strong>radians</strong>,
-     * but other usages are allowed for users who instantiate
-     * {@link org.apache.sis.referencing.operation.transform.InterpolatedTransform} themselves.
-     * The (<var>x₀</var>, <var>y₀</var>) coordinate is often the minimal (<var>x</var>,<var>y</var>) value
-     * of the grid, but not necessarily if the <var>Δx</var> or <var>Δy</var> increments are negative.
+     * The unit of measurements of input values, before conversion to grid indices by {@link #coordinateToGrid}.
+     * The coordinate unit is typically {@link javax.measure.unit.NonSI#DEGREE_ANGLE}.
+     *
+     * @see #getCoordinateUnit()
      */
-    protected final double x0, y0;
+    private final Unit<C> coordinateUnit;
 
     /**
-     * Multiplication factor for converting a coordinate into grid index.
-     * The (<var>gridX</var>, <var>gridY</var>) indices of a cell in the grid are given by:
+     * Conversion from the "real world" coordinates to grid indices including fractional parts.
+     * This is the conversion that needs to be applied before to interpolate.
      *
-     * <ul>
-     *   <li><var>gridX</var> = (<var>x</var> - <var>x₀</var>) ⋅ {@code scaleX}</li>
-     *   <li><var>gridY</var> = (<var>y</var> - <var>y₀</var>) ⋅ {@code scaleY}</li>
-     * </ul>
+     * @see #getCoordinateToGrid()
      */
-    protected final double scaleX, scaleY;
+    private final LinearTransform coordinateToGrid;
 
     /**
-     * Number of cells in the grid along the <var>x</var> and <var>y</var> axes.
+     * The unit of measurement of output values, as interpolated by the {@link #interpolateAt} method.
+     *
+     * @see #getTranslationUnit()
      */
-    protected final int nx, ny;
+    private final Unit<T> translationUnit;
 
     /**
-     * Creates a new datum shift grid for the given grid geometry.
-     * The actual offset values need to be provided by subclasses.
+     * {@code true} if the translation interpolated by {@link #interpolateInCell interpolateInCell(…)}
+     * are divided by grid cell size. If {@code true}, then the inverse of {@link #coordinateToGrid}
+     * needs to be applied on the interpolated values as a delta transform.
+     * Such conversion is applied (if needed) by the {@link #interpolateAt} method.
      *
-     * @param x0  First ordinate (often longitude in radians) of the center of the cell at grid index (0,0).
-     * @param y0  Second ordinate (often latitude in radians) of the center of the cell at grid index (0,0).
-     * @param Δx  Increment in <var>x</var> value between cells at index <var>gridX</var> and <var>gridX</var> + 1.
-     * @param Δy  Increment in <var>y</var> value between cells at index <var>gridY</var> and <var>gridY</var> + 1.
-     * @param nx  Number of cells along the <var>x</var> axis in the grid.
-     * @param ny  Number of cells along the <var>y</var> axis in the grid.
+     * @see #isCellValueRatio()
+     */
+    private final boolean isCellValueRatio;
+
+    /**
+     * Number of grid cells in along each dimension. This is usually an array of length 2 containing
+     * the number of grid cells along the <var>x</var> and <var>y</var> axes.
      */
-    protected DatumShiftGrid(final double x0, final double y0,
-                             final double Δx, final double Δy,
-                             final int    nx, final int    ny)
+    private final int[] gridSize;
+
+    /**
+     * Conversion from (λ,φ) coordinates in radians to grid indices (x,y).
+     *
+     * <ul>
+     *   <li>x  =  (λ - λ₀) ⋅ {@code scaleX}  =  λ ⋅ {@code scaleX} + x₀</li>
+     *   <li>y  =  (φ - φ₀) ⋅ {@code scaleY}  =  φ ⋅ {@code scaleY} + y₀</li>
+     * </ul>
+     *
+     * Those factors are extracted from the {@link #coordinateToGrid} transform for performance purposes.
+     */
+    private transient double scaleX, scaleY, x0, y0;
+
+    /**
+     * Creates a new datum shift grid for the given size and units.
+     * The actual cell values need to be provided by subclasses.
+     *
+     * <p>Meaning of argument values is documented more extensively in {@link #getCoordinateUnit()},
+     * {@link #getCoordinateToGrid()}, {@link #isCellValueRatio()} and {@link #getTranslationUnit()}
+     * methods. The argument order is roughly the order in which they are used in the process of
+     * interpolating translation vectors.</p>
+     *
+     * @param coordinateUnit    The unit of measurement of input values, before conversion to grid indices by {@code coordinateToGrid}.
+     * @param coordinateToGrid  Conversion from the "real world" coordinates to grid indices including fractional parts.
+     * @param gridSize          Number of cells along each axis in the grid. The length of this array shall be equal to {@code coordinateToGrid} target dimensions.
+     * @param isCellValueRatio  {@code true} if results of {@link #interpolateInCell interpolateInCell(…)} are divided by grid cell size.
+     * @param translationUnit   The unit of measurement of output values.
+     */
+    protected DatumShiftGrid(final Unit<C> coordinateUnit, final LinearTransform coordinateToGrid,
+            int[] gridSize, final boolean isCellValueRatio, final Unit<T> translationUnit)
     {
-        this.x0 = x0;
-        this.y0 = y0;
-        this.nx = nx;
-        this.ny = ny;
-        ArgumentChecks.ensureStrictlyPositive("nx", nx);
-        ArgumentChecks.ensureStrictlyPositive("ny", ny);
-        scaleX = 1 / Δx;
-        scaleY = 1 / Δy;
+        ArgumentChecks.ensureNonNull("coordinateUnit",   coordinateUnit);
+        ArgumentChecks.ensureNonNull("coordinateToGrid", coordinateToGrid);
+        ArgumentChecks.ensureNonNull("gridSize",         gridSize);
+        ArgumentChecks.ensureNonNull("translationUnit",  translationUnit);
+        int n = coordinateToGrid.getTargetDimensions();
+        if (n != gridSize.length) {
+            throw new MismatchedDimensionException(Errors.format(
+                    Errors.Keys.MismatchedDimension_3, "gridSize", n, gridSize.length));
+        }
+        this.coordinateUnit   = coordinateUnit;
+        this.coordinateToGrid = coordinateToGrid;
+        this.isCellValueRatio = isCellValueRatio;
+        this.translationUnit  = translationUnit;
+        this.gridSize = gridSize = gridSize.clone();
+        for (int i=0; i<gridSize.length; i++) {
+            if ((n = gridSize[i]) < 2) {
+                throw new IllegalArgumentException(Errors.format(n <= 0
+                        ? Errors.Keys.ValueNotGreaterThanZero_2
+                        : Errors.Keys.IllegalArgumentValue_2, "gridSize[" + i + ']', n));
+            }
+        }
+        computeConversionFactors();
     }
 
     /**
-     * Creates a new datum shift grid with the same grid geometry than the given grid.
+     * Computes the conversion factors needed by {@link #interpolateAtNormalized(double, double, double[])}.
+     * This method takes only the 2 first dimensions. If a conversion factor can not be computed, then it is
+     * set to NaN.
+     */
+    @SuppressWarnings("fallthrough")
+    private void computeConversionFactors() {
+        scaleX = Double.NaN;
+        scaleY = Double.NaN;
+        x0     = Double.NaN;
+        y0     = Double.NaN;
+        final UnitConverter c = coordinateUnit.toSI().getConverterTo(coordinateUnit);
+        if (c instanceof LinearConverter && c.convert(0) == 0) {
+            final Matrix m = coordinateToGrid.getMatrix();
+            if (Matrices.isAffine(m)) {
+                final int n = m.getNumCol() - 1;
+                final double toUnit = c.convert(1);
+                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;
+                    case 0:  break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the value on the diagonal of the given matrix, provided that all other non-translation terms are 0.
+     *
+     * @param m The matrix from which to get the scale factor on a row.
+     * @param j The row for which to get the scale factor.
+     * @param n Index of the last column.
+     * @return The scale factor on the diagonal, or NaN.
+     */
+    private static double diagonal(final Matrix m, final int j, int n) {
+        while (--n >= 0) {
+            if (j != n && m.getElement(j, n) != 0) {
+                return Double.NaN;
+            }
+        }
+        return m.getElement(j, j);
+    }
+
+    /**
+     * Creates a new datum shift grid with the same grid geometry (size and units) than the given grid.
      *
      * @param other The other datum shift grid from which to copy the grid geometry.
      */
-    protected DatumShiftGrid(final DatumShiftGrid other) {
-        x0 = other.x0;
-        y0 = other.y0;
-        nx = other.nx;
-        ny = other.ny;
-        scaleX = other.scaleX;
-        scaleY = other.scaleY;
+    protected DatumShiftGrid(final DatumShiftGrid<C,T> other) {
+        ArgumentChecks.ensureNonNull("other", other);
+        coordinateUnit   = other.coordinateUnit;
+        coordinateToGrid = other.coordinateToGrid;
+        isCellValueRatio = other.isCellValueRatio;
+        translationUnit  = other.translationUnit;
+        gridSize         = other.gridSize;
+        scaleX           = other.scaleX;
+        scaleY           = other.scaleY;
+        x0               = other.x0;
+        y0               = other.y0;
+    }
+
+    /**
+     * 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.
+     *
+     * <p>The unit of measurement for the coordinate values in the returned envelope is
+     * given by {@link #getCoordinateUnit()}. The complete CRS is undefined.</p>
+     *
+     * @return The domain covered by this grid.
+     * @throws TransformException if an error occurred while computing the envelope.
+     */
+    public Envelope getDomainOfValidity() throws TransformException {
+        final GeneralEnvelope env = new GeneralEnvelope(gridSize.length);
+        for (int i=0; i<gridSize.length; i++) {
+            env.setRange(i, -0.5, gridSize[i] - 0.5);
+        }
+        return Envelopes.transform(getCoordinateToGrid().inverse(), env);
+    }
+
+    /**
+     * Returns the unit of measurement of input values, before conversion to grid indices.
+     * The coordinate unit is usually {@link javax.measure.unit.NonSI#DEGREE_ANGLE}, but other units are allowed.
+     *
+     * @return The unit of measurement of input values before conversion to grid indices.
+     *
+     * @see org.apache.sis.referencing.operation.AbstractCoordinateOperation#getInterpolationCRS()
+     */
+    public Unit<C> getCoordinateUnit() {
+        return coordinateUnit;
+    }
+
+    /**
+     * 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()}.
+     * The output points are grid indices with integer values in the center of grid cells.
+     *
+     * <p>This transform is usually two-dimensional and linear, in which case conversions from (<var>x</var>,<var>y</var>)
+     * coordinates to ({@code gridX}, {@code gridY}) indices can be done with the following formulas:</p>
+     * <ul>
+     *   <li><var>gridX</var> = (<var>x</var> - <var>x₀</var>) / <var>Δx</var></li>
+     *   <li><var>gridY</var> = (<var>y</var> - <var>y₀</var>) / <var>Δy</var></li>
+     * </ul>
+     *
+     * where:
+     * <ul>
+     *   <li>(<var>x₀</var>, <var>y₀</var>) is the coordinate of the center of the cell at grid index (0,0).</li>
+     *   <li><var>Δx</var> and <var>Δy</var> are the distances between two cells on the <var>x</var> and <var>y</var>
+     *       axes respectively, in the unit of measurement given by {@link #getCoordinateUnit()}.</li>
+     * </ul>
+     *
+     * The {@code coordinateToGrid} transform for the above formulas can be represented by the following matrix:
+     *
+     * {@preformat math
+     *   ┌                      ┐
+     *   │ 1/Δx      0   -x₀/Δx │
+     *   │    0   1/Δy   -y₀/Δy │
+     *   │    0      0        1 │
+     *   └                      ┘
+     * }
+     *
+     * @return Conversion from the "real world" coordinates to grid indices including fractional parts.
+     */
+    public LinearTransform getCoordinateToGrid() {
+        return coordinateToGrid;
     }
 
     /**
      * Returns the number of dimensions of the translation vectors interpolated by this datum shift grid.
-     * The number of dimension is usually 2 or 3, but other values are allowed.
+     * This number of dimensions is not necessarily equals to the number of source or target dimensions
+     * of the "{@linkplain #getCoordinateToGrid() coordinate to grid}" transform.
+     * The number of translation dimensions is usually 2 or 3, but other values are allowed.
      *
      * @return Number of dimensions of translation vectors.
      */
-    public abstract int getShiftDimensions();
+    public abstract int getTranslationDimensions();
 
     /**
-     * Returns the domain of validity of the (<var>x</var>,<var>y</var>) coordinate values that can be specified
-     * to the {@link #offsetAt offsetAt(x, y, …)} method. Coordinates outside that domain of validity will still
-     * be accepted, but the result of offset computation may be very wrong.
+     * Returns the unit of measurement of output values, as interpolated by the {@code interpolateAt(…)} method.
+     * Apache SIS {@code MathTransform} implementations restrict the translation units to the following values:
      *
-     * <p>In datum shift grids used by {@link org.apache.sis.referencing.operation.transform.InterpolatedGeocentricTransform},
-     * the domain of validity is always expressed as longitudes and latitudes in <strong>radians</strong>.
-     * The envelope is usually in radians for simpler (non-geocentric) interpolations too, for consistency reasons.</p>
+     * <ul>
+     *   <li>For {@link org.apache.sis.referencing.operation.transform.InterpolatedTransform}, the translation
+     *       unit shall be the same than the {@linkplain #getCoordinateUnit() coordinate unit}.</li>
+     *   <li>For {@link org.apache.sis.referencing.operation.transform.InterpolatedGeocentricTransform},
+     *       the translation unit shall be the same than the unit of source ellipsoid axis lengths.</li>
+     * </ul>
      *
-     * @return The domain covered by this grid.
+     * @return The unit of measurement of output values interpolated by {@code interpolateAt(…)}.
+     *
+     * @see #interpolateAt
+     */
+    public Unit<T> getTranslationUnit() {
+        return translationUnit;
+    }
+
+    /**
+     * Interpolates the translation to apply for the given coordinate.
+     * The input values are in the unit given by {@link #getCoordinateUnit()}.
+     * The output values are in the unit given by {@link #getTranslationUnit()}.
+     * The length of the returned array is given by {@link #getTranslationDimensions()}.
+     *
+     * <div class="section">Default implementation</div>
+     * The default implementation performs the following steps:
+     * <ol>
+     *   <li>Convert the given coordinate into grid indices using the transform given by {@link #getCoordinateToGrid()}.</li>
+     *   <li>Interpolate the translation vector at the above grid indices with a call to {@link #interpolateInCell}.</li>
+     *   <li>If {@link #isCellValueRatio()} returns {@code true}, {@linkplain LinearTransform#deltaTransform delta transform}
+     *       the translation vector by the inverse of the conversion given at step 1.</li>
+     * </ol>
+     *
+     * @param ordinates  The "real world" ordinate (often longitude and latitude, but not necessarily)
+     *                   of the point for which to get the translation.
+     * @return The translation vector at the given position.
+     * @throws TransformException if an error occurred while computing the translation vector.
+     */
+    public double[] interpolateAt(final double... ordinates) throws TransformException {
+        final LinearTransform c = getCoordinateToGrid();
+        ArgumentChecks.ensureDimensionMatches("ordinates", c.getSourceDimensions(), ordinates);
+        final int dim = getTranslationDimensions();
+        double[] vector = new double[Math.max(dim, c.getTargetDimensions())];
+        c.transform(ordinates, 0, vector, 0, 1);
+        interpolateInCell(vector[0], vector[1], vector);
+        if (isCellValueRatio()) {
+            ((LinearTransform) c.inverse()).deltaTransform(vector, 0, vector, 0, 1);
+        }
+        if (vector.length != dim) {
+            vector = Arrays.copyOf(vector, dim);
+        }
+        return vector;
+    }
+
+    /**
+     * Interpolates the translation to apply for the given two-dimensional normalized coordinates.
+     * "Normalized coordinates" are coordinates in the unit of measurement given by {@link Unit#toSI()}.
+     * For angular coordinates, this is radians. For linear coordinates, this is metres.
+     *
+     * <p>The result is stored in the given {@code vector} array, which shall have a length of at least
+     * {@link #getTranslationDimensions()}. The output unit of measurement is the same than the one
+     * documented in {@link #getCellValue}.</p>
+     *
+     * @param x First "real world" ordinate (often longitude in radians) of the point for which to get the translation.
+     * @param y Second "real world" ordinate (often latitude in radians) of the point for which to get the translation.
+     * @param vector A pre-allocated array where to write the translation vector.
      */
-    public Envelope getDomainOfValidity() {
-        final Envelope2D domain = new Envelope2D(null, x0 - 0.5/scaleX, y0 - 0.5/scaleY, nx/scaleX, ny/scaleY);
-        if (domain.width < 0) {
-            domain.width = -domain.width;
-            domain.x += domain.width;
+    public final void interpolateAtNormalized(final double x, final double y, final double[] vector) {
+        interpolateInCell(x * scaleX + x0, y * scaleY + y0, vector);
+    }
+
+    /**
+     * Interpolates the translation to apply for the given two-dimensional grid indices. The result is stored in
+     * the given {@code vector} array, which shall have a length of at least {@link #getTranslationDimensions()}.
+     * The output unit of measurement is the same than the one documented in {@link #getCellValue}.
+     *
+     * <div class="section">Default implementation</div>
+     * The default implementation performs the following steps for each dimension <var>dim</var>,
+     * where the number of dimension is determined by {@link #getTranslationDimensions()}.
+     *
+     * <ol>
+     *   <li>Clamp the {@code gridX} index into the [0 … {@code gridSize[0]} - 2] range, inclusive.</li>
+     *   <li>Clamp the {@code gridY} index into the [0 … {@code gridSize[1]} - 2] range, inclusive.</li>
+     *   <li>Using {@link #getCellValue}, get the cell values around the given indices.</li>
+     *   <li>Apply a bilinear interpolation and store the result in {@code vector[dim]}.</li>
+     * </ol>
+     *
+     * @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.
+     * @param vector  A pre-allocated array where to write the translation vector.
+     */
+    public void interpolateInCell(double gridX, double gridY, final double[] vector) {
+        int ix = (int) gridX;  gridX -= ix;
+        int iy = (int) gridY;  gridY -= iy;
+        int n;
+        /*
+         * Because ((int) gridX) rounds toward 0, we know that (ix < 0) means that (gridX <= -1).
+         * With ix=0 we get (gridX-ix <= -1). So we set gridX = -1 for avoiding too far extrapolations.
+         * A similar reasoning apply to the gridX = +1 statement.
+         */
+        if (ix < 0) {
+            ix = 0;
+            gridX = -1;
+        } else if (ix > (n = gridSize[0] - 2)) {
+            ix = n;
+            gridX = +1;
         }
-        if (domain.height < 0) {
-            domain.height = -domain.height;
-            domain.y += domain.height;
+        if (iy < 0) {
+            iy = 0;
+            gridY = -1;
+        } else if (iy > (n = gridSize[1] - 2)) {
+            iy = n;
+            gridY = +1;
+        }
+        n = getTranslationDimensions();
+        for (int dim = 0; dim < n; dim++) {
+            double r0 = getCellValue(dim, ix, iy  );
+            double r1 = getCellValue(dim, ix, iy+1);
+            r0 +=  gridX * (getCellValue(dim, ix+1, iy  ) - r0);
+            r1 +=  gridX * (getCellValue(dim, ix+1, iy+1) - r1);
+            vector[dim] = gridY * (r1 - r0) + r0;
         }
-        return domain;
     }
 
     /**
-     * Returns an average offset value for the given dimension.
-     * Those average values shall provide a good "first guess" before to interpolate the actual offset value
+     * Returns the translation stored at the given 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:
+     *
+     * <ul>
+     *   <li>If {@code false}, the value returned by this method shall be in the unit of measurement
+     *       given by {@link #getTranslationUnit()}.</li>
+     *   <li>If {@code true}, the value returned by this method is the ratio of the translation divided by the
+     *       distance between grid cells in the <var>dim</var> dimension (<var>Δx</var> or <var>Δy</var> in the
+     *       {@linkplain #DatumShiftGrid(Unit, LinearTransform, int[], boolean, Unit) constructor javadoc}).</li>
+     * </ul>
+     *
+     * @param dim    The dimension of the translation vector component to get,
+     *               from 0 inclusive to {@link #getTranslationDimensions()} exclusive.
+     * @param gridX  The grid index on the <var>x</var> axis, from 0 inclusive to {@link #nx} exclusive.
+     * @param gridY  The grid index on the <var>y</var> axis, from 0 inclusive to {@link #ny} exclusive.
+     * @return The translation for the given dimension in the grid cell at the given index.
+     */
+    public abstract double getCellValue(int dim, int gridX, int gridY);
+
+    /**
+     * Returns an average translation value for the given dimension.
+     * Those average values shall provide a good "first guess" before to interpolate the actual translation value
      * at the (<var>x</var>,<var>y</var>) coordinate. This "first guess" is needed for inverse transform.
      *
      * <div class="section">Default implementation</div>
@@ -192,12 +523,14 @@ public abstract class DatumShiftGrid imp
      * are fixed by definition to -168, -60 and +320 metres for dimensions 0, 1 and 2 respectively
      * (geocentric <var>X</var>, <var>Y</var> and <var>Z</var>).</div>
      *
-     * @param dim  The dimension for which to get an average value,
-     *             from 0 inclusive to {@link #getShiftDimensions()} exclusive.
-     * @return A value close to the average for the given dimension.
+     * @param dim  The dimension for which to get an average translation value,
+     *             from 0 inclusive to {@link #getTranslationDimensions()} exclusive.
+     * @return A translation value close to the average for the given dimension.
      */
-    public double getAverageOffset(final int dim) {
+    public double getCellMean(final int dim) {
         final DoubleDouble sum = new DoubleDouble();
+        final int nx = gridSize[0];
+        final int ny = gridSize[1];
         for (int gridY=0; gridY<ny; gridY++) {
             for (int gridX=0; gridX<nx; gridX++) {
                 sum.add(getCellValue(dim, gridX, gridY));
@@ -207,52 +540,18 @@ public abstract class DatumShiftGrid imp
     }
 
     /**
-     * Interpolates the translation to apply for the given coordinate. The result is stored in the
-     * given {@code offsets} array, which shall have a length of at least {@link #getShiftDimensions()}.
-     * The computed translation values are often for the same dimensions than the given <var>x</var> and <var>y</var>
-     * values, but not necessarily.
-     * See the class javadoc for use cases.
+     * 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
+     * are the ratio of the translation divided by the distance between grid cells in the requested
+     * dimension (<var>Δx</var> or <var>Δy</var> in the {@linkplain #DatumShiftGrid(Unit, LinearTransform,
+     * int[], boolean, Unit) constructor javadoc}).
      *
-     * <div class="section">Default implementation</div>
-     * The default implementation performs the following steps for each dimension {@code dim},
-     * where the number of dimension is determined by {@link #getShiftDimensions()}.
-     * <ol>
-     *   <li>Convert the given (<var>x</var>,<var>y</var>) coordinate into grid coordinate
-     *       using the formula documented in {@link #scaleX} and {@link #scaleY} fields.</li>
-     *   <li>Clamp the grid coordinate into the [0 … {@link #nx} - 2] and [0 … {@link #ny} - 2] ranges, inclusive.</li>
-     *   <li>Using {@link #getCellValue(int, int, int)}, get the four cell values around the coordinate.</li>
-     *   <li>Apply a bilinear interpolation and store the result in {@code offsets[dim]}.</li>
-     * </ol>
-     *
-     * @param x        First ordinate (often longitude, but not necessarily) of the point for which to get the offset.
-     * @param y        Second ordinate (often latitude, but not necessarily) of the point for which to get the offset.
-     * @param offsets  A pre-allocated array where to write the translation vector.
-     */
-    public void offsetAt(double x, double y, double[] offsets) {
-        final int gridX = Math.max(0, Math.min(nx - 2, (int) Math.floor(x = (x - x0) * scaleX)));
-        final int gridY = Math.max(0, Math.min(ny - 2, (int) Math.floor(y = (y - y0) * scaleY)));
-        x -= gridX;
-        y -= gridY;
-        final int n = getShiftDimensions();
-        for (int dim = 0; dim < n; dim++) {
-            double r0 = getCellValue(dim, gridX,   gridY  );
-            double r1 = getCellValue(dim, gridX,   gridY+1);
-            r0 +=  x * (getCellValue(dim, gridX+1, gridY  ) - r0);
-            r1 +=  x * (getCellValue(dim, gridX+1, gridY+1) - r1);
-            offsets[dim] = y * (r1 - r0) + r0;
-        }
-    }
-
-    /**
-     * Returns the offset stored at the given grid indices along the given dimension.
-     *
-     * @param dim    The dimension of the offset component to get,
-     *               from 0 inclusive to {@link #getShiftDimensions()} exclusive.
-     * @param gridX  The grid index along the <var>x</var> axis, from 0 inclusive to {@link #nx} exclusive.
-     * @param gridY  The grid index along the <var>y</var> axis, from 0 inclusive to {@link #ny} exclusive.
-     * @return The offset at the given dimension in the grid cell at the given index.
+     * @return {@code true} if the translation values in the cells are divided by the cell size.
      */
-    protected abstract double getCellValue(int dim, int gridX, int gridY);
+    public boolean isCellValueRatio() {
+        return isCellValueRatio;
+    }
 
     /**
      * Returns {@code true} if the given object is a grid containing the same data than this grid.
@@ -264,13 +563,12 @@ public abstract class DatumShiftGrid imp
     @Override
     public boolean equals(final Object other) {
         if (other != null && other.getClass() == getClass()) {
-            final DatumShiftGrid that = (DatumShiftGrid) other;
-            return nx == that.nx
-                && ny == that.ny
-                && Numerics.equals(x0,     that.x0)
-                && Numerics.equals(y0,     that.y0)
-                && Numerics.equals(scaleX, that.scaleX)
-                && Numerics.equals(scaleY, that.scaleY);
+            final DatumShiftGrid<?,?> that = (DatumShiftGrid<?,?>) other;
+            return Arrays .equals(gridSize,         that.gridSize)     // Test first the value that are most likely to differ
+                && Objects.equals(coordinateToGrid, that.coordinateToGrid)
+                && Objects.equals(coordinateUnit,   that.coordinateUnit)
+                && Objects.equals(translationUnit,  that.translationUnit)
+                && isCellValueRatio == that.isCellValueRatio;
         }
         return false;
     }
@@ -284,10 +582,14 @@ public abstract class DatumShiftGrid imp
      */
     @Override
     public int hashCode() {
-        return Numerics.hashCode(Double.doubleToLongBits(x0)
-                         + 31 * (Double.doubleToLongBits(y0)
-                         + 31 * (Double.doubleToLongBits(scaleX)
-                         + 31 *  Double.doubleToLongBits(scaleY))))
-                         + 37 * nx + ny;
+        return Objects.hashCode(coordinateToGrid) + 37 * Arrays.hashCode(gridSize);
+    }
+
+    /**
+     * Invoked after deserialization. This method computes the transient fields.
+     */
+    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
+        in.defaultReadObject();
+        computeConversionFactors();
     }
 }

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrices.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrices.java?rev=1718271&r1=1718270&r2=1718271&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrices.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrices.java [UTF-8] Mon Dec  7 09:52:59 2015
@@ -223,7 +223,7 @@ public final class Matrices extends Stat
      * Argument validity shall be verified by the caller.
      *
      * @param useEnvelopes {@code true} if source and destination envelopes shall be taken in account.
-     *        If {@code false}, then source and destination envelopes will be ignored and may be null.
+     *        If {@code false}, then source and destination envelopes will be ignored and can be null.
      */
     @SuppressWarnings("null")
     private static MatrixSIS createTransform(final Envelope srcEnvelope, final AxisDirection[] srcAxes,
@@ -713,7 +713,7 @@ public final class Matrices extends Stat
      */
     public static MatrixSIS unmodifiable(final Matrix matrix) {
         if (matrix == null || matrix instanceof UnmodifiableMatrix) {
-            return (UnmodifiableMatrix) matrix;
+            return (MatrixSIS) matrix;
         } else {
             return new UnmodifiableMatrix(matrix);
         }

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractLinearTransform.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractLinearTransform.java?rev=1718271&r1=1718270&r2=1718271&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractLinearTransform.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractLinearTransform.java [UTF-8] Mon Dec  7 09:52:59 2015
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.referencing.operation.transform;
 
+import java.util.Arrays;
 import java.io.Serializable;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterDescriptorGroup;
@@ -161,6 +162,63 @@ abstract class AbstractLinearTransform e
     }
 
     /**
+     * Transforms an array of relative distance vectors. Distance vectors are transformed without applying
+     * the translation components. The default implementation is not very efficient, but it should not be
+     * an issue since this method is not invoked often.
+     *
+     * @since 0.7
+     */
+    @Override
+    public void deltaTransform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, int numPts) {
+        int offFinal = 0;
+        double[] dstFinal = null;
+        final int srcDim, dstDim;
+        int srcInc = srcDim = getSourceDimensions();
+        int dstInc = dstDim = getTargetDimensions();
+        if (srcPts == dstPts) {
+            switch (IterationStrategy.suggest(srcOff, srcDim, dstOff, dstDim, numPts)) {
+                case ASCENDING: {
+                    break;
+                }
+                case DESCENDING: {
+                    srcOff += (numPts - 1) * srcDim;  srcInc = -srcInc;
+                    dstOff += (numPts - 1) * dstDim;  dstInc = -dstInc;
+                    break;
+                }
+                default: {
+                    srcPts = Arrays.copyOfRange(srcPts, srcOff, srcOff + numPts*srcDim);
+                    srcOff = 0;
+                    break;
+                }
+                case BUFFER_TARGET: {
+                    dstFinal = dstPts; dstPts = new double[numPts * dstInc];
+                    offFinal = dstOff; dstOff = 0;
+                    break;
+                }
+            }
+        }
+        final double[] buffer = new double[dstDim];
+        while (--numPts >= 0) {
+            for (int j=0; j<dstDim; j++) {
+                double sum = 0;
+                for (int i=0; i<srcDim; i++) {
+                    final double e = getElement(j, i);
+                    if (e != 0) {   // See the comment in ProjectiveTransform for the purpose of this test.
+                        sum += srcPts[srcOff + i] * e;
+                    }
+                }
+                buffer[j] = sum;
+            }
+            System.arraycopy(buffer, 0, dstPts, dstOff, dstDim);
+            srcOff += srcInc;
+            dstOff += dstInc;
+        }
+        if (dstFinal != null) {
+            System.arraycopy(dstPts, 0, dstFinal, offFinal, dstPts.length);
+        }
+    }
+
+    /**
      * Compares this math transform with an object which is known to be of the same class.
      * Implementors can safely cast the {@code object} argument to their subclass.
      *

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=1718271&r1=1718270&r2=1718271&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:52:59 2015
@@ -18,6 +18,8 @@ package org.apache.sis.referencing.opera
 
 import java.util.Arrays;
 import javax.measure.unit.Unit;
+import javax.measure.quantity.Angle;
+import javax.measure.quantity.Length;
 import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterDescriptorGroup;
@@ -69,7 +71,7 @@ import org.apache.sis.util.Debug;
  * signs used in NTG_88 document. This is because NTG_88 grid defines shifts from target to source,
  * while this class expects shifts from source to target.
  *
- * <p>This algorithm is not the same as a (theoretical) {@link EllipsoidToCentricTransform} →
+ * <p><b>Note:</b> this algorithm is not the same as a (theoretical) {@link EllipsoidToCentricTransform} →
  * {@link InterpolatedTransform} → (inverse of {@code EllipsoidToCentricTransform}) concatenation
  * because the {@code DatumShiftGrid} inputs are geographic coordinates even if the interpolated
  * grid values are in geocentric space.</p></div>
@@ -175,28 +177,31 @@ public class InterpolatedGeocentricTrans
      * @param target      The target ellipsoid.
      * @param isTarget3D  {@code true} if the target coordinates have a height.
      * @param grid        The grid of datum shifts from source to target datum.
-     *                    The {@link DatumShiftGrid#offsetAt DatumShiftGrid.offsetAt(…)} method shall expect
-     *                    input geographic coordinates in <strong>radians</strong> and computes (ΔX, ΔY, ΔZ)
-     *                    shifts from <em>source</em> to <em>target</em> in the unit of source ellipsoid axes.
+     *                    The {@link DatumShiftGrid#interpolateInCell DatumShiftGrid.interpolateInCell(…)} method
+     *                    shall compute (ΔX, ΔY, ΔZ) translations from <em>source</em> to <em>target</em> in the
+     *                    unit of source ellipsoid axes.
      *
      * @see #createGeodeticTransformation(MathTransformFactory, Ellipsoid, boolean, Ellipsoid, boolean, DatumShiftGrid)
      */
     protected InterpolatedGeocentricTransform(final Ellipsoid source, final boolean isSource3D,
                                               final Ellipsoid target, final boolean isTarget3D,
-                                              final DatumShiftGrid grid)
+                                              final DatumShiftGrid<Angle,Length> grid)
     {
         super(source, isSource3D,
               target, isTarget3D,
-              grid.getAverageOffset(0),
-              grid.getAverageOffset(1),
-              grid.getAverageOffset(2),
-              grid, false,    // Non-abridged Molodensky
-              DESCRIPTOR);
+              grid.getCellMean(0),
+              grid.getCellMean(1),
+              grid.getCellMean(2),
+              grid, false, DESCRIPTOR);
 
-        final int dim = grid.getShiftDimensions();
+        final int dim = grid.getTranslationDimensions();
         if (dim != 3) {
             throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3, "grid", 3, dim));
         }
+        Object unit = "ratio";
+        if (grid.isCellValueRatio() || (unit = grid.getTranslationUnit()) != source.getAxisUnit()) {
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalUnitFor_2, "translation", unit));
+        }
         if (isSource3D || isTarget3D) {
             inverse = new Inverse(this, source, target);
         } else {
@@ -224,16 +229,16 @@ public class InterpolatedGeocentricTrans
      * @param target      The target ellipsoid.
      * @param isTarget3D  {@code true} if the target coordinates have a height.
      * @param grid        The grid of datum shifts from source to target datum.
-     *                    The {@link DatumShiftGrid#offsetAt DatumShiftGrid.offsetAt(…)} method shall expect
-     *                    input geographic coordinates in <strong>radians</strong> and computes (ΔX, ΔY, ΔZ)
-     *                    shifts from <em>source</em> to <em>target</em> in the unit of source ellipsoid axes.
+     *                    The {@link DatumShiftGrid#interpolateInCell DatumShiftGrid.interpolateInCell(…)} method
+     *                    shall compute (ΔX, ΔY, ΔZ) translations from <em>source</em> to <em>target</em> in the
+     *                    unit of source ellipsoid axes.
      * @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,
             final Ellipsoid source, final boolean isSource3D,
             final Ellipsoid target, final boolean isTarget3D,
-            final DatumShiftGrid grid) throws FactoryException
+            final DatumShiftGrid<Angle,Length> grid) throws FactoryException
     {
         ArgumentChecks.ensureNonNull("grid", grid);
         final InterpolatedGeocentricTransform tr;
@@ -258,12 +263,12 @@ public class InterpolatedGeocentricTrans
     final void completeParameters(final Parameters pg, final double semiMinor, final Unit<?> unit, double Δf) {
         super.completeParameters(pg, semiMinor, unit, Δf);
         if (pg != context) {
-            Δf = Δfmod / semiMajor;
+            Δf = Δfmod / semiMinor;
             pg.getOrCreate(Molodensky.AXIS_LENGTH_DIFFERENCE).setValue(Δa, unit);
             pg.getOrCreate(Molodensky.FLATTENING_DIFFERENCE) .setValue(Δf, Unit.ONE);
         }
-        if (grid instanceof DatumShiftGridFile) {
-            pg.getOrCreate(FranceGeocentricInterpolation.FILE).setValue(((DatumShiftGridFile) grid).file);
+        if (grid instanceof DatumShiftGridFile<?,?>) {
+            pg.getOrCreate(FranceGeocentricInterpolation.FILE).setValue(((DatumShiftGridFile<?,?>) grid).file);
         }
     }
 
@@ -280,12 +285,12 @@ public class InterpolatedGeocentricTrans
                             final double[] dstPts, final int dstOff,
                             final boolean derivate) throws TransformException
     {
-        final double[] offset = new double[3];
+        final double[] vector = new double[3];
         final double λ = srcPts[srcOff];
         final double φ = srcPts[srcOff+1];
-        grid.offsetAt(λ, φ, offset);
+        grid.interpolateAtNormalized(λ, φ, vector);
         return transform(λ, φ, isSource3D ? srcPts[srcOff+2] : 0,
-                dstPts, dstOff, offset[0], offset[1], offset[2], null, derivate);
+                dstPts, dstOff, vector[0], vector[1], vector[2], null, derivate);
     }
 
     /**
@@ -330,7 +335,7 @@ public class InterpolatedGeocentricTrans
         while (--numPts >= 0) {
             final double λ = srcPts[srcOff  ];
             final double φ = srcPts[srcOff+1];
-            grid.offsetAt(λ, φ, offset);
+            grid.interpolateAtNormalized(λ, φ, offset);
             transform(λ, φ, isSource3D ? srcPts[srcOff+2] : 0,
                       dstPts, dstOff, offset[0], offset[1], offset[2], null, false);
             srcOff += srcInc;

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform.java?rev=1718271&r1=1718270&r2=1718271&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform.java [UTF-8] Mon Dec  7 09:52:59 2015
@@ -18,6 +18,7 @@ package org.apache.sis.referencing.opera
 
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.TransformException;
 
 
 /**
@@ -61,7 +62,7 @@ import org.opengis.referencing.operation
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @since   0.4
- * @version 0.6
+ * @version 0.7
  * @module
  *
  * @see org.apache.sis.referencing.operation.transform.MathTransforms#linear(Matrix)
@@ -93,4 +94,27 @@ public interface LinearTransform extends
      * @see MathTransforms#getMatrix(MathTransform)
      */
     Matrix getMatrix();
+
+    /**
+     * Transforms an array of relative distance vectors.
+     * Distance vectors are transformed without applying the translation components.
+     * The supplied array of distance values will contain packed values.
+     *
+     * <div class="note"><b>Example:</b> if the source dimension is 3, then the values will be packed in this order:
+     * (<var>Δx₀</var>,<var>Δy₀</var>,<var>Δz₀</var>,
+     *  <var>Δx₁</var>,<var>Δy₁</var>,<var>Δz₁</var> …).
+     * </div>
+     *
+     * @param  srcPts The array containing the source vectors.
+     * @param  srcOff The offset to the first vector to be transformed in the source array.
+     * @param  dstPts The array into which the transformed vectors are returned. Can be the same than {@code srcPts}.
+     * @param  dstOff The offset to the location of the first transformed vector that is stored in the destination array.
+     * @param  numPts The number of vector objects to be transformed.
+     * @throws TransformException if a vector can not be transformed.
+     *
+     * @see java.awt.geom.AffineTransform#deltaTransform(double[], int, double[], int, int)
+     *
+     * @since 0.7
+     */
+    void deltaTransform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, int numPts) throws TransformException;
 }

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform1D.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform1D.java?rev=1718271&r1=1718270&r2=1718271&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform1D.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform1D.java [UTF-8] Mon Dec  7 09:52:59 2015
@@ -239,7 +239,7 @@ class LinearTransform1D extends Abstract
     public void transform(final double[] srcPts, int srcOff,
                           final double[] dstPts, int dstOff, int numPts)
     {
-        if (srcPts!=dstPts || srcOff>=dstOff) {
+        if (srcPts != dstPts || srcOff >= dstOff) {
             while (--numPts >= 0) {
                 dstPts[dstOff++] = offset + scale * srcPts[srcOff++];
             }
@@ -261,7 +261,7 @@ class LinearTransform1D extends Abstract
     public void transform(final float[] srcPts, int srcOff,
                           final float[] dstPts, int dstOff, int numPts)
     {
-        if (srcPts!=dstPts || srcOff>=dstOff) {
+        if (srcPts != dstPts || srcOff >= dstOff) {
             while (--numPts >= 0) {
                 dstPts[dstOff++] = (float) (offset + scale * srcPts[srcOff++]);
             }
@@ -301,6 +301,29 @@ class LinearTransform1D extends Abstract
         }
     }
 
+    /**
+     * Transforms many distance vectors in a list of ordinal values.
+     * The default implementation computes the values from the {@link #scale} coefficient only.
+     *
+     * @since 0.7
+     */
+    @Override
+    public void deltaTransform(final double[] srcPts, int srcOff,
+                               final double[] dstPts, int dstOff, int numPts)
+    {
+        if (srcPts != dstPts || srcOff >= dstOff) {
+            while (--numPts >= 0) {
+                dstPts[dstOff++] = scale * srcPts[srcOff++];
+            }
+        } else {
+            srcOff += numPts;
+            dstOff += numPts;
+            while (--numPts >= 0) {
+                dstPts[--dstOff] = scale * srcPts[--srcOff];
+            }
+        }
+    }
+
     /**
      * {@inheritDoc}
      */

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=1718271&r1=1718270&r2=1718271&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:52:59 2015
@@ -154,7 +154,7 @@ abstract class MolodenskyFormula extends
      * The grid of datum shifts from source datum to target datum.
      * This can be non-null only for {@link InterpolatedGeocentricTransform}.
      */
-    final DatumShiftGrid grid;
+    final DatumShiftGrid<?,?> grid;
 
     /**
      * Constructs the inverse of a Molodensky transform.
@@ -174,6 +174,7 @@ abstract class MolodenskyFormula extends
 
     /**
      * Creates a Molodensky transform from the specified parameters.
+     * If a non-null {@code grid} is specified, it is caller's responsibility to verify its validity.
      *
      * @param source      The source ellipsoid.
      * @param isSource3D  {@code true} if the source coordinates have a height.
@@ -190,7 +191,7 @@ abstract class MolodenskyFormula extends
     MolodenskyFormula(final Ellipsoid source, final boolean isSource3D,
                       final Ellipsoid target, final boolean isTarget3D,
                       final double tX, final double tY, final double tZ,
-                      final DatumShiftGrid grid, final boolean isAbridged,
+                      final DatumShiftGrid<?,?> grid, final boolean isAbridged,
                       final ParameterDescriptorGroup descriptor)
     {
         ArgumentChecks.ensureNonNull("source", source);
@@ -402,7 +403,7 @@ abstract class MolodenskyFormula extends
             if (offset == null) break;
 
             // Following is executed only in InterpolatedGeocentricTransform case.
-            grid.offsetAt(λt, φt, offset);
+            grid.interpolateAtNormalized(λt, φt, offset);
             tX = -offset[0];
             tY = -offset[1];
             tZ = -offset[2];
@@ -514,14 +515,14 @@ abstract class MolodenskyFormula extends
             return isSource3D == that.isSource3D
                 && isTarget3D == that.isTarget3D
                 && isAbridged == that.isAbridged
-                && Objects .equals      (grid,                that.grid)
                 && Numerics.epsilonEqual(tX,                  that.tX,                  mode)
                 && Numerics.epsilonEqual(tY,                  that.tY,                  mode)
                 && Numerics.epsilonEqual(tZ,                  that.tZ,                  mode)
                 && Numerics.epsilonEqual(Δa,                  that.Δa,                  mode)
                 && Numerics.epsilonEqual(Δfmod,               that.Δfmod,               mode)
                 && Numerics.epsilonEqual(semiMajor,           that.semiMajor,           mode)
-                && Numerics.epsilonEqual(eccentricitySquared, that.eccentricitySquared, mode);
+                && Numerics.epsilonEqual(eccentricitySquared, that.eccentricitySquared, mode)
+                && Objects .equals(grid, that.grid);
         }
         return false;
     }



Mime
View raw message