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) {