You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2018/11/23 02:06:38 UTC

[sis] branch geoapi-4.0 updated: First attempt to take in account the two-dimensional localization grid found in HYCOM files.

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 c0f83ed  First attempt to take in account the two-dimensional localization grid found in HYCOM files.
c0f83ed is described below

commit c0f83edc66774d9586892439320866c971e54866
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Fri Nov 23 03:05:47 2018 +0100

    First attempt to take in account the two-dimensional localization grid found in HYCOM files.
---
 .../java/org/apache/sis/math/RepeatedVector.java   |   8 +-
 .../src/main/java/org/apache/sis/math/Vector.java  |  60 +++++++-----
 .../java/org/apache/sis/internal/netcdf/Axis.java  |  87 +++++++++++------
 .../java/org/apache/sis/internal/netcdf/Grid.java  | 104 ++++++++++++++++-----
 .../sis/internal/netcdf/impl/ChannelDecoder.java   |  33 +++++--
 .../apache/sis/internal/netcdf/impl/GridInfo.java  |  20 +++-
 6 files changed, 219 insertions(+), 93 deletions(-)

diff --git a/core/sis-utility/src/main/java/org/apache/sis/math/RepeatedVector.java b/core/sis-utility/src/main/java/org/apache/sis/math/RepeatedVector.java
index 0a9b7a0..db76073 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/math/RepeatedVector.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/math/RepeatedVector.java
@@ -106,10 +106,10 @@ final class RepeatedVector extends Vector implements Serializable {
     }
 
     /**
-     * Creates a vector of repeated data from the result of a call to {@link Vector#repetitions()}.
+     * Creates a vector of repeated data from the result of a call to {@link Vector#repetitions(int...)}.
      *
      * @param base         the vector on which this vector is derived from.
-     * @param repetitions  results of {@link Vector#repetitions()}. Must be non-empty.
+     * @param repetitions  results of {@link Vector#repetitions(int...)}. Must be non-empty.
      * @param tolerance    tolerance factor for compression of the base vector.
      */
     RepeatedVector(final Vector base, final int[] repetitions, final double tolerance) {
@@ -177,10 +177,10 @@ final class RepeatedVector extends Vector implements Serializable {
 
     /**
      * Returns the parameters used by this {@code RepeatedVector} instance on the assumption
-     * that they are the result of a previous invocation to {@link Vector#repetitions()}.
+     * that they are the result of a previous invocation to {@link Vector#repetitions(int...)}.
      */
     @Override
-    public int[] repetitions() {
+    public int[] repetitions(final int... candidates) {
         if (cycleLength * occurrences >= size) {
             return new int[] {occurrences};
         } else {
diff --git a/core/sis-utility/src/main/java/org/apache/sis/math/Vector.java b/core/sis-utility/src/main/java/org/apache/sis/math/Vector.java
index 959b42b..8a4af37 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/math/Vector.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/math/Vector.java
@@ -530,12 +530,18 @@ public abstract class Vector extends AbstractList<Number> implements RandomAcces
      * Those grids sometime have constant longitude for the same column index, or constant latitude for the same row index.
      * This method can detect such regularity, which allows more efficient handling of the <cite>grid to CRS</cite> transform.</p>
      *
+     * @param  candidates  probable values, or {@code null} or an empty array if unknown. If non-empty, those values will be used
+     *         for narrowing the search, which may improve performances. There is no guarantees that the values returned by this
+     *         method will be among the given candidates.
      * @return the number of times that entities (numbers, or group of numbers) appears consecutively with identical values.
      *         If no such repetition is found, an empty array.
      *
      * @since 1.0
      */
-    public int[] repetitions() {
+    public int[] repetitions(int... candidates) {
+        if (candidates != null && candidates.length == 0) {
+            candidates = null;
+        }
         final int size = size();
         if (size >= 2) {
             /*
@@ -545,7 +551,7 @@ public abstract class Vector extends AbstractList<Number> implements RandomAcces
              * is faster.
              */
             int r0 = 0;
-            for (int i=0; i < size; i += r0) {
+            for (int i=0; i < size-1; i += r0) {
                 final int p = r0;
                 r0 = indexOf(i, i+1, false) - i;
                 if (r0 <= 1 || (p % r0) != 0) {
@@ -562,30 +568,38 @@ public abstract class Vector extends AbstractList<Number> implements RandomAcces
              * very slow when r0 = 1 because equals(…) is invoked for all values.  Computing an amount of values that
              * we can skip in the special case where r0 = 1 increases the speed a lot.
              */
+            int candidateIndex = 0;
             final int skip = (r0 == 1) ? indexOf(0, 1, false) : 0;
             int r = 0;
-nextMatch:  for (;;) {
-                r += r0;
-                if (skip != 0) {
-                    /*
-                     * Optimization for reducing the number of method calls when r0 = 1: the default algorithm is to
-                     * search for a position multiple of 'r0' where all values since the beginning of the vector are
-                     * repeated. But if 'r0' is 1, the default algorithms perform a costly check at every positions.
-                     * To avoid that, we use 'indexOf' for searching the index of the next position where a match may
-                     * exist (we don't care anymore about multiples of r0 since r0 is 1). If the first expected values
-                     * are constants, we use 'indexOf' again for checking efficiently those constants.
-                     */
-                    r = indexOf(0, r, true);
-                    if (skip != 1) {
-                        if (r + skip >= size) break;
-                        final int end = indexOf(r, r+1, false);
-                        if (end - r != skip) {
-                            r = end - 1;
-                            continue;
+search:     for (;;) {
+                if (candidates != null) {
+                    do {
+                        if (candidateIndex >= candidates.length) break search;
+                        r = r0 * candidates[candidateIndex++];
+                    } while (r <= 0 || r >= size);
+                } else {
+                    r += r0;
+                    if (skip != 0) {
+                        /*
+                         * Optimization for reducing the number of method calls when r0 = 1: the default algorithm is to
+                         * search for a position multiple of 'r0' where all values since the beginning of the vector are
+                         * repeated. But if 'r0' is 1, the default algorithms perform a costly check at every positions.
+                         * To avoid that, we use 'indexOf' for searching the index of the next position where a match may
+                         * exist (we don't care anymore about multiples of r0 since r0 is 1). If the first expected values
+                         * are constants, we use 'indexOf' again for checking efficiently those constants.
+                         */
+                        r = indexOf(0, r, true);
+                        if (skip != 1) {
+                            if (r + skip >= size) break;
+                            final int end = indexOf(r, r+1, false);
+                            if (end - r != skip) {
+                                r = end - 1;
+                                continue;
+                            }
                         }
                     }
+                    if (r >= size) break;
                 }
-                if (r >= size) break;
                 if (equals(skip, Math.min(r0, size - r), this, r + skip)) {
                     /*
                      * Found a possible repetition of length r. Verify if this repetition pattern is observed until
@@ -593,7 +607,7 @@ nextMatch:  for (;;) {
                      */
                     for (int i=r; i<size; i += r) {
                         if (!equals(0, Math.min(r, size - i), this, i)) {
-                            continue nextMatch;
+                            continue search;
                         }
                     }
                     break;      // At this point we verified that the repetition is observed until the vector end.
@@ -1199,7 +1213,7 @@ nextMatch:  for (;;) {
          * end must be at least 1/4 of the vector size.
          */
         if (length > 20*Integer.SIZE / getBitCount()) {
-            final int[] repetitions = repetitions();
+            final int[] repetitions = repetitions(MathFunctions.divisors(length));
             switch (repetitions.length) {
                 default: if (length - repetitions[1] < length/4) break;               // Otherwise fallthrough.
                 case 1:  return new RepeatedVector(this, repetitions, tolerance);
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java
index bae678d..999267f 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java
@@ -48,6 +48,9 @@ import ucar.nc2.constants.CF;
  * Whether the array length is 1 or 2 depends on whether the wrapped netCDF axis is an instance of
  * {@link ucar.nc2.dataset.CoordinateAxis1D} or {@link ucar.nc2.dataset.CoordinateAxis2D} respectively.
  *
+ * <p>The natural ordering of axes is based on the order in which dimensions are declared for a variable.
+ * This is not necessarily the same order than the order in which variables are listed in the netCDF file.</p>
+ *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.0
  *
@@ -56,7 +59,7 @@ import ucar.nc2.constants.CF;
  * @since 0.3
  * @module
  */
-public final class Axis extends NamedElement {
+public final class Axis extends NamedElement implements Comparable<Axis> {
     /**
      * The abbreviation, also used as a way to identify the axis type.
      * This is a controlled vocabulary: if any abbreviation is changed,
@@ -241,6 +244,19 @@ public final class Axis extends NamedElement {
     }
 
     /**
+     * Compares this axis with the given axis for ordering based on the dimensions declared in a variable.
+     * This is used for sorting axes in the same order than the dimension order on the variable using axes.
+     * This order is not necessarily the same than the order in which variables are listed in the netCDF file.
+     *
+     * @param  other  the other axis to compare to this axis.
+     * @return -1 if this axis should be before {@code other}, +1 if it should be after.
+     */
+    @Override
+    public int compareTo(final Axis other) {
+        return sourceDimensions[0] - other.sourceDimensions[0];
+    }
+
+    /**
      * Creates an ISO 19111 axis from the information stored in this netCDF axis.
      *
      * @param  factory  the factory to use for creating the coordinate system axis.
@@ -327,37 +343,41 @@ public final class Axis extends NamedElement {
     final boolean trySetTransform(final Matrix gridToCRS, final int srcEnd, final int tgtDim,
             final List<MathTransform> nonLinears) throws IOException, DataStoreException
     {
-        /*
-         * Normal case where the axis has only one dimension.
-         */
-        if (sourceDimensions.length == 1) {
-            final int srcDim = srcEnd - sourceDimensions[0];
-            if (coordinates.trySetTransform(gridToCRS, srcDim, tgtDim, null)) {
-                return true;
-            } else {
-                nonLinears.add(MathTransforms.interpolate(null, coordinates.read().doubleValues()));
-                return false;
+        switch (sourceDimensions.length) {
+            /*
+             * Defined as a matter of principle, but should never happen.
+             */
+            case 0: return true;
+            /*
+             * Normal case where the axis has only one dimension.
+             */
+            case 1: {
+                final int srcDim = srcEnd - sourceDimensions[0];
+                if (coordinates.trySetTransform(gridToCRS, srcDim, tgtDim, null)) {
+                    return true;
+                } else {
+                    nonLinears.add(MathTransforms.interpolate(null, coordinates.read().doubleValues()));
+                    return false;
+                }
             }
-        }
-        /*
-         * In netCDF files, axes are sometime associated to two-dimensional localization grids.
-         * If this is the case, then the following block checks if we can reduce those grids to
-         * one-dimensional vector. For example the following localisation grids:
-         *
-         *    10 10 10 10                  10 12 15 20
-         *    12 12 12 12        or        10 12 15 20
-         *    15 15 15 15                  10 12 15 20
-         *    20 20 20 20                  10 12 15 20
-         *
-         * can be reduced to a one-dimensional {10 12 15 20} vector (orientation matter however).
-         *
-         * Note: following block is currently restricted to the two-dimensional case, but it could
-         * be generalized to n-dimensional case if we resolve the default case in the switch statement.
-         */
-        if (sourceDimensions.length == 2) {
-            Vector data = coordinates.read();
-            if (!coordinates.readTriesToCompress() || data.getClass().getSimpleName().equals("RepeatedVector")) {
-                final int[] repetitions = data.repetitions();       // Detects repetitions as illustrated above.
+            /*
+             * In netCDF files, axes are sometime associated to two-dimensional localization grids.
+             * If this is the case, then the following block checks if we can reduce those grids to
+             * one-dimensional vector. For example the following localisation grids:
+             *
+             *    10 10 10 10                  10 12 15 20
+             *    12 12 12 12        or        10 12 15 20
+             *    15 15 15 15                  10 12 15 20
+             *    20 20 20 20                  10 12 15 20
+             *
+             * can be reduced to a one-dimensional {10 12 15 20} vector (orientation matter however).
+             *
+             * Note: following block is currently restricted to the two-dimensional case, but it could
+             * be generalized to n-dimensional case if we resolve the default case in the switch statement.
+             */
+            case 2: {
+                Vector data = coordinates.read();
+                final int[] repetitions = data.repetitions(sourceSizes);    // Detects repetitions as illustrated above.
                 if (repetitions.length != 0) {
                     for (int i=0; i<sourceDimensions.length; i++) {
                         final int srcDim = srcEnd - sourceDimensions[i];    // "Natural" order is reverse of netCDF order.
@@ -385,6 +405,11 @@ public final class Axis extends NamedElement {
                     }
                 }
             }
+            /*
+             * Localization grid of 3 dimensions or more are theoretically possible, but uncommon.
+             * Such grids are not yet supported. Note that we can read three or more dimensional data;
+             * it is only the localization grid which is limited to one or two dimensions.
+             */
         }
         nonLinears.add(null);
         return false;
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 c8e1d2f..48ac268 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
@@ -17,6 +17,7 @@
 package org.apache.sis.internal.netcdf;
 
 import java.util.List;
+import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.io.IOException;
@@ -32,10 +33,12 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.metadata.spatial.DimensionNameType;
 import org.apache.sis.internal.metadata.AxisDirections;
 import org.apache.sis.referencing.operation.matrix.Matrices;
+import org.apache.sis.referencing.operation.builder.LocalizationGridBuilder;
 import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.util.NullArgumentException;
+import org.apache.sis.math.Vector;
 
 
 /**
@@ -254,7 +257,7 @@ public abstract class Grid extends NamedElement {
              */
             int srcEnd = getSourceDimensions();
             int tgtEnd = getTargetDimensions();
-            final int[] deferred = new int[axes.length];
+            final int[] deferred = new int[axes.length];                    // Indices of axes that have been deferred.
             final List<MathTransform> nonLinears = new ArrayList<>();
             final Matrix affine = Matrices.createZero(tgtEnd + 1, srcEnd + 1);
             affine.setElement(tgtEnd--, srcEnd--, 1);
@@ -264,47 +267,106 @@ public abstract class Grid extends NamedElement {
                 }
             }
             /*
-             * If we have not been able to set coefficients in the matrix (because the transform is non-linear),
-             * set a single scale factor to 1 in the matrix row; we will concatenate later with a non-linear transform.
-             * The coefficient that we set to 1 is the one for the source dimension which is not already taken by another row.
+             * If we have not been able to set some coefficients in the matrix (because some transforms are non-linear),
+             * set a single scale factor to 1 in the matrix row. The coefficient that we set to 1 is the one for the source
+             * dimension which is not already taken by another row.
              */
-            final MathTransformFactory factory = decoder.getMathTransformFactory();
-            for (int i=0; i<nonLinears.size(); i++) {
+            final int[] sourceDimensions = new int[nonLinears.size()];
+            Arrays.fill(sourceDimensions, -1);
+            for (int i=0; i<sourceDimensions.length; i++) {
                 final int d = deferred[i];
-findFree:       for (int srcDim : axes[d].sourceDimensions) {
+                final Axis axis = axes[d];
+findFree:       for (int srcDim : axis.sourceDimensions) {
                     srcDim = srcEnd - srcDim;
                     for (int j=affine.getNumRow(); --j>=0;) {
                         if (affine.getElement(j, srcDim) != 0) {
                             continue findFree;
                         }
                     }
+                    sourceDimensions[i] = srcDim;
                     final int tgtDim = tgtEnd - d;
                     affine.setElement(tgtDim, srcDim, 1);
-                    /*
-                     * Prepare non-linear transform here for later concatenation.
-                     * TODO: what to do if the transform is null?
-                     */
-                    MathTransform tr = nonLinears.get(i);
-                    if (tr != null) {
-                        tr = factory.createPassThroughTransform(srcDim, tr, (srcEnd + 1) - (srcDim + tr.getSourceDimensions()));
-                        nonLinears.set(i, tr);
-                    }
                     break;
                 }
             }
             /*
+             * Search for non-linear transforms not yet constructed. It may be because the transform requires a
+             * two-dimensional localization grid. Those transforms require two variables, i.e. "two-dimensional"
+             * axes come in pairs.
+             */
+            final MathTransformFactory factory = decoder.getMathTransformFactory();
+            for (int i=0; i<nonLinears.size(); i++) {         // Length of 'nonLinears' may change in this loop.
+                if (nonLinears.get(i) == null) {
+                    final Axis axis = axes[deferred[i]];
+                    if (axis.sourceDimensions.length == 2) {
+                        final int d1 = axis.sourceDimensions[0];
+                        final int d2 = axis.sourceDimensions[1];
+                        for (int j=i; ++j < nonLinears.size();) {
+                            if (nonLinears.get(j) == null) {
+                                final Axis other = axes[deferred[j]];
+                                if (other.sourceDimensions.length == 2) {
+                                    final int o1 = other.sourceDimensions[0];
+                                    final int o2 = other.sourceDimensions[1];
+                                    if ((o1 == d1 && o2 == d2) || (o1 == d2 && o2 == d1)) {
+                                        /*
+                                         * Found two axes for the same set of dimensions, which implies that they have
+                                         * the same shape (width and height).  In current implementation, we also need
+                                         * those axes to be at consecutive source dimensions.
+                                         */
+                                        final int srcDim   = sourceDimensions[i];
+                                        final int otherDim = sourceDimensions[j];
+                                        if (Math.abs(srcDim - otherDim) == 1) {
+                                            final int width  = axis.sourceSizes[0];
+                                            final int height = axis.sourceSizes[1];
+                                            final LocalizationGridBuilder grid = new LocalizationGridBuilder(width, height);
+                                            final Vector v1 =  axis.coordinates.read();
+                                            final Vector v2 = other.coordinates.read();
+                                            final double[] target = new double[2];
+                                            int index = 0;
+                                            for (int y=0; y<height; y++) {
+                                                for (int x=0; x<width; x++) {
+                                                    target[0] = v1.doubleValue(index);
+                                                    target[1] = v2.doubleValue(index);
+                                                    grid.setControlPoint(x, y, target);
+                                                    index++;
+                                                }
+                                            }
+                                            /*
+                                             * 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.create(factory));
+                                            nonLinears.remove(j);
+                                            final int n = nonLinears.size() - j;
+                                            System.arraycopy(deferred,         j+1, deferred,         j, n);
+                                            System.arraycopy(sourceDimensions, j+1, sourceDimensions, j, n);
+                                            if (otherDim < srcDim) {
+                                                sourceDimensions[i] = otherDim;
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            /*
              * 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.
              */
             MathTransform gridToCRS = null;
+            final int nonLinearCount = nonLinears.size();
             nonLinears.add(factory.createAffineTransform(affine));
-            for (final MathTransform tr : nonLinears) {
+            for (int i=0; i <= nonLinearCount; i++) {
+                MathTransform tr = nonLinears.get(i);
                 if (tr != null) {
-                    if (gridToCRS == null) {
-                        gridToCRS = tr;
-                    } else {
-                        gridToCRS = factory.createConcatenatedTransform(gridToCRS, tr);
+                    if (i < nonLinearCount) {
+                        final int srcDim = sourceDimensions[i];
+                        tr = factory.createPassThroughTransform(srcDim, tr,
+                                        (srcEnd + 1) - (srcDim + tr.getSourceDimensions()));
                     }
+                    gridToCRS = (gridToCRS == null) ? tr : factory.createConcatenatedTransform(gridToCRS, tr);
                 }
             }
             geometry = new GridGeometry(getExtent(axes), PixelInCell.CELL_CENTER, gridToCRS, getCoordinateReferenceSystem(decoder));
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
index 033fcd0..f468756 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
@@ -18,9 +18,12 @@ package org.apache.sis.internal.netcdf.impl;
 
 import java.util.Set;
 import java.util.Map;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.AbstractMap;
+import java.util.AbstractList;
+import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.LinkedHashMap;
 import java.util.IdentityHashMap;
@@ -35,7 +38,6 @@ import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.nio.channels.ReadableByteChannel;
-import java.util.AbstractList;
 import javax.measure.UnitConverter;
 import javax.measure.IncommensurableException;
 import javax.measure.format.ParserException;
@@ -868,7 +870,8 @@ public final class ChannelDecoder extends Decoder {
              * For each variable, get its list of axes. More than one variable may have the same list of axes,
              * so we remember the previously created instances in order to share the grid geometry instances.
              */
-            final Set<VariableInfo> axes = new LinkedHashSet<>(4);
+            final Set<VariableInfo> axes = new LinkedHashSet<>(8);
+            final Set<Dimension> usedDimensions = new HashSet<>(8);
             final Map<GridInfo,GridInfo> shared = new LinkedHashMap<>();
 nextVar:    for (final VariableInfo variable : variables) {
                 if (variable.isCoordinateSystemAxis() || variable.dimensions.length == 0) {
@@ -881,38 +884,50 @@ nextVar:    for (final VariableInfo variable : variables) {
                  * of this method. If and only if we can find all axes, we create the GridGeometryInfo.
                  * This is a "all or nothing" operation.
                  */
+                axes.clear();
+                usedDimensions.clear();
                 final CharSequence[] coordinates = variable.getCoordinateVariables();
                 if (coordinates.length != 0) {
                     for (int i=coordinates.length; --i >= 0;) {
                         final VariableInfo axis = findVariable(coordinates[i].toString());
                         if (axis == null) {
+                            usedDimensions.clear();
                             axes.clear();
                             break;
                         }
                         axes.add(axis);
+                        usedDimensions.addAll(Arrays.asList(axis.dimensions));
                     }
                 }
-                if (axes.isEmpty()) {
-                    for (final Dimension dimension : variable.dimensions) {
+                /*
+                 * In theory the "coordinates" attribute would enumerate all axis needed for covering all dimensions,
+                 * and we would not need to check for variables having dimension names. However in practice there is
+                 * incomplete attributes, so we check for other dimensions even if the above loop did some work.
+                 */
+                int mixedFlag = axes.isEmpty() ? 0 : 1;
+                for (final Dimension dimension : variable.dimensions) {
+                    if (usedDimensions.add(dimension)) {
                         final List<VariableInfo> axis = dimToAxes.get(dimension);       // Should have only 1 element.
                         if (axis == null) {
-                            axes.clear();
                             continue nextVar;
                         }
                         axes.addAll(axis);
+                        mixedFlag |= 2;
                     }
                 }
                 /*
-                 * Creates the grid geometry using the given domain and range,
-                 * reusing existing instance if one exists.
+                 * Creates the grid geometry using the given domain and range, reusing existing instance if one exists.
+                 * We usually try to preserve axis order as declared in the netCDF file. But if we mixed axes inferred
+                 * from the "coordinates" attribute and axes inferred from variable names matching dimension names, we
+                 * are better to sort them.
                  */
-                GridInfo gridGeometry = new GridInfo(variable.dimensions, axes.toArray(new VariableInfo[axes.size()]));
+                GridInfo gridGeometry = new GridInfo(variable.dimensions,
+                        axes.toArray(new VariableInfo[axes.size()]), mixedFlag == 3);
                 GridInfo existing = shared.putIfAbsent(gridGeometry, gridGeometry);
                 if (existing != null) {
                     gridGeometry = existing;
                 }
                 variable.gridGeometry = gridGeometry;
-                axes.clear();
             }
             gridGeometries = shared.values().toArray(new Grid[shared.size()]);
         }
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/GridInfo.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/GridInfo.java
index 033c74a..cddf008 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/GridInfo.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/GridInfo.java
@@ -88,15 +88,22 @@ final class GridInfo extends Grid {
     private final VariableInfo[] range;
 
     /**
+     * Whether axes should be sorted instead than relying on the order found in netCDF file.
+     */
+    private final boolean sortAxes;
+
+    /**
      * Constructs a new grid geometry information.
      * The {@code domain} and {@code range} arrays often have the same length, but not necessarily.
      *
-     * @param  domain  describes the input values of the "grid to CRS" conversion.
-     * @param  range   the output values of the "grid to CRS" conversion.
+     * @param  domain    describes the input values of the "grid to CRS" conversion.
+     * @param  range     the output values of the "grid to CRS" conversion.
+     * @param  sortAxes  whether axes should be sorted instead than relying on the order found in netCDF file.
      */
-    GridInfo(final Dimension[] domain, final VariableInfo[] range) {
-        this.domain = domain;
-        this.range  = range;
+    GridInfo(final Dimension[] domain, final VariableInfo[] range, final boolean sortAxes) {
+        this.domain   = domain;
+        this.range    = range;
+        this.sortAxes = sortAxes;
     }
 
     /**
@@ -237,6 +244,9 @@ final class GridInfo extends Grid {
             axes[targetDim] = new Axis(this, axis, abbreviation, axis.getAttributeString(CF.POSITIVE),
                                        ArraysExt.resize(indices, i), ArraysExt.resize(sizes, i));
         }
+        if (sortAxes) {
+            Arrays.sort(axes);
+        }
         return axes;
     }