sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 02/02: WKT formatting of a MathTransform backed by normalization grid should show some elements from the grid.
Date Tue, 05 Feb 2019 18:24:50 GMT
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 9611edc8f994d2cb006bb20eceaa5a08c803a5d3
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Tue Feb 5 14:55:40 2019 +0100

    WKT formatting of a MathTransform backed by normalization grid should show some elements from the grid.
---
 .../main/java/org/apache/sis/io/wkt/Formatter.java |  43 +++---
 .../main/java/org/apache/sis/io/wkt/Symbols.java   |  15 ++
 .../sis/geometry/AbstractDirectPosition.java       |   5 +-
 .../org/apache/sis/geometry/AbstractEnvelope.java  |   8 +-
 .../sis/internal/referencing/WKTUtilities.java     | 103 +++++++++++++-
 .../referencing/provider/DatumShiftGridFile.java   |  18 +--
 .../operation/builder/ResidualGrid.java            | 155 ++++++++++++++++++++-
 .../transform/InterpolatedGeocentricTransform.java |   2 +-
 .../transform/InterpolatedMolodenskyTransform.java |   2 +-
 .../operation/transform/InterpolatedTransform.java |  10 +-
 .../org/apache/sis/geometry/ArrayEnvelopeTest.java |  10 +-
 .../apache/sis/geometry/GeneralEnvelopeTest.java   |  14 ++
 .../sis/internal/referencing/WKTUtilitiesTest.java |  29 +++-
 .../org/apache/sis/internal/util/Numerics.java     |  70 +++++++---
 .../java/org/apache/sis/math/StatisticsFormat.java |  29 ++--
 .../org/apache/sis/internal/util/NumericsTest.java |  14 +-
 16 files changed, 429 insertions(+), 98 deletions(-)

diff --git a/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java b/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java
index 033c40d..cef2fa3 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java
@@ -82,6 +82,7 @@ import org.apache.sis.measure.Range;
 import org.apache.sis.measure.MeasurementRange;
 import org.apache.sis.metadata.iso.ImmutableIdentifier;
 import org.apache.sis.metadata.iso.extent.Extents;
+import org.apache.sis.math.Vector;
 
 
 /**
@@ -1190,7 +1191,6 @@ public class Formatter implements Localized {
     public void append(final double number) {
         appendSeparator();
         setColor(ElementKind.NUMBER);
-        final double abs = Math.abs(number);
         /*
          * Use scientific notation if the number magnitude is too high or too low. The threshold values used here
          * may be different than the threshold values used in the standard 'StringBuilder.append(double)' method.
@@ -1201,7 +1201,7 @@ public class Formatter implements Localized {
          * Note that we perform this special formatting only if the 'NumberFormat' is not localized
          * (which is the usual case).
          */
-        if (Symbols.SCIENTIFIC_NOTATION && (abs < 1E-3 || abs >= 1E+9) && symbols.getLocale() == Locale.ROOT) {
+        if (symbols.useScientificNotation(Math.abs(number))) {
             buffer.append(number);
         } else {
             /*
@@ -1221,17 +1221,19 @@ public class Formatter implements Localized {
 
     /**
      * Appends rows of numbers. Each number is separated by a space, and each row is separated by a comma.
-     * This method is mostly for formatting geometries, but it could be used for other objects like matrix
-     * as well. Each row usually have the same length, but this is not mandatory.
+     * Rows usually have all the same length, but this is not mandatory.
+     * This method can be used for formatting geometries or matrix.
      *
-     * @param  rows            rows to append, or {@code null} if none.
-     * @param  fractionDigits  the number of fraction digits for each number in a row, or {@code null} for default.
-     *         If a row contains more numbers than {@code fractionDigits.length}, then the last value in this array
-     *         is repeated for all remaining row numbers.
+     * @param  rows            the rows to append, or {@code null} if none.
+     * @param  fractionDigits  the number of fraction digits for each column in a row, or {@code null} for default.
+     *         A precision can be specified for each column because those columns are often different dimensions of
+     *         a Coordinate Reference System (CRS), each with their own units of measurement.
+     *         If a row contains more numbers than {@code fractionDigits.length},
+     *         then the last value in this array is repeated for all remaining row numbers.
      *
      * @since 1.0
      */
-    public void append(final double[][] rows, int... fractionDigits) {
+    public void append(final Vector[] rows, int... fractionDigits) {
         if (rows == null || rows.length == 0) {
             return;
         }
@@ -1245,7 +1247,8 @@ public class Formatter implements Localized {
          * row starts on the same line than previous elements (or the keyword of this element, e.g. "BOX["), then we
          * will need a different amount of spaces if we want to have the numbers properly aligned.
          */
-        final boolean isMultiLines = (indentation > WKTFormat.SINGLE_LINE) && (rows.length > 1);
+        final int numRows = rows.length;
+        final boolean isMultiLines = (indentation > WKTFormat.SINGLE_LINE) && (numRows > 1);
         final boolean needsAlignment = !requestNewLine;
         final CharSequence marginBeforeRow;
         if (isMultiLines) {
@@ -1270,19 +1273,20 @@ public class Formatter implements Localized {
          * We compute those marks unconditionally for simplicity, but will ignore them if formatting on
          * a single line.
          */
-        final int[][] formattedNumberMarks = new int[rows.length][];
+        final int[][] formattedNumberMarks = new int[numRows][];
         int numColumns = 0;
-        for (int j=0; j<rows.length; j++) {
+        for (int j=0; j<numRows; j++) {
             if (j == 0) {
                 appendSeparator();      // It is up to the caller to decide if we begin with a new line.
             } else {
                 buffer.append(separatorNewLine).append(marginBeforeRow);
             }
-            final double[] numbers = rows[j];
-            numColumns = Math.max(numColumns, numbers.length);      // Store the length of longest row.
-            final int[] marks = new int[numbers.length << 1];       // Positions where numbers are formatted.
+            final Vector numbers = rows[j];
+            final int numCols = numbers.size();
+            numColumns = Math.max(numColumns, numCols);      // Store the length of longest row.
+            final int[] marks = new int[numCols << 1];       // Positions where numbers are formatted.
             formattedNumberMarks[j] = marks;
-            for (int i=0; i<numbers.length; i++) {
+            for (int i=0; i<numCols; i++) {
                 if (i != 0) buffer.append(Symbols.NUMBER_SEPARATOR);
                 if (i < fractionDigits.length) {                    // Otherwise, same than previous number.
                     final int f = fractionDigits[i];
@@ -1291,7 +1295,12 @@ public class Formatter implements Localized {
                 }
                 marks[i << 1] = buffer.length();                    // Store the start position where number is formatted.
                 setColor(ElementKind.NUMBER);
-                numberFormat.format(numbers[i], buffer, dummy);
+                final Number n = numbers.get(i);
+                if (n != null) {
+                    numberFormat.format(n, buffer, dummy);
+                } else {
+                    buffer.append('…');
+                }
                 resetColor();
                 marks[(i << 1) | 1] = buffer.length();              // Store the end position where number is formatted.
             }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Symbols.java b/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Symbols.java
index 7270658..ec56c35 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Symbols.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Symbols.java
@@ -571,6 +571,21 @@ public class Symbols implements Localized, Cloneable, Serializable {
     }
 
     /**
+     * Returns {@code true} if the formatter should use scientific notation for the given value.
+     * We use scientific notation if the number magnitude is too high or too low. The threshold values used here
+     * may be different than the threshold values used in the standard {@link StringBuilder#append(double)} method.
+     * In particular, we use a higher threshold for large numbers because ellipsoid axis lengths are above the JDK
+     * threshold when the axis length is given in feet (about 2.1E+7) while we still want to format them as usual numbers.
+     *
+     * Note that we perform this special formatting only if the 'NumberFormat' is not localized (which is the usual case).
+     *
+     * @param  abs  the absolute value of the number to format.
+     */
+    final boolean useScientificNotation(final double abs) {
+        return SCIENTIFIC_NOTATION && (abs < 1E-3 || abs >= 1E+9) && locale == Locale.ROOT;
+    }
+
+    /**
      * Returns {@code true} if the given WKT contains at least one instance of the given element.
      * Invoking this method is equivalent to invoking {@link String#contains(CharSequence)} except
      * for the following:
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractDirectPosition.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractDirectPosition.java
index d4bf4c5..f512d0a 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractDirectPosition.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractDirectPosition.java
@@ -39,6 +39,7 @@ import org.apache.sis.internal.metadata.WKTKeywords;
 import org.apache.sis.internal.referencing.WKTUtilities;
 import org.apache.sis.io.wkt.FormattableObject;
 import org.apache.sis.io.wkt.Formatter;
+import org.apache.sis.math.Vector;
 
 import static org.apache.sis.util.StringBuilders.trimFractionalPart;
 import static org.apache.sis.util.ArgumentChecks.ensureDimensionMatches;
@@ -221,7 +222,9 @@ public abstract class AbstractDirectPosition extends FormattableObject implement
      */
     @Override
     protected String formatTo(final Formatter formatter) {
-        final double[][] points = new double[][] {getCoordinate()};
+        final Vector[] points = {
+            Vector.create(getCoordinate())
+        };
         formatter.append(points, WKTUtilities.suggestFractionDigits(getCoordinateReferenceSystem(), points));
         return WKTKeywords.Point;
     }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
index 1f4b02d..77f14b1 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
@@ -42,6 +42,7 @@ import org.apache.sis.util.resources.Errors;
 import org.apache.sis.io.wkt.Formatter;
 import org.apache.sis.io.wkt.FormattableObject;
 import org.apache.sis.internal.referencing.WKTUtilities;
+import org.apache.sis.math.Vector;
 
 import static java.lang.Double.doubleToLongBits;
 import static org.apache.sis.internal.util.Numerics.SIGN_BIT_MASK;
@@ -1210,9 +1211,9 @@ public abstract class AbstractEnvelope extends FormattableObject implements Enve
      */
     @Override
     protected String formatTo(final Formatter formatter) {
-        final double[][] points = new double[][] {
-            getLowerCorner().getCoordinate(),
-            getUpperCorner().getCoordinate()
+        final Vector[] points = {
+            Vector.create(getLowerCorner().getCoordinate()),
+            Vector.create(getUpperCorner().getCoordinate())
         };
         formatter.append(points, WKTUtilities.suggestFractionDigits(getCoordinateReferenceSystem(), points));
         final int dimension = getDimension();
@@ -1220,6 +1221,7 @@ public abstract class AbstractEnvelope extends FormattableObject implements Enve
         if (dimension != 2) {
             keyword = new StringBuilder(keyword).append(dimension).append('D').toString();
         }
+        formatter.setInvalidWKT(Envelope.class, null);
         return keyword;
     }
 
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WKTUtilities.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WKTUtilities.java
index 9c32785..5b41ec8 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WKTUtilities.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WKTUtilities.java
@@ -16,6 +16,8 @@
  */
 package org.apache.sis.internal.referencing;
 
+import java.lang.reflect.Array;
+import java.util.function.Function;
 import javax.measure.Unit;
 import javax.measure.Quantity;
 import javax.measure.quantity.Angle;
@@ -51,6 +53,7 @@ import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.math.DecimalFunctions;
+import org.apache.sis.math.Vector;
 
 
 /**
@@ -317,7 +320,7 @@ public final class WKTUtilities extends Static {
      * @param  points  the sequence of points. It is not required that each point has the same dimension.
      * @return suggested amount of fraction digits as an array as long as the longest row.
      */
-    public static int[] suggestFractionDigits(final CoordinateReferenceSystem crs, final double[]... points) {
+    public static int[] suggestFractionDigits(final CoordinateReferenceSystem crs, final Vector[] points) {
         final int[] fractionDigits = Numerics.suggestFractionDigits(points);
         final Ellipsoid ellipsoid = ReferencingUtilities.getEllipsoid(crs);
         if (ellipsoid != null) {
@@ -350,4 +353,102 @@ public final class WKTUtilities extends Static {
         }
         return fractionDigits;
     }
+
+    /**
+     * Returns the values in the corners and in the center of the given tensor. The values are returned in a
+     * <var>n</var>-dimensional array of {@link Number} where <var>n</var> is the length of {@code size}.
+     * If some values have been skipped, {@code null} values are inserted in the rows or columns where the
+     * skipping occurs.
+     *
+     * @param  tensor      function providing values of the tensor. Inputs are indices of the desired value with
+     *                     index in each dimension ranging from 0 inclusive to {@code size[dimension]} exclusive.
+     * @param  size        size of the tensor. The length of this array is the tensor dimension.
+     * @param  cornerSize  number of values to keep in each corner.
+     * @return <var>n</var>-dimensional array of {@link Number} containing corners and center of the given tensor.
+     *
+     * @since 1.0
+     */
+    public static Object[] cornersAndCenter(final Function<int[],Number> tensor, final int[] size, final int cornerSize) {
+        /*
+         * The 'source' array will contain indices of values to fetch in the tensor, and the 'target' array will contain
+         * indices where to store those values in the returned data structure. Other arrays contain threshold indices of
+         * points of interest in the target data structure.
+         */
+        final int sizeLimit = cornerSize*2 + 1;
+        final int[] shown = size.clone();
+        final int[] empty = size.clone();           // Target index of row/column to leave empty, or an unreachable value if none.
+        for (int d=0; d<shown.length; d++) {
+            if (shown[d] > sizeLimit) {
+                shown[d] = sizeLimit;
+                empty[d] = cornerSize;
+            }
+        }
+        final int[] source = new int[shown.length];
+        final int[] target = new int[shown.length];
+        final Object[] numbers = (Object[]) Array.newInstance(Number.class, shown);
+        /*
+         * The loops below are used for simulating GOTO statements. This is usually a deprecated practice,
+         * but in this case we can hardly use normal loops because the number of nested loops is dynamic.
+         * We want something equivalent to the code below where 'n' - the number of nested loops - is not
+         * known at compile-time:
+         *
+         * for (int i0=0; i0<size[0]; i0++) {
+         *     for (int i1=0; i1<size[1]; i1++) {
+         *         for (int i2=0; i2<size[2]; i2++) {
+         *             // ... etc ...
+         *             for (int in=0; in<size[n]; in++) {
+         *             }
+         *         }
+         *     }
+         * }
+         *
+         * Since we can not have a varying number of nested loops in the code, we achieve the same effect with
+         * GOTO-like statements. It would be possible to achieve the same effect with recursive method calls,
+         * but the GOTO-like approach is a little bit more compact.
+         */
+        Number[] row = null;
+fill:   for (;;) {
+            if (row == null) {
+                Object[] walk = numbers;
+                for (int d=shown.length; --d >= 1;) {
+                    walk = (Object[]) walk[target[d]];
+                }
+                row = (Number[]) walk;
+            }
+            row[target[0]] = tensor.apply(source);
+            for (int d=0;;) {
+                source[d]++;
+                final int p = ++target[d];
+                if (p == shown[d]) {            // End of row (or higher dimension). This check must be first.
+                    row = null;
+                    source[d] = 0;
+                    target[d] = 0;
+                    if (++d >= shown.length) {
+                        break fill;
+                    }
+                    // Continue loop for incrementing the higher dimension.
+                } else {
+                    switch (p - empty[d]) {
+                        case 0:  continue;          // Column/row to leave null. Continue the loop for moving to next column/row.
+                        case 1:  source[d] = size[d] - cornerSize;            // Skip source columns/rows (or higher dimensions).
+                    }
+                    continue fill;                  // Stop incrementing indices and fetch the value at current location.
+                }
+            }
+        }
+        /*
+         * Add the center value in the empty location (in the middle).
+         */
+        Object walk = numbers;
+        Object[] previous = null;
+        for (int d=size.length; --d >= 0;) {
+            previous = (Object[]) walk;
+            walk = previous[empty[d]];
+            source[d] = size[d] / 2;
+        }
+        if (walk == null) {
+            previous[empty[0]] = tensor.apply(source);
+        }
+        return numbers;
+    }
 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java
index e62974f..8953a92 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java
@@ -44,7 +44,7 @@ import org.apache.sis.internal.util.Strings;
  *   <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
+ *   <li>{@link #descriptor}, {@link #gridToTarget()} and {@link #setGridParameters(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>
@@ -53,7 +53,7 @@ import org.apache.sis.internal.util.Strings;
  * The main concrete subclass is {@link DatumShiftGridFile.Float}.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  *
  * @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}
@@ -93,7 +93,8 @@ public abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quanti
     /**
      * The files from which the grid has been loaded. This is not used directly by this class
      * (except for {@link #equals(Object)} and {@link #hashCode()}), but can be used by math
-     * transform for setting the parameter values.
+     * transform for setting the parameter values. Never empty but may be null if the grid is
+     * computed instead than loaded from file(s).
      */
     private final Path[] files;
 
@@ -138,7 +139,7 @@ public abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quanti
     {
         super(coordinateUnit, coordinateToGrid, new int[] {nx, ny}, isCellValueRatio, translationUnit);
         this.descriptor = descriptor;
-        this.files      = files;
+        this.files      = (files.length != 0) ? files : null;
         this.nx         = nx;
         this.accuracy   = Double.NaN;
     }
@@ -261,12 +262,13 @@ public abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quanti
     }
 
     /**
-     * Sets all parameters for a value of type {@link Path} to the values given to th constructor.
+     * Sets all parameters for a value of type {@link Path} to the values given to the constructor.
+     * Subclasses may override for defining other kinds of parameters too.
      *
      * @param  parameters  the parameter group where to set the values.
      */
-    public final void setFileParameters(final Parameters parameters) {
-        if (files.length != 0) {
+    public void setGridParameters(final Parameters parameters) {
+        if (files != null) {
             int i = 0;
             for (final GeneralParameterDescriptor gd : descriptor.descriptors()) {
                 if (gd instanceof ParameterDescriptor<?>) {
@@ -316,7 +318,7 @@ public abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quanti
      */
     @Override
     public String toString() {
-        return Strings.toString(getClass(), "file", (files.length != 0) ? files[0] : null);
+        return Strings.toString(getClass(), "file", (files != null) ? files[0] : null);
     }
 
 
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ResidualGrid.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ResidualGrid.java
index 7d99315..6c53cc3 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ResidualGrid.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ResidualGrid.java
@@ -16,20 +16,32 @@
  */
 package org.apache.sis.referencing.operation.builder;
 
+import java.util.function.Function;
 import javax.measure.quantity.Dimensionless;
+import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterDescriptorGroup;
+import org.opengis.referencing.operation.Matrix;
+import org.apache.sis.parameter.Parameters;
+import org.apache.sis.parameter.ParameterBuilder;
+import org.apache.sis.referencing.operation.matrix.Matrix3;
 import org.apache.sis.referencing.operation.transform.LinearTransform;
+import org.apache.sis.referencing.operation.transform.ContextualParameters;
 import org.apache.sis.internal.referencing.provider.DatumShiftGridFile;
-import org.apache.sis.parameter.ParameterBuilder;
+import org.apache.sis.internal.referencing.WKTUtilities;
+import org.apache.sis.internal.util.Constants;
+import org.apache.sis.internal.util.Numerics;
+import org.apache.sis.io.wkt.FormattableObject;
+import org.apache.sis.io.wkt.Formatter;
+import org.apache.sis.math.Statistics;
+import org.apache.sis.math.Vector;
 import org.apache.sis.measure.Units;
-import org.opengis.referencing.operation.Matrix;
 
 
 /**
  * The residuals after an affine approximation has been created for a set of matching control point pairs.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.8
  * @module
  */
@@ -41,11 +53,53 @@ final class ResidualGrid extends DatumShiftGridFile<Dimensionless,Dimensionless>
 
     /**
      * The parameter descriptors for the "Localization grid" operation.
+     * Current implementation accepts a maximum of 3 dimensions; if the residual grid contains more dimensions,
+     * the extra dimensions will not be shown in the parameters. This is not a committed set of parameters and
+     * they may change in any future SIS version. We define them mostly for {@code toString()} implementation.
      */
     private static final ParameterDescriptorGroup PARAMETERS;
     static {
-        final ParameterBuilder builder = new ParameterBuilder();
-        PARAMETERS = builder.addName("Localization grid").createGroup();
+        final ParameterBuilder builder = new ParameterBuilder().setRequired(true);
+        @SuppressWarnings("rawtypes")
+        final ParameterDescriptor<?>[] grids = new ParameterDescriptor[] {
+            builder.addName(Constants.NUM_ROW).createBounded(Integer.class, 2, null, null),
+            builder.addName(Constants.NUM_COL).createBounded(Integer.class, 2, null, null),
+            builder.addName("num_dim").createBounded(Integer.class, 1, null, 2),
+            builder.addName("grid_x").create(Matrix.class, null),
+            builder.addName("grid_y").setRequired(false).create(Matrix.class, null),
+            builder.addName("grid_z").create(Matrix.class, null)
+        };
+        PARAMETERS = builder.addName("Localization grid").createGroup(grids);
+    }
+
+    /**
+     * Sets the parameters of the {@code InterpolatedTransform} which uses that localization grid.
+     * The given {@code parameters} must have been created from {@link #PARAMETERS} descriptor.
+     * This method sets the matrix parameters using views over the {@link #offsets} array.
+     */
+    @Override
+    public void setGridParameters(final Parameters parameters) {
+        super.setGridParameters(parameters);
+        final Matrix denormalization;
+        if (parameters instanceof ContextualParameters) {
+            denormalization = ((ContextualParameters) parameters).getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION);
+        } else {
+            denormalization = new Matrix3();            // Identity.
+        }
+        final int[] size = getGridSize();
+        parameters.parameter(Constants.NUM_ROW).setValue(size[1]);
+        parameters.parameter(Constants.NUM_COL).setValue(size[0]);
+        parameters.parameter("num_dim").setValue(numDim);
+        String name = "grid_x";
+        for (int dim = 0; dim < numDim; dim++) {
+            final Matrix m = new Data(dim, denormalization);
+            parameters.parameter(name).setValue(m);
+            switch (dim) {
+                case 0: name = "grid_y"; break;         // Next parameter to set after "grid_x".
+                case 1: name = "grid_z"; break;         // Next parameter to set after "grid_y".
+                default: return;                        // Current implementation does not show more than 3 dimensions.
+            }
+        }
     }
 
     /**
@@ -149,6 +203,97 @@ final class ResidualGrid extends DatumShiftGridFile<Dimensionless,Dimensionless>
     }
 
     /**
+     * View over one dimension of the offset vectors. This is used for populating the {@link ParameterDescriptorGroup}
+     * that describes the {@code MathTransform}. Those parameters are themselves used for formatting Well Known Text.
+     */
+    private final class Data extends FormattableObject implements Matrix, Function<int[],Number> {
+        /** Coefficients from the denormalization matrix for the row corresponding to this dimension. */
+        private final double[] affine;
+
+        /** Creates a new matrix for the specified dimension. */
+        Data(final int dim, final Matrix denormalization) {
+            affine = new double[denormalization.getNumCol()];
+            for (int i=0; i<affine.length; i++) {
+                affine[i] = denormalization.getElement(dim, i);
+            }
+        }
+
+        @SuppressWarnings("CloneInNonCloneableClass")
+        @Override public Matrix  clone()                            {return this;}
+        @Override public boolean isIdentity()                       {return false;}
+        @Override public int     getNumCol()                        {return nx;}
+        @Override public int     getNumRow()                        {return getGridSize()[1];}
+        @Override public Number  apply     (int[] p)                {return getElement(p[1], p[0]);}
+        @Override public void    setElement(int y, int x, double v) {throw new UnsupportedOperationException();}
+
+        /** Computes the matrix element in the given row and column. */
+        @Override public double  getElement(final int y, final int x) {
+            int i = affine.length;
+            double sum = affine[--i];
+            while (--i >= 0) {
+                sum += affine[i] * getCellValue(i, x, y);       // TODO: use Math.fma with JDK9.
+            }
+            return sum;
+        }
+
+        /**
+         * Returns a short string representation on one line. This appears as a single row
+         * in the table formatted for {@link ParameterDescriptorGroup} string representation.
+         */
+        @Override public String toString() {
+            final int[] size = getGridSize();
+            return new StringBuilder(80).append('[')
+                    .append(getElement(0, 0)).append(", …, ")
+                    .append(getElement(size[1] - 1, size[0] - 1))
+                    .append(']').toString();
+        }
+
+        /**
+         * Returns a multi-lines string representation. This appears in the Well Known Text (WKT)
+         * formatting of {@link org.opengis.referencing.operation.MathTransform}.
+         */
+        @Override protected String formatTo(final Formatter formatter) {
+            final Object[] numbers = WKTUtilities.cornersAndCenter(this, getGridSize(), 3);
+            final Vector[] rows = new Vector[numbers.length];
+            final Statistics stats = new Statistics(null);          // For computing accuracy.
+            Vector before = null;
+            for (int j=0; j<rows.length; j++) {
+                final Vector row = Vector.create(numbers[j], false);
+                /*
+                 * Estimate an accuracy to use for formatting values. This computation is specific to ResidualGrid
+                 * since it assumes that values in each corner are globally increasing or decreasing. Consequently
+                 * the differences between consecutive values are assumed a good indication of desired accuracy
+                 * (this assumption does not hold for arbitrary matrix).
+                 */
+                Number right = null;
+                for (int i=row.size(); --i >= 0;) {
+                    final Number n = row.get(i);
+                    if (n != null) {
+                        final double value = n.doubleValue();
+                        if (right != null) {
+                            stats.accept(Math.abs(right.doubleValue() - value));
+                        }
+                        if (before != null) {
+                            final Number up = before.get(i);
+                            if (up != null) {
+                                stats.accept(Math.abs(up.doubleValue() - value));
+                            }
+                        }
+                    }
+                    right = n;
+                }
+                before  = row;
+                rows[j] = row;
+            }
+            final int accuracy = Numerics.suggestFractionDigits(stats);
+            formatter.newLine();
+            formatter.append(rows, Math.max(0, accuracy));
+            formatter.setInvalidWKT(Matrix.class, null);
+            return "Matrix";
+        }
+    }
+
+    /**
      * Returns {@code true} if the given object is a grid containing the same data than this grid.
      */
     @Override
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransform.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransform.java
index a0e7abc..6962dbe 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransform.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransform.java
@@ -236,7 +236,7 @@ public class InterpolatedGeocentricTransform extends DatumShiftTransform {
         setContextParameters(semiMajor, semiMinor, unit, target);
         context.getOrCreate(Molodensky.DIMENSION).setValue(isSource3D ? 3 : 2);
         if (grid instanceof DatumShiftGridFile<?,?>) {
-            ((DatumShiftGridFile<?,?>) grid).setFileParameters(context);
+            ((DatumShiftGridFile<?,?>) grid).setGridParameters(context);
         }
         /*
          * The above setContextParameters(…) method converted the axis lengths of target ellipsoid in the same units
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedMolodenskyTransform.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedMolodenskyTransform.java
index a36dea0..65e3b26 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedMolodenskyTransform.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedMolodenskyTransform.java
@@ -224,7 +224,7 @@ public class InterpolatedMolodenskyTransform extends MolodenskyFormula {
             pg.getOrCreate(Molodensky.FLATTENING_DIFFERENCE) .setValue(Δf, Units.UNITY);
         }
         if (grid instanceof DatumShiftGridFile<?,?>) {
-            ((DatumShiftGridFile<?,?>) grid).setFileParameters(pg);
+            ((DatumShiftGridFile<?,?>) grid).setGridParameters(pg);
         }
     }
 
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedTransform.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedTransform.java
index d737d98..29058ab 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedTransform.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedTransform.java
@@ -149,9 +149,6 @@ public class InterpolatedTransform extends DatumShiftTransform {
             throw new IllegalArgumentException(Resources.format(Resources.Keys.IllegalUnitFor_2, "translation", unit));
         }
         dimension = grid.getTranslationDimensions();
-        if (grid instanceof DatumShiftGridFile<?,?>) {
-            ((DatumShiftGridFile<?,?>) grid).setFileParameters(context);
-        }
         /*
          * 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)
@@ -197,6 +194,13 @@ public class InterpolatedTransform extends DatumShiftTransform {
         }
         context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION).setMatrix(denormalize);
         inverse = createInverse();
+        /*
+         * Parameters completed last because some DatumShiftGridFile subclasses (e.g. ResidualGrid) needs the
+         * (de)normalization matrices.
+         */
+        if (grid instanceof DatumShiftGridFile<?,?>) {
+            ((DatumShiftGridFile<?,?>) grid).setGridParameters(context);
+        }
     }
 
     /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/geometry/ArrayEnvelopeTest.java b/core/sis-referencing/src/test/java/org/apache/sis/geometry/ArrayEnvelopeTest.java
index 4e1e4b1..ce2a886 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/geometry/ArrayEnvelopeTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/geometry/ArrayEnvelopeTest.java
@@ -21,7 +21,7 @@ import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
-import static org.apache.sis.test.Assert.*;
+import static org.apache.sis.test.MetadataAssert.*;
 
 
 /**
@@ -71,11 +71,11 @@ public final strictfp class ArrayEnvelopeTest extends TestCase {
     @Test
     public void testFormatWKT() {
         ArrayEnvelope envelope = new ArrayEnvelope(new double[] {4, -10, 50, 2});
-        assertMultilinesEquals("BOX[ 4 -10,\n" +
-                               "    50   2]", envelope.toWKT());
+        assertWktEquals("BOX[ 4 -10,\n" +
+                        "    50   2]", envelope);
         envelope.crs = AbstractEnvelopeTest.WGS84;
-        assertMultilinesEquals("BOX[ 4.00000000 -10.00000000,\n" +
-                               "    50.00000000   2.00000000]", envelope.toWKT());
+        assertWktEquals("BOX[ 4.00000000 -10.00000000,\n" +
+                        "    50.00000000   2.00000000]", envelope);
 
     }
 
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java b/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java
index add8c08..ab42d3e 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java
@@ -639,6 +639,20 @@ public strictfp class GeneralEnvelopeTest extends TestCase {
     }
 
     /**
+     * Tests {@link GeneralEnvelope#toWKT()} on a {@link GeneralEnvelope}.
+     */
+    @Test
+    public void testWktFormatting() {
+        final GeneralEnvelope envelope = new GeneralEnvelope(3);
+        envelope.setRange(0,  6, 10);
+        envelope.setRange(1, 16, 20);
+        envelope.setRange(2, 23, 50);
+        assertWktEquals(
+                "BOX3D[ 6 16 23,\n" +
+                "      10 20 50]", envelope);
+    }
+
+    /**
      * Tests the {@link GeneralEnvelope#equals(Object)} and
      * {@link GeneralEnvelope#equals(Envelope, double, boolean)} methods.
      */
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WKTUtilitiesTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WKTUtilitiesTest.java
index 3978e59..4e913cb 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WKTUtilitiesTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WKTUtilitiesTest.java
@@ -16,11 +16,13 @@
  */
 package org.apache.sis.internal.referencing;
 
+import java.util.function.Function;
 import org.opengis.referencing.cs.*;
 import org.apache.sis.referencing.crs.HardCodedCRS;
 import org.apache.sis.internal.metadata.WKTKeywords;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
+import org.apache.sis.math.Vector;
 import org.junit.Test;
 
 import static org.junit.Assert.*;
@@ -58,13 +60,30 @@ public final strictfp class WKTUtilitiesTest extends TestCase {
     }
 
     /**
-     * Tests {@link WKTUtilities#suggestFractionDigits(CoordinateReferenceSystem, double[]...)}.
+     * Tests {@link WKTUtilities#suggestFractionDigits(CoordinateReferenceSystem, Vector[])}.
      */
     @Test
     public void testSuggestFractionDigits() {
-        assertArrayEquals(new int[] {8, 9}, WKTUtilities.suggestFractionDigits(HardCodedCRS.WGS84, new double[][] {
-            {40, -10},
-            {50, -10.000000001}
-        }));
+        final Vector[] points = {
+            Vector.create(new double[] {40, -10}),
+            Vector.create(new double[] {50, -10.000000001})
+        };
+        assertArrayEquals(new int[] {8, 9}, WKTUtilities.suggestFractionDigits(HardCodedCRS.WGS84, points));
+    }
+
+    /**
+     * Tests {@link WKTUtilities#cornersAndCenter(Function, int[], int)}.
+     */
+    @Test
+    public void testCornersAndCenter() {
+        final Object[] values = cornersAndCenter((p) -> 100 * p[1] + p[0], new int[] {12, 15}, 3);
+        assertArrayEquals(new Number[] {   0,    1,    2, null,    9,   10,   11}, (Number[]) values[0]);
+        assertArrayEquals(new Number[] { 100,  101,  102, null,  109,  110,  111}, (Number[]) values[1]);
+        assertArrayEquals(new Number[] { 200,  201,  202, null,  209,  210,  211}, (Number[]) values[2]);
+        assertArrayEquals(new Number[] {null, null, null,  706, null, null, null}, (Number[]) values[3]);
+        assertArrayEquals(new Number[] {1200, 1201, 1202, null, 1209, 1210, 1211}, (Number[]) values[4]);
+        assertArrayEquals(new Number[] {1300, 1301, 1302, null, 1309, 1310, 1311}, (Number[]) values[5]);
+        assertArrayEquals(new Number[] {1400, 1401, 1402, null, 1409, 1410, 1411}, (Number[]) values[6]);
+        assertEquals(7, values.length);
     }
 }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java
index fe51d38..7621d73 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java
@@ -26,10 +26,14 @@ import org.apache.sis.util.Static;
 import org.apache.sis.util.Workaround;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.math.DecimalFunctions;
+import org.apache.sis.math.Statistics;
+import org.apache.sis.math.Vector;
 import org.opengis.referencing.operation.Matrix;    // For javadoc
 
+import static java.lang.Math.min;
 import static java.lang.Math.max;
 import static java.lang.Math.abs;
+import static java.lang.Math.ulp;
 
 
 /**
@@ -480,6 +484,39 @@ public final class Numerics extends Static {
     }
 
     /**
+     * Suggests an amount of fraction digits for data having the given statistics.
+     * This method uses heuristic rules that may be modified in any future SIS version.
+     *
+     * @param  stats  statistics on the data to format.
+     * @return number of fraction digits suggested. May be negative.
+     *
+     * @since 1.0
+     */
+    public static int suggestFractionDigits(final Statistics stats) {
+        final double minimum = stats.minimum();
+        final double maximum = stats.maximum();
+        double delta = stats.standardDeviation(true);                       // 'true' is for avoiding NaN when count = 1.
+        if (delta == 0) {
+            delta = stats.span();                                           // May happen that the span is very narrow.
+            if (delta == 0) {
+                delta = abs(maximum) / 1E+6;                                // The 1E+6 factor is arbitrary.
+            }
+        } else {
+            /*
+             * Computes a representative range of values. We take 2 standard deviations away
+             * from the mean. Assuming that data have a gaussian distribution, this is 97.7%
+             * of data. If the data have a uniform distribution, then this is 100% of data.
+             */
+            final double mean = stats.mean();
+            delta *= 2;
+            delta  = min(maximum, mean+delta) - max(minimum, mean-delta);   // Range of 97.7% of values.
+            delta /= min(stats.count() * 1E+2, 1E+6);                       // Mean delta for uniform distribution + 2 decimal digits.
+            delta  = max(delta, max(ulp(minimum), ulp(maximum)));           // Not finer than 'double' accuracy.
+        }
+        return DecimalFunctions.fractionDigitsForDelta(delta, false);
+    }
+
+    /**
      * Suggests an amount of fraction digits to use for formatting numbers in each column of the given matrix.
      * The number of fraction digits may be negative if we could round the numbers to 10, 100, <i>etc</i>.
      *
@@ -487,41 +524,34 @@ public final class Numerics extends Static {
      * @return suggested amount of fraction digits as an array as long as the longest row.
      *
      * @see org.apache.sis.referencing.operation.matrix.Matrices#toString(Matrix)
+     *
+     * @todo Move into {@link org.apache.sis.internal.referencing.WKTUtilities}
+     *       if we move WKT parser/formatter to referencing module.
      */
-    public static int[] suggestFractionDigits(final double[][] rows) {
+    public static int[] suggestFractionDigits(final Vector[] rows) {
         int length = 0;
         final int n = rows.length - 1;
         for (int j=0; j <= n; j++) {
-            final int rl = rows[j].length;
+            final int rl = rows[j].size();
             if (rl > length) length = rl;
         }
         final int[] fractionDigits = new int[length];
+        final Statistics stats = new Statistics(null);
         for (int i=0; i<length; i++) {
-            double min = Double.POSITIVE_INFINITY;
-            double max = Double.NEGATIVE_INFINITY;
             boolean isInteger = true;
-            for (final double[] row : rows) {
-                if (row.length > i) {
-                    final double value = row[i];
-                    if (value < min) min = value;
-                    if (value > max) max = value;
+            for (final Vector row : rows) {
+                if (row.size() > i) {
+                    final double value = row.doubleValue(i);
+                    stats.accept(value);
                     if (isInteger && Math.floor(value) != value && !Double.isNaN(value)) {
                         isInteger = false;
                     }
                 }
             }
             if (!isInteger) {
-                final double  delta;
-                final boolean strict;
-                if (min < max) {
-                    delta  = (max - min) / n;
-                    strict = (n == 1);
-                } else {
-                    delta  = Math.max(Math.abs(min), Math.abs(max)) * 1E-6;     // The 1E-6 factor is arbitrary.
-                    strict = false;
-                }
-                fractionDigits[i] = DecimalFunctions.fractionDigitsForDelta(delta, strict);
+                fractionDigits[i] = suggestFractionDigits(stats);
             }
+            stats.reset();
         }
         return fractionDigits;
     }
@@ -538,7 +568,7 @@ public final class Numerics extends Static {
     @Workaround(library="JDK", version="10")
     public static String useScientificNotationIfNeeded(final Format format, final Object value, final BiFunction<Format,Object,String> action) {
         if (value instanceof Number) {
-            final double m = Math.abs(((Number) value).doubleValue());
+            final double m = abs(((Number) value).doubleValue());
             if (m > 0 && (m >= 1E+9 || m < 1E-4) && format instanceof DecimalFormat) {
                 final DecimalFormat df = (DecimalFormat) format;
                 final String pattern = df.toPattern();
diff --git a/core/sis-utility/src/main/java/org/apache/sis/math/StatisticsFormat.java b/core/sis-utility/src/main/java/org/apache/sis/math/StatisticsFormat.java
index b96cd6b..99f2bfe 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/math/StatisticsFormat.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/math/StatisticsFormat.java
@@ -34,8 +34,7 @@ import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.collection.BackingStoreException;
-
-import static java.lang.Math.*;
+import org.apache.sis.internal.util.Numerics;
 
 
 /**
@@ -75,12 +74,6 @@ public class StatisticsFormat extends TabularFormat<Statistics> {
     private static final long serialVersionUID = 6914760410359494163L;
 
     /**
-     * Number of additional digits, to be added to the number of digits computed from the
-     * range and the number of sample values. This is an arbitrary parameter.
-     */
-    private static final int ADDITIONAL_DIGITS = 2;
-
-    /**
      * The locale for row and column headers.
      * This is usually the same than the format locale, but not necessarily.
      */
@@ -410,9 +403,6 @@ public class StatisticsFormat extends TabularFormat<Statistics> {
      * @return the formatter to use. May be a clone of the given formatter.
      */
     private static Format configure(final Format format, final Statistics stats, final boolean clone) {
-        final double minimum  = stats.minimum();
-        final double maximum  = stats.maximum();
-        final double extremum = max(abs(minimum), abs(maximum));
         int multiplier = 1;
         if (format instanceof DecimalFormat) {
             DecimalFormat df = (DecimalFormat) format;
@@ -423,6 +413,7 @@ public class StatisticsFormat extends TabularFormat<Statistics> {
              * enough). If the numbers seem to require scientific notation, switch to that notation only
              * if the user has not already set a different number pattern.
              */
+            final double extremum = Math.max(Math.abs(stats.minimum()), Math.abs(stats.maximum()));
             if (multiplier == 1 && (extremum >= 1E+10 || extremum <= 1E-4)) {
                 final String pattern = df.toPattern();
                 for (int i = pattern.length(); --i >= 0;) {
@@ -443,19 +434,15 @@ public class StatisticsFormat extends TabularFormat<Statistics> {
             }
         }
         /*
-         * Computes a representative range of values. We take 2 standard deviations away
-         * from the mean. Assuming that data have a gaussian distribution, this is 97.7%
-         * of data. If the data have a uniform distribution, then this is 100% of data.
+         * Numerics.suggestFractionDigits(stats) computes a representative range of values
+         * based on 2 standard deviations away from the mean. For a gaussian distribution,
+         * this covers 97.7% of data. If the data have a uniform distribution, then this is
+         * 100% of data.
          */
-        double delta;
-        final double mean = stats.mean();
-        delta = 2 * stats.standardDeviation(true);                      // 'true' is for avoiding NaN when count == 1.
-        delta = min(maximum, mean+delta) - max(minimum, mean-delta);    // Range of 97.7% of values.
-        delta = max(delta/stats.count(), ulp(extremum));                // Mean delta for uniform distribution, not finer than 'double' accuracy.
         if (format instanceof NumberFormat) {
-            int digits = DecimalFunctions.fractionDigitsForDelta(delta, false);
+            int digits = Numerics.suggestFractionDigits(stats);
             digits -= DecimalFunctions.floorLog10(multiplier);
-            digits = max(0, digits + ADDITIONAL_DIGITS);
+            digits = Math.max(0, digits);
             NumberFormat nf = (NumberFormat) format;
             if (digits != nf.getMinimumFractionDigits() ||
                 digits != nf.getMaximumFractionDigits())
diff --git a/core/sis-utility/src/test/java/org/apache/sis/internal/util/NumericsTest.java b/core/sis-utility/src/test/java/org/apache/sis/internal/util/NumericsTest.java
index a7c7284..2ac340f 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/internal/util/NumericsTest.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/internal/util/NumericsTest.java
@@ -21,6 +21,7 @@ import org.apache.sis.math.MathFunctions;
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.TestCase;
 import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.math.Vector;
 import org.junit.Test;
 
 import static java.lang.Double.NaN;
@@ -190,15 +191,14 @@ public final strictfp class NumericsTest extends TestCase {
     }
 
     /**
-     * Tests {@link Numerics#suggestFractionDigits(double[][])}.
+     * Tests {@link Numerics#suggestFractionDigits(Vector[])}.
      */
     @Test
     public void testSuggestFractionDigits() {
-        final int[] f = Numerics.suggestFractionDigits(new double[][] {
-            {10, 100,   0.1, 1000.1},
-            {15, 140.1, 0.4, Double.NaN},
-            {20, 400,   0.5, Double.NaN}
-        });
-        assertArrayEquals(new int[] {0, -2, 1, 3}, f);
+        final int[] f = Numerics.suggestFractionDigits(new Vector[] {
+            Vector.create(new double[] {10, 100,   0.1, 1000.1}),
+            Vector.create(new double[] {15, 140.1, 0.4, Double.NaN}),
+            Vector.create(new double[] {20, 400,   0.5, Double.NaN})});
+        assertArrayEquals(new int[] {0, 0, 3, 3}, f);
     }
 }


Mime
View raw message