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 faa3997687 Verify in a test that extended precision is propagated through MathTransform inversion and concatenation. faa3997687 is described below commit faa399768721c37113e9b66e41172a37b9f050db Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Wed Jan 11 12:04:38 2023 +0100 Verify in a test that extended precision is propagated through MathTransform inversion and concatenation. https://issues.apache.org/jira/browse/SIS-568 --- .../transform/ProjectiveTransformTest.java | 83 ++++++++++++++++------ .../transform/TransformResultComparator.java | 46 +++++++++++- 2 files changed, 106 insertions(+), 23 deletions(-) diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ProjectiveTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ProjectiveTransformTest.java index 45e55585a9..f7c03e26d8 100644 --- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ProjectiveTransformTest.java +++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ProjectiveTransformTest.java @@ -26,8 +26,12 @@ import org.opengis.referencing.operation.TransformException; import org.apache.sis.referencing.operation.matrix.Matrices; import org.apache.sis.referencing.operation.matrix.MatrixSIS; import org.apache.sis.referencing.operation.matrix.Matrix2; +import org.apache.sis.referencing.operation.matrix.Matrix4; +import org.apache.sis.internal.referencing.ExtendedPrecisionMatrix; import org.apache.sis.internal.referencing.provider.Affine; +import org.apache.sis.internal.util.DoubleDouble; import org.apache.sis.parameter.Parameterized; +import org.apache.sis.math.Fraction; // Test imports import org.opengis.test.Validators; @@ -75,35 +79,35 @@ public class ProjectiveTransformTest extends AffineTransformTest { super(new MathTransformFactoryBase() { @Override public MathTransform createAffineTransform(final Matrix matrix) { - final ProjectiveTransform pt; + final ProjectiveTransform tested; if (matrix.getNumRow() == 3 && matrix.getNumCol() == 3) { - pt = new ProjectiveTransform2D(matrix); + tested = new ProjectiveTransform2D(matrix); } else { - pt = new ProjectiveTransform(matrix); + tested = new ProjectiveTransform(matrix); } - MathTransform tr = pt.optimize(); - if (tr != pt) { + final MathTransform reference = tested.optimize(); + if (tested != reference) { /* * Opportunistically tests `ScaleTransform` together with `ProjectiveTransform`. * We take `ScaleTransform` as a reference implementation because it is simpler. */ - tr = new TransformResultComparator(tr, pt, 1E-12); + return new TransformResultComparator(reference, tested, 1E-12); } - return tr; + return tested; } }); } /** - * Returns the transform without {@link TransformResultComparator} wrapper. - * The transform is the one computed by {@link ProjectiveTransform#optimize()}. + * Whether the post-test validation should skip its check for a transform of the given dimension. + * {@code ProjectiveTransformTest} needs to skip the case for dimension 1 because there is no + * {@code ProjectiveTransform1D} class. However, {@link LinearTransformTest} can do the check + * for all dimensions. + * + * @see #ensureImplementRightInterface() */ - private MathTransform getOptimizedTransform() { - MathTransform tr = transform; - if (tr instanceof TransformResultComparator) { - tr = ((TransformResultComparator) tr).reference; - } - return tr; + boolean skipInterfaceCheckForDimension(final int dimension) { + return dimension == 1; } /* @@ -170,13 +174,48 @@ public class ProjectiveTransformTest extends AffineTransformTest { } /** - * {@code true} if {@link #ensureImplementRightInterface()} should skip its check for a transform - * of the given dimension. {@code ProjectiveTransformTest} needs to skip the case for dimension 1 - * because there is no {@code ProjectiveTransform1D} class. However, {@link LinearTransformTest} - * can check for all dimensions. + * Tests the concatenation of transforms that would result in rounding errors + * in extended-precision matrix operations were not used. + * + * @throws FactoryException if the transform cannot be created. + * @throws TransformException if a coordinate conversion failed. + * + * @since 1.4 */ - boolean skipInterfaceCheckForDimension(final int dimension) { - return dimension == 1; + @Test + public void testRoundingErrors() throws FactoryException, TransformException { + final Matrix4 num = new Matrix4(); num.m00 = 2; num.m11 = 3.25; num.m22 = -17; + final Matrix4 den = new Matrix4(); den.m00 = 37; den.m11 = 1000; den.m22 = 127; + transform = TransformResultComparator.concatenate( + mtFactory.createAffineTransform(num), + mtFactory.createAffineTransform(den).inverse(), + mtFactory); + matrix = ((LinearTransform) getOptimizedTransform()).getMatrix(); + /* + * Verify matrix elements after inversion and concatenation. + * The extended precision types should be used. + */ + final ExtendedPrecisionMatrix m = (ExtendedPrecisionMatrix) matrix; + assertEquals(new Fraction(2, 37), m.getElementOrNull(0,0)); + assertEquals(DoubleDouble.of(325).divide(100000), m.getElementOrNull(1,1)); + assertEquals(new Fraction(-17, 127), m.getElementOrNull(2,2)); + /* + * Test a transformation, expecting exact result. + */ + verifyTransform(new double[] {2645.5, 19500, 2222.5}, + new double[] { 143, 63.375, -297.5}); + } + + /** + * Returns the transform without {@link TransformResultComparator} wrapper. + * The transform is the one computed by {@link ProjectiveTransform#optimize()}. + */ + private MathTransform getOptimizedTransform() { + MathTransform tr = transform; + while (tr instanceof TransformResultComparator) { + tr = ((TransformResultComparator) tr).reference; + } + return tr; } /** @@ -193,7 +232,7 @@ public class ProjectiveTransformTest extends AffineTransformTest { * possible types, so the test would fail if checking its result. More complete optimizations * are tested in the `LinearTransformTest` subclass. */ - if (transform instanceof TransformResultComparator) { + while (transform instanceof TransformResultComparator) { transform = ((TransformResultComparator) transform).tested; } /* diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformResultComparator.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformResultComparator.java index 03824c865a..f142b8f6ad 100644 --- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformResultComparator.java +++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformResultComparator.java @@ -21,17 +21,19 @@ import org.opengis.geometry.DirectPosition; import org.opengis.geometry.MismatchedDimensionException; import org.opengis.referencing.operation.Matrix; import org.opengis.referencing.operation.MathTransform; +import org.opengis.referencing.operation.MathTransformFactory; import org.opengis.referencing.operation.TransformException; import org.opengis.referencing.operation.NoninvertibleTransformException; import static org.opengis.test.Assert.*; +import org.opengis.util.FactoryException; /** * Compares the results of two {@link MathTransform} implementations. * * @author Martin Desruisseaux (Geomatys) - * @version 1.1 + * @version 1.4 * @since 0.7 */ final class TransformResultComparator implements MathTransform { @@ -182,4 +184,46 @@ final class TransformResultComparator implements MathTransform { public String toWKT() { return tested.toWKT(); } + + /** + * Concatenates two transforms that may be comparators. + * Instances of {@code TransformResultComparator} are unwrapped before concatenation, + * then the concatenation result is re-wrapped in {@code TransformResultComparator}. + * + * @param tr1 the first math transform. + * @param tr2 the second math transform. + * @param factory the factory which is (indirectly) invoking this method, or {@code null} if none. + * @return the concatenated transform. + */ + static MathTransform concatenate(final MathTransform tr1, final MathTransform tr2, + final MathTransformFactory factory) throws FactoryException + { + double tolerance; + final MathTransform t1, r1, t2, r2; + if (tr1 instanceof TransformResultComparator) { + final TransformResultComparator c = (TransformResultComparator) tr1; + t1 = c.tested; + r1 = c.reference; + tolerance = c.tolerance; + } else { + t1 = r1 = tr1; + tolerance = 0; + } + if (tr2 instanceof TransformResultComparator) { + final TransformResultComparator c = (TransformResultComparator) tr2; + t2 = c.tested; + r2 = c.reference; + if (c.tolerance > tolerance) { + tolerance = c.tolerance; + } + } else { + t2 = r2 = tr2; + } + final MathTransform tested = ConcatenatedTransform.create(t1, t2, factory); + if (r1 == t1 && r2 == t2) { + return tested; + } + final MathTransform reference = ConcatenatedTransform.create(r1, r2, factory); + return new TransformResultComparator(reference, tested, tolerance); + } }