Author: desruisseaux Date: Mon Jul 20 10:26:14 2015 New Revision: 1691910 URL: http://svn.apache.org/r1691910 Log: Remove the comparison between elliptical and spherical formulas from the assertions in the main code. Instead, perform the comparison in the test suite with the help of ProjectionResultComparator. The reason is that performing comparisons in the main code was very difficult because of approximations in formulas (especially when using series expansion), for example in TransverseMercator for points far from the central meridian. This resulted in either assertion failures because of too strict tolerance threshold, or not very useful assertions because of too high tolerance threshold. By performing the comparisons in the test suite instead, we can control the domain of the projected points, and choose the tolerance threshold accordingly.
Added: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ProjectionResultComparator.java - copied, changed from r1690754, sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Assertions.java Removed: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Assertions.java Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConformal.java sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/EquirectangularTest.java sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConformalTest.java sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorTest.java sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolarStereographicTest.java sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConformal.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConformal.java?rev=1691910&r1=1691909&r2=1691910&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConformal.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConformal.java [UTF-8] Mon Jul 20 10:26:14 2015 @@ -521,25 +521,21 @@ public class LambertConformal extends Co } final double x = ρ * sinθ; final double y = ρ * cosθ; - Matrix derivative = null; - if (derivate) { - final double dρ; - if (absφ < PI/2) { - dρ = n*ρ / cos(φ); - } else { - dρ = NaN; - } - derivative = new Matrix2(y, dρ*sinθ, // ∂x/∂λ , ∂x/∂φ - -x, dρ*cosθ); // ∂y/∂λ , ∂y/∂φ - } - // Following part is common to all spherical projections: verify, store and return. - assert Assertions.checkDerivative(derivative, super.transform(srcPts, srcOff, dstPts, dstOff, derivate)) - && Assertions.checkTransform(dstPts, dstOff, x, y); // dstPts = result from ellipsoidal formulas. if (dstPts != null) { dstPts[dstOff ] = x; dstPts[dstOff+1] = y; } - return derivative; + if (!derivate) { + return null; + } + final double dρ; + if (absφ < PI/2) { + dρ = n*ρ / cos(φ); + } else { + dρ = NaN; + } + return new Matrix2(y, dρ*sinθ, // ∂x/∂λ , ∂x/∂φ + -x, dρ*cosθ); // ∂y/∂λ , ∂y/∂φ } /** @@ -553,24 +549,8 @@ public class LambertConformal extends Co double x = srcPts[srcOff ]; double y = srcPts[srcOff+1]; final double ρ = hypot(x, y); - x = atan2(x, y); // Really (x,y), not (y,x) - y = PI/2 - 2*atan(pow(1/ρ, 1/n)); - assert checkInverseTransform(srcPts, srcOff, dstPts, dstOff, x, y); - dstPts[dstOff ] = x; - dstPts[dstOff+1] = y; - } - - /** - * Computes using ellipsoidal formulas and compare with the - * result from spherical formulas. Used in assertions only. - */ - private boolean checkInverseTransform(final double[] srcPts, final int srcOff, - final double[] dstPts, final int dstOff, - final double θ, final double φ) - throws ProjectionException - { - super.inverseTransform(srcPts, srcOff, dstPts, dstOff); - return Assertions.checkInverseTransform(dstPts, dstOff, θ, φ); + dstPts[dstOff ] = atan2(x, y); // Really (x,y), not (y,x); + dstPts[dstOff+1] = PI/2 - 2*atan(pow(1/ρ, 1/n)); } } Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java?rev=1691910&r1=1691909&r2=1691910&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java [UTF-8] Mon Jul 20 10:26:14 2015 @@ -332,7 +332,6 @@ public class Mercator extends ConformalP final double[] dstPts, final int dstOff, final boolean derivate) throws ProjectionException { - final double λ = srcPts[srcOff ]; final double φ = srcPts[srcOff+1]; final double sinφ = sin(φ); if (dstPts != null) { @@ -354,7 +353,7 @@ public class Mercator extends ConformalP y = copySign(a <= (PI/2 + ANGULAR_TOLERANCE) ? POSITIVE_INFINITY : NaN, φ); } } - dstPts[dstOff ] = λ; // Scale will be applied by the denormalization matrix. + dstPts[dstOff ] = srcPts[srcOff]; // Scale will be applied by the denormalization matrix. dstPts[dstOff+1] = y; } /* @@ -465,40 +464,29 @@ public class Mercator extends ConformalP final double[] dstPts, final int dstOff, final boolean derivate) throws ProjectionException { - final double λ = srcPts[srcOff ]; final double φ = srcPts[srcOff+1]; - /* - * Projection of zero is zero. However the formulas below have a slight rounding error - * which produce values close to 1E-10, so we will avoid them when y=0. In addition of - * avoiding rounding error, this also preserve the sign (positive vs negative zero). - */ - final double y; - if (φ == 0) { - y = φ; - } else { - // See class javadoc for a note about explicit check for poles. - final double a = abs(φ); - if (a < PI/2) { - y = log(tan(PI/4 + 0.5*φ)); // Part of Snyder (7-2) + if (dstPts != null) { + /* + * Projection of zero is zero. However the formulas below have a slight rounding error + * which produce values close to 1E-10, so we will avoid them when y=0. In addition of + * avoiding rounding error, this also preserve the sign (positive vs negative zero). + */ + final double y; + if (φ == 0) { + y = φ; } else { - y = copySign(a <= (PI/2 + ANGULAR_TOLERANCE) ? POSITIVE_INFINITY : NaN, φ); + // See class javadoc for a note about explicit check for poles. + final double a = abs(φ); + if (a < PI/2) { + y = log(tan(PI/4 + 0.5*φ)); // Part of Snyder (7-2) + } else { + y = copySign(a <= (PI/2 + ANGULAR_TOLERANCE) ? POSITIVE_INFINITY : NaN, φ); + } } - } - Matrix derivative = null; - if (derivate) { - derivative = new Matrix2(1, 0, 0, 1/cos(φ)); - } - /* - * Following part is common to all spherical projections: verify, store and return. - */ - assert (excentricity != 0) // Can not perform the following assertions if excentricity is not zero. - || (Assertions.checkDerivative(derivative, super.transform(srcPts, srcOff, dstPts, dstOff, derivate)) - && Assertions.checkTransform(dstPts, dstOff, λ, y)); // dstPts = result from ellipsoidal formulas. - if (dstPts != null) { - dstPts[dstOff ] = λ; + dstPts[dstOff ] = srcPts[srcOff]; dstPts[dstOff+1] = y; } - return derivative; + return derivate ? new Matrix2(1, 0, 0, 1/cos(φ)) : null; } /** @@ -542,26 +530,9 @@ public class Mercator extends ConformalP final double[] dstPts, final int dstOff) throws ProjectionException { - double x = srcPts[srcOff ]; - double y = srcPts[srcOff+1]; - y = PI/2 - 2 * atan(exp(-y)); // Part of Snyder (7-4) - assert (excentricity != 0) // Can not perform the following assertion if excentricity is not zero. - || checkInverseTransform(srcPts, srcOff, dstPts, dstOff, x, y); - dstPts[dstOff ] = x; - dstPts[dstOff+1] = y; - } - - /** - * Computes using ellipsoidal formulas and compare with the - * result from spherical formulas. Used in assertions only. - */ - private boolean checkInverseTransform(final double[] srcPts, final int srcOff, - final double[] dstPts, final int dstOff, - final double λ, final double φ) - throws ProjectionException - { - super.inverseTransform(srcPts, srcOff, dstPts, dstOff); - return Assertions.checkInverseTransform(dstPts, dstOff, λ, φ); + final double y = srcPts[srcOff+1]; // Must be before writing x. + dstPts[dstOff ] = srcPts[srcOff]; // Must be before writing y. + dstPts[dstOff+1] = PI/2 - 2*atan(exp(-y)); // Part of Snyder (7-4); } } } Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java?rev=1691910&r1=1691909&r2=1691910&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java [UTF-8] Mon Jul 20 10:26:14 2015 @@ -353,20 +353,16 @@ public class PolarStereographic extends final double t = tan(PI/4 + 0.5*φ); final double x = t * sinθ; // Synder 21-5 final double y = t * cosθ; // Synder 21-6 - Matrix derivative = null; - if (derivate) { - final double dt = t / cos(φ); - derivative = new Matrix2(y, dt*sinθ, // ∂x/∂λ , ∂x/∂φ - -x, dt*cosθ); // ∂y/∂λ , ∂y/∂φ - } - // Following part is common to all spherical projections: verify, store and return. - assert Assertions.checkDerivative(derivative, super.transform(srcPts, srcOff, dstPts, dstOff, derivate)) - && Assertions.checkTransform(dstPts, dstOff, x, y); // dstPts = result from ellipsoidal formulas. if (dstPts != null) { dstPts[dstOff ] = x; dstPts[dstOff+1] = y; } - return derivative; + if (!derivate) { + return null; + } + final double dt = t / cos(φ); + return new Matrix2(y, dt*sinθ, // ∂x/∂λ , ∂x/∂φ + -x, dt*cosθ); // ∂y/∂λ , ∂y/∂φ } /** @@ -380,24 +376,8 @@ public class PolarStereographic extends double x = srcPts[srcOff ]; double y = srcPts[srcOff+1]; final double ρ = hypot(x, y); - x = atan2(x, y); // Really (x,y), not (y,x) - y = 2*atan(ρ) - PI/2; // (20-14) with φ1=90° and cos(y) = sin(π/2 + y). - assert checkInverseTransform(srcPts, srcOff, dstPts, dstOff, x, y); - dstPts[dstOff ] = x; - dstPts[dstOff+1] = y; - } - - /** - * Computes using ellipsoidal formulas and compare with the - * result from spherical formulas. Used in assertions only. - */ - private boolean checkInverseTransform(final double[] srcPts, final int srcOff, - final double[] dstPts, final int dstOff, - final double λ, final double φ) - throws ProjectionException - { - super.inverseTransform(srcPts, srcOff, dstPts, dstOff); - return Assertions.checkInverseTransform(dstPts, dstOff, λ, φ); + dstPts[dstOff ] = atan2(x, y); // Really (x,y), not (y,x); + dstPts[dstOff+1] = 2*atan(ρ) - PI/2; // (20-14) with φ1=90° and cos(y) = sin(π/2 + y).; } } } Modified: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/EquirectangularTest.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/EquirectangularTest.java?rev=1691910&r1=1691909&r2=1691910&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/EquirectangularTest.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/EquirectangularTest.java [UTF-8] Mon Jul 20 10:26:14 2015 @@ -44,11 +44,11 @@ import static org.apache.sis.internal.me public final strictfp class EquirectangularTest extends MapProjectionTestCase { /** * Initializes a simple Equirectangular projection on sphere. This method is different than the - * {@code initialize(boolean)} method in all other test classes, because it does not create an - * instance of {@link NormalizedProjection}. Instead, it creates an affine transform for the - * whole projection (not only the normalized part). + * {@code createNormalizedProjection(boolean)} method in all other test classes, because it does + * not create an instance of {@link NormalizedProjection}. Instead, it creates an affine transform + * for the whole projection (not only the normalized part). */ - private void initialize() throws FactoryException { + private void createCompleteProjection() throws FactoryException { final Equirectangular provider = new Equirectangular(); final Parameters parameters = parameters(provider, false); transform = new MathTransformFactoryMock(provider).createParameterizedTransform(parameters); @@ -65,7 +65,7 @@ public final strictfp class Equirectangu */ @Test public void testWKT() throws FactoryException { - initialize(); + createCompleteProjection(); assertWktEquals( "PARAM_MT[“Equirectangular”,\n" + " PARAMETER[“semi_major”, 6371007.0],\n" + @@ -95,7 +95,7 @@ public final strictfp class Equirectangu */ @Test public void testSimpleTransform() throws FactoryException, TransformException { - initialize(); + createCompleteProjection(); verifyTransform( new double[] { // (λ,φ) coordinates in degrees to project. 0, 0, @@ -118,7 +118,7 @@ public final strictfp class Equirectangu */ @Test public void testRandomPoints() throws FactoryException, TransformException { - initialize(new Equirectangular(), true, + createCompleteProjection(new Equirectangular(), true, 0.5, // Central meridian 0, // Latitude of origin (none) 20, // Standard parallel Modified: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConformalTest.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConformalTest.java?rev=1691910&r1=1691909&r2=1691910&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConformalTest.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConformalTest.java [UTF-8] Mon Jul 20 10:26:14 2015 @@ -59,7 +59,7 @@ public final strictfp class LambertConfo * @param ellipse {@code false} for a sphere, or {@code true} for WGS84 ellipsoid. * @param latitudeOfOrigin The latitude of origin, in decimal degrees. */ - private void initialize(final boolean ellipse, final double latitudeOfOrigin) { + private void createNormalizedProjection(final boolean ellipse, final double latitudeOfOrigin) { final LambertConformal1SP method = new LambertConformal1SP(); final Parameters parameters = parameters(method, ellipse); parameters.getOrCreate(LambertConformal1SP.LATITUDE_OF_ORIGIN).setValue(latitudeOfOrigin); @@ -77,7 +77,7 @@ public final strictfp class LambertConfo */ @Test public void testNormalizedWKT() { - initialize(true, 40); + createNormalizedProjection(true, 40); assertWktEquals( "PARAM_MT[“Lambert_Conformal_Conic_1SP”,\n" + " PARAMETER[“semi_major”, 1.0],\n" + @@ -93,7 +93,7 @@ public final strictfp class LambertConfo @Test public void testSpecialLatitudes() throws ProjectionException { if (transform == null) { // May have been initialized by 'testSphericalCase'. - initialize(true, 40); // Elliptical case + createNormalizedProjection(true, 40); // Elliptical case } final double INF = POSITIVE_INFINITY; assertEquals ("Not a number", NaN, transform(NaN), NORMALIZED_TOLERANCE); @@ -114,7 +114,7 @@ public final strictfp class LambertConfo assertEquals ("Inverse -∞", +PI/2, inverseTransform(-INF), NORMALIZED_TOLERANCE); // Like the north case, but with sign inversed. - initialize(((LambertConformal) transform).excentricity != 0, -40); + createNormalizedProjection(((LambertConformal) transform).excentricity != 0, -40); validate(); assertEquals ("Not a number", NaN, transform(NaN), NORMALIZED_TOLERANCE); @@ -143,7 +143,7 @@ public final strictfp class LambertConfo @DependsOnMethod("testSpecialLatitudes") public void testDerivative() throws TransformException { if (transform == null) { // May have been initialized by 'testSphericalCase'. - initialize(true, 40); // Elliptical case + createNormalizedProjection(true, 40); // Elliptical case } final double delta = toRadians(100.0 / 60) / 1852; // Approximatively 100 metres. derivativeDeltas = new double[] {delta, delta}; @@ -222,7 +222,7 @@ public final strictfp class LambertConfo @Test @DependsOnMethod("testLambertConicConformal1SP") public void testLambertConicConformalWestOrientated() throws FactoryException, TransformException { - initialize(new LambertConformal1SP(), false, + createCompleteProjection(new LambertConformal1SP(), false, 0.5, // Central meridian 40, // Latitude of origin 0, // Standard parallel (none) @@ -231,7 +231,7 @@ public final strictfp class LambertConfo 100); // False northing final MathTransform reference = transform; - initialize(new LambertConformalWest(), false, + createCompleteProjection(new LambertConformalWest(), false, 0.5, // Central meridian 40, // Latitude of origin 0, // Standard parallel (none) @@ -274,25 +274,35 @@ public final strictfp class LambertConfo } /** - * Verifies the consistency of spherical formulas with the elliptical formulas. + * Performs the same tests than {@link #testSpecialLatitudes()} and {@link #testDerivative()}, + * but using spherical formulas. * * @throws FactoryException if an error occurred while creating the map projection. * @throws TransformException if an error occurred while projecting a coordinate. */ @Test - @DependsOnMethod("testSpecialLatitudes") + @DependsOnMethod({"testSpecialLatitudes", "testDerivative"}) public void testSphericalCase() throws FactoryException, TransformException { - initialize(false, 40); // Spherical case + createNormalizedProjection(false, 40); // Spherical case testSpecialLatitudes(); testDerivative(); - /* - * Make sure that the above methods did not changed the 'transform' field type. - */ + + // Make sure that the above methods did not overwrote the 'transform' field. assertEquals("transform.class", LambertConformal.Spherical.class, transform.getClass()); - /* - * For some random points, compare the result of spherical formulas with the ellipsoidal ones. - */ - initialize(new LambertConformal1SP(), false, + } + + /** + * Verifies the consistency of elliptical formulas with the spherical formulas. + * This test compares the results of elliptical formulas with the spherical ones + * for some random points. + * + * @throws FactoryException if an error occurred while creating the map projection. + * @throws TransformException if an error occurred while projecting a coordinate. + */ + @Test + @DependsOnMethod("testSphericalCase") + public void compareEllipticalWithSpherical() throws FactoryException, TransformException { + createCompleteProjection(new LambertConformal1SP(), false, 0.5, // Central meridian 40, // Latitude of origin 0, // Standard parallel (none) @@ -300,6 +310,6 @@ public final strictfp class LambertConfo 200, // False easting 100); // False northing tolerance = Formulas.LINEAR_TOLERANCE; - verifyInDomain(CoordinateDomain.GEOGRAPHIC_SAFE, 268617081); + compareEllipticalWithSpherical(CoordinateDomain.GEOGRAPHIC_SAFE, 0); } } Modified: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java?rev=1691910&r1=1691909&r2=1691910&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java [UTF-8] Mon Jul 20 10:26:14 2015 @@ -19,11 +19,13 @@ package org.apache.sis.referencing.opera import javax.measure.unit.NonSI; import org.opengis.util.FactoryException; import org.opengis.referencing.datum.Ellipsoid; +import org.opengis.referencing.operation.TransformException; import org.opengis.test.referencing.ParameterizedTransformTest; import org.apache.sis.parameter.Parameters; -import org.apache.sis.internal.referencing.provider.MapProjection; import org.apache.sis.internal.util.Constants; +import org.apache.sis.internal.referencing.provider.MapProjection; import org.apache.sis.referencing.operation.DefaultOperationMethod; +import org.apache.sis.referencing.operation.transform.CoordinateDomain; import org.apache.sis.referencing.operation.transform.MathTransformTestCase; import org.apache.sis.test.mock.MathTransformFactoryMock; import org.apache.sis.test.mock.GeodeticDatumMock; @@ -80,9 +82,9 @@ strictfp class MapProjectionTestCase ext /** * Initializes a complete projection (including conversion from degrees to radians) for the given provider. * This method uses arbitrary central meridian, scale factor, false easting and false northing for increasing - * the chances to detect a mismatch. + * the chances to detect a mismatch. The result is stored in the {@link #transform} field. */ - final void initialize(final DefaultOperationMethod provider, final boolean ellipse, + final void createCompleteProjection(final DefaultOperationMethod provider, final boolean ellipse, final double centralMeridian, final double latitudeOfOrigin, final double standardParallel, @@ -148,4 +150,24 @@ strictfp class MapProjectionTestCase ext } return φ; } + + /** + * Compares the elliptical formulas with the spherical formulas for random points in the given domain. + * The spherical formulas are arbitrarily selected as the reference implementation because they are simpler, + * so less bug-prone, than the elliptical formulas. + * + * @param domain The domain of the numbers to be generated. + * @param randomSeed The seed for the random number generator, or 0 for choosing a random seed. + * @throws TransformException If a conversion, transformation or derivative failed. + */ + final void compareEllipticalWithSpherical(final CoordinateDomain domain, final long randomSeed) + throws TransformException + { + transform = ProjectionResultComparator.sphericalAndEllipsoidal(transform); + if (derivativeDeltas == null) { + final double delta = toRadians(100.0 / 60) / 1852; // Approximatively 100 metres. + derivativeDeltas = new double[] {delta, delta}; + } + verifyInDomain(domain, randomSeed); + } } Modified: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorTest.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorTest.java?rev=1691910&r1=1691909&r2=1691910&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorTest.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorTest.java [UTF-8] Mon Jul 20 10:26:14 2015 @@ -53,7 +53,7 @@ public final strictfp class MercatorTest * * @param ellipse {@code false} for a sphere, or {@code true} for WGS84 ellipsoid. */ - private void initialize(final boolean ellipse) { + private void createNormalizedProjection(final boolean ellipse) { final Mercator2SP method = new Mercator2SP(); transform = new Mercator(method, parameters(method, ellipse)); if (!ellipse) { @@ -70,7 +70,7 @@ public final strictfp class MercatorTest */ @Test public void testNormalizedWKT() { - initialize(true); + createNormalizedProjection(true); assertWktEquals( "PARAM_MT[“Mercator_2SP”,\n" + " PARAMETER[“semi_major”, 1.0],\n" + @@ -85,7 +85,7 @@ public final strictfp class MercatorTest @Test public void testSpecialLatitudes() throws ProjectionException { if (transform == null) { // May have been initialized by 'testSphericalCase'. - initialize(true); // Elliptical case + createNormalizedProjection(true); // Elliptical case } assertEquals ("Not a number", NaN, transform(NaN), tolerance); assertEquals ("Out of range", NaN, transform(+2), tolerance); @@ -116,11 +116,11 @@ public final strictfp class MercatorTest @DependsOnMethod("testSpecialLatitudes") public void testDerivative() throws TransformException { if (transform == null) { // May have been initialized by 'testSphericalCase'. - initialize(true); // Elliptical case + createNormalizedProjection(true); // Elliptical case } - final double delta = toRadians(100.0 / 60) / 1852; // Approximatively 100 metres. + final double delta = toRadians(100.0 / 60) / 1852; // Approximatively 100 metres. derivativeDeltas = new double[] {delta, delta}; - tolerance = 1E-9; + tolerance = 1E-9; // More severe than Formulas.LINEAR_TOLERANCE. verifyDerivative(toRadians(15), toRadians( 30)); verifyDerivative(toRadians(10), toRadians(-60)); } @@ -216,25 +216,35 @@ public final strictfp class MercatorTest } /** - * Verifies the consistency of spherical formulas with the elliptical formulas. + * Performs the same tests than {@link #testSpecialLatitudes()} and {@link #testDerivative()}, + * but using spherical formulas. * * @throws FactoryException if an error occurred while creating the map projection. * @throws TransformException if an error occurred while projecting a coordinate. */ @Test - @DependsOnMethod("testSpecialLatitudes") + @DependsOnMethod({"testSpecialLatitudes", "testDerivative"}) public void testSphericalCase() throws FactoryException, TransformException { - initialize(false); // Spherical case + createNormalizedProjection(false); // Spherical case testSpecialLatitudes(); testDerivative(); - /* - * Make sure that the above methods did not overwrote the 'transform' field. - */ + + // Make sure that the above methods did not overwrote the 'transform' field. assertEquals("transform.class", Mercator.Spherical.class, transform.getClass()); - /* - * For some random points, compare the result of spherical formulas with the ellipsoidal ones. - */ - initialize(new Mercator1SP(), false, + } + + /** + * Verifies the consistency of elliptical formulas with the spherical formulas. + * This test compares the results of elliptical formulas with the spherical ones + * for some random points. + * + * @throws FactoryException if an error occurred while creating the map projection. + * @throws TransformException if an error occurred while projecting a coordinate. + */ + @Test + @DependsOnMethod("testSphericalCase") + public void compareEllipticalWithSpherical() throws FactoryException, TransformException { + createCompleteProjection(new Mercator1SP(), false, 0.5, // Central meridian 0, // Latitude of origin (none) 0, // Standard parallel (none) @@ -242,6 +252,6 @@ public final strictfp class MercatorTest 200, // False easting 100); // False northing tolerance = Formulas.LINEAR_TOLERANCE; - verifyInDomain(CoordinateDomain.GEOGRAPHIC_SAFE, 84018710); + compareEllipticalWithSpherical(CoordinateDomain.GEOGRAPHIC_SAFE, 0); } } Modified: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolarStereographicTest.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolarStereographicTest.java?rev=1691910&r1=1691909&r2=1691910&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolarStereographicTest.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolarStereographicTest.java [UTF-8] Mon Jul 20 10:26:14 2015 @@ -45,14 +45,16 @@ public final strictfp class PolarStereog * @param ellipse {@code false} for a sphere, or {@code true} for WGS84 ellipsoid. * @param latitudeOfOrigin The latitude of origin, in decimal degrees. */ - private void initialize(final boolean ellipse, final double latitudeOfOrigin) { + private void createNormalizedProjection(final boolean ellipse, final double latitudeOfOrigin) { final PolarStereographicA method = new PolarStereographicA(); final Parameters parameters = parameters(method, ellipse); parameters.getOrCreate(PolarStereographicA.LATITUDE_OF_ORIGIN).setValue(latitudeOfOrigin); - transform = new PolarStereographic(method, parameters); + NormalizedProjection projection = new PolarStereographic(method, parameters); if (!ellipse) { - transform = new PolarStereographic.Spherical((PolarStereographic) transform); + projection = new ProjectionResultComparator(projection, + new PolarStereographic.Spherical((PolarStereographic) projection)); } + transform = projection; tolerance = NORMALIZED_TOLERANCE; validate(); } @@ -65,7 +67,7 @@ public final strictfp class PolarStereog */ @Test public void testSphericalCaseSouth() throws FactoryException, TransformException { - initialize(false, -90); + createNormalizedProjection(false, -90); final double delta = toRadians(100.0 / 60) / 1852; // Approximatively 100 metres. derivativeDeltas = new double[] {delta, delta}; verifyInDomain(CoordinateDomain.GEOGRAPHIC_RADIANS_SOUTH, 56763886); @@ -81,7 +83,7 @@ public final strictfp class PolarStereog @Test @DependsOnMethod("testSphericalCaseSouth") public void testSphericalCaseNorth() throws FactoryException, TransformException { - initialize(false, 90); + createNormalizedProjection(false, 90); final double delta = toRadians(100.0 / 60) / 1852; // Approximatively 100 metres. derivativeDeltas = new double[] {delta, delta}; verifyInDomain(CoordinateDomain.GEOGRAPHIC_RADIANS_NORTH, 56763886); Copied: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ProjectionResultComparator.java (from r1690754, sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Assertions.java) URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ProjectionResultComparator.java?p2=sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ProjectionResultComparator.java&p1=sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Assertions.java&r1=1690754&r2=1691910&rev=1691910&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Assertions.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ProjectionResultComparator.java [UTF-8] Mon Jul 20 10:26:14 2015 @@ -16,16 +16,29 @@ */ package org.apache.sis.referencing.operation.projection; +import java.util.Arrays; +import java.util.List; +import org.opengis.parameter.ParameterValueGroup; +import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.Matrix; -import org.apache.sis.util.Static; +import org.apache.sis.internal.referencing.Formulas; +import org.apache.sis.referencing.operation.transform.MathTransforms; +import org.apache.sis.util.ComparisonMode; +import org.apache.sis.util.Debug; -import static java.lang.Math.*; +import static org.junit.Assert.*; /** - * Static methods for assertions. This is used only when Java 1.4 assertions are enabled. - * When a point has been projected using spherical formulas, compares with the same point - * transformed using spherical formulas and throw an exception if the result differ. + * Compares the result of two map projection implementations. + * This class is used in two cases: + * + * <ul> + * <li>When a point has been projected using spherical formulas, compares with the same point + * transformed using elliptical formulas and throw an exception if the result differ.</li> + * <li>Same as the above, but with a projection which is considered a limiting case of another + * more general projection.</li> + * </ul> * * @author Martin Desruisseaux (Geomatys) * @author Rémi Maréchal (Geomatys) @@ -33,128 +46,143 @@ import static java.lang.Math.*; * @version 0.6 * @module */ -final class Assertions extends Static { +@SuppressWarnings("serial") +final strictfp class ProjectionResultComparator extends NormalizedProjection { /** * Maximum difference allowed when comparing the result of an inverse projections, in radians. - * A value of 1E-7 radians is approximatively 0.5 kilometres. + * A value of 1E-11 radians is approximatively 5 centimetres. * Note that inverse projections are typically less accurate than forward projections. - * This tolerance is set to such high value for avoiding too intrusive assertion errors. - * This is okay only for catching gross programming errors. */ - private static final double INVERSE_TOLERANCE = 1E-7; + private static final double INVERSE_TOLERANCE = NormalizedProjection.ANGULAR_TOLERANCE; /** * Maximum difference allowed when comparing the result of forward projections, - * in distance on the unit ellipse. A value of 1E-7 is approximatively 0.1 metres. + * in distance on the unit ellipse. A value of 1E-8 is approximatively 0.01 metres. + */ + private static final double FORWARD_TOLERANCE = Formulas.LINEAR_TOLERANCE; + + /** + * Maximum difference allowed between spherical and elliptical formulas when comparing derivatives. + * Units are the same than {@link #FORWARD_TOLERANCE}. + */ + private static final double DERIVATIVE_TOLERANCE = FORWARD_TOLERANCE; + + /** + * The map projection to be used as the reference projection. */ - private static final double FORWARD_TOLERANCE = 1E-7; + private final NormalizedProjection reference; /** - * Maximum difference allowed between spherical and ellipsoidal formulas when - * comparing derivatives. Units are metres. + * The map projection to be compared with the {@link #reference} one. */ - private static final double DERIVATIVE_TOLERANCE = 1E-1; + private final NormalizedProjection tested; /** - * Do not allows instantiation of this class. + * Creates a projection which will compare the results of the two given projections. */ - private Assertions() { + ProjectionResultComparator(final NormalizedProjection reference, final NormalizedProjection tested) { + super(reference); + this.reference = reference; + this.tested = tested; } /** - * Checks if transform using spherical formulas produces the same result than ellipsoidal formulas. - * This method is invoked during assertions only. - * - * @param expected The (easting,northing) computed by ellipsoidal formulas. - * @param offset Index of the coordinate in the {@code expected} array. - * @param x The easting computed by spherical formulas on the unit sphere. - * @param y The northing computed by spherical formulas on the unit sphere. - * @return Always {@code true}. - * @throws ProjectionException if the comparison failed. + * Replaces the given {@code transform} steps, which is expected to contain exactly one {@link NormalizedProjection} + * instance using spherical formulas, by new steps for the same map projection, but comparing the spherical formulas + * with the elliptical ones. This method searches for a {@link NormalizedProjection} instance, which is expected to + * be an inner class named "Spherical". The inner class is then converted to the outer class using reflection, and + * those two classes are given to the {@link ProjectionResultComparator} constructor. The later is inserted in the + * chain in place of the original spherical formulas. + */ + static MathTransform sphericalAndEllipsoidal(MathTransform transform) { + int numReplacements = 0; + final List<MathTransform> steps = MathTransforms.getSteps(transform); + for (int i=steps.size(); --i >= 0;) { + final MathTransform step = steps.get(i); + if (step instanceof NormalizedProjection) { + final NormalizedProjection spherical = (NormalizedProjection) step; + final Class<?> sphericalClass = spherical.getClass(); + final Class<?> ellipticalClass = sphericalClass.getSuperclass(); + assertEquals("Class name for the spherical formulas.", "Spherical", sphericalClass.getSimpleName()); + assertEquals("Excentricity of spherical case.", 0, spherical.excentricity, 0); + assertSame("In SIS implementation, the spherical cases are defined as inner classes named “Spherical”" + + " which extend their enclosing class. This is only a convention, which we verify here. But" + + " there is nothing wrong if a future version choose to not follow this convention anymore.", + sphericalClass.getEnclosingClass(), ellipticalClass); + final Object elliptical; + try { + elliptical = ellipticalClass.getDeclaredConstructor(ellipticalClass).newInstance(spherical); + } catch (ReflectiveOperationException e) { + throw new AssertionError(e); // Considered as a test failure. + } + /* + * Arbitrarily selects spherical formulas as the reference implementation because + * they are simpler, so less bug-prone, than the elliptical formulas. + */ + steps.set(i, new ProjectionResultComparator(spherical, (NormalizedProjection) elliptical)); + numReplacements++; + } + } + assertEquals("Unexpected number of NormalizedTransform instances in the transformation chain.", 1, numReplacements); + transform = steps.get(0); + for (int i=1; i<steps.size(); i++) { + transform = MathTransforms.concatenate(transform, steps.get(i)); + } + return transform; + } + + /** + * Checks if transform using {@link #tested} formulas produces the same result than the {@link #reference} formulas. */ - static boolean checkTransform(final double[] expected, final int offset, final double x, final double y) - throws ProjectionException + @Override + public Matrix transform(final double[] srcPts, final int srcOff, + final double[] dstPts, final int dstOff, boolean derivate) throws ProjectionException { - if (expected != null) { - compare("x", expected[offset ], x, FORWARD_TOLERANCE); - compare("y", expected[offset+1], y, FORWARD_TOLERANCE); + final double[] point = Arrays.copyOfRange(srcPts, srcOff, srcOff + 2); + final Matrix derivative = tested.transform(srcPts, srcOff, dstPts, dstOff, derivate); + final Matrix expected = reference.transform(point, 0, point, 0, derivate); + if (dstPts != null) { + assertEquals("x", point[0], dstPts[dstOff ], FORWARD_TOLERANCE); + assertEquals("y", point[1], dstPts[dstOff+1], FORWARD_TOLERANCE); } - return true; + if (expected != null && derivative != null) { + assertEquals("m00", expected.getElement(0,0), derivative.getElement(0,0), DERIVATIVE_TOLERANCE); + assertEquals("m01", expected.getElement(0,1), derivative.getElement(0,1), DERIVATIVE_TOLERANCE); + assertEquals("m10", expected.getElement(1,0), derivative.getElement(1,0), DERIVATIVE_TOLERANCE); + assertEquals("m11", expected.getElement(1,1), derivative.getElement(1,1), DERIVATIVE_TOLERANCE); + } + return derivative; } /** - * Checks if inverse transform using spherical formulas produces the same result than ellipsoidal formulas. - * This method is invoked during assertions only. - * - * @param expected The (longitude,latitude) computed by ellipsoidal formulas. - * @param offset Index of the coordinate in the {@code expected} array. - * @param λ The longitude computed by spherical formulas, in radians. - * @param φ The latitude computed by spherical formulas, in radians. - * @return Always {@code true}. - * @throws ProjectionException if the comparison failed. + * Checks if transform using {@link #tested} inverse formulas produces the same result than the + * {@link #reference} inverse formulas. */ - static boolean checkInverseTransform(final double[] expected, final int offset, final double λ, final double φ) - throws ProjectionException + @Override + protected void inverseTransform(final double[] srcPts, final int srcOff, + final double[] dstPts, final int dstOff) throws ProjectionException { - compare("latitude", expected[offset+1], φ, INVERSE_TOLERANCE); - compare("longitude", expected[offset ], λ, INVERSE_TOLERANCE / abs(cos(φ))); - return true; + final double[] point = Arrays.copyOfRange(srcPts, srcOff, srcOff + 2); + tested.inverseTransform(srcPts, srcOff, dstPts, dstOff); + reference.inverseTransform(point, 0, point, 0); + assertEquals("φ", point[0], dstPts[dstOff ], INVERSE_TOLERANCE); + assertEquals("λ", point[1], dstPts[dstOff+1], INVERSE_TOLERANCE); } /** - * Checks if derivatives using spherical formulas produces the same result than ellipsoidal formulas. - * This method is invoked during assertions only. The spherical formulas are used for the "expected" - * results since they are simpler than the ellipsoidal formulas. - */ - @SuppressWarnings("null") - static boolean checkDerivative(final Matrix spherical, final Matrix ellipsoidal) throws ProjectionException { - if (spherical != null || ellipsoidal != null) { // NullPointerException is ok if only one is null. - compare("m00", spherical.getElement(0,0), ellipsoidal.getElement(0,0), DERIVATIVE_TOLERANCE); - compare("m01", spherical.getElement(0,1), ellipsoidal.getElement(0,1), DERIVATIVE_TOLERANCE); - compare("m10", spherical.getElement(1,0), ellipsoidal.getElement(1,0), DERIVATIVE_TOLERANCE); - compare("m11", spherical.getElement(1,1), ellipsoidal.getElement(1,1), DERIVATIVE_TOLERANCE); - } - return true; + * Delegates to the {@link #tested} implementation. + */ + @Debug + @Override + public ParameterValueGroup getParameterValues() { + return tested.getParameterValues(); } /** - * Compares two value for equality up to some tolerance threshold. This is used during assertions only. - * The comparison does not fail if at least one value to compare is {@link Double#NaN} or infinity. - * - * <p><strong>Hack:</strong> if the {@code variable} name starts by lower-case {@code L} - * (as in "longitude" and "latitude"), then the value is assumed to be an angle in radians. - * This is used for formatting an error message, if needed.</p> - * - * @throws ProjectionException if the comparison failed. + * Delegates to the {@link #tested} implementation. */ - private static void compare(final String variable, double expected, double actual, final double tolerance) - throws ProjectionException - { - final double delta = abs(expected - actual); - if (delta > tolerance) { - if (variable.charAt(0) == 'l') { - actual = toDegrees(actual); - expected = toDegrees(expected); - } else if (abs(actual) > 30 && abs(expected) > 30) { - /* - * If the projected point tend toward infinity, treats the value as if is was - * really infinity. Note that 30 is considered as "close to infinity" because - * of the result we get when projecting 90°N using Mercator spherical formula: - * - * y = log(tan(π/4 + φ/2)) - * - * Because there is no exact representation of π/2 in base 2, the tangent - * function gives 1.6E+16 instead of infinity, which leads the logarithmic - * function to give us 37.3. - * - * This behavior is tested in MercatorTest.testSphericalFormulas(). - */ - if (signum(actual) == signum(expected)) { - return; - } - } - throw new ProjectionException("Assertion error: expected " + variable + "= " + expected + " but got " + - actual + ". Difference is " + delta + " and comparison threshold was " + tolerance + '.'); - } + @Override + public boolean equals(final Object object, final ComparisonMode mode) { + return tested.equals(object, mode); } } Modified: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java?rev=1691910&r1=1691909&r2=1691910&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java [UTF-8] Mon Jul 20 10:26:14 2015 @@ -41,7 +41,7 @@ public final strictfp class TransverseMe * * @param ellipse {@code false} for a sphere, or {@code true} for WGS84 ellipsoid. */ - private void initialize(final boolean ellipse, final double latitudeOfOrigin) { + private void createNormalizedProjection(final boolean ellipse, final double latitudeOfOrigin) { final org.apache.sis.internal.referencing.provider.TransverseMercator method = new org.apache.sis.internal.referencing.provider.TransverseMercator(); final Parameters parameters = parameters(method, ellipse); @@ -89,7 +89,7 @@ public final strictfp class TransverseMe @Test @org.junit.Ignore("Missing implementation of the projection derivative.") public void testSphericalDerivative() throws TransformException { - initialize(false, 0); + createNormalizedProjection(false, 0); tolerance = 1E-9; final double delta = toRadians(100.0 / 60) / 1852; // Approximatively 100 metres. @@ -107,7 +107,7 @@ public final strictfp class TransverseMe @Test @org.junit.Ignore("Missing implementation of the projection derivative.") public void testEllipsoidalDerivative() throws TransformException { - initialize(true, 0); + createNormalizedProjection(true, 0); tolerance = 1E-9; final double delta = toRadians(100.0 / 60) / 1852; // Approximatively 100 metres.