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 a7dc22f9c4d2273b331dd3abb399752c9805d21f
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Wed Jan 19 14:16:32 2022 +0100

    Resolve more issues with the use of axis direction such as "South along 90° 
East".
    It fixes some exceptions that were thrown when using Polar Stereographic 
projection.
---
 .../apache/sis/coverage/grid/DimensionReducer.java |   2 +-
 .../org/apache/sis/coverage/grid/GridGeometry.java |   2 +-
 .../sis/coverage/grid/GridDerivationTest.java      |  27 ++++
 .../sis/internal/referencing/AxesMapper.java       | 160 +++++++++++++++++++++
 .../sis/internal/referencing/AxisDirections.java   |  73 ++++------
 .../sis/referencing/cs/CoordinateSystems.java      |  26 +++-
 .../sis/referencing/cs/DirectionAlongMeridian.java |  15 +-
 .../apache/sis/referencing/cs/package-info.java    |   2 +-
 .../sis/internal/referencing/AxesMapperTest.java   |  97 +++++++++++++
 .../internal/referencing/AxisDirectionsTest.java   |  27 ++--
 .../sis/test/suite/ReferencingTestSuite.java       |   1 +
 ide-project/NetBeans/nbproject/genfiles.properties |   2 +-
 ide-project/NetBeans/nbproject/project.xml         |   2 +
 .../org/apache/sis/internal/netcdf/Linearizer.java |  43 ++----
 14 files changed, 377 insertions(+), 102 deletions(-)

diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DimensionReducer.java
 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DimensionReducer.java
index 884380d..568cd1c 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DimensionReducer.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DimensionReducer.java
@@ -63,7 +63,7 @@ final class DimensionReducer {
                 final CoordinateSystem sourceCS = 
sourceCRS.getCoordinateSystem();
                 final CoordinateSystem targetCS = 
targetCRS.getCoordinateSystem();
                 if (sourceCS.getDimension() < targetCS.getDimension()) {
-                    dimensions = AxisDirections.indicesOfColinear(targetCS, 
sourceCS);
+                    dimensions = 
AxisDirections.indicesOfLenientMapping(targetCS, sourceCS);
                     if (dimensions != null) {
                         Arrays.sort(dimensions);
                         reducedCRS = CRS.reduce(targetCRS, dimensions);
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
index d635d13..79ae461 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
@@ -625,7 +625,7 @@ public class GridGeometry implements LenientComparable, 
Serializable {
                     if (targetCRS != sourceCRS) {
                         final CoordinateSystem sourceCS = 
sourceCRS.getCoordinateSystem();
                         final CoordinateSystem targetCS = 
targetCRS.getCoordinateSystem();
-                        sourceDimensions = 
AxisDirections.indicesOfColinear(sourceCS, targetCS);
+                        sourceDimensions = 
AxisDirections.indicesOfLenientMapping(sourceCS, targetCS);
                         if (sourceDimensions != null) {
                             final double[] lowerCorner = new 
double[sourceDimensions.length];
                             final double[] upperCorner = new 
double[sourceDimensions.length];
diff --git 
a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridDerivationTest.java
 
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridDerivationTest.java
index a4ab028..f322466 100644
--- 
a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridDerivationTest.java
+++ 
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridDerivationTest.java
@@ -32,6 +32,7 @@ import org.apache.sis.geometry.DirectPosition2D;
 import org.apache.sis.geometry.GeneralDirectPosition;
 import org.apache.sis.internal.referencing.Formulas;
 import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
+import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.referencing.cs.AxesConvention;
 import org.apache.sis.referencing.crs.DefaultCompoundCRS;
 import org.apache.sis.referencing.operation.matrix.Matrices;
@@ -305,6 +306,32 @@ public final strictfp class GridDerivationTest extends 
TestCase {
     }
 
     /**
+     * Tests {@link GridDerivation#subgrid(Envelope, double...)} on a grid 
using a polar projection.
+     * The test also uses a geographic envelope with more dimensions than the 
source grid geometry.
+     * The difficulty is that axis directions do not match directly: the 
source grid has directions
+     * such as "South along 90° meridian".
+     */
+    @Test
+    public void testSubgridOnPolarProjection() {
+        GeneralEnvelope envelope = new 
GeneralEnvelope(CommonCRS.WGS84.universal(90, 0));
+        envelope.setRange(0, -1000, 1500);
+        envelope.setRange(1, -2000, 1800);
+        GridGeometry grid = new GridGeometry(new GridExtent(500, 400), 
envelope, GridOrientation.HOMOTHETY);
+
+        envelope = new GeneralEnvelope(HardCodedCRS.WGS84_WITH_TIME);
+        envelope.setRange(0, -45, -44);
+        envelope.setRange(1,  64,  65);
+        envelope.setRange(2,  20,  40);
+        final GridDerivation derivation = grid.derive();
+        grid = derivation.subgrid(envelope, 0.002, 0.001).build();
+        /*
+         * The main test is to check that we reach this point without an 
exception being thrown.
+         * We add a small arbitrary test below as a matter of principle.
+         */
+        assertTrue(envelope.subEnvelope(0, 2).intersects(grid.getEnvelope()));
+    }
+
+    /**
      * Tests {@link GridDerivation#subgrid(GridExtent, int...)}
      * with an integer amount of tiles, operating only on extents.
      */
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxesMapper.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxesMapper.java
new file mode 100644
index 0000000..462526f
--- /dev/null
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxesMapper.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.referencing;
+
+import org.opengis.referencing.cs.AxisDirection;
+import org.opengis.referencing.cs.CoordinateSystem;
+import org.apache.sis.referencing.cs.CoordinateSystems;
+import org.apache.sis.internal.util.Numerics;
+
+import static java.lang.Long.numberOfTrailingZeros;
+
+
+/**
+ * Maps coordinate axes for a "sub-coordinate system" to the axes of a 
coordinate systems with more dimensions.
+ * This enumeration contains the criterion to apply for matching axes, in 
priority order.
+ *
+ * <p>In current implementation, the enumeration values are used only 
internally.
+ * The useful method is {@link #indices(CoordinateSystem, 
CoordinateSystem)}.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.2
+ * @since   1.2
+ * @module
+ */
+enum AxesMapper {
+    /**
+     * Match axes having the exact same direction.
+     */
+    SAME,
+
+    /**
+     * Match axes having opposite direction.
+     */
+    OPPOSITE,
+
+    /**
+     * Match axes of the kind "South along 90° East".
+     */
+    POLAR;
+
+    /**
+     * Bitmask for axis direction handled in a special way.
+     * They are the substitute for directions like "South along 90° East".
+     * We arbitrarily replace the first direction by East and the second 
direction by North.
+     */
+    private static final int EAST = 1, NORTH = 2;
+
+    /**
+     * Returns the indices in {@code cs} of axes presumed covariant with the 
{@code subCS} axes.
+     * Directions such as "South along 90°E" are arbitrarily handled as if 
they were covariant with East and North.
+     *
+     * <h4>Limitations</h4>
+     * Current implementation considers only the first 64 dimensions.
+     * If there is more dimensions, the extra ones are silently ignored.
+     *
+     * @param  cs     the coordinate system which contains all axes, or {@code 
null}.
+     * @param  subCS  the coordinate system to search into {@code cs}.
+     * @return indices in {@code cs} of axes covariant with {@code subCS} axes 
in the order they appear in {@code subCS},
+     *         or {@code null} if at least one {@code subCS} axis can not be 
mapped to a {@code cs} axis.
+     */
+    public static int[] indices(final CoordinateSystem cs, final 
CoordinateSystem subCS) {
+        final int[] indices = new int[subCS.getDimension()];
+        long  axesToSearch  = Numerics.bitmask(indices.length) - 1;      // 
Bit mask of `subCS` axes not yet used.
+        long  availableAxes = Numerics.bitmask(cs.getDimension()) - 1;   // 
Bit mask of `cs` axes not yet used.
+        int   directionUsed = 0;
+        for (final AxesMapper comparisonMode : values()) {
+            /*
+             * We will do 3 attempts to match axes, with matching criteria 
relaxed on each iteration:
+             *
+             *   1) Matches axes having the same direction.
+             *   2) Matches axes having opposite directions.
+             *   3) Matches axes of the kind "South along 90° East".
+             *
+             * On each `comparisonMode` iteration, we iterate over all `subCS` 
axes which have not be selected in
+             * a previous iteration. The `iterSubCS` bitmask has a bit set to 
1 for each remaining axes to visit.
+             */
+            long iterSubCS = axesToSearch, clearSubCS;
+            for (int dimSubCS; hasMore(dimSubCS = 
numberOfTrailingZeros(iterSubCS)); iterSubCS &= clearSubCS) {
+                clearSubCS = clearMask(dimSubCS);
+                AxisDirection search = subCS.getAxis(dimSubCS).getDirection();
+                switch (comparisonMode) {
+                    case OPPOSITE: {
+                        if (search == (search = 
AxisDirections.opposite(search)) || search == null) {
+                            continue;               // Axis already examined 
in previous iteration.
+                        }
+                        break;
+                    }
+                    case POLAR: {
+                        if (CoordinateSystems.isAlongMeridian(search)) {
+                            switch (directionUsed) {
+                                case 0:     // For first axis, fallback on 
EAST.
+                                case NORTH: search = AxisDirection.EAST;  
break;
+                                case EAST:  search = AxisDirection.NORTH; 
break;
+                                default:    return null;  // Too many such 
axes.
+                            }
+                        }
+                    }
+                }
+                /*
+                 * At this point we got the axis direction to search adjusted 
for the comparison mode.
+                 * Now iterate over all `cs` dimensions in search for an axis 
having a compatible direction.
+                 */
+                long iterCS = availableAxes, clearCS;
+                for (int dimCS; hasMore(dimCS = 
numberOfTrailingZeros(iterCS)); iterCS &= clearCS) {
+                    clearCS = clearMask(dimCS);
+                    AxisDirection candidate = cs.getAxis(dimCS).getDirection();
+                    if (comparisonMode == POLAR && 
CoordinateSystems.isAlongMeridian(candidate)) {
+                        switch (directionUsed) {
+                            case 0:     // For first axis, fallback on EAST.
+                            case NORTH: candidate = AxisDirection.EAST;  break;
+                            case EAST:  candidate = AxisDirection.NORTH; break;
+                            default:    return null;    // Too many such axes.
+                        }
+                    }
+                    if (search.equals(candidate)) {
+                        if (search.equals(AxisDirection.EAST))  directionUsed 
|= EAST;
+                        if (search.equals(AxisDirection.NORTH)) directionUsed 
|= NORTH;
+                        indices[dimSubCS] = dimCS;
+                        availableAxes &= clearCS;
+                        axesToSearch  &= clearSubCS;
+                        if (axesToSearch == 0) {
+                            return indices;
+                        }
+                        break;      // Move to next `subCS` axis.
+                    }
+                }
+            }
+        }
+        return (axesToSearch == 0) ? indices : null;
+    }
+
+    /**
+     * Returns {@code true} if the given value computed by {@link 
Long#numberOfTrailingZeros(long)}
+     * means that there is at least one more dimension to process. This is 
used for stopping iterations.
+     */
+    private static boolean hasMore(final int numBits) {
+        return (numBits & ~(Long.SIZE - 1)) == 0;
+    }
+
+    /**
+     * Returns a mask for clearing the bit associated to the given coordinate 
system dimension.
+     */
+    private static long clearMask(final int dim) {
+        return ~(1L << dim);
+    }
+}
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxisDirections.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxisDirections.java
index 2dd092b..2102cd0 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxisDirections.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxisDirections.java
@@ -70,6 +70,8 @@ public final class AxisDirections extends Static {
     /**
      * Ordinal of the last element in the {@link AxisDirection} code list.
      * This is used for differentiating the standard codes from the 
user-defined ones.
+     *
+     * @see #isUserDefined(AxisDirection)
      */
     private static final int LAST_ORDINAL = DISPLAY_DOWN.ordinal();
 
@@ -265,22 +267,6 @@ public final class AxisDirections extends Static {
     }
 
     /**
-     * Returns {@code true} if the given axis is such as "South along 90°E".
-     *
-     * <p><b>This is a temporary method to be removed!</b>
-     * Latest ISO 19111 revision uses another mechanism for declaring such 
axis direction.
-     * This method will be removed after we upgraded the API accordingly.
-     *
-     * @param  dir  the axis to test, or {@code null}.
-     * @return {@code true} if the given axis is such as "South along 90°E".
-     *
-     * @since 1.2
-     */
-    public static boolean isAlongMeridian(final AxisDirection dir) {
-        return (dir != null) && !isCompass(dir);
-    }
-
-    /**
      * Returns {@code true} if the given direction is {@code UP} or {@code 
DOWN}.
      *
      * @param  dir  the direction to test, or {@code null}.
@@ -558,37 +544,6 @@ next:       for (int i=0; i <= limit; i++) {
     }
 
     /**
-     * Returns the indices in {@code cs} of axes colinear with the {@code 
subCS} axes.
-     * If many axes have the same direction (should not happen except for 
temporal axes),
-     * this method gives precedence to a sequence of consecutive indices.
-     *
-     * <p>This method is similar to {@link #indexOfColinear(CoordinateSystem, 
CoordinateSystem)} except that it
-     * enumerates the indices instead of returning only the first index. If 
{@code indexOfColinear(…)} can not
-     * find consecutive indices, then this method fallbacks on a sequence of 
indices regardless their order.</p>
-     *
-     * @param  cs     the coordinate system which contains all axes, or {@code 
null}.
-     * @param  subCS  the coordinate system to search into {@code cs}.
-     * @return indices in {@code cs} of axes colinear with {@code subCS} axes 
in the order they appear in {@code subCS},
-     *         or {@code null} if at least one {@code subCS} axis can not be 
mapped to a {@code cs} axis.
-     *
-     * @since 1.1
-     */
-    public static int[] indicesOfColinear(final CoordinateSystem cs, final 
CoordinateSystem subCS) {
-        final int dim = subCS.getDimension();
-        final int index = indexOfColinear(cs, subCS);           // More robust 
than fallback below.
-        if (index >= 0) {
-            return ArraysExt.range(index, index + dim);
-        }
-        final int[] indices = new int[dim];
-        for (int i=0; i<dim; i++) {
-            if ((indices[i] = indexOfColinear(cs, 
subCS.getAxis(i).getDirection())) < 0) {
-                return null;
-            }
-        }
-        return indices;
-    }
-
-    /**
      * Returns whether the second axis is colinear with the first axis. This 
method returns {@code true}
      * if the {@linkplain #absolute absolute} direction of the given 
directions are equal.
      * For example "down" is considered colinear with "up".
@@ -602,6 +557,30 @@ next:       for (int i=0; i <= limit; i++) {
     }
 
     /**
+     * Returns the indices of {@code cs} axes presumed covariant with {@code 
subCS} axes.
+     * The mapping is based on axis directions only, with collinear axes 
mapped in priority.
+     * If some axes can not be mapped using collinearity criterion, then 
directions from poles
+     * (e.g. <cite>"South along 90°E"</cite>) are arbitrarily handled as if 
they were covariant
+     * with East and North directions, in that order.
+     *
+     * @param  cs     the coordinate system which contains all axes, or {@code 
null}.
+     * @param  subCS  the coordinate system for which to search axes into 
{@code cs}.
+     * @return indices in {@code cs} of axes covariant with {@code subCS} axes 
in the order they appear in {@code subCS},
+     *         or {@code null} if at least one {@code subCS} axis can not be 
mapped to a {@code cs} axis.
+     *
+     * @see #indexOfColinear(CoordinateSystem, CoordinateSystem)
+     *
+     * @since 1.2
+     */
+    public static int[] indicesOfLenientMapping(final CoordinateSystem cs, 
final CoordinateSystem subCS) {
+        final int index = indexOfColinear(cs, subCS);           // More robust 
than fallback below.
+        if (index >= 0) {
+            return ArraysExt.range(index, index + subCS.getDimension());
+        }
+        return AxesMapper.indices(cs, subCS);
+    }
+
+    /**
      * Searches for an axis direction having the given name in the specified 
list of directions.
      * This method compares the given name with the name of each {@code 
AxisDirection} in a lenient way:
      *
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/CoordinateSystems.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/CoordinateSystems.java
index 4ad1b6f..3df656a 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/CoordinateSystems.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/CoordinateSystems.java
@@ -57,7 +57,7 @@ import org.apache.sis.referencing.operation.matrix.MatrixSIS;
  * between two coordinate systems.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   0.4
  * @module
  */
@@ -138,6 +138,30 @@ public final class CoordinateSystems extends Static {
     }
 
     /**
+     * Returns {@code true} if the given axis direction seems to be a 
direction along a meridian.
+     *
+     * <table class="sis">
+     *   <caption>Examples</caption>
+     *   <tr><th>Axis name</th>               <th>Return value</th></tr>
+     *   <tr><td>North along 90 deg East</td> <td>{@code true}</td></tr>
+     *   <tr><td>South along 90 deg East</td> <td>{@code true}</td></tr>
+     *   <tr><td>South</td>                   <td>{@code false}</td></tr>
+     *   <tr><td>East</td>                    <td>{@code false}</td></tr>
+     * </table>
+     *
+     * Note that {@code true} is not a guarantee that {@link 
#parseAxisDirection(String)} will succeed.
+     * But it means that there is reasonable chances of success based on brief 
inspection of axis name.
+     *
+     * @param  direction  the direction to test. Can be null.
+     * @return if the given direction is non-null and seems to be a direction 
along a meridian.
+     *
+     * @since 1.2
+     */
+    public static boolean isAlongMeridian(final AxisDirection direction) {
+        return AxisDirections.isUserDefined(direction) && 
DirectionAlongMeridian.matches(direction.name());
+    }
+
+    /**
      * Returns the arithmetic (counterclockwise) angle from the first axis 
direction to the second direction.
      * This method returns a value between -180° and +180°, or {@code null} if 
no angle can be computed.
      *
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DirectionAlongMeridian.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DirectionAlongMeridian.java
index e106ef9..9265f6c 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DirectionAlongMeridian.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DirectionAlongMeridian.java
@@ -43,11 +43,8 @@ import org.apache.sis.util.resources.Errors;
  * that contains (through its coordinate system) the axes having those 
directions. This is consistent with
  * ISO 19162:2015 §7.5.4(iv) - WKT 2 formatting.
  *
- * <h2>Immutability and thread safety</h2>
- * This final class is immutable and thus inherently thread-safe.
- *
  * @author  Martin Desruisseaux (IRD)
- * @version 0.8
+ * @version 1.2
  * @since   0.4
  * @module
  */
@@ -108,6 +105,14 @@ final class DirectionAlongMeridian extends 
FormattableObject implements Comparab
     }
 
     /**
+     * Returns {@code true} if the given direction is non-null
+     * and has a name which seems to be a direction along meridian.
+     */
+    public static boolean matches(final String direction) {
+        return (direction != null) && EPSG.matcher(direction).matches();
+    }
+
+    /**
      * Returns the direction along meridian for the specified axis direction, 
or {@code null} if none.
      *
      * <p>TIP: caller can check {@link 
AxisDirections#isUserDefined(AxisDirection)} before to invoke this method
@@ -282,7 +287,7 @@ final class DirectionAlongMeridian extends 
FormattableObject implements Comparab
             buffer.append(meridian < 0 ? 'W' : 'E');
         }
         name = buffer.toString();
-        assert EPSG.matcher(name).matches() : name;
+        assert matches(name) : name;
         return name;
     }
 
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/package-info.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/package-info.java
index 2b24494..955e459 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/package-info.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/package-info.java
@@ -33,7 +33,7 @@
  * and units between two coordinate systems, or filtering axes.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   0.4
  * @module
  */
diff --git 
a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/AxesMapperTest.java
 
b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/AxesMapperTest.java
new file mode 100644
index 0000000..ca70c23
--- /dev/null
+++ 
b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/AxesMapperTest.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.referencing;
+
+import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.referencing.cs.HardCodedCS;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Tests the {@link AxesMapper} class.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.2
+ * @since   1.2
+ * @module
+ */
+public final strictfp class AxesMapperTest extends TestCase {
+    /**
+     * Tests with axes having same direction in same order.
+     */
+    @Test
+    public void testSameDirections() {
+        assertArrayEquals(new int[] {0, 1}, AxesMapper.indices(
+                HardCodedCS.GEODETIC_3D,
+                HardCodedCS.GEODETIC_2D));
+    }
+
+    /**
+     * Tests with axes having same direction but different order.
+     */
+    @Test
+    public void testAxisOrderChange() {
+        assertArrayEquals(new int[] {1, 0}, AxesMapper.indices(
+                HardCodedCS.GEODETIC_φλ,
+                HardCodedCS.GEODETIC_2D));
+
+        assertArrayEquals(new int[] {1, 0}, AxesMapper.indices(
+                HardCodedCS.GEODETIC_3D,
+                HardCodedCS.GEODETIC_φλ));
+    }
+
+    /**
+     * Tests selection of vertical axis, including opposite direction.
+     */
+    @Test
+    public void testVerticalAxis() {
+        assertArrayEquals(new int[] {2}, AxesMapper.indices(
+                HardCodedCS.GEODETIC_3D,
+                HardCodedCS.ELLIPSOIDAL_HEIGHT));
+
+        assertArrayEquals(new int[] {2}, AxesMapper.indices(
+                HardCodedCS.GEODETIC_3D,
+                HardCodedCS.DEPTH));
+    }
+
+    /**
+     * Tests the search of axes that do not exist in source coordinate system.
+     */
+    @Test
+    public void testNotFound() {
+        assertNull(AxesMapper.indices(
+                HardCodedCS.GEODETIC_3D,
+                HardCodedCS.DAYS));
+
+        assertNull(AxesMapper.indices(
+                HardCodedCS.GEODETIC_2D,
+                HardCodedCS.GEODETIC_3D));
+    }
+
+    /**
+     * Tests with directions such as "South along 90° East".
+     */
+    @Test
+    public void testDirectionsFromPole() {
+        assertArrayEquals(new int[] {1,0}, AxesMapper.indices(
+                HardCodedCS.GEODETIC_φλ,
+                CommonCRS.WGS84.universal(90, 0).getCoordinateSystem()));
+    }
+}
diff --git 
a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/AxisDirectionsTest.java
 
b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/AxisDirectionsTest.java
index 5df9414..25352ce 100644
--- 
a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/AxisDirectionsTest.java
+++ 
b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/AxisDirectionsTest.java
@@ -25,6 +25,7 @@ import org.apache.sis.referencing.cs.HardCodedAxes;
 import org.apache.sis.referencing.cs.HardCodedCS;
 import org.apache.sis.measure.Units;
 import org.apache.sis.test.DependsOnMethod;
+import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
@@ -39,14 +40,12 @@ import static 
org.apache.sis.internal.referencing.AxisDirections.COUNTER_CLOCKWI
 /**
  * Tests the {@link AxisDirections} class.
  *
- * <p>The {@code AxisDirections} class is defined in the {@code sis-metadata} 
module, but tested in the
- * {@code sis-referencing} module because those tests use {@link 
HardCodedAxes} constants.</p>
- *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   0.4
  * @module
  */
+@DependsOn(AxesMapperTest.class)
 public final strictfp class AxisDirectionsTest extends TestCase {
     /**
      * Tests {@link AxisDirections#absolute(AxisDirection)}.
@@ -421,37 +420,37 @@ public final strictfp class AxisDirectionsTest extends 
TestCase {
     }
 
     /**
-     * Tests {@link AxisDirections#indicesOfColinear(CoordinateSystem, 
CoordinateSystem)}.
+     * Tests {@link AxisDirections#indicesOfLenientMapping(CoordinateSystem, 
CoordinateSystem)}.
      *
-     * @since 1.1
+     * @see AxesMapperTest
      */
     @Test
-    public void testIndicesOfColinear() {
-        assertArrayEquals(new int[] {0, 1}, AxisDirections.indicesOfColinear(
+    public void testIndicesOfLenientMapping() {
+        assertArrayEquals(new int[] {0, 1}, 
AxisDirections.indicesOfLenientMapping(
                 HardCodedCS.GEODETIC_3D,
                 HardCodedCS.GEODETIC_2D));
 
-        assertArrayEquals(new int[] {1, 0}, AxisDirections.indicesOfColinear(
+        assertArrayEquals(new int[] {1, 0}, 
AxisDirections.indicesOfLenientMapping(
                 HardCodedCS.GEODETIC_φλ,
                 HardCodedCS.GEODETIC_2D));
 
-        assertArrayEquals(new int[] {1, 0}, AxisDirections.indicesOfColinear(
+        assertArrayEquals(new int[] {1, 0}, 
AxisDirections.indicesOfLenientMapping(
                 HardCodedCS.GEODETIC_3D,
                 HardCodedCS.GEODETIC_φλ));
 
-        assertArrayEquals(new int[] {2}, AxisDirections.indicesOfColinear(
+        assertArrayEquals(new int[] {2}, 
AxisDirections.indicesOfLenientMapping(
                 HardCodedCS.GEODETIC_3D,
                 HardCodedCS.ELLIPSOIDAL_HEIGHT));
 
-        assertArrayEquals(new int[] {2}, AxisDirections.indicesOfColinear(
+        assertArrayEquals(new int[] {2}, 
AxisDirections.indicesOfLenientMapping(
                 HardCodedCS.GEODETIC_3D,
                 HardCodedCS.DEPTH));
 
-        assertArrayEquals(null, AxisDirections.indicesOfColinear(
+        assertArrayEquals(null, AxisDirections.indicesOfLenientMapping(
                 HardCodedCS.GEODETIC_3D,
                 HardCodedCS.DAYS));
 
-        assertArrayEquals(null, AxisDirections.indicesOfColinear(
+        assertArrayEquals(null, AxisDirections.indicesOfLenientMapping(
                 HardCodedCS.GEODETIC_2D,
                 HardCodedCS.GEODETIC_3D));
     }
diff --git 
a/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
 
b/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
index 261adfd..42130b8 100644
--- 
a/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
+++ 
b/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
@@ -34,6 +34,7 @@ import org.junit.BeforeClass;
     org.apache.sis.internal.referencing.FormulasTest.class,
     org.apache.sis.internal.referencing.j2d.AbstractShapeTest.class,
     org.apache.sis.internal.referencing.j2d.ShapeUtilitiesTest.class,
+    org.apache.sis.internal.referencing.AxesMapperTest.class,
     org.apache.sis.internal.referencing.AxisDirectionsTest.class,
     org.apache.sis.internal.referencing.VerticalDatumTypesTest.class,
     org.apache.sis.internal.referencing.PositionalAccuracyConstantTest.class,
diff --git a/ide-project/NetBeans/nbproject/genfiles.properties 
b/ide-project/NetBeans/nbproject/genfiles.properties
index 26927a9..72e6bc5 100644
--- a/ide-project/NetBeans/nbproject/genfiles.properties
+++ b/ide-project/NetBeans/nbproject/genfiles.properties
@@ -3,6 +3,6 @@
 build.xml.data.CRC32=58e6b21c
 build.xml.script.CRC32=462eaba0
 build.xml.stylesheet.CRC32=28e38971@1.53.1.46
-nbproject/build-impl.xml.data.CRC32=d3e84221
+nbproject/build-impl.xml.data.CRC32=f92ba43e
 nbproject/build-impl.xml.script.CRC32=6b1e829a
 nbproject/build-impl.xml.stylesheet.CRC32=12e0a6c2@1.101.0.48
diff --git a/ide-project/NetBeans/nbproject/project.xml 
b/ide-project/NetBeans/nbproject/project.xml
index 00659e4..e45ea3e 100644
--- a/ide-project/NetBeans/nbproject/project.xml
+++ b/ide-project/NetBeans/nbproject/project.xml
@@ -86,7 +86,9 @@
             <word>classname</word>
             <word>classnames</word>
             <word>classpath</word>
+            <word>collinearity</word>
             <word>complexify</word>
+            <word>covariant</word>
             <word>deserialization</word>
             <word>deserialized</word>
             <word>endianness</word>
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 d02505b..4a4f6a0 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
@@ -100,7 +100,7 @@ public final class Linearizer {
          * </ul>
          *
          * <div class="note"><b>Rational:</b>
-         * the intend is to increase the chances to get the Polar 
Stereographic projection for images close to pole.
+         * the intent is to increase the chances to get the Polar 
Stereographic projection for images close to pole.
          * This is necessary because longitude values may become far from 
central meridian at latitudes such as 88°,
          * causing the Transverse Mercator projection to produce NaN 
numbers.</div>
          */
@@ -112,34 +112,6 @@ public final class Linearizer {
          * 60° is the limit of the domain of validity of Polar Stereographic 
methods.
          */
         static final int POLAR_THRESHOLD = 60;
-
-        /**
-         * Returns a coordinate system containing the axes to search and 
replace in order to build
-         * a "CRS after linearization" from a "CRS before linearization". The 
caller will use only
-         * the axis directions (not the names) of the returned coordinate 
system.
-         *
-         * <h4>Rational</h4>
-         * Usually, the source CRS is geographic and the target CRS is 
projected.
-         * We can generally associate a source axis to a target axis by 
looking at their directions.
-         * For example the "Longitude" source axis is approximately colinear 
with the "Easting" target axis.
-         * {@link org.opengis.referencing.cs.AxisDirection#EAST} can be used 
as a criterion for mapping them.
-         * Note however that axis names can not be used, because they differ 
in geographic and projected CRS.
-         *
-         * <p>However there is an exception where target axis directions will 
not work neither.
-         * If the projection is a polar projection with axis directions such 
as "South along 90°E",
-         * then {@link AxisDirections#indicesOfColinear(CoordinateSystem, 
CoordinateSystem)} will
-         * not find a match. We can workaround by using arbitrary (East, 
North) directions instead.</p>
-         *
-         * @param  targetCS  coordinate system of {@link Linearizer#targetCRS}.
-         */
-        final CoordinateSystem getAxisReplacement(final CoordinateSystem 
targetCS) {
-            for (int i = targetCS.getDimension(); --i >= 0;) {
-                if 
(AxisDirections.isAlongMeridian(targetCS.getAxis(i).getDirection())) {
-                    return CommonCRS.defaultGeographic().getCoordinateSystem();
-                }
-            }
-            return targetCS;
-        }
     }
 
     /**
@@ -364,11 +336,20 @@ public final class Linearizer {
         Matrix original = null;
 search: for (final GridCacheValue replacement : replacements) {
             final SingleCRS targetCRS = replacement.linearizationTarget;
-            final CoordinateSystem targetCS = 
replacement.linearizationType.getAxisReplacement(targetCRS.getCoordinateSystem());
+            final CoordinateSystem targetCS = targetCRS.getCoordinateSystem();
             int firstDimension = 0;
             for (int i=0; i < components.length; i++) {
                 final SingleCRS sourceCRS = components[i];
-                final int[] r = 
AxisDirections.indicesOfColinear(sourceCRS.getCoordinateSystem(), targetCS);
+                /*
+                 * In the most typical cases, the source CRS is geographic and 
the target CRS is projected.
+                 * We can generally associate a source axis to a target axis 
by looking at their directions.
+                 * For example the "Longitude" source axis is approximately 
colinear with the "Easting" target axis.
+                 * However there is a use case where target axis directions 
can not be matched directly to source:
+                 * if the projection is a polar projection with target axis 
directions such as "South along 90°E",
+                 * then `AxisDirections.indexOfColinear(CoordinateSystem, 
CoordinateSystem)} will not find a match.
+                 * We need the more flexible `indicesOfLenientMapping(…)` 
method.
+                 */
+                final int[] r = 
AxisDirections.indicesOfLenientMapping(sourceCRS.getCoordinateSystem(), 
targetCS);
                 if (r != null) {
                     components[i] = targetCRS;
                     if (replacement.axisSwap) {

Reply via email to