You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2015/03/30 00:54:14 UTC

svn commit: r1669971 - in /sis/branches/JDK8/core/sis-referencing/src: main/java/org/apache/sis/internal/referencing/ main/java/org/apache/sis/referencing/operation/projection/ test/java/org/apache/sis/internal/referencing/ test/java/org/apache/sis/ref...

Author: desruisseaux
Date: Sun Mar 29 22:54:13 2015
New Revision: 1669971

URL: http://svn.apache.org/r1669971
Log:
Referencing: review our policy about the tolerance threshold in map projections.

Modified:
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.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/NormalizedProjection.java
    sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/OperationMethodsTest.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/NormalizedProjectionTest.java
    sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ContextualParametersTest.java
    sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java?rev=1669971&r1=1669970&r2=1669971&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java [UTF-8] Sun Mar 29 22:54:13 2015
@@ -37,8 +37,8 @@ import static org.apache.sis.internal.me
 public final class Formulas extends Static {
     /**
      * Default tolerance threshold for comparing ordinate values in a projected CRS,
-     * assuming that the unit of measurement is metre. This is not a tolerance for
-     * testing map projection accuracy.
+     * assuming that the unit of measurement is metre. This constant determines also
+     * (indirectly) the minimum accuracy of iterative methods in map projections.
      *
      * @see #ANGULAR_TOLERANCE
      * @see org.apache.sis.internal.util.Numerics#COMPARISON_THRESHOLD
@@ -50,6 +50,8 @@ public final class Formulas extends Stat
      * assuming that the unit of measurement is decimal degrees and using the standard
      * nautical mile length.
      *
+     * <p>For a {@link #LINEAR_TOLERANCE} of 1 centimetre, this is slightly less than 1E-7°.</p>
+     *
      * @see #LINEAR_TOLERANCE
      * @see org.apache.sis.internal.util.Numerics#COMPARISON_THRESHOLD
      */

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=1669971&r1=1669970&r2=1669971&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] Sun Mar 29 22:54:13 2015
@@ -25,6 +25,7 @@ import org.apache.sis.internal.referenci
 import org.apache.sis.internal.referencing.provider.Mercator2SP;
 import org.apache.sis.internal.referencing.provider.PseudoMercator;
 import org.apache.sis.internal.referencing.provider.MillerCylindrical;
+import org.apache.sis.internal.referencing.Formulas;
 import org.apache.sis.internal.util.DoubleDouble;
 import org.apache.sis.referencing.operation.matrix.MatrixSIS;
 import org.apache.sis.referencing.operation.matrix.Matrix2;
@@ -112,7 +113,7 @@ public class Mercator extends Normalized
          * where the parameter is included in the EPSG dataset for completeness in CRS labelling but is not allowed
          * to be assigned any value other than zero.
          */
-        if (φ0 != 0) {
+        if (!(abs(φ0) <= Formulas.ANGULAR_TOLERANCE)) {   // Use '!' for catching NaN.
             throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalParameterValue_2,
                     Mercator2SP.LATITUDE_OF_ORIGIN.getName(), φ0));
         }
@@ -219,21 +220,19 @@ public class Mercator extends Normalized
          * 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).
-         *
-         * We also check for poles, but using (sinφ == 1) as a criterion instead than (φ == PI/2).
-         * There is almost 100 millions floating point values around PI/2 for which sin(φ) == 1.
-         * The sine become 0.9999999999999999 only after we move at least 6E-7° away from the pole,
-         * which is less than 10 centimetres.
          */
         final double y;
-        if (sinφ == 0) {
+        if (φ == 0) {
             y = φ;
-        } else if (sinφ == 1 && φ >= PI/2) {    // Really exact comparison, no epsilon here.
-            y = POSITIVE_INFINITY;              // See javadoc of Spherical inner class for rational.
-        } else if (sinφ == -1 && φ <= -PI/2) {
-            y = NEGATIVE_INFINITY;
         } else {
-            y = log(expOfNorthing(φ, excentricity * sinφ));     // Snyder (7-7)
+            // See the javadoc of the Spherical inner class for a note
+            // about why we perform explicit checks for the pole cases.
+            final double a = abs(φ);
+            if (a < PI/2) {
+                y = log(expOfNorthing(φ, excentricity * sinφ));     // Snyder (7-7)
+            } else {
+                y = copySign(a <= (PI/2 + ANGULAR_TOLERANCE) ? POSITIVE_INFINITY : NaN, φ);
+            }
         }
         if (dstPts != null) {
             dstPts[dstOff]   = λ;
@@ -253,7 +252,7 @@ public class Mercator extends Normalized
      * are written at the same locations than the source coordinates. In such case, we can take advantage of the
      * fact that the λ values are not modified by the normalized Mercator projection.</div>
      *
-     * @throws TransformException if a point can not be transformed.
+     * @throws TransformException if a point can not be converted.
      */
     @Override
     public void transform(final double[] srcPts, int srcOff,
@@ -266,15 +265,15 @@ public class Mercator extends Normalized
             dstOff--;
             while (--numPts >= 0) {
                 final double φ = dstPts[dstOff += 2]; // Same as srcPts[srcOff + 1].
-                final double y;
                 if (φ != 0) {
-                    final double sinφ = sin(φ);
-                    if (sinφ == 1 && φ >= PI/2) {            // Really exact comparison, no epsilon here.
-                        y = POSITIVE_INFINITY;               // See javadoc of Spherical inner class for rational.
-                    } else if (sinφ == -1 && φ <= -PI/2) {
-                        y = NEGATIVE_INFINITY;
+                    // See the javadoc of the Spherical inner class for a note
+                    // about why we perform explicit checks for the pole cases.
+                    final double a = abs(φ);
+                    final double y;
+                    if (a < PI/2) {
+                        y = log(expOfNorthing(φ, excentricity * sin(φ)));
                     } else {
-                        y = log(expOfNorthing(φ, excentricity * sinφ));
+                        y = copySign(a <= (PI/2 + ANGULAR_TOLERANCE) ? POSITIVE_INFINITY : NaN, φ);
                     }
                     dstPts[dstOff] = y;
                 }
@@ -308,14 +307,13 @@ public class Mercator extends Normalized
      * which is the correct answer. In practice the infinite value emerges by itself at only one pole, and the other
      * one produces a high value (approximatively 1E+16). This is because there is no accurate representation of π/2,
      * and consequently {@code tan(π/2)} does not return the infinite value. We workaround this issue with an explicit
-     * check for φ = π/2 only. Note that:
+     * check for abs(φ) ≊ π/2. Note that:
      *
      * <ul>
-     *   <li>We check for strict equality, not a comparison with a tolerance threshold, because the arithmetic is not
-     *       broken for values close to pole. We check π/2 because this is the result of converting 90°N to radians,
-     *       and we presume that the user really wanted to said 90°N. But for all other values we can let the math do
-     *       their "natural" work.</li>
-     *   <li>There is no need to check for φ = -π/2 as our arithmetic already produces negative infinity.</li>
+     *   <li>The arithmetic is not broken for values close to pole. We check π/2 because this is the result of
+     *       converting 90°N to radians, and we presume that the user really wanted to said 90°N. But for most
+     *       other values we could let the math do their "natural" work.</li>
+     *   <li>For φ = -π/2 our arithmetic already produces negative infinity.</li>
      * </ul>
      * </div>
      *
@@ -359,10 +357,14 @@ public class Mercator extends Normalized
             final double y;
             if (φ == 0) {
                 y = φ;
-            } else if (φ == PI/2) {             // Really exact comparison, no epsilon here.
-                y = POSITIVE_INFINITY;          // See class javadoc for rational.
             } else {
-                y = log(tan(PI/4 + 0.5*φ));    // Part of Snyder (7-2)
+                // 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) {
@@ -398,12 +400,14 @@ public class Mercator extends Normalized
                 dstOff--;
                 while (--numPts >= 0) {
                     final double φ = dstPts[dstOff += 2];   // Same as srcPts[srcOff + 1].
-                    final double y;
                     if (φ != 0) {
-                        if (φ == PI/2) {                    // Really exact comparison, no epsilon here.
-                            y = POSITIVE_INFINITY;          // See class javadoc for rational.
-                        } else {
+                        // See class javadoc for a note about explicit check for poles.
+                        final double a = abs(φ);
+                        final double y;
+                        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, φ);
                         }
                         dstPts[dstOff] = y;
                     }

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java?rev=1669971&r1=1669970&r2=1669971&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java [UTF-8] Sun Mar 29 22:54:13 2015
@@ -34,6 +34,7 @@ import org.apache.sis.referencing.operat
 import org.apache.sis.referencing.operation.transform.AbstractMathTransform2D;
 import org.apache.sis.referencing.operation.transform.ContextualParameters;
 import org.apache.sis.internal.referencing.provider.MapProjection;
+import org.apache.sis.internal.referencing.Formulas;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.internal.util.Numerics;
 
@@ -122,20 +123,24 @@ public abstract class NormalizedProjecti
 
     /**
      * Maximum difference allowed when comparing longitudes or latitudes in radians.
-     * A tolerance of 1E-6 is about 0.2 second of arcs, which is about 6 kilometers
-     * (computed from the standard length of nautical mile).
+     * The current value take the system-wide angular tolerance value (equivalent to
+     * about 1 cm on Earth) converted to radians.
      *
      * <p>Some formulas use this tolerance value for testing sines or cosines of an angle.
      * In the sine case, this is justified because sin(θ) ≅ θ when θ is small.
      * Similar reasoning applies to cosine with cos(θ) ≅ θ + π/2 when θ is small.</p>
      */
-    static final double ANGLE_TOLERANCE = 1E-6;
+    static final double ANGULAR_TOLERANCE = Formulas.ANGULAR_TOLERANCE * (PI/180);
 
     /**
-     * Difference allowed in iterative computations. A value of 1E-10 causes the {@link #φ(double)} function
-     * to compute the latitude at a precision of 1E-10 radians, which is slightly smaller than one millimetre.
+     * Desired accuracy for the result of iterative computations, in radians.
+     * This constant defines the desired accuracy of methods like {@link #φ(double)}.
+     *
+     * <p>The current value is 0.25 time the accuracy derived from {@link Formulas#LINEAR_TOLERANCE}.
+     * So if the linear tolerance is 1 cm, then the accuracy that we will seek for is 0.25 cm (about
+     * 4E-10 radians). The 0.25 factor is a safety margin for meeting the 1 cm accuracy.</p>
      */
-    static final double ITERATION_TOLERANCE = 1E-10;
+    static final double ITERATION_TOLERANCE = Formulas.ANGULAR_TOLERANCE * (PI/180) * 0.25;
 
     /**
      * Maximum number of iterations for iterative computations.
@@ -143,11 +148,6 @@ public abstract class NormalizedProjecti
     static final int MAXIMUM_ITERATIONS = 15;
 
     /**
-     * Maximum difference allowed when comparing real numbers (other cases).
-     */
-    static final double EPSILON = 1E-7;
-
-    /**
      * The parameters used for creating this projection. They are used for formatting <cite>Well Known Text</cite> (WKT)
      * and error messages. Subclasses shall not use the values defined in this object for computation purpose, except at
      * construction time.
@@ -243,14 +243,29 @@ public abstract class NormalizedProjecti
      */
     final double getAndStore(final Parameters parameters, final ParameterDescriptor<Double> descriptor) {
         final double value = parameters.doubleValue(descriptor);    // Apply a unit conversion if needed.
-        final Double defaultValue = descriptor.getDefaultValue();
-        if (defaultValue == null || value != defaultValue) {
+        Comparable<Double> check = descriptor.getMinimumValue();
+        if (check instanceof Number && !(value >= ((Number) check).doubleValue())) {
+            throw outOfBounds(descriptor, value);
+        }
+        check = descriptor.getMaximumValue();
+        if (check instanceof Number && !(value <= ((Number) check).doubleValue())) {
+            throw outOfBounds(descriptor, value);
+        }
+        check = descriptor.getDefaultValue();
+        if (check == null || !check.equals(value)) {
             context.parameter(descriptor.getName().getCode()).setValue(value);
         }
         return value;
     }
 
     /**
+     * Creates the exception for a parameter value out of bounds.
+     */
+    private static IllegalArgumentException outOfBounds(final ParameterDescriptor<Double> descriptor, double value) {
+        return new IllegalArgumentException(Errors.format(Errors.Keys.IllegalParameterValue_2, descriptor.getName(), value));
+    }
+
+    /**
      * Returns the sequence of <cite>normalization</cite> → {@code this} → <cite>denormalization</cite> transforms
      * as a whole. The transform returned by this method except (<var>longitude</var>, <var>latitude</var>)
      * coordinates in <em>degrees</em> and returns (<var>x</var>,<var>y</var>) coordinates in <em>metres</em>.
@@ -567,26 +582,36 @@ public abstract class NormalizedProjecti
      * <p>The Mercator projection is given by the {@linkplain Math#log(double) natural logarithm} of the
      * value returned by this method. This function is <em>almost</em> the converse of {@link #φ(double)}.
      *
-     * <div class="section">Properties</div>
-     * This function has the following identity (ignoring rounding errors):
-     *
-     * <blockquote>expOfNorthing(-φ)  =  1 / expOfNorthing(φ)</blockquote>
      *
-     * This function has a periodicity of 2π.
-     * The result is always a positive value when φ is valid (more on it below).
-     * More specifically its behavior at some particular points is:
+     * <div class="section">Properties</div>
+     * This function is used with φ values in the [-π/2 … π/2] range and has a periodicity of 2π.
+     * The result is always a positive number when the φ argument is inside the above-cited range.
+     * If, after removal of any 2π periodicity, φ is still outside the [-π/2 … π/2] range, then the
+     * result is a negative number. In a Mercator projection, such negative number will result in NaN.
      *
+     * <p>Some values are:</p>
      * <ul>
+     *   <li>expOfNorthing(NaN)    =  NaN</li>
+     *   <li>expOfNorthing(±∞)     =  NaN</li>
      *   <li>expOfNorthing(-π/2)   =   0</li>
      *   <li>expOfNorthing( 0  )   =   1</li>
      *   <li>expOfNorthing(+π/2)   →   ∞  (actually some large value like 1.633E+16)</li>
-     *   <li>expOfNorthing( ∞  )   =  NaN</li>
-     *   <li>expOfNorthing(NaN)    =  NaN</li>
-     *   <li>If φ, after removal of any 2π periodicity, still outside the [-π/2 … π/2] range,
-     *       then the result is a negative number. If the caller is going to compute the logarithm
-     *       of the returned value as in the Mercator projection, (s)he will get NaN.</li>
+     *   <li>expOfNorthing(-φ)     =  1 / expOfNorthing(φ)</li>
      * </ul>
      *
+     *
+     * <div class="section">The π/2 special case</div>
+     * The value at {@code Math.PI/2} is not exactly infinity because there is no exact representation of π/2.
+     * However since the conversion of 90° to radians gives {@code Math.PI/2}, we can presume that the user was
+     * expecting infinity. The caller should check for the PI/2 special case himself if desired, as this method
+     * does nothing special about it.
+     *
+     * <p>Note that the result for the φ value after {@code Math.PI/2} (as given by {@link Math#nextUp(double)})
+     * is still positive, maybe because {@literal PI/2 < π/2 < nextUp(PI/2)}. Only the {@code nextUp(nextUp(PI/2))}
+     * value become negative. Callers may need to take this behavior in account: special check for {@code Math.PI/2}
+     * is not sufficient, the check needs to include at least the {@code nextUp(Math.PI/2)} case.</p>
+     *
+     *
      * <div class="section">Relationship with Snyder</div>
      * This function is related to the following functions from Snyder:
      *

Modified: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/OperationMethodsTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/OperationMethodsTest.java?rev=1669971&r1=1669970&r2=1669971&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/OperationMethodsTest.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/OperationMethodsTest.java [UTF-8] Sun Mar 29 22:54:13 2015
@@ -90,7 +90,7 @@ public final strictfp class OperationMet
     }
 
     /**
-     * Tests {@link OperationMethods#checkDimensions(OperationMethod, MathTransform)}.
+     * Tests {@link OperationMethods#checkDimensions(OperationMethod, MathTransform, Map)}.
      */
     @Test
     public void testCheckDimensions() {

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=1669971&r1=1669970&r2=1669971&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] Sun Mar 29 22:54:13 2015
@@ -16,10 +16,13 @@
  */
 package org.apache.sis.referencing.operation.projection;
 
+import org.opengis.referencing.datum.Ellipsoid;
 import org.apache.sis.parameter.Parameters;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.internal.referencing.provider.Mercator2SP;
 import org.apache.sis.referencing.operation.transform.MathTransformTestCase;
+import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
 import org.junit.Test;
 
@@ -43,22 +46,20 @@ import static org.apache.sis.referencing
 public final strictfp class MercatorTest extends MathTransformTestCase {
     /**
      * Returns a new instance of {@link Mercator} for an ellipsoid.
+     *
+     * @param ellipse {@code false} for a sphere, or {@code true} for WGS84 ellipsoid.
      */
-    private void initialize() {
+    private void initialize(final boolean ellipse) {
         final Mercator2SP method = new Mercator2SP();
         final Parameters parameters = Parameters.castOrWrap(method.getParameters().createValue());
-        parameters.parameter(Constants.SEMI_MAJOR).setValue(6378137);
-        parameters.parameter(Constants.SEMI_MINOR).setValue(6356752);
+        final Ellipsoid ellipsoid = (ellipse ? CommonCRS.WGS84 : CommonCRS.SPHERE).ellipsoid();
+        parameters.parameter(Constants.SEMI_MAJOR).setValue(ellipsoid.getSemiMajorAxis());
+        parameters.parameter(Constants.SEMI_MINOR).setValue(ellipsoid.getSemiMinorAxis());
         transform = new Mercator(method, parameters);
         tolerance = 1E-12;
-    }
-
-    /**
-     * Replaces the ellipsoidal formulas by the spherical ones.
-     */
-    private void switchToSpherical() {
-        assertEquals(Mercator.class, transform.getClass());
-        transform = new Mercator.Spherical((Mercator) transform);
+        if (!ellipse) {
+            transform = new Mercator.Spherical((Mercator) transform);
+        }
     }
 
     /**
@@ -101,22 +102,30 @@ public final strictfp class MercatorTest
     }
 
     /**
-     * Tests the projection at a few extreme points.
+     * Verifies the consistency of spherical formulas with the elliptical formulas.
      *
      * @throws ProjectionException Should never happen.
      */
     @Test
-    public void testExtremes() throws ProjectionException {
-        initialize();
-        doTestExtremes();
-//      switchToSpherical();
-//      doTestExtremes();
+    @DependsOnMethod("testSpecialLatitudes")
+    public void testSphericalCase() throws ProjectionException {
+        initialize(false); // Spherical case
+        testSpecialLatitudes();
+
+        // Make sure that 'testSpecialLatitudes' did not overwrite the 'transform' field.
+        assertTrue(transform instanceof Mercator.Spherical);
     }
 
     /**
-     * Implementation of {@link #testExtremes()}, to be executed twice.
+     * Tests the projection at some special latitudes (0, ±π/2, NaN).
+     *
+     * @throws ProjectionException Should never happen.
      */
-    private void doTestExtremes() throws ProjectionException {
+    @Test
+    public void testSpecialLatitudes() throws ProjectionException {
+        if (transform == null) {    // May have been initialized by 'testSphericalCase'.
+            initialize(true);       // Elliptical case
+        }
         validate();
 
         assertEquals ("Not a number",     NaN,                    transform(NaN),           tolerance);

Modified: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NormalizedProjectionTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NormalizedProjectionTest.java?rev=1669971&r1=1669970&r2=1669971&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NormalizedProjectionTest.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NormalizedProjectionTest.java [UTF-8] Sun Mar 29 22:54:13 2015
@@ -19,12 +19,15 @@ package org.apache.sis.referencing.opera
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.test.referencing.TransformTestCase;
 import org.apache.sis.referencing.operation.transform.AbstractMathTransform1D;
+import org.apache.sis.internal.util.DoubleDouble;
 import org.apache.sis.test.DependsOnMethod;
+import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestUtilities;
 import org.junit.Test;
 
 import static java.lang.Double.*;
 import static java.lang.StrictMath.*;
+import static org.apache.sis.internal.metadata.ReferencingServices.NAUTICAL_MILE;
 import static org.junit.Assert.*;
 
 
@@ -36,6 +39,9 @@ import static org.junit.Assert.*;
  * @version 0.6
  * @module
  */
+@DependsOn({
+    org.apache.sis.internal.referencing.provider.MapProjectionTest.class
+})
 public final strictfp class NormalizedProjectionTest extends TransformTestCase {
     /**
      * Tolerance level for comparing floating point numbers.
@@ -87,12 +93,11 @@ public final strictfp class NormalizedPr
      */
     @Test
     public void testDocumentation() {
-        double minutes = toDegrees(NormalizedProjection.ANGLE_TOLERANCE) * 60;
-        assertEquals("Documentation said 0.2″ precision.", 0.2, minutes*60, 0.1);
-        assertEquals("Documentation said 6 km precision.", 6, minutes*1852, 0.5);
+        double minutes = toDegrees(NormalizedProjection.ANGULAR_TOLERANCE) * 60;
+        assertEquals("Documentation said 1 cm precision.", 0.01, minutes*NAUTICAL_MILE, 0.005);
 
         minutes = toDegrees(NormalizedProjection.ITERATION_TOLERANCE) * 60;
-        assertEquals("Documentation said 1 mm precision.", 0.001, minutes*1852, 0.0005);
+        assertEquals("Documentation said 2.5 mm precision.", 0.0025, minutes*NAUTICAL_MILE, 0.0005);
     }
 
     /**
@@ -123,6 +128,14 @@ public final strictfp class NormalizedPr
         assertEquals("Forward (90+ε)°N", NaN, log(tan(nextUp(PI/2))),      TOLERANCE);
         assertEquals("Inverse +∞",       0, atan(exp(NEGATIVE_INFINITY)),  TOLERANCE);
         assertEquals("Inverse +∞ appr.", 0, atan(exp(-(LN_INFINITY + 1))), TOLERANCE);
+        /*
+         * Some checks performed in our projection implementations assume that
+         * conversion of 90° to radians give exactly Math.PI/2.
+         */
+        final DoubleDouble dd = DoubleDouble.createDegreesToRadians();
+        dd.multiply(90);
+        assertEquals(PI/2, dd.value, 0.0);
+        assertEquals(PI/2, toRadians(90), 0.0);
     }
 
     /**
@@ -147,25 +160,45 @@ public final strictfp class NormalizedPr
      * The {@link #projection} field must have been set before this method is called.
      */
     private void doTestExpOfNorthing() {
-        assertEquals("Function contract",  NaN, expOfNorthing(NaN),               tolerance);
-        assertEquals("Function contract",  NaN, expOfNorthing(POSITIVE_INFINITY), tolerance);
-        assertEquals("Function contract",  NaN, expOfNorthing(NEGATIVE_INFINITY), tolerance);
-        assertEquals("Function contract",    1, expOfNorthing(0),                 tolerance);
-        assertEquals("Function contract",    0, expOfNorthing(-PI/2),             tolerance);
-        assertTrue  ("Function contract",       expOfNorthing(+PI/2)              > 1E+16);
-        assertTrue  ("Out of bounds",           expOfNorthing(-PI/2 - 0.1)        < 0);
-        assertTrue  ("Out of bounds",           expOfNorthing(+PI/2 + 0.1)        < 0);
-        assertEquals("Out of bounds",       -1, expOfNorthing(-PI),               tolerance);
-        assertTrue  ("Out of bounds",           expOfNorthing(-PI*3/2)            < -1E+16);
-        assertEquals("Function periodicity", 1, expOfNorthing(-2*PI),             tolerance);
-        assertEquals("Function periodicity", 0, expOfNorthing(-PI*5/2),           tolerance);
+        assertEquals("f(NaN) = NaN",       NaN, expOfNorthing(NaN),               tolerance);
+        assertEquals("f( ±∞) = NaN",       NaN, expOfNorthing(NEGATIVE_INFINITY), tolerance);
+        assertEquals("f( ±∞) = NaN",       NaN, expOfNorthing(POSITIVE_INFINITY), tolerance);
+        assertEquals("f(  0°) = 1",          1, expOfNorthing(0),                 tolerance);
+        assertEquals("f(-90°) = 0",          0, expOfNorthing(-PI/2),             tolerance);
+        assertTrue  ("f(< -90°) < 0",           expOfNorthing(-PI/2 - 0.1)        < 0);
+        assertTrue  ("f(< -90°) < 0",           expOfNorthing(nextDown(-PI/2))    < 0);
+        /*
+         * Values around π/2 are a special case. Theoretically the result should be positive infinity.
+         * But since we do not have an exact representatation of π/2, we instead get a high number.
+         * Furthermore the value does not become negative immediately after π/2; we have to skip an
+         * other IEEE 754 double value. This is because the real π/2 value is actually between PI/2
+         * and nextUp(PI/2):
+         *
+         *      PI/2          =   1.570796326794896558…
+         *      π/2           =   1.570796326794896619…
+         *      nextUp(PI/2)  =   1.570796326794896780…
+         */
+        assertTrue("f(+90°) → ∞",   expOfNorthing(+PI/2) > exp(LN_INFINITY));
+        assertTrue("f(> +90°) < 0", expOfNorthing(+PI/2 + 0.1) < 0);
+        assertTrue("f(> +90°) < 0", expOfNorthing(nextUp(nextUp(+PI/2))) < 0);
+        /*
+         * Test function periodicity. This is not a strong requirement for the expOfNorthing(…) function,
+         * but we nevertheless try to ensure that the method behaves correctly with unexpected values.
+         */
+        assertEquals("f(+360°)",  1, expOfNorthing(+2*PI),   tolerance);
+        assertEquals("f(+270°)",  0, expOfNorthing(+PI*3/2), tolerance);
+        assertEquals("f(+180°)", -1, expOfNorthing(+PI),     tolerance);
+        assertEquals("f(-180°)", -1, expOfNorthing(-PI),     tolerance);
+        assertTrue  ("f(-270°) → ∞", expOfNorthing(-PI*3/2)  < exp(-LN_INFINITY));
+        assertEquals("f(-360°)",  1, expOfNorthing(-2*PI),   tolerance);
+        assertEquals("f(-450°)",  0, expOfNorthing(-PI*5/2), tolerance);
         /*
          * Use in a way close to (but not identical)
          * to the way the Mercator projection need it.
          */
-        assertEquals("Forward 0°N",  0,                 log(expOfNorthing(0)),     tolerance);
-        assertEquals("Forward 90°S", NEGATIVE_INFINITY, log(expOfNorthing(-PI/2)), tolerance);
-        assertTrue  ("Forward 90°N", LN_INFINITY <      log(expOfNorthing(+PI/2)));
+        assertEquals("Mercator(0°)",   0,                 log(expOfNorthing(0)),     tolerance);
+        assertEquals("Mercator(90°S)", NEGATIVE_INFINITY, log(expOfNorthing(-PI/2)), tolerance);
+        assertTrue  ("Mercator(90°N)", LN_INFINITY <      log(expOfNorthing(+PI/2)));
     }
 
     /**
@@ -192,16 +225,16 @@ public final strictfp class NormalizedPr
      * The {@link #projection} field must have been set before this method is called.
      */
     private void doTest_φ() throws ProjectionException {
-        assertEquals("Function contract",  NaN,  φ(NaN),               tolerance);
-        assertEquals("Function contract",  PI/2, φ(0),                 tolerance);
-        assertEquals("Function contract",  PI/2, φ(MIN_VALUE),         tolerance);
-        assertEquals("Function contract",  0,    φ(1),                 tolerance);
-        assertEquals("Function contract", -PI/2, φ(MAX_VALUE),         tolerance);
-        assertEquals("Function contract", -PI/2, φ(POSITIVE_INFINITY), tolerance);
-        assertEquals("Out of bounds",   PI+PI/2, φ(NEGATIVE_INFINITY), tolerance);
-        assertEquals("Out of bounds",   PI+PI/2, φ(-MAX_VALUE),        tolerance);
-        assertEquals("Out of bounds",   PI,      φ(-1),                tolerance);
-        assertEquals("Almost f. contract", PI/2, φ(-MIN_VALUE),        tolerance);
+        assertEquals("φ(NaN) = NaN",    NaN,   φ(NaN),               tolerance);
+        assertEquals("φ( ∞)  = -90°", -PI/2,   φ(POSITIVE_INFINITY), tolerance);
+        assertEquals("φ( ∞)  = -90°", -PI/2,   φ(MAX_VALUE),         tolerance);
+        assertEquals("φ( 1)  =   0°",    0,    φ(1),                 tolerance);
+        assertEquals("φ( ε)  →  90°",  PI/2,   φ(MIN_VALUE),         tolerance);
+        assertEquals("φ( 0)  =  90°",  PI/2,   φ(0),                 tolerance);
+        assertEquals("φ(-ε)  →  90°",  PI/2,   φ(-MIN_VALUE),        tolerance);
+        assertEquals("φ(-1)  = 180°",  PI,     φ(-1),                tolerance);
+        assertEquals("φ(-∞)  = 270°",  PI*1.5, φ(-MAX_VALUE),        tolerance);
+        assertEquals("φ(-∞)  = 270°",  PI*1.5, φ(NEGATIVE_INFINITY), tolerance);
         /*
          * Using t(φ) as a reference.
          */

Modified: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ContextualParametersTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ContextualParametersTest.java?rev=1669971&r1=1669970&r2=1669971&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ContextualParametersTest.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ContextualParametersTest.java [UTF-8] Sun Mar 29 22:54:13 2015
@@ -147,7 +147,7 @@ public final strictfp class ContextualPa
         assertMatrixEquals("denormalize", new Matrix3(
                 180/PI,  0,       18,
                 0,       180/PI,  0,
-                0,       0,       1), denormalize, 1E-16);
+                0,       0,       1), denormalize, STRICT);
 
         assertMatrixEquals("product", new Matrix3(
                 1, 0, 6,

Modified: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java?rev=1669971&r1=1669970&r2=1669971&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java [UTF-8] Sun Mar 29 22:54:13 2015
@@ -87,20 +87,20 @@ import org.junit.BeforeClass;
     org.apache.sis.referencing.operation.transform.MathTransformsTest.class,
     org.apache.sis.referencing.operation.transform.ContextualParametersTest.class,
 
-    // Test map projections.
+    // Registration of map projections and other math transforms.
     org.apache.sis.referencing.operation.DefaultFormulaTest.class,
     org.apache.sis.referencing.operation.DefaultOperationMethodTest.class,
-    org.apache.sis.referencing.operation.projection.NormalizedProjectionTest.class,
-    org.apache.sis.referencing.operation.projection.MercatorTest.class,
-
-    // Registration of map projections and other math transforms.
+    org.apache.sis.referencing.operation.transform.OperationMethodSetTest.class,
+    org.apache.sis.internal.referencing.OperationMethodsTest.class,
     org.apache.sis.internal.referencing.provider.AffineTest.class,
     org.apache.sis.internal.referencing.provider.LongitudeRotationTest.class,
     org.apache.sis.internal.referencing.provider.MapProjectionTest.class,
     org.apache.sis.internal.referencing.provider.AllProvidersTest.class,
-    org.apache.sis.referencing.operation.transform.OperationMethodSetTest.class,
     org.apache.sis.referencing.operation.transform.DefaultMathTransformFactoryTest.class,
-    org.apache.sis.internal.referencing.OperationMethodsTest.class,
+
+    // Test map projections.
+    org.apache.sis.referencing.operation.projection.NormalizedProjectionTest.class,
+    org.apache.sis.referencing.operation.projection.MercatorTest.class,
 
     // Coordinate Reference System components.
     org.apache.sis.referencing.datum.BursaWolfParametersTest.class,