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 c7a27e45c20ad8fac4d4df9b56c588e96c5475e3 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Sun Jan 30 17:06:58 2022 +0100 Add transform derivative (Jacobian matrix) for rotated pole. This work completes https://issues.apache.org/jira/browse/SIS-533 --- .../operation/transform/RotatedPole.java | 50 ++++++++++++++++------ .../operation/transform/RotatedPoleTest.java | 34 +++++++++------ 2 files changed, 60 insertions(+), 24 deletions(-) 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 5026903..1831e2d 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 @@ -39,6 +39,7 @@ import org.apache.sis.internal.util.Numerics; import org.apache.sis.internal.util.Constants; import org.apache.sis.metadata.iso.citation.Citations; import org.apache.sis.referencing.ImmutableIdentifier; +import org.apache.sis.referencing.operation.matrix.Matrix2; import org.apache.sis.util.ComparisonMode; import org.apache.sis.util.Debug; @@ -300,25 +301,50 @@ public class RotatedPole extends AbstractMathTransform2D implements Serializable * transform pre-concatenated to this transform, simply by subtracting λp from the longitude value. * This is simpler than performing the rotation in Cartesian coordinates. */ - double λ = srcPts[srcOff]; - double φ = srcPts[srcOff+1]; - double z = sin(φ); - double cosφ = cos(φ); - double y = sin(λ) * cosφ; - double x = cos(λ) * cosφ; + final double λ = srcPts[srcOff]; + final double φ = srcPts[srcOff+1]; + final double z = sin(φ); + final double cosφ = cos(φ); + final double sinλ = sin(λ); + final double cosλ = cos(λ); + final double x = cosφ * cosλ; + final double y = cosφ * sinλ; + final double y2 = y * y; /* * Apply the rotation around Y axis (so the y value stay unchanged) * and convert back to spherical coordinates. */ - double xr = cosφp * z - sinφp * x; - double zr = -cosφp * x - sinφp * z; - double R = fastHypot(xr, y); // The slower hypot(…) is not needed because values are close to 1. - dstPts[dstOff] = atan2(y, xr); - dstPts[dstOff+1] = atan2(zr, R); + final double xsinφp = x * sinφp; + final double xcosφp = x * cosφp; + final double zsinφp = z * sinφp; + final double zcosφp = z * cosφp; + final double xt = zcosφp - xsinφp; + final double zt = -xcosφp - zsinφp; + final double r2 = xt*xt + y2; // yt = y in ihis algorithm. + final double r = sqrt(r2); // The slower hypot(…) is not needed because values are close to 1. + if (dstPts != null) { + dstPts[dstOff] = atan2(y, xt); + dstPts[dstOff+1] = atan2(zt, r); + } if (!derivate) { return null; } - throw new TransformException(); // TODO + /* + * We used WxMaxima for a first derivation of formulas below, + * then simplified the formulas by hand. + * + * https://svn.apache.org/repos/asf/sis/analysis/Rotated%20pole.wxmx + */ + final double dxφ = cosλ * zsinφp + cosφ * cosφp; + final double dyφ = cosλ * zcosφp - cosφ * sinφp; + final double zλ = z * sinλ; + final double zr = zt / r; + final double rc = r2 + zt*zt; + return new Matrix2( + (xt*x - y2*sinφp) / r2, // ∂x/∂λ + -(xt*zλ + y*dxφ) / r2, // ∂x/∂φ + (r*cosφp - zr*(x + sinφp*xt))*y / rc, // ∂y/∂λ + (r*dyφ + zr*(y*zλ - dxφ*xt)) / rc); // ∂y/∂φ } /** 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 8320148..dba1a4c 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 @@ -44,6 +44,13 @@ public final strictfp class RotatedPoleTest extends MathTransformTestCase { } /** + * Creates a new test case. + */ + public RotatedPoleTest() { + tolerance = Formulas.ANGULAR_TOLERANCE; + } + + /** * Creates a new transform which should be the inverse of current transform according the * parameters declared in {@link RotatedPole#context}. Those parameters may be wrong even * if the coordinates transformed by {@code transform.inverse()} are corrects because the @@ -82,8 +89,6 @@ public final strictfp class RotatedPoleTest extends MathTransformTestCase { @Test public void testRotateSouthPoleOnGreenwich() throws FactoryException, TransformException { transform = RotatedPole.rotateSouthPole(factory(), -60, 0, 0); - tolerance = Formulas.ANGULAR_TOLERANCE; - isDerivativeSupported = false; final double[] coordinates = { // (λ,φ) coordinates to convert. 0, -51, 20, -51, @@ -111,8 +116,6 @@ public final strictfp class RotatedPoleTest extends MathTransformTestCase { @DependsOnMethod("testRotateSouthPoleOnGreenwich") public void testRotateSouthPoleWithAngle() 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, @@ -137,8 +140,6 @@ public final strictfp class RotatedPoleTest extends MathTransformTestCase { @Test public void testRotateSouthToOppositeHemisphere() 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, @@ -170,8 +171,6 @@ public final strictfp class RotatedPoleTest extends MathTransformTestCase { @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, @@ -205,8 +204,6 @@ public final strictfp class RotatedPoleTest extends MathTransformTestCase { @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, @@ -231,8 +228,6 @@ public final strictfp class RotatedPoleTest extends MathTransformTestCase { @Test public void testRotateNorthToOppositeHemisphere() throws FactoryException, TransformException { transform = RotatedPole.rotateNorthPole(factory(), -50, 20, 10); - tolerance = Formulas.ANGULAR_TOLERANCE; - isDerivativeSupported = false; final double[] coordinates = { // (λ,φ) coordinates to convert. 20, -51, 80, -44, @@ -247,4 +242,19 @@ public final strictfp class RotatedPoleTest extends MathTransformTestCase { inverseNorthPoleTransform(); verifyTransform(expected, coordinates); } + + /** + * Tests derivative. + * + * @throws FactoryException if the transform can not be created. + * @throws TransformException if an error occurred while computing a derivative. + */ + @Test + public void testDerivative() throws FactoryException, TransformException { + transform = RotatedPole.rotateSouthPole(factory(), -50, 0, 0); + derivativeDeltas = new double[] {1E-6, 1E-6}; + verifyDerivative( 0, -51); + verifyDerivative( 20, -58); + verifyDerivative(-30, -40); + } }