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 b677477aec0b6aba473ec838709ad4092b276312 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Sat Jan 29 16:02:27 2022 +0100 Add tests for north pole rotation. --- .../referencing/provider/RotatedNorthPole.java | 5 + .../referencing/provider/RotatedSouthPole.java | 4 + .../operation/transform/RotatedPole.java | 9 +- .../operation/transform/RotatedPoleTest.java | 115 ++++++++++++++++++++- 4 files changed, 126 insertions(+), 7 deletions(-) diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/RotatedNorthPole.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/RotatedNorthPole.java index e338bea..ba921ad 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/RotatedNorthPole.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/RotatedNorthPole.java @@ -37,6 +37,11 @@ import org.apache.sis.measure.Units; * This is similar to the WMO Rotated Latitude/Longitude but rotating north pole instead of * south pole. * + * <h2>Comparison with UCAR library</h2> + * {@link ucar.unidata.geoloc.projection.RotatedPole} in UCAR netCDF library version 5.5.2 + * gives results with an offset of 180° in longitude values compared to our implementation. + * See {@code RotatedPoleTest.testRotateNorthPoleOnGreenwich()} for more details. + * * @author Martin Desruisseaux (Geomatys) * @version 1.2 * diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/RotatedSouthPole.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/RotatedSouthPole.java index b485905..2f18264 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/RotatedSouthPole.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/RotatedSouthPole.java @@ -36,6 +36,10 @@ import org.apache.sis.measure.Units; * The provider for the WMO <cite>Rotated Latitude/Longitude</cite> coordinate operation. * This is defined by the World Meteorological Organization (WMO) in GRIB2 template 3.1. * + * <h2>Comparison with UCAR library</h2> + * This is consistent with {@link ucar.unidata.geoloc.projection.RotatedLatLon} + * in UCAR netCDF library version 5.5.2. + * * @author Martin Desruisseaux (Geomatys) * @version 1.2 * @since 1.2 diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/RotatedPole.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/RotatedPole.java index 6e613fd..513d4cd 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/RotatedPole.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/RotatedPole.java @@ -153,7 +153,9 @@ public class RotatedPole extends AbstractMathTransform2D implements Serializable i = 3 - i; } double value = -((Number) ((ParameterValue<?>) values.get(i)).getValue()).doubleValue(); - if (i == 0) value = IEEEremainder(value + 180, 360); + if (i == 0 && RotatedSouthPole.PARAMETERS.equals(forward.getDescriptor())) { + value = IEEEremainder(value + 180, 360); + } target.setValue(value); return true; } @@ -340,8 +342,9 @@ public class RotatedPole extends AbstractMathTransform2D implements Serializable public boolean isIdentity() { return sinφp == -1; /* - * We do not test `cosφp` because that value may be small but non-zero, especially since there is no exact - * representation of `cos(π/2)`. Testing `sinφp == -1` is a way to allow for a small tolerance around π/2. + * We do not test `cosφp` because that value can be small but not zero, because + * there is no way to compute exactly `cos(π/2)` with `Math.PI` approximation. + * Testing `sinφp == -1` is a way to allow for a small tolerance around π/2. * This policy is also needed for consistency with `RotatedPole(RotatedPole)` implementation. */ } diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/RotatedPoleTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/RotatedPoleTest.java index 6764670..7072333 100644 --- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/RotatedPoleTest.java +++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/RotatedPoleTest.java @@ -59,9 +59,22 @@ public final strictfp class RotatedPoleTest extends MathTransformTestCase { } /** + * Creates a new transform which should be the inverse of current transform according + * the parameters declared in {@link RotatedPole#context}. This is the same work than + * {@link #inverseSouthPoleTransform()} but for the other transform. + */ + private void inverseNorthPoleTransform() throws FactoryException, TransformException { + final ParameterValueGroup pg = ((Parameterized) transform.inverse()).getParameterValues(); + transform = RotatedPole.rotateSouthPole(factory(), + pg.parameter("grid_north_pole_latitude") .doubleValue(), + pg.parameter("grid_north_pole_longitude").doubleValue(), + pg.parameter("north_pole_grid_longitude").doubleValue()); + } + + /** * Tests a rotation of south pole with the new pole on Greenwich. - * The {@link ucar.unidata.geoloc.LatLonPoint} class has been used - * as a reference implementation for computing the expected values. + * The {@link ucar.unidata.geoloc.projection.RotatedLatLon} class + * has been used as a reference implementation for expected values. * * @throws FactoryException if the transform can not be created. * @throws TransformException if an error occurred while transforming a point. @@ -88,8 +101,8 @@ public final strictfp class RotatedPoleTest extends MathTransformTestCase { /** * Tests a rotation of south pole with the pole on arbitrary meridian. - * The {@link ucar.unidata.geoloc.LatLonPoint} class has been used as - * a reference implementation for computing the expected values. + * The {@link ucar.unidata.geoloc.projection.RotatedLatLon} class has + * been used as a reference implementation for expected values. * * @throws FactoryException if the transform can not be created. * @throws TransformException if an error occurred while transforming a point. @@ -114,4 +127,98 @@ public final strictfp class RotatedPoleTest extends MathTransformTestCase { inverseSouthPoleTransform(); verifyTransform(expected, coordinates); } + + /** + * Tries rotating a pole to opposite hemisphere. + * + * @throws FactoryException if the transform can not be created. + * @throws TransformException if an error occurred while transforming a point. + */ + @Test + public void testRotateToOppositeHemisphere() throws FactoryException, TransformException { + transform = RotatedPole.rotateSouthPole(factory(), 50, 20, 10); + tolerance = Formulas.ANGULAR_TOLERANCE; + isDerivativeSupported = false; + final double[] coordinates = { // (λ,φ) coordinates to convert. + 20, 51, + 80, 44, + -30, 89 + }; + final double[] expected = { // (λ,φ) coordinates after conversion. + -10.000000000, -89.000000000, + 64.651211252, -49.758697265, + -11.207848626, -50.636582758 + }; + verifyTransform(coordinates, expected); + inverseSouthPoleTransform(); + verifyTransform(expected, coordinates); + } + + /** + * Tests a rotation of north pole with the new pole on Greenwich. + * + * <h4>Comparison with UCAR library</h4> + * {@link ucar.unidata.geoloc.projection.RotatedPole} in UCAR netCDF library version 5.5.2 + * gives results with an offset of 180° in longitude values compared to our implementation. + * But geometrical reasoning suggests that our implementation is correct: if we rotate the + * pole to 60°N, then latitude of 54°N on Greenwich meridian become only 6° below new pole, + * i.e. 84°N but still on the same meridian (Greenwich) because we did not cross the pole. + * + * @throws FactoryException if the transform can not be created. + * @throws TransformException if an error occurred while transforming a point. + */ + @Test + public void testRotateNorthPoleOnGreenwich() throws FactoryException, TransformException { + transform = RotatedPole.rotateNorthPole(factory(), 60, 0, 0); + tolerance = Formulas.ANGULAR_TOLERANCE; + isDerivativeSupported = false; + final double[] coordinates = { // (λ,φ) coordinates to convert. + 0, 54, + 20, 62, + -30, 89 + }; + final double[] expected = { // (λ,φ) coordinates after conversion. + 0.000000000, 84.000000000, + 110.307140436, 80.141810970, + -178.973119126, 60.862133738 + }; + verifyTransform(coordinates, expected); + inverseNorthPoleTransform(); + verifyTransform(expected, coordinates); + } + + /** + * Tests a rotation of north pole with the pole on arbitrary meridian. + * Result can be compared with PROJ using the following command, where + * {@code coords.txt} is a file containing input coordinates in (λ,φ) + * order and the output is in (φ,λ) order. + * + * {@preformat shell + * cs2cs -I -E -f %g "EPSG:4326" +to +type=crs +proj=ob_tran +o_proj=longlat +datum=WGS84 +no_defs \ + * +o_lat_p=70 +o_lon_p=40 +lon_0=0 coords.txt + * } + * + * @throws FactoryException if the transform can not be created. + * @throws TransformException if an error occurred while transforming a point. + */ + @Test + @DependsOnMethod("testRotateNorthPoleOnGreenwich") + public void testRotateNorthPole() throws FactoryException, TransformException { + transform = RotatedPole.rotateNorthPole(factory(), 70, 40, 0); + tolerance = Formulas.ANGULAR_TOLERANCE; + isDerivativeSupported = false; + final double[] coordinates = { // (λ,φ) coordinates to convert. + 0, 54, + 20, 62, + -30, 89 + }; + final double[] expected = { // (λ,φ) coordinates after conversion. + -68.817428350, 66.096411904, + -54.967324181, 78.691210976, + -177.208632734, 70.320491507 + }; + verifyTransform(coordinates, expected); + inverseNorthPoleTransform(); + verifyTransform(expected, coordinates); + } }