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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new a00f7dd  Fix an axis order problem when a linearization (currently a 
UTM map projection) is applied on the localization grid of a netCDF file. 
Before this fix, geographic bounding box were wrong because map projection 
applied on coordinates in the wrong order.
a00f7dd is described below

commit a00f7dd2ef67f0ec781a825feae3123d7eeabe3a
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Tue Mar 9 23:51:44 2021 +0100

    Fix an axis order problem when a linearization (currently a UTM map 
projection) is applied on the localization grid of a netCDF file.
    Before this fix, geographic bounding box were wrong because map projection 
applied on coordinates in the wrong order.
---
 .../internal/referencing/ReferencingUtilities.java |  14 ++
 .../operation/builder/LinearTransformBuilder.java  |   8 +
 .../operation/builder/LocalizationGridBuilder.java | 185 +++++++++++++--------
 .../apache/sis/internal/earth/netcdf/GCOM_C.java   |   9 +-
 .../org/apache/sis/internal/netcdf/CRSBuilder.java |  34 ++--
 .../java/org/apache/sis/internal/netcdf/Grid.java  |  32 ++--
 .../apache/sis/internal/netcdf/GridCacheKey.java   |   5 +-
 .../apache/sis/internal/netcdf/GridCacheValue.java |  45 ++---
 .../org/apache/sis/internal/netcdf/Linearizer.java |  60 +++++--
 9 files changed, 254 insertions(+), 138 deletions(-)

diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
index 03c02a2..c174b14 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
@@ -365,6 +365,20 @@ public final class ReferencingUtilities extends Static {
     }
 
     /**
+     * Returns {@code true} if the given coordinate system has at least 2 
dimensions and
+     * the 2 first axis have (North, East) directions. This method is used for 
assertions.
+     *
+     * @param  cs  the coordinate system to verify.
+     * @return whether the coordinate system starts with (North, East) 
direction.
+     */
+    public static boolean startsWithNorthEast(final CoordinateSystem cs) {
+        final int dimension = cs.getDimension();
+        return (dimension >= 2)
+                && AxisDirection.NORTH.equals(cs.getAxis(0).getDirection())
+                && AxisDirection.EAST .equals(cs.getAxis(1).getDirection());
+    }
+
+    /**
      * Returns the properties of the given object but potentially with a 
modified name.
      * Current implement truncates the name at the first non-white character 
which is not
      * a valid Unicode identifier part, with the following exception:
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
index 1f8a882..46377c4 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
@@ -1545,6 +1545,14 @@ search:         for (int j=domain(); --j >= 0;) {
     }
 
     /**
+     * Returns the transform computed by {@link #create(MathTransformFactory)},
+     * of {@code null} if that method has not yet been invoked.
+     */
+    final LinearTransform transform() {
+        return transform;
+    }
+
+    /**
      * Returns a string representation of this builder for debugging purpose.
      * Current implementation shows the following information:
      *
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java
index fca7409..6c5b6d2 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java
@@ -103,13 +103,13 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
      * The transform for the linear part.
      * Always created with a grid size specified to the constructor.
      */
-    private final LinearTransformBuilder linear;
+    private final LinearTransformBuilder linearBuilder;
 
     /**
      * A temporary array for two-dimensional source coordinates.
      * Used for reducing object allocations.
      */
-    private final int[] tmp = new int[SOURCE_DIMENSION];
+    private final int[] gridCoordinates = new int[SOURCE_DIMENSION];
 
     /**
      * Conversions from source real-world coordinates to grid indices before 
interpolation.
@@ -141,7 +141,7 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
     /**
      * If the coordinates in some dimensions are cyclic, their periods. 
Otherwise {@code null}.
      * Values are in units of the target CRS. For longitude wraparounds, the 
period is typically 360°.
-     * Array length shall be {@code linear.getTargetDimensions()} and 
non-cyclic dimensions shall have
+     * Array length shall be {@code linearBuilder.getTargetDimensions()} and 
non-cyclic dimensions shall have
      * a period of zero (not {@link Double#NaN}, because we will use this 
array as a displacement vector).
      *
      * @see #resolveWraparoundAxis(int, int, double)
@@ -156,8 +156,8 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
      * @param height  the number of rows in the grid of target positions.
      */
     public LocalizationGridBuilder(final int width, final int height) {
-        linear = new LinearTransformBuilder(width, height);
-        sourceToGrid = MathTransforms.identity(SOURCE_DIMENSION);
+        linearBuilder = new LinearTransformBuilder(width, height);
+        sourceToGrid  = MathTransforms.identity(SOURCE_DIMENSION);
     }
 
     /**
@@ -180,7 +180,7 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
         final Matrix fromGrid = new Matrix3();
         final int width  = infer(sourceX, fromGrid, 0);
         final int height = infer(sourceY, fromGrid, 1);
-        linear = new LinearTransformBuilder(width, height);
+        linearBuilder = new LinearTransformBuilder(width, height);
         try {
             sourceToGrid = MathTransforms.linear(fromGrid).inverse();
         } catch (NoninvertibleTransformException e) {
@@ -218,8 +218,8 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
         ArgumentChecks.ensureNonNull("localizations", localizations);
         int n = localizations.getGridDimensions();
         if (n == SOURCE_DIMENSION) {
-            linear = localizations;
-            sourceToGrid = MathTransforms.identity(SOURCE_DIMENSION);
+            linearBuilder = localizations;
+            sourceToGrid  = MathTransforms.identity(SOURCE_DIMENSION);
         } else {
             if (n < 0) {
                 final Vector[] sources = localizations.sources();
@@ -228,14 +228,14 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
                     final Matrix fromGrid = new Matrix3();
                     final int width  = infer(sources[0], fromGrid, 0);
                     final int height = infer(sources[1], fromGrid, 1);
-                    linear = new LinearTransformBuilder(width, height);
-                    linear.setControlPoints(localizations.getControlPoints());
+                    linearBuilder = new LinearTransformBuilder(width, height);
+                    
linearBuilder.setControlPoints(localizations.getControlPoints());
                     try {
                         sourceToGrid = 
MathTransforms.linear(fromGrid).inverse();
                     } catch (NoninvertibleTransformException e) {
                         throw (ArithmeticException) new 
ArithmeticException(e.getLocalizedMessage()).initCause(e);
                     }
-                    linear.setLinearizers(localizations);
+                    linearBuilder.setLinearizers(localizations);
                     return;
                 }
             }
@@ -296,7 +296,7 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
      * Throws {@link IllegalStateException} if this builder can not be 
modified anymore.
      */
     private void ensureModifiable() throws IllegalStateException {
-        if (!linear.isModifiable()) {
+        if (!linearBuilder.isModifiable()) {
             throw new 
IllegalStateException(Errors.format(Errors.Keys.UnmodifiableObject_1, 
LocalizationGridBuilder.class));
         }
     }
@@ -428,7 +428,7 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
      * @since 1.0
      */
     public Envelope getSourceEnvelope(final boolean fullArea) throws 
TransformException {
-        Envelope envelope = linear.getSourceEnvelope();
+        Envelope envelope = linearBuilder.getSourceEnvelope();
         if (fullArea) {
             for (int i = envelope.getDimension(); --i >= 0;) {
                 final GeneralEnvelope ge = 
GeneralEnvelope.castOrCopy(envelope);
@@ -453,7 +453,7 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
     public void setControlPoints(final Vector... coordinates) {
         ensureModifiable();
         ArgumentChecks.ensureNonNull("coordinates", coordinates);
-        linear.setControlPoints(coordinates);
+        linearBuilder.setControlPoints(coordinates);
     }
 
     /**
@@ -472,9 +472,9 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
      */
     public void setControlPoint(final int gridX, final int gridY, final 
double... target) {
         ensureModifiable();
-        tmp[0] = gridX;
-        tmp[1] = gridY;
-        linear.setControlPoint(tmp, target);
+        gridCoordinates[0] = gridX;
+        gridCoordinates[1] = gridY;
+        linearBuilder.setControlPoint(gridCoordinates, target);
     }
 
     /**
@@ -488,9 +488,9 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
      * @throws IllegalArgumentException if the {@code x} or {@code y} 
coordinate value is out of grid range.
      */
     public double[] getControlPoint(final int gridX, final int gridY) {
-        tmp[0] = gridX;
-        tmp[1] = gridY;
-        return linear.getControlPoint(tmp);
+        gridCoordinates[0] = gridX;
+        gridCoordinates[1] = gridY;
+        return linearBuilder.getControlPoint(gridCoordinates);
     }
 
     /**
@@ -504,9 +504,9 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
      * @since 1.0
      */
     public Vector getRow(final int dimension, final int row) {
-        tmp[0] = 0;
-        tmp[1] = row;
-        return linear.getTransect(dimension, tmp, 0);
+        gridCoordinates[0] = 0;
+        gridCoordinates[1] = row;
+        return linearBuilder.getTransect(dimension, gridCoordinates, 0);
     }
 
     /**
@@ -520,9 +520,9 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
      * @since 1.0
      */
     public Vector getColumn(final int dimension, final int column) {
-        tmp[0] = column;
-        tmp[1] = 0;
-        return linear.getTransect(dimension, tmp, 1);
+        gridCoordinates[0] = column;
+        gridCoordinates[1] = 0;
+        return linearBuilder.getTransect(dimension, gridCoordinates, 1);
     }
 
     /**
@@ -570,18 +570,18 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
      */
     public NumberRange<Double> resolveWraparoundAxis(final int dimension, 
final int direction, final double period) {
         ensureModifiable();
-        ArgumentChecks.ensureBetween("dimension", 0, 
linear.getTargetDimensions() - 1, dimension);
-        ArgumentChecks.ensureBetween("direction", 0, 
linear.getSourceDimensions() - 1, direction);
+        ArgumentChecks.ensureBetween("dimension", 0, 
linearBuilder.getTargetDimensions() - 1, dimension);
+        ArgumentChecks.ensureBetween("direction", 0, 
linearBuilder.getSourceDimensions() - 1, direction);
         ArgumentChecks.ensureStrictlyPositive("period", period);
         if (periods == null) {
-            periods = new double[linear.getTargetDimensions()];
+            periods = new double[linearBuilder.getTargetDimensions()];
         }
         if (periods[dimension] != 0) {
             throw new IllegalStateException(Errors.format(
                     Errors.Keys.ValueAlreadyDefined_1, 
Strings.bracket("periods", dimension)));
         }
         periods[dimension] = period;
-        return linear.resolveWraparoundAxis(dimension, direction, period);
+        return linearBuilder.resolveWraparoundAxis(dimension, direction, 
period);
     }
 
     /**
@@ -622,7 +622,7 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
     public void addLinearizers(final Map<String,MathTransform> projections, 
final boolean compensate, final int... projToGrid) {
         ArgumentChecks.ensureNonNull("projections", projections);
         ensureModifiable();
-        linear.addLinearizers(projections, compensate, projToGrid);
+        linearBuilder.addLinearizers(projections, compensate, projToGrid);
     }
 
     /**
@@ -657,14 +657,14 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
     @Override
     public MathTransform create(final MathTransformFactory factory) throws 
FactoryException {
         if (transform == null) {
-            final LinearTransform gridToCoord = linear.create(factory);
+            final LinearTransform gridToCoord = linearBuilder.create(factory);
             /*
              * Make a first check about whether the result of above 
LinearTransformBuilder.create() call
              * can be considered a good fit. If true, then we may return the 
linear transform directly.
              */
             boolean isExact  = true;
             boolean isLinear = true;
-            for (final double c : linear.correlation()) {
+            for (final double c : linearBuilder.correlation()) {
                 isExact &= (c == 1);
                 if (!(c >= 0.9999)) {                           // Empirical 
threshold (may need to be revisited).
                     isLinear = false;
@@ -675,9 +675,9 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
             if (isExact) {
                 step = MathTransforms.concatenate(sourceToGrid, gridToCoord);
             } else {
-                final int      width    = linear.gridSize(0);
-                final int      height   = linear.gridSize(1);
-                final float[]  residual = new float [SOURCE_DIMENSION * 
linear.gridLength];
+                final int      width    = linearBuilder.gridSize(0);
+                final int      height   = linearBuilder.gridSize(1);
+                final float[]  residual = new float [SOURCE_DIMENSION * 
linearBuilder.gridLength];
                 final double[] grid     = new double[SOURCE_DIMENSION * width];
                 double gridPrecision    = precision;
                 try {
@@ -706,9 +706,9 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
                      */
                     final MathTransform coordToGrid = gridToCoord.inverse();
                     for (int k=0,y=0; y<height; y++) {
-                        tmp[0] = 0;
-                        tmp[1] = y;
-                        linear.getControlRow(tmp, grid);                       
             // Expected positions.
+                        gridCoordinates[0] = 0;
+                        gridCoordinates[1] = y;
+                        linearBuilder.getControlRow(gridCoordinates, grid);    
             // Expected positions.
                         coordToGrid.transform(grid, 0, grid, 0, width);        
             // As grid coordinate.
                         for (int i=0,x=0; x<width; x++) {
                             final double dx = grid[i++] - x;
@@ -722,10 +722,10 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
                     if (isLinear) {
                         step = MathTransforms.concatenate(sourceToGrid, 
gridToCoord);
                     } else {
-                        step = 
InterpolatedTransform.createGeodeticTransformation(nonNull(factory),
-                                new ResidualGrid(sourceToGrid, gridToCoord, 
width, height, residual,
-                                (gridPrecision > 0) ? gridPrecision : 
DEFAULT_PRECISION, periods,
-                                linear.appliedLinearizer()));
+                        final ResidualGrid shifts = new 
ResidualGrid(sourceToGrid, gridToCoord, width, height,
+                                residual, (gridPrecision > 0) ? gridPrecision 
: DEFAULT_PRECISION, periods,
+                                linearBuilder.appliedLinearizer());
+                        step = 
InterpolatedTransform.createGeodeticTransformation(nonNull(factory), shifts);
                     }
                 } catch (TransformException e) {
                     throw new FactoryException(e);                             
             // Should never happen.
@@ -736,7 +736,7 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
              * If those target coordinates have been modified in order to make 
that step more
              * linear, apply the inverse transformation after the step.
              */
-            final ProjectedTransformTry linearizer = 
linear.appliedLinearizer();
+            final ProjectedTransformTry linearizer = 
linearBuilder.appliedLinearizer();
             if (linearizer != null && linearizer.reverseAfterLinearization) 
try {
                 step = factory.createConcatenatedTransform(step, 
linearizer.getValue().inverse());
             } catch (NoninvertibleTransformException e) {
@@ -780,7 +780,7 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
      * @since 1.1
      */
     public Optional<Map.Entry<String,MathTransform>> linearizer(final boolean 
ifNotCompensated) {
-        ProjectedTransformTry linearizer = linear.appliedLinearizer();
+        ProjectedTransformTry linearizer = linearBuilder.appliedLinearizer();
         if (ifNotCompensated && linearizer != null && 
linearizer.reverseAfterLinearization) {
             linearizer = null;
         }
@@ -788,46 +788,54 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
     }
 
     /**
-     * Returns statistics of differences between values calculated by the 
given transform and actual values.
-     * The given math transform is typically the transform computed by {@link 
#create(MathTransformFactory)},
-     * but not necessarily. The returned statistics are:
+     * Returns statistics of differences between values calculated by the 
transform and actual values.
+     * The tested transform is
+     * the one computed by {@link #create(MathTransformFactory)} if the {@code 
linear} argument is {@code false},
+     * or the transform computed by {@link LinearTransformBuilder} if the 
{@code linear} argument is {@code true}.
+     * The returned statistics are:
      *
      * <ol class="verbose">
      *   <li>One {@code Statistics} instance for each target dimension, 
containing statistics about the differences between
      *     coordinates computed by the given transform and expected 
coordinates. For each (<var>i</var>,<var>j</var>) indices
      *     in this grid, the indices are transformed by a call to {@code 
mt.transform(…)} and the result is compared with the
      *     coordinates given by <code>{@linkplain #getControlPoint(int, int) 
getControlPoint}(i,j)</code>.
-     *     Those statistics are identified by labels like “P → x” and “P → y” 
where <var>P</var> stands for pixel coordinates.</li>
+     *     Those statistics are identified by labels like “Δx” and “Δy”.</li>
      *   <li>One {@code Statistics} instance for each source dimension, 
containing statistics about the differences between
      *     coordinates computed by the <em>inverse</em> of the transform and 
expected coordinates.
      *     For each (<var>x</var>,<var>y</var>) control point in this grid, 
the points are transformed by a call
      *     to {@code mt.inverse().transform(…)} and the result is compared 
with the pixel indices of that point.
-     *     Those statistics are identified by labels like “i ← P′” and “j ← 
P′” where <var>P′</var> stands for
-     *     the control point.</li>
+     *     Those statistics are identified by labels like "Δi" and "Δj".</li>
      * </ol>
      *
-     * @param  mt  the transform to test.
+     * @param  linear  {@code false} for computing errors using the complete 
transform,
+     *                 or {@code true} for using only the linear part.
      * @return statistics of difference between computed values and expected 
values for each target dimension.
-     * @throws NoninvertibleTransformException if an error occurred while 
inverting a transform.
+     * @throws IllegalStateException if {@link #create(MathTransformFactory)} 
has not yet been invoked.
      *
-     * @since 1.0
+     * @see #toString(boolean, Locale)
+     *
+     * @since 1.1
      */
-    public Statistics[] error(final MathTransform mt) throws 
NoninvertibleTransformException {
+    public Statistics[] errors(final boolean linear) {
+        final MathTransform mt = linear ? linearBuilder.transform() : 
transform;
+        if (mt == null) {
+            throw new 
IllegalStateException(Errors.format(Errors.Keys.Uninitialized_1, 
getClass().getSimpleName()));
+        }
         final int           tgtDim = mt.getTargetDimensions();
         final double[]      point  = new double[Math.max(tgtDim, 
SOURCE_DIMENSION)];
         final Statistics[]  stats  = new Statistics[tgtDim + SOURCE_DIMENSION];
         final StringBuilder buffer = new StringBuilder();
         for (int i=0; i<stats.length; i++) {
             buffer.setLength(0);
+            buffer.append('Δ');
             if (i < tgtDim) {
-                buffer.append("P → ");
                 if (i < 3) {
                     buffer.append((char) ('x' + i));
                 } else {
                     buffer.append('z').append(i - 1);       // After (x,y,z) 
continue with z2, z3, z4, etc.
                 }
             } else {
-                buffer.append((char) ('i' + (i - tgtDim))).append(" ← P′");
+                buffer.append((char) ('i' + (i - tgtDim)));
             }
             stats[i] = new Statistics(buffer.toString());
         }
@@ -837,19 +845,24 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
          * The way that we get the transform below should be the same way than 
in `create(…)`, except that we
          * apply the inverse transform unconditionally.
          */
-        final ProjectedTransformTry linearizer = linear.appliedLinearizer();
-        final MathTransform complete = (linearizer != null) ? 
linearizer.getValue().inverse() : null;
-        final MathTransform inverse = mt.inverse();
-        final int width  = linear.gridSize(0);
-        final int height = linear.gridSize(1);
+        final MathTransform complete, inverse;
+        try {
+            final ProjectedTransformTry linearizer = 
linearBuilder.appliedLinearizer();
+            complete = (linearizer != null && 
linearizer.reverseAfterLinearization) ? linearizer.getValue().inverse() : null;
+            inverse = mt.inverse();
+        } catch (NoninvertibleTransformException e) {
+            throw new IllegalStateException(e);
+        }
+        final int width  = linearBuilder.gridSize(0);
+        final int height = linearBuilder.gridSize(1);
         for (int y=0; y<height; y++) {
             for (int x=0; x<width; x++) {
-                point[0] = tmp[0] = x;
-                point[1] = tmp[1] = y;
+                point[0] = gridCoordinates[0] = x;
+                point[1] = gridCoordinates[1] = y;
                 final double[] expected;
                 try {
                     mt.transform(point, 0, point, 0, 1);
-                    expected = linear.getControlPoint(tmp);
+                    expected = linearBuilder.getControlPoint(gridCoordinates);
                     if (complete != null) {
                         complete.transform(expected, 0, expected, 0, 1);
                     }
@@ -868,7 +881,7 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
                     continue;                           // Ignore the points 
that we fail to transform.
                 }
                 for (int i=0; i<SOURCE_DIMENSION; i++) {
-                    stats[tgtDim + i].accept(expected[i] - tmp[i]);
+                    stats[tgtDim + i].accept(expected[i] - gridCoordinates[i]);
                 }
             }
         }
@@ -876,6 +889,18 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
     }
 
     /**
+     * @deprecated Replaced by {@link #errors(boolean)}.
+     *
+     * @since 1.0
+     */
+    @Deprecated
+    public Statistics[] error(final MathTransform mt) throws 
NoninvertibleTransformException {
+        if (mt == transform) return errors(false);
+        if (mt == linearBuilder.transform()) return errors(true);
+        throw new IllegalArgumentException();
+    }
+
+    /**
      * Returns a string representation of this builder in the given locale.
      * Current implementation shows the following information:
      *
@@ -883,21 +908,25 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
      *   <li>Number of points.</li>
      *   <li>Linearizers and their correlation coefficients (if 
available).</li>
      *   <li>The linear component of the transform.</li>
-     *   <li>Error statistics, as documented in the {@link 
#error(MathTransform)} method.</li>
+     *   <li>Error statistics, as documented in the {@link #errors(boolean)} 
method.</li>
      * </ul>
      *
      * The string representation may change in any future version.
      *
+     * @param  linear  {@code false} for errors using the complete transform,
+     *                 or {@code true} for using only the linear part.
      * @param  locale  the locale for formatting messages and some numbers, or 
{@code null} for the default.
      * @return a string representation of this builder.
      *
-     * @since 1.0
+     * @see #errors(boolean)
+     *
+     * @since 1.1
      */
-    public String toString(final Locale locale) {
+    public String toString(final boolean linear, final Locale locale) {
         final StringBuilder buffer = new StringBuilder(400);
         String lineSeparator = null;
         try {
-            lineSeparator = linear.appendTo(buffer, getClass(), locale, 
Vocabulary.Keys.LinearTransformation);
+            lineSeparator = linearBuilder.appendTo(buffer, getClass(), locale, 
Vocabulary.Keys.LinearTransformation);
             if (transform != null) {
                 buffer.append(Strings.CONTINUATION_ITEM);
                 final Vocabulary vocabulary = Vocabulary.getResources(locale);
@@ -909,11 +938,11 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
                 } else {
                     sf = StatisticsFormat.getInstance();
                 }
-                sf.format(error(transform), buffer);
+                sf.format(errors(linear), buffer);
             }
         } catch (IOException e) {
             throw new UncheckedIOException(e);
-        } catch (NoninvertibleTransformException e) {
+        } catch (IllegalStateException e) {
             // Ignore - we will not report error statistics.
         }
         Strings.insertLineInLeftMargin(buffer, lineSeparator);
@@ -921,6 +950,16 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
     }
 
     /**
+     * @deprecated Replaced by {@link #toString(boolean, Locale)}.
+     *
+     * @since 1.0
+     */
+    @Deprecated
+    public String toString(final Locale locale) {
+        return toString(false, locale);
+    }
+
+    /**
      * Returns a string representation of this builder for debugging purpose.
      * The string representation is for debugging purpose and may change in 
any future version.
      * The default implementation delegates to {@link #toString(Locale)} with 
a null locale.
@@ -931,6 +970,6 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
      */
     @Override
     public String toString() {
-        return toString(null);
+        return toString(false, null);
     }
 }
diff --git 
a/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/GCOM_C.java
 
b/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/GCOM_C.java
index d4e1dd3..af3265b 100644
--- 
a/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/GCOM_C.java
+++ 
b/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/GCOM_C.java
@@ -129,10 +129,17 @@ public final class GCOM_C extends Convention {
 
     /**
      * Mapping from ACDD or CF-Convention attribute names to names of 
attributes used by GCOM-C.
+     * This map does not include attributes for geographic extent because the 
"Lower_left_latitude",
+     * "Lower_left_longitude", "Lower_right_latitude", <i>etc.</i> attributes 
are difficult to use.
+     * They are corners in the grid with no clear relationship with "real 
world" West and East bounds.
+     * We have no way to detect anti-meridian spanning (the {@code left > 
right} test is useless) and
+     * the minimal latitude may be in the middle of a border. Consequently a 
bounding box made from
+     * the corner minimal and maximal coordinates is not guaranteed to 
encompass the whole data,
+     * and may even contain no data at all.
      */
     private static final Map<String,String> ATTRIBUTES;
     static {
-        final Map<String,String> m = new HashMap<>();
+        final Map<String,String> m = new HashMap<>(16);
         m.put(AttributeNames.TITLE,               "Product_name");             
// identification­Info / citation / title
         m.put(AttributeNames.PRODUCT_VERSION,     "Product_version");          
// identification­Info / citation / edition
         m.put(AttributeNames.IDENTIFIER.TEXT,     "Product_file_name");        
// identification­Info / citation / identifier / code
diff --git 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java
 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java
index e62c53e..439a592 100644
--- 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java
+++ 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java
@@ -57,8 +57,8 @@ import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArraysExt;
+import org.apache.sis.measure.NumberRange;
 import org.apache.sis.measure.Units;
-import org.apache.sis.math.Vector;
 
 
 /**
@@ -171,20 +171,20 @@ abstract class CRSBuilder<D extends Datum, CS extends 
CoordinateSystem> {
      * Infers a new CRS for a {@link Grid}.
      *
      * <h4>CRS replacements</h4>
-     * The {@code linearizationTargets} argument allows to replace some CRSs 
inferred by this method by hard-coded CRSs.
-     * This is non-empty only when reading a netCDF file for a specific 
profile, i.e. a file decoded with a subclass of
-     * {@link Convention}. The CRS to be replaced is inferred from the axis 
directions.
+     * The {@code linearizations} argument allows to replace some CRSs 
inferred by this method by hard-coded CRSs.
+     * This is non-empty only when reading a netCDF file for a specific 
profile, i.e. a file decoded with a subclass
+     * of {@link Convention}. The CRS to be replaced is inferred from the axis 
directions.
      *
-     * @param  decoder               the decoder of the netCDF from which the 
CRS are constructed.
-     * @param  grid                  the grid for which the CRS are 
constructed.
-     * @param  linearizationTargets  CRS to use instead of CRS inferred by 
this method, or null or empty if none.
-     * @param  reorderGridToCRS      an affine transform doing a final step in 
a "grid to CRS" transform for ordering axes.
+     * @param  decoder           the decoder of the netCDF from which the CRS 
are constructed.
+     * @param  grid              the grid for which the CRS are constructed.
+     * @param  linearizations    contains CRS to use instead of CRS inferred 
by this method, or null or empty if none.
+     * @param  reorderGridToCRS  an affine transform doing a final step in a 
"grid to CRS" transform for ordering axes.
      *         Not used by this method, but may be modified for taking in 
account axis order changes caused by replacements
-     *         defined in {@code linearizationTargets}. Ignored (can be null) 
if {@code linearizationTargets} is null.
+     *         defined in {@code linearizations}. Ignored (can be null) if 
{@code linearizations} is null.
      * @return coordinate reference system from the given axes, or {@code 
null}.
      */
     public static CoordinateReferenceSystem assemble(final Decoder decoder, 
final Grid grid,
-            final List<CoordinateReferenceSystem> linearizationTargets, final 
Matrix reorderGridToCRS)
+            final List<GridCacheValue> linearizations, final Matrix 
reorderGridToCRS)
             throws DataStoreException, FactoryException, IOException
     {
         final List<CRSBuilder<?,?>> builders = new ArrayList<>(4);
@@ -200,8 +200,8 @@ abstract class CRSBuilder<D extends Datum, CS extends 
CoordinateSystem> {
          * We do not verify the datum; we assume that the linearizer that 
built the CRS
          * was consistent with `Convention.defaultHorizontalCRS(false)`.
          */
-        if ((linearizationTargets != null) && !linearizationTargets.isEmpty()) 
{
-            Linearizer.replaceInCompoundCRS(components, linearizationTargets, 
reorderGridToCRS);
+        if ((linearizations != null) && !linearizations.isEmpty()) {
+            Linearizer.replaceInCompoundCRS(components, linearizations, 
reorderGridToCRS);
         }
         switch (components.length) {
             case 0: return null;
@@ -426,12 +426,10 @@ previous:   for (int i=components.size(); --i >= 0;) {
             for (int i=cs.getDimension(); --i >= 0;) {
                 final CoordinateSystemAxis axis = cs.getAxis(i);
                 if (RangeMeaning.WRAPAROUND.equals(axis.getRangeMeaning())) {
-                    final Vector coordinates = axes[i].read();                 
         // Typically a cached vector.
-                    final int length = coordinates.size();
-                    if (length != 0) {
-                        final double first = coordinates.doubleValue(0);
-                        final double last  = coordinates.doubleValue(length - 
1);
-                        if (Math.min(first, last) >= 0 && Math.max(first, 
last) > axis.getMaximumValue()) {
+                    final NumberRange<?> range = axes[i].read().range();       
         // Vector is cached.
+                    if (range != null) {
+                        // Note: minimum/maximum are not necessarily first and 
last values in the vector.
+                        if (range.getMinDouble() >= 0 && range.getMaxDouble() 
> axis.getMaximumValue()) {
                             referenceSystem = (SingleCRS) 
AbstractCRS.castOrCopy(referenceSystem)
                                                 
.forConvention(AxesConvention.POSITIVE_RANGE);
                             break;
diff --git 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Grid.java 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Grid.java
index fe64f24..5471b56 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Grid.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Grid.java
@@ -289,27 +289,27 @@ public abstract class Grid extends NamedElement {
      * this method because this CRS will be used for adjusting axis order or 
for completion if grid mapping does not include
      * information for all dimensions.</p>
      *
-     * @param  decoder               the decoder for which CRS are constructed.
-     * @param  warnings              previous warnings, for avoiding to log 
the same message twice. Can be null.
-     * @param  linearizationTargets  CRS to use instead of CRS inferred by 
this method, or null or empty if none.
-     * @param  reorderGridToCRS      an affine transform doing a final step in 
a "grid to CRS" transform for ordering axes.
+     * @param  decoder           the decoder for which CRS are constructed.
+     * @param  warnings          previous warnings, for avoiding to log the 
same message twice. Can be null.
+     * @param  linearizations    contains CRS to use instead of CRS inferred 
by this method, or null or empty if none.
+     * @param  reorderGridToCRS  an affine transform doing a final step in a 
"grid to CRS" transform for ordering axes.
      *         Not used by this method, but may be modified for taking in 
account axis order changes caused by replacements
-     *         defined in {@code linearizationTargets}. Ignored (can be null) 
if {@code linearizationTargets} is null.
+     *         defined in {@code linearizations}. Ignored (can be null) if 
{@code linearizations} is null.
      * @return the CRS for this grid geometry, or {@code null}.
      * @throws IOException if an I/O operation was necessary but failed.
      * @throws DataStoreException if the CRS can not be constructed.
      */
     final CoordinateReferenceSystem getCoordinateReferenceSystem(final Decoder 
decoder, final List<Exception> warnings,
-            final List<CoordinateReferenceSystem> linearizationTargets, final 
Matrix reorderGridToCRS)
+            final List<GridCacheValue> linearizations, final Matrix 
reorderGridToCRS)
             throws IOException, DataStoreException
     {
-        final boolean isCached = (linearizationTargets == null) || 
linearizationTargets.isEmpty();
-        if (isCached & isCRSDetermined) {
+        final boolean useCache = (linearizations == null) || 
linearizations.isEmpty();
+        if (useCache & isCRSDetermined) {
             return crs;
         } else try {
-            if (isCached) isCRSDetermined = true;               // Set now for 
avoiding new attempts if creation fail.
-            final CoordinateReferenceSystem result = 
CRSBuilder.assemble(decoder, this, linearizationTargets, reorderGridToCRS);
-            if (isCached) crs = result;
+            if (useCache) isCRSDetermined = true;               // Set now for 
avoiding new attempts if creation fail.
+            final CoordinateReferenceSystem result = 
CRSBuilder.assemble(decoder, this, linearizations, reorderGridToCRS);
+            if (useCache) crs = result;
             return result;
         } catch (FactoryException | NullArgumentException ex) {
             if (isNewWarning(ex, warnings)) {
@@ -448,7 +448,7 @@ findFree:       for (int srcDim : 
axis.gridDimensionIndices) {
              * two-dimensional localization grid. Those transforms require two 
variables, i.e. "two-dimensional"
              * axes come in pairs.
              */
-            final List<CoordinateReferenceSystem> linearizationTargets = new 
ArrayList<>();
+            final List<GridCacheValue> linearizations = new ArrayList<>();
             for (int i=0; i<nonLinears.size(); i++) {         // Length of 
'nonLinears' may change in this loop.
                 if (nonLinears.get(i) == null) {
                     for (int j=i; ++j < nonLinears.size();) {
@@ -476,7 +476,7 @@ findFree:       for (int srcDim : 
axis.gridDimensionIndices) {
                                  * Replace the first transform by the 
two-dimensional localization grid and
                                  * remove the other transform. Removals need 
to be done in arrays too.
                                  */
-                                nonLinears.set(i, grid.transform);
+                                nonLinears.set(i, grid.gridToCRS);
                                 nonLinears.remove(j);
                                 final int n = nonLinears.size() - j;
                                 System.arraycopy(deferred,             j+1, 
deferred,             j, n);
@@ -484,7 +484,9 @@ findFree:       for (int srcDim : 
axis.gridDimensionIndices) {
                                 if (otherDim < srcDim) {
                                     gridDimensionIndices[i] = otherDim;     // 
Index of the first dimension.
                                 }
-                                
grid.getLinearizationTarget(linearizationTargets);
+                                if (grid.linearizationTarget != null) {
+                                    linearizations.add(grid);
+                                }
                                 break;                                      // 
Continue the 'i' loop.
                             }
                         }
@@ -506,7 +508,7 @@ findFree:       for (int srcDim : 
axis.gridDimensionIndices) {
              * This modification happens only if `Convention.linearizers()` 
specified transforms to apply on the
              * localization grid for making it more linear. This is a 
profile-dependent feature.
              */
-            final CoordinateReferenceSystem crs = 
getCoordinateReferenceSystem(decoder, null, linearizationTargets, affine);
+            final CoordinateReferenceSystem crs = 
getCoordinateReferenceSystem(decoder, null, linearizations, affine);
             /*
              * Final transform, as the concatenation of the non-linear 
transforms followed by the affine transform.
              * We concatenate the affine transform last because it may change 
axis order.
diff --git 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridCacheKey.java
 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridCacheKey.java
index fae9e03..6412ddc 100644
--- 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridCacheKey.java
+++ 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridCacheKey.java
@@ -37,6 +37,7 @@ import org.apache.sis.math.Vector;
  * </ul>
  *
  * The base class if for local cache. The inner class is for the global cache.
+ * {@code GridCacheKey}s are associated to {@link GridCacheValue}s in a hash 
map.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
@@ -67,7 +68,9 @@ class GridCacheKey {
     }
 
     /**
-     * Creates a global key from the given local key.
+     * Creates a global key from the given local key. This constructor is for 
{@link Global} construction only,
+     * because the information stored by this constructor are not sufficient 
for testing if two grids are equal.
+     * The {@link Global} subclass will add a MD5 checksum.
      */
     private GridCacheKey(final GridCacheKey keyLocal) {
         width  = keyLocal.width;
diff --git 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridCacheValue.java
 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridCacheValue.java
index cd3be6d..3f08c59 100644
--- 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridCacheValue.java
+++ 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridCacheValue.java
@@ -16,10 +16,10 @@
  */
 package org.apache.sis.internal.netcdf;
 
+import java.util.Map;
 import java.util.Set;
-import java.util.List;
+import java.util.Optional;
 import org.opengis.util.FactoryException;
-import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransformFactory;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
@@ -29,6 +29,7 @@ import 
org.apache.sis.referencing.operation.builder.LocalizationGridBuilder;
 /**
  * A value cached in {@link GridCacheKey.Global#CACHE}.
  * This is used for sharing common localization grids between different netCDF 
files.
+ * {@code GridCacheValue}s are associated to {@link GridCacheKey}s in a hash 
map.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
@@ -39,13 +40,23 @@ final class GridCacheValue {
     /**
      * The transform from grid coordinates to geographic or projected 
coordinates.
      */
-    final MathTransform transform;
+    final MathTransform gridToCRS;
 
     /**
-     * The target CRS of {@link #transform} if different than the CRS inferred 
by {@link CRSBuilder}.
+     * The target CRS of {@link #gridToCRS} if different than the CRS inferred 
by {@link CRSBuilder}.
      * This field is non-null if the target CRS has been changed by 
application of a linearizer.
      */
-    private CoordinateReferenceSystem targetCRS;
+    final CoordinateReferenceSystem linearizationTarget;
+
+    /**
+     * Whether axes need to be swapped in order to have the same direction 
before and after linearization.
+     * For example if input coordinates stored in the localization grid have 
(east, north) directions, then
+     * {@link #linearizationTarget} coordinates shall have (east, north) 
directions as well.
+     * This flag specifies whether input coordinates must be swapped for 
making above condition true.
+     *
+     * <p>This flag assumes that {@link #linearizationTarget} has two 
dimensions.</p>
+     */
+    final boolean axisSwap;
 
     /**
      * Creates a new "grid to CRS" together with target CRS.
@@ -53,25 +64,19 @@ final class GridCacheValue {
     GridCacheValue(final Set<Linearizer> linearizers, final 
LocalizationGridBuilder grid,
                    final MathTransformFactory factory) throws FactoryException
     {
-        transform = grid.create(factory);
-        grid.linearizer(true).ifPresent((e) -> {
-            final String name = e.getKey();
+        gridToCRS = grid.create(factory);
+        final Optional<Map.Entry<String,MathTransform>> e = 
grid.linearizer(true);
+        if (e.isPresent()) {
+            final String name = e.get().getKey();
             for (final Linearizer linearizer : linearizers) {
                 if (name.equals(linearizer.name())) {
-                    targetCRS = linearizer.getTargetCRS();
-                    break;
+                    linearizationTarget = linearizer.getTargetCRS();
+                    axisSwap = linearizer.axisSwap();
+                    return;
                 }
             }
-        });
-    }
-
-    /**
-     * Adds the target CRS to the given list if that CRS is different than the 
CRS inferred by {@link CRSBuilder}.
-     * This is an element of the list to provide to {@link 
CRSBuilder#assemble(Decoder, Grid, List, Matrix)}.
-     */
-    final void getLinearizationTarget(final List<CoordinateReferenceSystem> 
list) {
-        if (targetCRS != null) {
-            list.add(targetCRS);
         }
+        linearizationTarget = null;
+        axisSwap = false;
     }
 }
diff --git 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Linearizer.java
 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Linearizer.java
index 5a0f50f..7f46913 100644
--- 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Linearizer.java
+++ 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Linearizer.java
@@ -34,7 +34,9 @@ import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.internal.util.Strings;
 import org.apache.sis.internal.referencing.AxisDirections;
+import org.apache.sis.internal.referencing.ReferencingUtilities;
 import org.apache.sis.storage.DataStoreReferencingException;
+import org.apache.sis.util.ArraysExt;
 
 
 /**
@@ -42,6 +44,11 @@ import org.apache.sis.storage.DataStoreReferencingException;
  * Non-linear transforms are tested in "trials and errors" and the one 
resulting in best correlation
  * coefficients is selected.
  *
+ * <p>Before linearization, source coordinates may be in (latitude, longitude) 
or (longitude, latitude) order
+ * depending on the order of dimensions in netCDF variable. But after 
linearization, axes will be in a fixed
+ * order determined by {@link #targetCRS}. In other words, netCDF dimension 
order shall be ignored if a
+ * linearization is applied.</p>
+ *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
  *
@@ -84,10 +91,19 @@ public final class Linearizer {
 
     /**
      * The target coordinate reference system after application of the 
non-linear transform.
+     * May depend on the netCDF file being read (for example for choosing a 
UTM zone).
      */
     private CoordinateReferenceSystem targetCRS;
 
     /**
+     * Whether axes need to be swapped in order to have the same direction 
before and after the transform.
+     * For example if input coordinates have (east, north) directions, then 
output coordinates shall have
+     * (east, north) directions as well. This flag specifies whether input 
coordinates must be swapped for
+     * making above condition true.
+     */
+    private boolean axisSwap;
+
+    /**
      * Creates a new linearizer working on the specified datum.
      *
      * @param  datum  the datum to use. Should be consistent with {@link 
Convention#defaultHorizontalCRS(boolean)}.
@@ -113,6 +129,17 @@ public final class Linearizer {
     }
 
     /**
+     * Returns whether axes need to be swapped in order to have the same 
direction before and after the transform.
+     * For example if input coordinates have (east, north) directions, then 
output coordinates shall have (east, north)
+     * directions as well. This flag specifies whether input coordinates must 
be swapped for making above condition true.
+     *
+     * @see GridCacheValue#axisSwap
+     */
+    final boolean axisSwap() {
+        return axisSwap;
+    }
+
+    /**
      * Returns a string representation for debugging purposes.
      */
     @Override
@@ -141,25 +168,28 @@ public final class Linearizer {
                 throw new AssertionError(type);
             }
             /*
-             * Create a Universal Transverse Mercator (UTM) projection
-             * for the zone containing a point in the middle of the grid.
+             * Create a Universal Transverse Mercator (UTM) projection for the 
zone containing a point in
+             * the middle of the grid. We apply `Math.signum(…)` on the 
latitude for avoiding stereographic
+             * projections near poles and for avoiding Norway and Svalbard 
special cases.
              */
             case UTM: {
                 final Envelope bounds = grid.getSourceEnvelope(false);
                 final double[] median = grid.getControlPoint(
                         (int) Math.round(bounds.getMedian(0)),
                         (int) Math.round(bounds.getMedian(1)));
-                ProjectedCRS crs = datum.universal(median[ydim], median[xdim]);
+                final ProjectedCRS crs = 
datum.universal(Math.signum(median[ydim]), median[xdim]);
+                assert 
ReferencingUtilities.startsWithNorthEast(crs.getBaseCRS().getCoordinateSystem());
                 transform = crs.getConversionFromBase().getMathTransform();
                 targetCRS = crs;
                 break;
             }
         }
         /*
-         * Above transform expects (latitude, longitude) inputs. If grid 
coordinates
-         * are in (longitude, latitude) order, we need to swap input 
coordinates.
+         * Above transform expects (latitude, longitude) inputs (verified by 
assertion).
+         * If grid coordinates are in (longitude, latitude) order, we must 
swap inputs.
          */
-        if (xdim < ydim) {
+        axisSwap = ydim < xdim;
+        if (!axisSwap) {
             final Matrix3 m = new Matrix3();
             m.m00 = m.m11 = 0;
             m.m01 = m.m10 = 1;
@@ -199,6 +229,11 @@ public final class Linearizer {
                 final MathTransform transform = 
linearizer.gridToTargetCRS(grid, xdim, ydim);
                 projections.put(linearizer.name(), transform);
             }
+            /*
+             * Axis order before linearization was taken in account by above 
`gridToTargetCRS(…, xdim, ydim)`.
+             * Consequently arguments below shall specify only the dimensions 
to select without reordering axes.
+             * Note that after linearization, axes will be in a fixed order 
determined by the CRS.
+             */
             grid.addLinearizers(projections, false, Math.min(xdim, ydim), 
Math.max(xdim, ydim));
         }
     }
@@ -207,22 +242,27 @@ public final class Linearizer {
      * Given CRS components inferred by {@link CRSBuilder}, replaces CRS 
components in the dimensions
      * where linearization has been applied. The CRS components to replace are 
inferred from axis directions.
      *
+     * <p>This static method is defined here for keeping in a single class all 
codes related to linearization.</p>
+     *
      * @param  components        the components of the compound CRS that 
{@link CRSBuilder} inferred.
      * @param  replacements      the {@link #targetCRS} of linearizations.
      * @param  reorderGridToCRS  an affine transform doing a final step in a 
"grid to CRS" transform for ordering axes.
      *         Not used by this method, but modified for taking in account 
axis order changes caused by replacements.
      */
-    static void replaceInCompoundCRS(final SingleCRS[] components,
-            final List<CoordinateReferenceSystem> replacements, final Matrix 
reorderGridToCRS)
-            throws DataStoreReferencingException
+    static void replaceInCompoundCRS(final SingleCRS[] components, final 
List<GridCacheValue> replacements,
+                                     final Matrix reorderGridToCRS) throws 
DataStoreReferencingException
     {
         Matrix original = null;
-search: for (final CoordinateReferenceSystem targetCRS : replacements) {
+search: for (final GridCacheValue cache : replacements) {
+            final CoordinateReferenceSystem targetCRS = 
cache.linearizationTarget;
             int firstDimension = 0;
             for (int i=0; i < components.length; i++) {
                 final SingleCRS sourceCRS = components[i];
                 final int[] r = 
AxisDirections.indicesOfColinear(sourceCRS.getCoordinateSystem(), 
targetCRS.getCoordinateSystem());
                 if (r != null) {
+                    if (cache.axisSwap) {
+                        ArraysExt.swap(r, 0, 1);
+                    }
                     for (int j=0; j<r.length; j++) {
                         if (r[j] != j) {
                             final int oldRow = r[j] + firstDimension;

Reply via email to