sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1788983 [1/2] - in /sis/trunk: ./ core/sis-feature/src/main/java/org/apache/sis/feature/ core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ core/sis-referencing/src/main/java/org/apache/sis/internal/ref...
Date Mon, 27 Mar 2017 16:38:01 GMT
Author: desruisseaux
Date: Mon Mar 27 16:38:00 2017
New Revision: 1788983

URL: http://svn.apache.org/viewvc?rev=1788983&view=rev
Log:
Merge LocalizationGridBuilder and related work from JDK7 branch.

Added:
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java
      - copied unchanged from r1788982, sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ResidualGrid.java
      - copied unchanged from r1788982, sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ResidualGrid.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/TransformBuilder.java
      - copied unchanged from r1788982, sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/TransformBuilder.java
    sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilderTest.java
      - copied unchanged from r1788982, sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilderTest.java
Modified:
    sis/trunk/   (props changed)
    sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java
    sis/trunk/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationFormat.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ExtendedPrecisionMatrix.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridCompressed.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DatumShiftGrid.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/package-info.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrices.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/MatrixSIS.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/package-info.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ZonedGridSystem.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedTransform.java
    sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilderTest.java
    sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/GeneralMatrixTest.java
    sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix3Test.java
    sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/MatrixTestCase.java
    sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/math/ArrayVector.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/math/CompoundDirectPositions.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/math/Line.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/math/Plane.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/math/StatisticsFormat.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
    sis/trunk/core/sis-utility/src/test/java/org/apache/sis/math/PlaneTest.java
    sis/trunk/ide-project/NetBeans/nbproject/project.properties
    sis/trunk/pom.xml

Propchange: sis/trunk/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Mon Mar 27 16:38:00 2017
@@ -1,5 +1,5 @@
 /sis/branches/Android:1430670-1480699
 /sis/branches/JDK6:1394364-1758914
-/sis/branches/JDK7:1394913-1786926
-/sis/branches/JDK8:1584960-1786922
+/sis/branches/JDK7:1394913-1788982
+/sis/branches/JDK8:1584960-1788977
 /sis/branches/JDK9:1773327-1773512

Modified: sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java?rev=1788983&r1=1788982&r2=1788983&view=diff
==============================================================================
--- sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java [UTF-8] (original)
+++ sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java [UTF-8] Mon Mar 27 16:38:00 2017
@@ -64,12 +64,11 @@ import org.apache.sis.util.Characters;
  *   └────────────┴─────────┴─────────────┴───────────┘
  * }</div>
  *
- * Like most {@link java.text.Format} implementations, this class is not thread-safe.
- * Each thread should use its own {@code FeatureFormat} instance or apply synchronization.
- *
- * <div class="warning"><b>Limitation:</b>
- * the current implementation can only format features — parsing is not yet implemented.
- * </div>
+ * <p><b>Limitations:</b></p>
+ * <ul>
+ *   <li>The current implementation can only format features — parsing is not yet implemented.</li>
+ *   <li>{@code FeatureFormat}, like most {@code java.text.Format} subclasses, is not thread-safe.</li>
+ * </ul>
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.5

Modified: sis/trunk/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationFormat.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationFormat.java?rev=1788983&r1=1788982&r2=1788983&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationFormat.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationFormat.java [UTF-8] Mon Mar 27 16:38:00 2017
@@ -87,12 +87,11 @@ import org.apache.sis.metadata.iso.citat
  * }
  * </div>
  *
- * Like most {@link java.text.Format} implementations, this class is not thread-safe.
- * Each thread should use its own {@code LocationFormat} instance or apply synchronization.
- *
- * <div class="warning"><b>Limitation:</b>
- * the current implementation can only format locations — parsing is not yet implemented.
- * </div>
+ * <p><b>Limitations:</b></p>
+ * <ul>
+ *   <li>The current implementation can only format features — parsing is not yet implemented.</li>
+ *   <li>{@code LocationFormat}, like most {@code java.text.Format} subclasses, is not thread-safe.</li>
+ * </ul>
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.8

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ExtendedPrecisionMatrix.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ExtendedPrecisionMatrix.java?rev=1788983&r1=1788982&r2=1788983&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ExtendedPrecisionMatrix.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ExtendedPrecisionMatrix.java [UTF-8] Mon Mar 27 16:38:00 2017
@@ -25,12 +25,19 @@ import org.opengis.referencing.operation
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.5
- * @version 0.6
+ * @version 0.8
  * @module
  */
 public interface ExtendedPrecisionMatrix extends Matrix {
     /**
      * A sentinel value for {@link org.apache.sis.referencing.operation.matrix.Matrices#create(int, int, Number[])}
+     * meaning that we request an extended precision matrix with all elements initialized to zero.
+     * This is a non-public feature because we try to hide our extended-precision mechanism from the users.
+     */
+    Number[] ZERO = new Number[0];
+
+    /**
+     * A sentinel value for {@link org.apache.sis.referencing.operation.matrix.Matrices#create(int, int, Number[])}
      * meaning that we request an extended precision matrix initialized to the identity (or diagonal) matrix.
      * This is a non-public feature because we try to hide our extended-precision mechanism from the users.
      */

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridCompressed.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridCompressed.java?rev=1788983&r1=1788982&r2=1788983&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridCompressed.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridCompressed.java [UTF-8] Mon Mar 27 16:38:00 2017
@@ -27,9 +27,9 @@ 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}).
+ * @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
@@ -123,7 +123,7 @@ final class DatumShiftGridCompressed<C e
      * Returns a new grid with the same geometry than this grid but different data arrays.
      */
     @Override
-    final DatumShiftGridFile<C,T> setData(final Object[] other) {
+    protected final DatumShiftGridFile<C,T> setData(final Object[] other) {
         return new DatumShiftGridCompressed<>(this, averages, (short[][]) other, scale);
     }
 
@@ -140,11 +140,13 @@ final class DatumShiftGridCompressed<C e
     }
 
     /**
-     * Returns direct references (not cloned) to the data arrays.
+     * Returns direct references (not cloned) to the data arrays. This method is for cache management,
+     * {@link #equals(Object)} and {@link #hashCode()} implementations only and should not be invoked
+     * in other context.
      */
     @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    final Object[] getData() {
+    protected final Object[] getData() {
         return data;
     }
 

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java?rev=1788983&r1=1788982&r2=1788983&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java [UTF-8] Mon Mar 27 16:38:00 2017
@@ -24,29 +24,36 @@ import javax.measure.Quantity;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterDescriptorGroup;
 import org.opengis.parameter.GeneralParameterDescriptor;
+import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.apache.sis.math.DecimalFunctions;
 import org.apache.sis.util.collection.Cache;
 import org.apache.sis.util.Debug;
 import org.apache.sis.parameter.Parameters;
 import org.apache.sis.referencing.datum.DatumShiftGrid;
+import org.apache.sis.referencing.operation.transform.LinearTransform;
 import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
+import org.apache.sis.internal.util.Utilities;
 
 import org.apache.sis.internal.jdk8.JDK8;
 
 /**
  * A datum shift grid loaded from a file.
  * The filename is usually a parameter defined in the EPSG database.
+ * This class should not be in public API because it requires implementation to expose internal mechanic:
  *
- * <p>This class is in internal package (not public API) because it makes the following assumptions:</p>
  * <ul>
- *   <li>Values <var>x₀</var>, <var>y₀</var>, <var>Δx</var> and <var>Δy</var>
- *       given to the constructor are in degrees and needs to be converted to radians.</li>
- *   <li>Single floating-point precision ({@code float)} is sufficient.</li>
- *   <li>Values were defined in base 10, usually in ASCII files. This assumption has an impact on conversions
- *       from {@code float} to {@code double} performed by the {@link #getCellValue(int, int, int)} method.</li>
+ *   <li>Subclasses need to give an access to their internal data (not a copy) through the {@link #getData()}
+ *       and {@link #setData(Object[])} methods. We use that for managing the cache, reducing memory usage by
+ *       sharing data and for {@link #equals(Object)} and {@link #hashCode()} implementations.</li>
+ *   <li>{@link #descriptor}, {@link #gridToTarget()} and {@link #setFileParameters(Parameters)} are convenience
+ *       members for {@link org.apache.sis.referencing.operation.transform.InterpolatedTransform} constructor.
+ *       What they do are closely related to how {@code InterpolatedTransform} works, and trying to document that
+ *       in a public API would probably be too distracting for the users.</li>
  * </ul>
  *
+ * The main concrete subclass is {@link DatumShiftGridFile.Float}.
+ *
  * @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}).
@@ -55,6 +62,8 @@ import org.apache.sis.internal.jdk8.JDK8
  * @since   0.7
  * @version 0.8
  * @module
+ *
+ * @see org.apache.sis.referencing.operation.transform.InterpolatedTransform
  */
 public abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quantity<T>> extends DatumShiftGrid<C,T> {
     /**
@@ -92,7 +101,7 @@ public abstract class DatumShiftGridFile
     /**
      * Number of grid cells along the <var>x</var> axis.
      */
-    final int nx;
+    protected final int nx;
 
     /**
      * The best translation accuracy that we can expect from this file.
@@ -102,7 +111,37 @@ public abstract class DatumShiftGridFile
      *
      * @see #getCellPrecision()
      */
-    double accuracy;
+    protected double accuracy;
+
+    /**
+     * Creates a new datum shift grid for the given grid geometry.
+     * The actual offset values need to be provided by subclasses.
+     *
+     * @param  coordinateUnit    the unit of measurement of input values, before conversion to grid indices by {@code coordinateToGrid}.
+     * @param  translationUnit   the unit of measurement of output values.
+     * @param  isCellValueRatio  {@code true} if results of {@link #interpolateInCell interpolateInCell(…)} are divided by grid cell size.
+     * @param  coordinateToGrid  conversion from the "real world" coordinates to grid indices including fractional parts.
+     * @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.
+     * @param  descriptor        the parameter descriptor of the provider that created this grid.
+     * @param  files             the file(s) from which the grid has been loaded.
+     *
+     * @since 0.8
+     */
+    protected DatumShiftGridFile(final Unit<C> coordinateUnit,
+                                 final Unit<T> translationUnit,
+                                 final boolean isCellValueRatio,
+                                 final LinearTransform coordinateToGrid,
+                                 final int nx, final int ny,
+                                 final ParameterDescriptorGroup descriptor,
+                                 final Path... files)
+    {
+        super(coordinateUnit, coordinateToGrid, new int[] {nx, ny}, isCellValueRatio, translationUnit);
+        this.descriptor = descriptor;
+        this.files      = files;
+        this.nx         = nx;
+        this.accuracy   = Double.NaN;
+    }
 
     /**
      * Creates a new datum shift grid for the given grid geometry.
@@ -124,15 +163,8 @@ public abstract class DatumShiftGridFile
                        final ParameterDescriptorGroup descriptor,
                        final Path... files) throws NoninvertibleTransformException
     {
-        super(coordinateUnit, new AffineTransform2D(Δx, 0, 0, Δy, x0, y0).inverse(),
-                new int[] {nx, ny}, isCellValueRatio, translationUnit);
-        this.descriptor = descriptor;
-        this.files      = files;
-        this.nx         = nx;
-        this.accuracy   = Double.NaN;
-        if (files.length == 0) {
-            throw new IllegalArgumentException();
-        }
+        this(coordinateUnit, translationUnit, isCellValueRatio,
+                new AffineTransform2D(Δx, 0, 0, Δy, x0, y0).inverse(), nx, ny, descriptor, files);
     }
 
     /**
@@ -140,7 +172,7 @@ public abstract class DatumShiftGridFile
      *
      * @param  other  the other datum shift grid from which to copy the grid geometry.
      */
-    DatumShiftGridFile(final DatumShiftGridFile<C,T> other) {
+    protected DatumShiftGridFile(final DatumShiftGridFile<C,T> other) {
         super(other);
         descriptor = other.descriptor;
         files      = other.files;
@@ -149,21 +181,29 @@ public abstract class DatumShiftGridFile
     }
 
     /**
-     * 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.
+     * Returns {@code this} casted to the given type, after verification that those types are valid.
+     * This method is invoked after {@link NADCON}, {@link NTv2} or other providers got an existing
+     * {@code DatumShiftGridFile} instance from the {@link #CACHE}.
      */
-    @Override
-    public double getCellPrecision() {
-        return accuracy / 10;   // Division by 10 is arbitrary.
+    @SuppressWarnings("unchecked")
+    final <NC extends Quantity<NC>, NT extends Quantity<NT>> 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;
     }
 
     /**
      * If a grid exists in the cache for the same data, returns a new grid sharing the same data arrays.
      * Otherwise returns {@code this}.
+     *
+     * @return a grid using the same data than this grid, or {@code this}.
+     *
+     * @see #getData()
+     * @see #setData(Object[])
      */
-    final DatumShiftGridFile<C,T> useSharedData() {
+    protected final DatumShiftGridFile<C,T> useSharedData() {
         final Object[] data = getData();
         for (final DatumShiftGridFile<?,?> grid : CACHE.values()) {
             final Object[] other = grid.getData();
@@ -180,13 +220,44 @@ public abstract class DatumShiftGridFile
      * the same data than an existing grid. The typical use case is when a filename is different but still
      * reference the same grid (e.g. symbolic link, lower case versus upper case in a case-insensitive file
      * system).
+     *
+     * @param  other  data from another {@code DatumShiftGridFile} that we can share.
+     * @return a new {@code DatumShiftGridFile} using the given data reference.
      */
-    abstract DatumShiftGridFile<C,T> setData(Object[] other);
+    protected abstract DatumShiftGridFile<C,T> setData(Object[] other);
 
     /**
-     * Returns the data for each shift dimensions.
+     * Returns the data for each shift dimensions. This method is for cache management, {@link #equals(Object)}
+     * and {@link #hashCode()} implementations only and should not be invoked in other context.
+     *
+     * @return a direct (not cloned) reference to the internal data array.
      */
-    abstract Object[] getData();
+    protected abstract Object[] getData();
+
+    /**
+     * 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.
+    }
+
+    /**
+     * Returns the transform from grid coordinates to "real world" coordinates after the datum shift has been applied,
+     * or {@code null} for the default. This is usually the inverse of the transform from "real world" coordinates to
+     * grid coordinates before datum shift, since NADCON and NTv2 transformations have source and target coordinates
+     * in the same coordinate system (with axis units in degrees). But this method may be overridden by subclasses that
+     * use {@code DatumShiftGridFile} for other kind of transformations.
+     *
+     * @return the transformation from grid coordinates to "real world" coordinates after datum shift,
+     *         or {@code null} for the default (namely the inverse of the "source to grid" transformation).
+     */
+    public Matrix gridToTarget() {
+        return null;
+    }
 
     /**
      * Sets all parameters for a value of type {@link Path} to the values given to th constructor.
@@ -194,31 +265,21 @@ public abstract class DatumShiftGridFile
      * @param  parameters  the parameter group where to set the values.
      */
     public final void setFileParameters(final Parameters parameters) {
-        int i = 0;  // The 'files' array should always contains at least one element.
-        for (final GeneralParameterDescriptor gd : descriptor.descriptors()) {
-            if (gd instanceof ParameterDescriptor<?>) {
-                final ParameterDescriptor<?> d = (ParameterDescriptor<?>) gd;
-                if (Path.class.isAssignableFrom(d.getValueClass())) {
-                    parameters.getOrCreate(d).setValue(files[i]);
-                    if (++i == files.length) break;
+        if (files.length != 0) {
+            int i = 0;
+            for (final GeneralParameterDescriptor gd : descriptor.descriptors()) {
+                if (gd instanceof ParameterDescriptor<?>) {
+                    final ParameterDescriptor<?> d = (ParameterDescriptor<?>) gd;
+                    if (Path.class.isAssignableFrom(d.getValueClass())) {
+                        parameters.getOrCreate(d).setValue(files[i]);
+                        if (++i == files.length) break;
+                    }
                 }
             }
         }
     }
 
     /**
-     * Returns {@code this} casted to the given type, after verification that those types are valid.
-     */
-    @SuppressWarnings("unchecked")
-    final <NC extends Quantity<NC>, NT extends Quantity<NT>> 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.
@@ -255,7 +316,7 @@ public abstract class DatumShiftGridFile
     @Debug
     @Override
     public String toString() {
-        return "DatumShiftGrid[\"" + files[0].getFileName() + "\"]";
+        return Utilities.toString(getClass(), "file", (files.length != 0) ? files[0] : null);
     }
 
 
@@ -263,6 +324,14 @@ public abstract class DatumShiftGridFile
 
     /**
      * An implementation of {@link DatumShiftGridFile} which stores the offset values in {@code float[]} arrays.
+     * This class is in internal package (not public API) because it makes the following assumptions:
+     * <ul>
+     *   <li>Values <var>x₀</var>, <var>y₀</var>, <var>Δx</var> and <var>Δy</var>
+     *       given to the constructor are in degrees and needs to be converted to radians.</li>
+     *   <li>Single floating-point precision ({@code float)} is sufficient.</li>
+     *   <li>Values were defined in base 10, usually in ASCII files. This assumption has an impact on conversions
+     *       from {@code float} to {@code double} performed by the {@link #getCellValue(int, int, int)} method.</li>
+     * </ul>
      *
      * @author  Martin Desruisseaux (Geomatys)
      * @since   0.7
@@ -314,16 +383,18 @@ public abstract class DatumShiftGridFile
          * Returns a new grid with the same geometry than this grid but different data arrays.
          */
         @Override
-        final DatumShiftGridFile<C,T> setData(final Object[] other) {
+        protected final DatumShiftGridFile<C,T> setData(final Object[] other) {
             return new Float<>(this, (float[][]) other);
         }
 
         /**
-         * Returns direct references (not cloned) to the data arrays.
+         * Returns direct references (not cloned) to the data arrays. This method is for cache management,
+         * {@link #equals(Object)} and {@link #hashCode()} implementations only and should not be invoked
+         * in other context.
          */
         @Override
         @SuppressWarnings("ReturnOfCollectionOrArrayField")
-        final Object[] getData() {
+        protected final Object[] getData() {
             return offsets;
         }
 

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java?rev=1788983&r1=1788982&r2=1788983&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java [UTF-8] Mon Mar 27 16:38:00 2017
@@ -98,12 +98,11 @@ import static org.apache.sis.util.collec
  *   <tr><td><code>{@linkplain IdentifiedObject}[]</code></td><td>Accepted only for {@link ContentLevel#NAME_SUMMARY}.</td></tr>
  * </table>
  *
- * Like most {@link java.text.Format} implementations, this class is not thread-safe.
- * Each thread should use its own {@code ParameterFormat} instance or apply synchronization.
- *
- * <div class="warning"><b>Limitation:</b>
- * the current implementation can only format parameters — parsing is not yet implemented.
- * </div>
+ * <p><b>Limitations:</b></p>
+ * <ul>
+ *   <li>The current implementation can only format features — parsing is not yet implemented.</li>
+ *   <li>{@code ParameterFormat}, like most {@code java.text.Format} subclasses, is not thread-safe.</li>
+ * </ul>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @since   0.4

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DatumShiftGrid.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DatumShiftGrid.java?rev=1788983&r1=1788982&r2=1788983&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DatumShiftGrid.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DatumShiftGrid.java [UTF-8] Mon Mar 27 16:38:00 2017
@@ -233,9 +233,9 @@ public abstract class DatumShiftGrid<C e
     }
 
     /**
-     * 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.
+     * Computes the conversion factors needed by {@link #interpolateInCell(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() {
@@ -308,7 +308,7 @@ public abstract class DatumShiftGrid<C e
      * 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>
+     * given by {@link #getCoordinateUnit()}. The envelope CRS is undefined.</p>
      *
      * @return the domain covered by this grid.
      * @throws TransformException if an error occurred while computing the envelope.
@@ -327,6 +327,7 @@ public abstract class DatumShiftGrid<C e
      *
      * @return the unit of measurement of input values before conversion to grid indices.
      *
+     * @see #getTranslationUnit()
      * @see org.apache.sis.referencing.operation.AbstractCoordinateOperation#getInterpolationCRS()
      */
     public Unit<C> getCoordinateUnit() {
@@ -339,7 +340,7 @@ public abstract class DatumShiftGrid<C e
      * 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>)
+     * <p>This transform is usually two-dimensional, 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>
@@ -416,6 +417,7 @@ public abstract class DatumShiftGrid<C e
      *
      * @return the unit of measurement of output values interpolated by {@code interpolateAt(…)}.
      *
+     * @see #getCoordinateUnit()
      * @see #interpolateAt
      */
     public Unit<T> getTranslationUnit() {
@@ -461,7 +463,7 @@ public abstract class DatumShiftGrid<C e
     /**
      * 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}.
+     * The output unit of measurement is the same than the one documented in {@link #getCellValue(int, int, int)}.
      *
      * <div class="section">Default implementation</div>
      * The default implementation performs the following steps for each dimension <var>dim</var>,

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java?rev=1788983&r1=1788982&r2=1788983&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java [UTF-8] Mon Mar 27 16:38:00 2017
@@ -16,189 +16,723 @@
  */
 package org.apache.sis.referencing.operation.builder;
 
+import java.util.Map;
+import java.util.Arrays;
 import java.io.IOException;
+import org.opengis.util.FactoryException;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
+import org.opengis.geometry.coordinate.Position;
+import org.opengis.referencing.operation.Matrix;
+import org.opengis.referencing.operation.MathTransformFactory;
 import org.apache.sis.io.TableAppender;
 import org.apache.sis.math.Line;
 import org.apache.sis.math.Plane;
+import org.apache.sis.math.Vector;
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.operation.matrix.MatrixSIS;
 import org.apache.sis.referencing.operation.transform.LinearTransform;
-import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.internal.referencing.ExtendedPrecisionMatrix;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.Debug;
 
+// Branch-dependent imports
+import org.apache.sis.internal.jdk8.JDK8;
+
 
 /**
- * Creates a linear (usually affine) transform which will map approximatively the given source points to
- * the given target points. The transform coefficients are determined using a <cite>least squares</cite>
- * estimation method, with the assumption that source points are precise and all uncertainty is in the
- * target points.
+ * Creates an affine transform which will map approximatively the given source positions to the given target positions.
+ * In many cases, the <em>source</em> positions are grid indices and the <em>target</em> positions are geographic or
+ * projected coordinates, but this is not mandatory. If the source positions are known to be grid indices,
+ * then a builder created by the {@link #LinearTransformBuilder(int...)} constructor will be more efficient.
+ * Otherwise a builder created by the {@link #LinearTransformBuilder()} constructor will be able to handle
+ * randomly distributed coordinates.
  *
- * <div class="note"><b>Implementation note:</b>
- * The quantity that current implementation tries to minimize is not strictly the squared Euclidian distance.
- * The current implementation rather processes each target dimension independently, which may not give the same
- * result than if we tried to minimize the squared Euclidian distances by taking all dimensions in account together.
- * This algorithm may change in future SIS versions.
- * </div>
+ * <p>The transform coefficients are determined using a <cite>least squares</cite> estimation method,
+ * with the assumption that source positions are exact and all the uncertainty is in the target positions.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.5
- * @version 0.5
+ * @version 0.8
  * @module
  *
+ * @see LocalizationGridBuilder
  * @see LinearTransform
  * @see Line
  * @see Plane
  */
-public class LinearTransformBuilder {
+public class LinearTransformBuilder extends TransformBuilder {
     /**
-     * The arrays of source ordinate values, for example (x[], y[]).
-     * This is {@code null} if not yet specified.
+     * Number of grid columns and rows, or {@code null} if the coordinates are not distributed on a regular grid.
+     * If the grid size is known, then the {@link #sources} coordinates do not need to be specified.
+     */
+    private final int[] gridSize;
+
+    /**
+     * The arrays of source ordinate values. Accessed with indices in that order: {@code sources[dimension][point]}.
+     * This layout allows to create only a few (typically two) large arrays instead of a multitude of small arrays.
+     * Example: {x[], y[]}.
+     *
+     * <p>In the special case where {@link #gridSize} is non-null, then this array does not need to be specified
+     * and can be {@code null}. In such case, the source coordinates are implicitly:</p>
+     *
+     * <blockquote>
+     * (0,0), (1,0), (2,0), (3,0) … ({@link #gridSize}[0]-1, 0),<br>
+     * (0,1), (1,1), (2,1), (3,1) … ({@link #gridSize}[0]-1, 1),<br>
+     * (0,2), (1,2), (2,2), (3,2) … ({@link #gridSize}[0]-1, 2),<br>
+     * (0,{@link #gridSize}[1]-1) … ({@link #gridSize}[0]-1, {@link #gridSize}[1]-1).
+     * </blockquote>
      */
     private double[][] sources;
 
     /**
-     * The arrays of target ordinate values, for example (x[], y[], z[]).
+     * The arrays of target ordinate values. Accessed with indices in that order: {@code targets[dimension][point]}.
+     * This layout allows to create only a few (typically two) large arrays instead of a multitude of small arrays.
+     * Example: {x[], y[], z[]}.
      * This is {@code null} if not yet specified.
      */
     private double[][] targets;
 
     /**
+     * The product of all {@link #gridSize} values, or 0 if none if {@link #gridSize} is null.
+     * If non-zero, then this is the length of {@link #targets} arrays to create.
+     */
+    final int gridLength;
+
+    /**
+     * Number of valid positions in the {@link #sources} or {@link #targets} arrays.
+     * Note that the "valid" positions may contain {@link Double#NaN} ordinate values.
+     * This field is only indicative if this {@code LinearTransformBuilder} instance
+     * has been created by {@link #LinearTransformBuilder(int...)}.
+     */
+    private int numPoints;
+
+    /**
      * The transform created by the last call to {@link #create()}.
+     * This is reset to {@code null} when coordinates are modified.
      */
-    private LinearTransform transform;
+    private transient LinearTransform transform;
 
     /**
      * An estimation of the Pearson correlation coefficient for each target dimension.
-     * This is {@code null} if not yet specified.
+     * This is {@code null} if not yet computed.
      */
-    private double[] correlation;
+    private transient double[] correlation;
 
     /**
-     * Creates a new linear transform builder.
+     * Creates a new linear transform builder for randomly distributed positions.
+     *
+     * <div class="note"><b>Tip:</b>
+     * if the source coordinates are grid indices, then
+     * the {@link #LinearTransformBuilder(int...)} constructor will create a more efficient builder.
+     * </div>
      */
     public LinearTransformBuilder() {
+        gridSize = null;
+        gridLength = 0;
+    }
+
+    /**
+     * Creates a new linear transform builder for source positions distributed on a regular grid.
+     * This constructor notifies {@code LinearTransformBuilder} that ordinate values of all source positions will
+     * be integers in the [0 … {@code gridSize[0]}-1] range for the first dimension (typically column indices),
+     * in the [0 … {@code gridSize[1]}-1] range for the second dimension (typically row indices), <i>etc.</i>
+     * The dimension of all source positions is the length of the given {@code gridSize} array.
+     *
+     * <p>An empty array is equivalent to invoking the no-argument constructor,
+     * i.e. no restriction is put on the source coordinates.</p>
+     *
+     * @param  gridSize  the number of integer ordinate values in each grid dimension.
+     * @throws IllegalArgumentException if a grid size is not strictly positive, or if the product
+     *         of all values (∏{@code gridSize}) is greater than {@link Integer#MAX_VALUE}.
+     *
+     * @since 0.8
+     */
+    public LinearTransformBuilder(int... gridSize) {
+        ArgumentChecks.ensureNonNull("gridSize", gridSize);
+        if (gridSize.length == 0) {
+            this.gridSize = null;
+            this.gridLength = 0;
+        } else {
+            gridSize = gridSize.clone();
+            long length = 1;
+            for (int s : gridSize) {
+                ArgumentChecks.ensureStrictlyPositive("gridSize", s);
+                length = JDK8.multiplyExact(length, s);
+            }
+            if (length > Integer.MAX_VALUE) {
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.ValueOutOfRange_4,
+                        "∏gridSize", 1, Integer.MAX_VALUE, length));
+            }
+            this.gridSize = gridSize;
+            gridLength = (int) length;
+        }
+    }
+
+    /**
+     * Returns the grid size for the given dimension. It is caller's responsibility to ensure that
+     * this method is invoked only on instances created by {@link #LinearTransformBuilder(int...)}.
+     */
+    final int gridSize(final int srcDim) {
+        return gridSize[srcDim];
+    }
+
+    /**
+     * Allocates memory for a builder created for source positions distributed on a grid.
+     * All target values need to be initialized to NaN because we can not rely on {@link #numPoints}.
+     *
+     * <p>If this builder has been created for randomly distributed source points, then the allocation
+     * should rather be performed as below:</p>
+     *
+     * {@preformat java
+     *    sources = new double[srcDim][capacity];
+     *    targets = new double[tgtDim][capacity];
+     * }
+     */
+    private void allocate(final int tgtDim) {
+        targets = new double[tgtDim][gridLength];
+        for (final double[] r : targets) {
+            Arrays.fill(r, Double.NaN);
+        }
+    }
+
+    /**
+     * Resize all the given arrays to the given capacity. This method should be invoked only for
+     * {@code LinearTransformBuilder} instances created for randomly distributed source positions.
+     */
+    private static void resize(double[][] data, final int capacity) {
+        for (int i=0; i<data.length; i++) {
+            data[i] = ArraysExt.resize(data[i], capacity);
+        }
+    }
+
+    /**
+     * Returns the offset of the given source grid coordinate, or -1 if none. The algorithm implemented in this
+     * method is inefficient, but should rarely be used. This is only a fallback when {@link #flatIndex(int[])}
+     * can not be used.
+     */
+    private int search(final int[] source) {
+        assert gridSize == null;         // This method should not be invoked for points distributed on a grid.
+search: for (int j=0; j<numPoints; j++) {
+            for (int i=0; i<source.length; i++) {
+                if (source[i] != sources[i][j]) {
+                    continue search;                            // Search another position for the same source.
+                }
+            }
+            return j;
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the offset where to store a target position for the given source position in the flattened array.
+     * This method should be invoked only when this {@code LinearTransformBuilder} has been created for a grid
+     * of known size. Caller must have verified the array length before to invoke this method.
+     *
+     * @throws IllegalArgumentException if an ordinate value is illegal.
+     */
+    private int flatIndex(final int[] source) {
+        assert sources == null;               // This method should not be invoked for randomly distributed points.
+        int offset = 0;
+        for (int i = gridSize.length; i != 0;) {
+            final int size = gridSize[--i];
+            final int index = source[i];
+            if (index < 0 || index >= size) {
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.ValueOutOfRange_4, "source", 0, size-1, index));
+            }
+            offset = offset * size + index;
+        }
+        return offset;
+    }
+
+    /**
+     * Returns the index where to store a target position for the given source position in the flattened array.
+     * This method should be invoked only when this {@code LinearTransformBuilder} has been created for a grid
+     * of known size. Callers must have verified the position dimension before to invoke this method.
+     *
+     * @throws IllegalArgumentException if an ordinate value is illegal.
+     */
+    private int flatIndex(final DirectPosition source) {
+        assert sources == null;               // This method should not be invoked for randomly distributed points.
+        int offset = 0;
+        for (int i = gridSize.length; i != 0;) {
+            final int size = gridSize[--i];
+            final double ordinate = source.getOrdinate(i);
+            final int index = (int) ordinate;
+            if (index != ordinate) {
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.NotAnInteger_1, ordinate));
+            }
+            if (index < 0 || index >= size) {
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.ValueOutOfRange_4, "source", 0, size-1, index));
+            }
+            offset = offset * size + index;
+        }
+        return offset;
+    }
+
+    /**
+     * Verifies that the given number of dimensions is equal to the expected value.
+     * No verification are done if the source point is the first point of randomly distributed points.
+     */
+    private void verifySourceDimension(final int actual) {
+        final int expected;
+        if (gridSize != null) {
+            expected = gridSize.length;
+        } else if (sources != null) {
+            expected = sources.length;
+        } else {
+            return;
+        }
+        if (actual != expected) {
+            throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3, "source", expected, actual));
+        }
+    }
+
+    /**
+     * Builds the exception message for an unexpected position dimension. This method assumes
+     * that positions are stored in this builder as they are read from user-provided collection.
+     */
+    private String mismatchedDimension(final String name, final int expected, final int actual) {
+        return Errors.format(Errors.Keys.MismatchedDimension_3, name + '[' + numPoints + ']', expected, actual);
+    }
+
+    /**
+     * Returns the error message to be given to {@link IllegalStateException} when there is no data.
+     */
+    private static String noData() {
+        return Errors.format(Errors.Keys.MissingValueForProperty_1, "sourceToTarget");
+    }
+
+    /**
+     * Returns the number of dimensions in source positions.
+     *
+     * @return the dimension of source points.
+     * @throws IllegalStateException if the number of source dimensions is not yet known.
+     *
+     * @see LinearTransform#getSourceDimensions()
+     *
+     * @since 0.8
+     */
+    public int getSourceDimensions() {
+        if (gridSize != null) return gridSize.length;
+        if (sources  != null) return sources.length;
+        throw new IllegalStateException(noData());
     }
 
     /**
-     * Extracts the ordinate values of the given points into separated arrays, one for each dimension.
+     * Returns the number of dimensions in target positions.
      *
-     * @param  points     the points from which to extract the ordinate values.
-     * @param  dimension  the expected number of dimensions.
+     * @return the dimension of target points.
+     * @throws IllegalStateException if the number of target dimensions is not yet known.
+     *
+     * @see LinearTransform#getTargetDimensions()
+     *
+     * @since 0.8
+     */
+    public int getTargetDimensions() {
+        if (targets != null) return targets.length;
+        throw new IllegalStateException(noData());
+    }
+
+    /**
+     * Returns the direct position of the given position, or {@code null} if none.
+     */
+    private static DirectPosition position(final Position p) {
+        return (p != null) ? p.getDirectPosition() : null;
+    }
+
+    /**
+     * Sets all matching control point pairs, overwriting any previous setting. The source positions are the keys in
+     * the given map, and the target positions are the associated values in the map. The map should not contain two
+     * entries with the same source position. Coordinate reference systems are ignored.
+     * Null positions are silently ignored.
+     *
+     * <p>All source positions shall have the same number of dimensions (the <cite>source dimension</cite>),
+     * and all target positions shall have the same number of dimensions (the <cite>target dimension</cite>).
+     * However the source dimension does not need to be the same the target dimension.
+     * Apache SIS currently supports only one- or two-dimensional source positions,
+     * together with arbitrary target dimension.</p>
+     *
+     * <p>If this builder has been created with the {@link #LinearTransformBuilder(int...)} constructor,
+     * then the ordinate values of all source positions shall be integers in the [0 … {@code gridSize[0]}-1]
+     * range for the first dimension (typically column indices), in the [0 … {@code gridSize[1]}-1] range for
+     * the second dimension (typically row indices), <i>etc</i>. This constraint does not apply for builders
+     * created with the {@link #LinearTransformBuilder()} constructor.</p>
+     *
+     * @param  sourceToTarget  a map of source positions to target positions.
+     *         Source positions are assumed precise and target positions are assumed uncertain.
+     * @throws IllegalArgumentException if this builder has been {@linkplain #LinearTransformBuilder(int...)
+     *         created for a grid} but some source ordinates are not indices in that grid.
+     * @throws MismatchedDimensionException if some positions do not have the expected number of dimensions.
+     *
+     * @since 0.8
+     */
+    public void setControlPoints(final Map<? extends Position, ? extends Position> sourceToTarget)
+            throws MismatchedDimensionException
+    {
+        ArgumentChecks.ensureNonNull("sourceToTarget", sourceToTarget);
+        pendingSources = null;
+        pendingTargets = null;
+        transform   = null;
+        correlation = null;
+        sources     = null;
+        targets     = null;
+        numPoints   = 0;
+        int srcDim  = 0;
+        int tgtDim  = 0;
+        for (final Map.Entry<? extends Position, ? extends Position> entry : sourceToTarget.entrySet()) {
+            final DirectPosition src = position(entry.getKey());   if (src == null) continue;
+            final DirectPosition tgt = position(entry.getValue()); if (tgt == null) continue;
+            /*
+             * The first time that we get a non-null source and target coordinate, allocate the arrays.
+             * The sources arrays are allocated only if the source coordiantes are randomly distributed.
+             */
+            if (targets == null) {
+                tgtDim = tgt.getDimension();
+                if (tgtDim <= 0) {
+                    throw new MismatchedDimensionException(mismatchedDimension("target", 2, tgtDim));
+                }
+                if (gridSize == null) {
+                    srcDim = src.getDimension();
+                    if (srcDim <= 0) {
+                        throw new MismatchedDimensionException(mismatchedDimension("source", 2, srcDim));
+                    }
+                    final int capacity = sourceToTarget.size();
+                    sources = new double[srcDim][capacity];
+                    targets = new double[tgtDim][capacity];
+                } else {
+                    srcDim = gridSize.length;
+                    allocate(tgtDim);
+                }
+            }
+            /*
+             * Verify that the source and target coordinates have the expected number of dimensions before to store
+             * the coordinates. If the grid size is known, we do not need to store the source coordinates. Instead,
+             * we compute its index in the fixed-size target arrays.
+             */
+            int d;
+            if ((d = src.getDimension()) != srcDim) throw new MismatchedDimensionException(mismatchedDimension("source", srcDim, d));
+            if ((d = tgt.getDimension()) != tgtDim) throw new MismatchedDimensionException(mismatchedDimension("target", tgtDim, d));
+            int index;
+            if (gridSize != null) {
+                index = flatIndex(src);
+            } else {
+                index = numPoints;
+                for (int i=0; i<srcDim; i++) {
+                    sources[i][index] = src.getOrdinate(i);
+                }
+            }
+            for (int i=0; i<tgtDim; i++) {
+                targets[i][index] = tgt.getOrdinate(i);
+            }
+            numPoints++;
+        }
+    }
+
+    /**
+     * Sets a single matching control point pair. Source position is assumed precise and target position is assumed uncertain.
+     * If the given source position was already associated with another target position, then the old target position is discarded.
+     *
+     * <div class="note"><b>Performance note:</b>
+     * current implementation is efficient for builders {@linkplain #LinearTransformBuilder(int...) created for a grid}
+     * but inefficient for builders {@linkplain #LinearTransformBuilder() created for randomly distributed points}.
+     * In the later case, the {@link #setControlPoints(Map)} method is a more efficient alternative.</div>
+     *
+     * @param  source  the source coordinates. If this builder has been created with the {@link #LinearTransformBuilder(int...)} constructor,
+     *                 then for every index <var>i</var> the {@code source[i]} value shall be in the [0 … {@code gridSize[i]}-1] range inclusive.
+     *                 If this builder has been created with the {@link #LinearTransformBuilder()} constructor, then no constraint apply.
+     * @param  target  the target coordinates, assumed uncertain.
+     * @throws IllegalArgumentException if this builder has been {@linkplain #LinearTransformBuilder(int...) created for a grid}
+     *         but some source ordinates are out of index range.
+     * @throws MismatchedDimensionException if the source or target position does not have the expected number of dimensions.
+     *
+     * @since 0.8
      */
-    private static double[][] toArrays(final DirectPosition[] points, final int dimension) {
-        final int length = points.length;
-        final double[][] ordinates = new double[dimension][length];
-        for (int j=0; j<length; j++) {
-            final DirectPosition p = points[j];
-            final int d = p.getDimension();
-            if (d != dimension) {
-                throw new MismatchedDimensionException(Errors.format(
-                        Errors.Keys.MismatchedDimension_3, "points[" + j + ']', dimension, d));
+    public void setControlPoint(final int[] source, final double[] target) {
+        ArgumentChecks.ensureNonNull("source", source);
+        ArgumentChecks.ensureNonNull("target", target);
+        verifySourceDimension(source.length);
+        final int tgtDim = target.length;
+        if (targets != null && tgtDim != targets.length) {
+            throw new MismatchedDimensionException(Errors.format(
+                    Errors.Keys.MismatchedDimension_3, "target", targets.length, tgtDim));
+        }
+        int index;
+        if (gridSize != null) {
+            index = flatIndex(source);        // Invoked first for validating argument before to allocate arrays.
+            if (targets == null) {
+                allocate(tgtDim);
             }
-            for (int i=0; i<dimension; i++) {
-                ordinates[i][j] = p.getOrdinate(i);
+        } else {
+            /*
+             * Case of randomly distributed points. Algorithm used below is inefficient, but Javadoc
+             * warns the user that (s)he should use setControlPoints(Map) instead in such case.
+             */
+            final int srcDim = source.length;
+            if (targets == null) {
+                targets = new double[tgtDim][20];                   // Arbitrary initial capacity of 20 points.
+                sources = new double[srcDim][20];
+            }
+            index = search(source);
+            if (index < 0) {
+                index = numPoints++;
+                if (numPoints >= targets[0].length) {
+                    final int n = JDK8.multiplyExact(numPoints, 2);
+                    resize(sources, n);
+                    resize(targets, n);
+                }
+            }
+            for (int i=0; i<srcDim; i++) {
+                sources[i][index] = source[i];
+            }
+        }
+        for (int i=0; i<tgtDim; i++) {
+            targets[i][index] = target[i];
+        }
+    }
+
+    /**
+     * Returns a single target coordinate for the given source coordinate, or {@code null} if none.
+     * This method can be used for retrieving points set by previous calls to
+     * {@link #setControlPoint(int[], double[])} or {@link #setControlPoints(Map)}.
+     *
+     * <div class="note"><b>Performance note:</b>
+     * current implementation is efficient for builders {@linkplain #LinearTransformBuilder(int...) created for a grid}
+     * but inefficient for builders {@linkplain #LinearTransformBuilder() created for randomly distributed points}.</div>
+     *
+     * @param  source  the source coordinates. If this builder has been created with the {@link #LinearTransformBuilder(int...)} constructor,
+     *                 then for every index <var>i</var> the {@code source[i]} value shall be in the [0 … {@code gridSize[i]}-1] range inclusive.
+     *                 If this builder has been created with the {@link #LinearTransformBuilder()} constructor, then no constraint apply.
+     * @return the target coordinates associated to the given source, or {@code null} if none.
+     * @throws IllegalArgumentException if this builder has been {@linkplain #LinearTransformBuilder(int...) created for a grid}
+     *         but some source ordinates are out of index range.
+     * @throws MismatchedDimensionException if the source position does not have the expected number of dimensions.
+     *
+     * @since 0.8
+     */
+    public double[] getControlPoint(final int[] source) {
+        processPendings();
+        ArgumentChecks.ensureNonNull("source", source);
+        verifySourceDimension(source.length);
+        if (targets == null) {
+            return null;
+        }
+        final int index;
+        if (gridSize != null) {
+            index = flatIndex(source);
+        } else {
+            index = search(source);
+            if (index < 0) {
+                return null;
             }
         }
-        return ordinates;
+        boolean isNaN = true;
+        final double[] target = new double[targets.length];
+        for (int i=0; i<target.length; i++) {
+            isNaN &= Double.isNaN(target[i] = targets[i][index]);
+        }
+        return isNaN ? null : target;
+    }
+
+    /**
+     * More straightforward version of {@link #getControlPoint(int[])} for the case where this
+     * {@code LinearTransformBuilder} is known to have been built for grid source coordinates.
+     * This method is for {@link LocalizationGridBuilder#create()} internal usage.
+     */
+    final void getControlPoint2D(final int[] source, final double[] target) {
+        assert gridSize != null;
+        final int index = flatIndex(source);
+        final int tgtDim = targets.length;
+        for (int i=0; i<tgtDim; i++) {
+            target[i] = targets[i][index];
+        }
     }
 
     /**
-     * Sets the source points. The number of points shall be the same than the number of target points.
+     * Sets the source points, overwriting any previous setting. The number of source points will need to be the same
+     * than the number of {@linkplain #setTargetPoints target points} when the {@link #create()} method will be invoked.
+     * In current Apache SIS implementation, the source points must be one or two-dimensional.
      *
-     * <p><b>Limitation:</b> in current implementation, the source points must be one or two-dimensional.
-     * This restriction may be removed in a future SIS version.</p>
+     * <p>If this builder has been created with the {@link #LinearTransformBuilder(int, int)} constructor,
+     * then all given points must be two-dimensional and all ordinate values must be integers in the
+     * [0 … <var>width</var>-1] or [0 … <var>height</var>-1] range for the first and second dimension
+     * respectively. This constraint does not apply if this builder has been created with the
+     * {@link #LinearTransformBuilder()} constructor.</p>
+     *
+     * <p>It is caller's responsibility to ensure that no source point is duplicated.
+     * If the same source point is repeated twice, then {@code LinearTransformBuilder} behavior is undefined.</p>
      *
      * @param  points  the source points, assumed precise.
      * @throws MismatchedDimensionException if at least one point does not have the expected number of dimensions.
+     *
+     * @deprecated Replaced by {@link #setControlPoints(Map)}.
      */
+    @Deprecated
     public void setSourcePoints(final DirectPosition... points) throws MismatchedDimensionException {
         ArgumentChecks.ensureNonNull("points", points);
-        if (points.length != 0) {
-            sources = toArrays(points, points[0].getDimension() == 1 ? 1 : 2);
-        } else {
-            sources = null;
-        }
         transform   = null;
         correlation = null;
+        sources     = null;
+        targets     = null;
+        numPoints   = 0;
+        pendingSources = points.clone();
     }
 
     /**
-     * Sets the target points. The number of points shall be the same than the number of source points.
+     * Sets the target points, overwriting any previous setting. The number of target points will need to be the same
+     * than the number of {@linkplain #setSourcePoints source points} when the {@link #create()} method will be invoked.
      * Target points can have any number of dimensions (not necessarily 2), but all points shall have
      * the same number of dimensions.
      *
      * @param  points  the target points, assumed uncertain.
      * @throws MismatchedDimensionException if not all points have the same number of dimensions.
+     *
+     * @deprecated Replaced by {@link #setControlPoints(Map)}.
      */
+    @Deprecated
     public void setTargetPoints(final DirectPosition... points) throws MismatchedDimensionException {
         ArgumentChecks.ensureNonNull("points", points);
-        if (points.length != 0) {
-            targets = toArrays(points, points[0].getDimension());
-        } else {
-            targets = null;
-        }
         transform   = null;
         correlation = null;
+        sources     = null;
+        targets     = null;
+        numPoints   = 0;
+        pendingTargets = points.clone();
     }
 
-    /*
-     * No getters yet because we did not determined what they should return.
-     * Array? Collection? Map<source,target>?
-     */
+    @Deprecated
+    private transient DirectPosition[] pendingSources, pendingTargets;
+
+    @Deprecated
+    private void processPendings() {
+        if (pendingSources != null || pendingTargets != null) {
+            if (pendingSources == null || pendingTargets == null) {
+                throw new IllegalStateException(Errors.format(
+                        Errors.Keys.MissingValueForProperty_1, (pendingSources == null) ? "sources" : "targets"));
+            }
+            final int length = pendingSources.length;
+            if (pendingTargets.length != length) {
+                throw new IllegalStateException(Errors.format(Errors.Keys.MismatchedArrayLengths));
+            }
+            final Map<DirectPosition,DirectPosition> sourceToTarget = new java.util.HashMap<>(length);
+            for (int i=0; i<length; i++) {
+                sourceToTarget.put(pendingSources[i], pendingTargets[i]);
+            }
+            setControlPoints(sourceToTarget);
+        }
+    }
 
     /**
-     * Creates a linear transform approximation from the source points to the target points.
-     * This method assumes that source points are precise and all uncertainty is in the target points.
+     * Creates a linear transform approximation from the source positions to the target positions.
+     * This method assumes that source positions are precise and that all uncertainty is in the target positions.
      *
      * @return the fitted linear transform.
+     *
+     * @deprecated Replaced by {@link #create(MathTransformFactory)}.
      */
+    @Deprecated
     public LinearTransform create() {
+        try {
+            return create(null);
+        } catch (FactoryException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Creates a linear transform approximation from the source positions to the target positions.
+     * This method assumes that source positions are precise and that all uncertainty is in the target positions.
+     *
+     * @param  factory  the factory to use for creating the transform, or {@code null} for the default factory.
+     *                  The {@link MathTransformFactory#createAffineTransform(Matrix)} method of that factory
+     *                  shall return {@link LinearTransform} instances.
+     * @return the fitted linear transform.
+     * @throws FactoryException if the transform can not be created,
+     *         for example because the source or target points have not be specified.
+     *
+     * @since 0.8
+     */
+    @Override
+    @SuppressWarnings("serial")
+    public LinearTransform create(final MathTransformFactory factory) throws FactoryException {
         if (transform == null) {
+            processPendings();
             final double[][] sources = this.sources;                    // Protect from changes.
             final double[][] targets = this.targets;
-            if (sources == null || targets == null) {
-                throw new IllegalStateException(Errors.format(
-                        Errors.Keys.MissingValueForProperty_1, (sources == null) ? "sources" : "targets"));
+            if (targets == null) {
+                throw new FactoryException(noData());
             }
-            final int sourceDim = sources.length;
+            final int sourceDim = (sources != null) ? sources.length : gridSize.length;
             final int targetDim = targets.length;
             correlation = new double[targetDim];
-            final MatrixSIS matrix = Matrices.createZero(targetDim + 1, sourceDim + 1);
+            final MatrixSIS matrix = Matrices.create(targetDim + 1, sourceDim + 1,  ExtendedPrecisionMatrix.ZERO);
             matrix.setElement(targetDim, sourceDim, 1);
-            switch (sourceDim) {
-                case 1: {
-                    final Line line = new Line();
-                    for (int j=0; j < targets.length; j++) {
-                        correlation[j] = line.fit(sources[0], targets[j]);
-                        matrix.setElement(j, 0, line.slope());
-                        matrix.setElement(j, 1, line.y0());
+            for (int j=0; j < targetDim; j++) {
+                final double c;
+                switch (sourceDim) {
+                    case 1: {
+                        final int row = j;
+                        final Line line = new Line() {
+                            @Override public void setEquation(final Number slope, final Number y0) {
+                                super.setEquation(slope, y0);
+                                matrix.setNumber(row, 0, slope);    // Preserve the extended precision (double-double).
+                                matrix.setNumber(row, 1, y0);
+                            }
+                        };
+                        if (sources != null) {
+                            c = line.fit(vector(sources[0]), vector(targets[j]));
+                        } else {
+                            c = line.fit(Vector.createSequence(0, 1, gridSize[0]),
+                                         Vector.create(targets[j], false));
+                        }
+                        break;
                     }
-                    break;
-                }
-                case 2: {
-                    final Plane plan = new Plane();
-                    for (int j=0; j < targets.length; j++) {
-                        correlation[j] = plan.fit(sources[0], sources[1], targets[j]);
-                        matrix.setElement(j, 0, plan.slopeX());
-                        matrix.setElement(j, 1, plan.slopeY());
-                        matrix.setElement(j, 2, plan.z0());
+                    case 2: {
+                        final int row = j;
+                        final Plane plan = new Plane() {
+                            @Override public void setEquation(final Number sx, final Number sy, final Number z0) {
+                                super.setEquation(sx, sy, z0);
+                                matrix.setNumber(row, 0, sx);       // Preserve the extended precision (double-double).
+                                matrix.setNumber(row, 1, sy);
+                                matrix.setNumber(row, 2, z0);
+                            }
+                        };
+                        if (sources != null) {
+                            c = plan.fit(vector(sources[0]), vector(sources[1]), vector(targets[j]));
+                        } else try {
+                            c = plan.fit(gridSize[0], gridSize[1], Vector.create(targets[j], false));
+                        } catch (IllegalArgumentException e) {
+                            // This may happen if the z vector still contain some "NaN" values.
+                            throw new FactoryException(noData(), e);
+                        }
+                        break;
+                    }
+                    default: {
+                        throw new FactoryException(Errors.format(Errors.Keys.ExcessiveNumberOfDimensions_1, sourceDim));
                     }
-                    break;
                 }
-                default: throw new AssertionError(sourceDim);   // Should have been verified by setSourcePoints(…) method.
+                correlation[j] = c;
             }
-            transform = MathTransforms.linear(matrix);
+            transform = (LinearTransform) nonNull(factory).createAffineTransform(matrix);
         }
         return transform;
     }
 
     /**
+     * Wraps the given array in a vector of length {@link #numPoints}. This method should be
+     * invoked only when this builder has been created by {@link #LinearTransformBuilder()}.
+     * This can be identified by {@code sources != null} or {@code gridSize == null}.
+     */
+    private Vector vector(final double[] data) {
+        assert gridSize == null;
+        return Vector.create(data, false).subList(0, numPoints);
+    }
+
+    /**
      * Returns the correlation coefficients of the last transform created by {@link #create()},
      * or {@code null} if none. If non-null, the array length is equals to the number of target
      * dimensions.
@@ -227,13 +761,9 @@ public class LinearTransformBuilder {
             buffer.append(':').append(lineSeparator);
             final TableAppender table = new TableAppender(buffer, " ");
             table.setMultiLinesCells(true);
-            table.append(Matrices.toString(transform.getMatrix()));
-            table.nextColumn();
-            table.append(lineSeparator);
-            table.append("  ");
-            table.append(Vocabulary.format(Vocabulary.Keys.Correlation));
-            table.append(" =");
-            table.nextColumn();
+            table.append(Matrices.toString(transform.getMatrix())).nextColumn();
+            table.append(lineSeparator).append("  ")
+                 .append(Vocabulary.format(Vocabulary.Keys.Correlation)).append(" =").nextColumn();
             table.append(Matrices.create(correlation.length, 1, correlation).toString());
             try {
                 table.flush();

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/package-info.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/package-info.java?rev=1788983&r1=1788982&r2=1788983&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/package-info.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/package-info.java [UTF-8] Mon Mar 27 16:38:00 2017
@@ -31,7 +31,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.5
- * @since   0.5
+ * @since   0.8
  * @module
  */
 package org.apache.sis.referencing.operation.builder;

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrices.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrices.java?rev=1788983&r1=1788982&r2=1788983&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrices.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrices.java [UTF-8] Mon Mar 27 16:38:00 2017
@@ -68,7 +68,7 @@ import org.apache.sis.internal.referenci
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @since   0.4
- * @version 0.7
+ * @version 0.8
  * @module
  *
  * @see org.apache.sis.parameter.TensorParameters
@@ -203,8 +203,12 @@ public final class Matrices extends Stat
      */
     public static MatrixSIS create(final int numRow, final int numCol, final Number[] elements) {
         ArgumentChecks.ensureNonNull("elements", elements);
-        if (elements == ExtendedPrecisionMatrix.IDENTITY) { // Intentionally undocumented features.
-            return GeneralMatrix.createExtendedPrecision(numRow, numCol, true);
+        /*
+         * Below is an intantionally undocumented feature. We use those sentinel values as a way to create
+         * matrices with extended precision without exposing our double-double arithmetic in public API.
+         */
+        if (elements == ExtendedPrecisionMatrix.IDENTITY || elements == ExtendedPrecisionMatrix.ZERO) {
+            return GeneralMatrix.createExtendedPrecision(numRow, numCol, elements == ExtendedPrecisionMatrix.IDENTITY);
         }
         final GeneralMatrix matrix = GeneralMatrix.createExtendedPrecision(numRow, numCol, false);
         if (matrix.setElements(elements)) {

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/MatrixSIS.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/MatrixSIS.java?rev=1788983&r1=1788982&r2=1788983&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/MatrixSIS.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/MatrixSIS.java [UTF-8] Mon Mar 27 16:38:00 2017
@@ -56,7 +56,7 @@ import org.apache.sis.util.resources.Err
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @since   0.4
- * @version 0.7
+ * @version 0.8
  * @module
  *
  * @see Matrices
@@ -556,6 +556,46 @@ public abstract class MatrixSIS implemen
     }
 
     /**
+     * Returns a new vector which is the result of multiplying this matrix with the specified vector.
+     * In other words, returns {@code this} × {@code vector}. The length of the given vector must be
+     * equal to the number of columns in this matrix, and the length of the returned vector will be
+     * equal to the number of rows in this matrix.
+     *
+     * <div class="section">Relationship with coordinate operations</div>
+     * In the context of coordinate operations, {@code Matrix.multiply(vector)} is related to
+     * <code>{@linkplain AffineTransform#transform(double[], int, double[], int, int) AffineTransform.transform}(…)</code>
+     * except that the last {@code vector} number is implicitly 1 in {@code AffineTransform} operations.
+     * While this {@code multiply(double[])} method could be used for coordinate transformation, it is not its purpose.
+     * This method is designed for occasional uses when accuracy is more important than performance.
+     *
+     * @param  vector  the vector to multiply to this matrix.
+     * @return the result of {@code this} × {@code vector}.
+     * @throws MismatchedMatrixSizeException if the length of the given vector is not equals to the
+     *         number of columns in this matrix.
+     *
+     * @since 0.8
+     */
+    public double[] multiply(final double[] vector) {
+        final int numCol = getNumCol();
+        if (vector.length != numCol) {
+            throw new MismatchedMatrixSizeException(Errors.format(Errors.Keys.UnexpectedArrayLength_2,  numCol, vector.length));
+        }
+        final double[] target = new double[getNumRow()];
+        final DoubleDouble ele = new DoubleDouble();
+        final DoubleDouble sum = new DoubleDouble();
+        for (int j=0; j<target.length; j++) {
+            for (int i=0; i<numCol; i++) {
+                get(j, i, ele);
+                ele.multiply(vector[i]);
+                sum.add(ele);
+            }
+            target[j] = sum.value;
+            sum.clear();
+        }
+        return target;
+    }
+
+    /**
      * Returns the value of <var>U</var> which solves {@code this} × <var>U</var> = {@code matrix}.
      * This is equivalent to first computing the inverse of {@code this}, then multiplying the result
      * by the given matrix.

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/package-info.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/package-info.java?rev=1788983&r1=1788982&r2=1788983&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/package-info.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/package-info.java [UTF-8] Mon Mar 27 16:38:00 2017
@@ -70,7 +70,7 @@
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @since   0.4
- * @version 0.6
+ * @version 0.8
  * @module
  */
 package org.apache.sis.referencing.operation.matrix;

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ZonedGridSystem.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ZonedGridSystem.java?rev=1788983&r1=1788982&r2=1788983&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ZonedGridSystem.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ZonedGridSystem.java [UTF-8] Mon Mar 27 16:38:00 2017
@@ -55,6 +55,9 @@ import org.opengis.parameter.ParameterVa
  * current implementation can only be backed by the Transverse Mercator projection,
  * but future versions could apply to some other projections if needed.</div>
  *
+ * <p>Examples of CRS using this projection are <cite>WGS 84 / UTM grid system</cite>
+ * EPSG:32600 (northern hemisphere) and EPSG:32700 (southern hemisphere).</p>
+ *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.8
  * @version 0.8

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedTransform.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedTransform.java?rev=1788983&r1=1788982&r2=1788983&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedTransform.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedTransform.java [UTF-8] Mon Mar 27 16:38:00 2017
@@ -59,7 +59,7 @@ import org.apache.sis.internal.referenci
  * 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>
+ * then the translations are extrapolated. The translation is finally 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}.
@@ -72,6 +72,9 @@ import org.apache.sis.internal.referenci
  * @since   0.7
  * @version 0.8
  * @module
+ *
+ * @see DatumShiftGrid
+ * @see org.apache.sis.referencing.operation.builder.LocalizationGridBuilder
  */
 public class InterpolatedTransform extends DatumShiftTransform {
     /**
@@ -148,7 +151,7 @@ public class InterpolatedTransform exten
             ((DatumShiftGridFile<?,?>) grid).setFileParameters(context);
         }
         /*
-         * Set the normalization matrix to the conversion from grid coordinates (e.g. seconds of angle)
+         * Set the normalization matrix to the conversion from source 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.
          */
@@ -177,9 +180,20 @@ public class InterpolatedTransform exten
             }
         }
         /*
-         * Denormalization is the inverse of all above conversions.
+         * Denormalization is the inverse of all above conversions in the usual case (NADCON and NTv2) where the
+         * source coordinate system is the same than the target coordinate system, for example with axis unit in
+         * degrees. However we also use this InterpolatedTransform implementation for other operation, like the
+         * one created by LocalizationGridBuilder. Those later operations may require a different denormalization
+         * matrix.
          */
-        context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION).setMatrix(normalize.inverse());
+        Matrix denormalize = null;
+        if (grid instanceof DatumShiftGridFile<?,?>) {
+            denormalize = ((DatumShiftGridFile<?,?>) grid).gridToTarget();
+        }
+        if (denormalize == null) {
+            denormalize = normalize.inverse();                      // Normal NACDON and NTv2 case.
+        }
+        context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION).setMatrix(denormalize);
         inverse = createInverse();
     }
 



Mime
View raw message