sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1690623 - in /sis/branches/JDK8/core/sis-referencing/src: main/java/org/apache/sis/referencing/operation/projection/ test/java/org/apache/sis/referencing/operation/projection/ test/java/org/apache/sis/test/suite/
Date Mon, 13 Jul 2015 10:29:21 GMT
Author: desruisseaux
Date: Mon Jul 13 10:29:21 2015
New Revision: 1690623

URL: http://svn.apache.org/r1690623
Log:
Use a threshold for checking when the planet's excentricity is too high for the accuracy of
the series expansion.
In such case, we will fall back on an iterative method.

Added:
    sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/GeneralLambertTest.java
      - copied, changed from r1690389, sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NormalizedProjectionTest.java
Modified:
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/GeneralLambert.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/MercatorAlternative.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/test/suite/ReferencingTestSuite.java

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/GeneralLambert.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/GeneralLambert.java?rev=1690623&r1=1690622&r2=1690623&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/GeneralLambert.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/GeneralLambert.java
[UTF-8] Mon Jul 13 10:29:21 2015
@@ -17,6 +17,8 @@
 package org.apache.sis.referencing.operation.projection;
 
 import java.util.Map;
+import java.io.IOException;
+import java.io.ObjectInputStream;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.referencing.operation.OperationMethod;
 import org.apache.sis.util.resources.Errors;
@@ -43,9 +45,32 @@ abstract class GeneralLambert extends No
     private static final long serialVersionUID = 458860570536642265L;
 
     /**
+     * The threshold value of {@link #excentricity} at which we consider the accuracy of
the
+     * series expansion insufficient. This threshold is determined empirically with the help
+     * of the {@code MercatorAlternative} class in the test directory.
+     * We choose the value where:
+     *
+     * <ul>
+     *   <li>the average error of series expansion become greater than {@link NormalizedProjection#ITERATION_TOLERANCE},</li>
+     *   <li>the maximal error of series expansion become greater than {@link NormalizedProjection#ANGULAR_TOLERANCE}.</li>
+     * </ul>
+     */
+    static final double EXCENTRICITY_THRESHOLD = 0.16;
+
+    /**
      * Coefficients in the series expansion used by {@link #φ(double)}.
+     *
+     * <p>Consider those fields as final. They are not only of the purpose of {@link
#readObject(ObjectInputStream)}.</p>
+     */
+    private transient double c2χ, c4χ, c6χ, c8χ;
+
+    /**
+     * {@code true} if the {@link #excentricity} value is greater than or equals to {@link
#EXCENTRICITY_THRESHOLD},
+     * in which case the {@link #φ(double)} method will need to use an iterative method.
+     *
+     * <p>Consider this field as final. It is not only of the purpose of {@link #readObject(ObjectInputStream)}.</p>
      */
-    private final double c2χ, c4χ, c6χ, c8χ;
+    private transient boolean useIterations;
 
     /**
      * Constructs a new map projection from the supplied parameters.
@@ -59,6 +84,14 @@ abstract class GeneralLambert extends No
             final Map<ParameterRole, ? extends ParameterDescriptor<Double>> roles)
     {
         super(method, parameters, roles);
+        initialize();
+    }
+
+    /**
+     * Computes the transient fields after construction or deserialization.
+     */
+    private void initialize() {
+        useIterations = (excentricity >= EXCENTRICITY_THRESHOLD);
         final double e2 = excentricitySquared;
         final double e4 = e2 * e2;
         final double e6 = e2 * e4;
@@ -81,6 +114,7 @@ abstract class GeneralLambert extends No
      */
     GeneralLambert(final GeneralLambert other) {
         super(other);
+        useIterations = other.useIterations;
         c2χ = other.c2χ;
         c4χ = other.c4χ;
         c6χ = other.c6χ;
@@ -129,31 +163,41 @@ abstract class GeneralLambert extends No
          * values, for reducing rounding errors due to IEEE 754 arithmetic. We also store
in ε the value
          * of the smallest term.
          */
-        double ε;
-        φ += (ε = c8χ * sin(8*φ))
-                + c6χ * sin(6*φ)
-                + c4χ * sin(4*φ)
-                + c2χ * sin(2*φ);
-        /*
-         * If the smallest term is smaller or equals to the tolerance threshold, we are done.
-         * This is always the case for the WGS84 ellipsoid.
-         */
-        if (!(abs(ε) > ITERATION_TOLERANCE)) {   // Use '!' for catching NaN.
+        φ += c8χ * sin(8*φ)
+           + c6χ * sin(6*φ)
+           + c4χ * sin(4*φ)
+           + c2χ * sin(2*φ);
+
+        if (!useIterations) {
             return φ;
         }
         /*
          * We should never reach this point for map projections on Earth. But if the ellipsoid
is for some
-         * other planet having a hight excentricity, the above series expansion may not be
sufficient.
-         * Try to improve by iteratively solving equation (7-9) from Snyder.
+         * other planet having a high excentricity, then the above series expansion may not
be sufficient.
+         * Try to improve by iteratively solving equation (7-9) from Snyder. However instead
than using
+         * Snyder (7-11) as the starting point, we take the result of above calculation as
the initial φ.
+         * Assuming that it is closer to the real φ value, this save us some iteration loops
and usually
+         * gives us more accurate results (according MercatorAlternative tests).
          */
         final double hℯ = 0.5 * excentricity;
         for (int i=0; i<MAXIMUM_ITERATIONS; i++) {
             final double ℯsinφ = excentricity * sin(φ);
-            ε = abs(φ - (φ = PI/2 - 2*atan(expOfSouthing * pow((1 - ℯsinφ)/(1 + ℯsinφ),
hℯ))));
+            double ε = abs(φ - (φ = PI/2 - 2*atan(expOfSouthing * pow((1 - ℯsinφ)/(1
+ ℯsinφ), hℯ))));
             if (ε <= ITERATION_TOLERANCE) {
                 return φ;
             }
         }
+        if (Double.isNaN(expOfSouthing)) {
+            return Double.NaN;
+        }
         throw new ProjectionException(Errors.Keys.NoConvergence);
     }
+
+    /**
+     * Restores transient fields after deserialization.
+     */
+    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException
{
+        in.defaultReadObject();
+        initialize();
+    }
 }

Copied: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/GeneralLambertTest.java
(from r1690389, 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/GeneralLambertTest.java?p2=sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/GeneralLambertTest.java&p1=sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NormalizedProjectionTest.java&r1=1690389&r2=1690623&rev=1690623&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/GeneralLambertTest.java
[UTF-8] Mon Jul 13 10:29:21 2015
@@ -17,10 +17,7 @@
 package org.apache.sis.referencing.operation.projection;
 
 import java.util.Random;
-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;
@@ -28,55 +25,27 @@ 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.*;
 
 
 /**
- * Tests the {@link NormalizedProjection} class.
+ * Tests the {@link GeneralLambert} class.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.6
  * @version 0.6
  * @module
  */
-@DependsOn({
-    // Following dependency is where the basic parameters (e.g. SEMI_MAJOR) are tested.
-    // Those parameters are needed by NoOp pseudo-projection, which is used in this class.
-    org.apache.sis.internal.referencing.provider.MapProjectionTest.class
-})
-public final strictfp class NormalizedProjectionTest extends TransformTestCase {
+@DependsOn(NormalizedProjectionTest.class)
+public final strictfp class GeneralLambertTest extends TransformTestCase {
     /**
-     * Tolerance level for comparing floating point numbers.
-     */
-    private static final double TOLERANCE = 1E-12;
-
-    /**
-     * Natural logarithm of the pseudo-infinity as returned by Mercator formulas in the spherical
-     * case, truncated to nearest integer. This is not a real infinity because there is no
exact
-     * representation of π/2 in base 2, so tan(π/2) is not positive infinity.
-     */
-    static final int LN_INFINITY = 37;
-
-    /**
-     * Computes {@link NormalizedProjection#expOfNorthing(double, double)} for the given
latitude.
-     *
-     * @param  projection The projection on which to invoke {@code expOfNorthing(…)}.
-     * @param  φ The latitude in radians.
-     * @return {@code Math.exp} of the Mercator projection of the given latitude.
-     */
-    static double expOfNorthing(final NormalizedProjection projection, final double φ) {
-        return projection.expOfNorthing(φ, projection.excentricity * sin(φ));
-    }
-
-    /**
-     * Computes {@link NormalizedProjection#expOfNorthing(double, double)} for the given
latitude.
+     * Computes {@link GeneralLambert#expOfNorthing(double, double)} for the given latitude.
      *
      * @param  φ The latitude in radians.
      * @return {@code Math.exp} of the Mercator projection of the given latitude.
      */
     private double expOfNorthing(final double φ) {
-        return expOfNorthing((NormalizedProjection) transform, φ);
+        return NormalizedProjectionTest.expOfNorthing((GeneralLambert) transform, φ);
     }
 
     /**
@@ -91,133 +60,6 @@ public final strictfp class NormalizedPr
     }
 
     /**
-     * Tests the value documented in the javadoc. Those value may be freely changed;
-     * those tests exist only to increase the chances that the documented values are right.
-     */
-    @Test
-    public void testDocumentation() {
-        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 2.5 mm precision.", 0.0025, minutes*NAUTICAL_MILE,
0.0005);
-    }
-
-    /**
-     * Tests the {@link NormalizedProjection#excentricity} value.
-     */
-    @Test
-    public void testExcentricity() {
-        NormalizedProjection projection;
-        transform = projection = new NoOp(false);
-        assertEquals("excentricity", 0.0, projection.excentricity, 0.0);
-
-        transform = projection = new NoOp(true);
-        assertEquals("excentricity", 0.08181919084262157, projection.excentricity, TOLERANCE);
-    }
-
-    /**
-     * Tests a few formulas used by the Mercator projection in the spherical case.
-     * This is a little bit more a Java test than an Apache SIS test (or to be more
-     * accurate, a test of our understanding of the {@code java.lang.Math} library).
-     *
-     * {@preformat text
-     *   Forward:  y = log(tan(π/4 + φ/2))
-     *   Inverse:  φ = π/2 - 2*atan(exp(-y))
-     * }
-     */
-    @Test
-    public void testMath() {
-        assertEquals("Forward 0°N",      0, log(tan(PI/4)),                   TOLERANCE);
-        assertEquals("Inverse 0 m",      0, PI/2 - 2*atan(exp(0)),            TOLERANCE);
-        assertEquals("Forward 90°S",     NEGATIVE_INFINITY, log(tan(0)),      TOLERANCE);
-        assertEquals("Forward (90+ε)°S", NaN,  log(tan(-nextUp(0))),          TOLERANCE);
-        assertEquals("Inverse -∞",       PI/2, atan(exp(-NEGATIVE_INFINITY)), TOLERANCE);
-        assertEquals("Inverse -∞ appr.", PI/2, atan(exp(LN_INFINITY + 1)),    TOLERANCE);
-        /*
-         * tan(PI/2) do not produces positive infinity as we would expect, because there
is no
-         * exact representation of PI in base 2.  Experiments show that we get some high
value
-         * instead (1.633E+16 on my machine, having a logarithm of 37.332).
-         */
-        assertTrue  ("Forward 90°N",     1E+16 < tan(PI/2));
-        assertTrue  ("Forward 90°N",     LN_INFINITY < log(tan(PI/2)));
-        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);
-    }
-
-    /**
-     * Tests the {@link NormalizedProjection#expOfNorthing(double, double)} function.
-     *
-     * {@preformat text
-     *   Forward:  y = -log(t(φ))
-     *   Inverse:  φ = φ(exp(-y))
-     * }
-     */
-    @Test
-    public void testExpOfNorthing() {
-        transform = new NoOp(false);   // Spherical case
-        tolerance = TOLERANCE;
-        doTestExpOfNorthing();
-        transform = new NoOp(true);    // Ellipsoidal case
-        doTestExpOfNorthing();
-    }
-
-    /**
-     * Implementation of {@link #testExpOfNorthing()}.
-     * The {@link #projection} field must have been set before this method is called.
-     */
-    private void doTestExpOfNorthing() {
-        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("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)));
-    }
-
-    /**
      * Tests the {@link GeneralLambert#φ(double)} function. We expect it to be
      * the converse of the {@link NormalizedProjection#expOfNorthing(double, double)} function.
      * In theory only the [-90° … +90°] range needs to be tested. However the function
is still
@@ -229,7 +71,7 @@ public final strictfp class NormalizedPr
     @DependsOnMethod("testExpOfNorthing")
     public void test_φ() throws ProjectionException {
         transform = new NoOp(false);   // Spherical case
-        tolerance = TOLERANCE;
+        tolerance = NormalizedProjectionTest.TOLERANCE;
         doTest_φ();
         transform = new NoOp(true);    // Ellipsoidal case
         tolerance = NormalizedProjection.ITERATION_TOLERANCE;
@@ -288,50 +130,7 @@ public final strictfp class NormalizedPr
             final double byIterativeMethod = comparator.byIterativeMethod(t);
             final double bySeriesExpansion = comparator.bySeriesExpansion(t);
             assertEquals(bySeriesExpansion, byIterativeMethod, 1E-11);
-            assertEquals(bySeriesExpansion, projection.φ(t),   1E-11);
+            assertEquals(bySeriesExpansion, projection.φ(t),   1E-15);
         }
     }
-
-    /**
-     * Tests the {@link NormalizedProjection#dy_dφ(double, double)} method.
-     *
-     * @throws TransformException Should never happen.
-     */
-    @Test
-    @DependsOnMethod("testExpOfNorthing")
-    public void test_dy_dφ() throws TransformException {
-        tolerance = 1E-7;
-        doTest_dy_dφ(new NoOp(false));      // Spherical case
-        doTest_dy_dφ(new NoOp(true));       // Ellipsoidal case
-    }
-
-    /**
-     * Implementation of {@link #test_dy_dφ()}.
-     * The {@link #projection} field must have been set before this method is called.
-     */
-    private void doTest_dy_dφ(final NoOp projection) throws TransformException {
-        transform = new AbstractMathTransform1D() {
-            @Override public double transform(final double φ) {
-                return expOfNorthing(projection, φ);
-            }
-            @Override public double derivative(final double φ) {
-                final double sinφ = sin(φ);
-                return projection.dy_dφ(sinφ, cos(φ)) * expOfNorthing(projection, φ);
-            }
-        };
-        verifyInDomain(-89 * (PI/180), 89 * (PI/180));  // Verify from 85°S to 85°N.
-    }
-
-    /**
-     * Convenience method invoking {@link TransformTestCase#verifyInDomain} for an 1D transform.
-     */
-    private void verifyInDomain(final double min, final double max) throws TransformException
{
-        derivativeDeltas = new double[] {2E-8};
-        isInverseTransformSupported = false;
-        verifyInDomain(
-                new double[] {min},     // Minimal value to test.
-                new double[] {max},     // Maximal value to test.
-                new int[]    {100},     // Number of points to test.
-                TestUtilities.createRandomNumberGenerator());
-    }
 }

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=1690623&r1=1690622&r2=1690623&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 13 10:29:21 2015
@@ -50,7 +50,7 @@ import static org.junit.Assert.*;
  * @version 0.6
  * @module
  */
-@DependsOn(NormalizedProjectionTest.class)
+@DependsOn(GeneralLambertTest.class)
 public final strictfp class LambertConformalTest extends MapProjectionTestCase {
     /**
      * Creates a new instance of {@link LambertConformal}. See the class javadoc for an explanation

Modified: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorAlternative.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorAlternative.java?rev=1690623&r1=1690622&r2=1690623&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorAlternative.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorAlternative.java
[UTF-8] Mon Jul 13 10:29:21 2015
@@ -19,11 +19,12 @@ package org.apache.sis.referencing.opera
 import java.util.Random;
 import java.io.IOException;
 import java.io.PrintStream;
+import org.apache.sis.io.TableAppender;
 import org.apache.sis.math.Statistics;
 import org.apache.sis.math.StatisticsFormat;
-import org.apache.sis.measure.Latitude;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.internal.metadata.ReferencingServices;
 
 import static java.lang.Math.*;
 
@@ -58,6 +59,11 @@ import static java.lang.Math.*;
  */
 public final class MercatorAlternative {   // No 'strictfp' keyword here since we want to
compare with Mercator class.
     /**
+     * Where to print the outputs of this class.
+     */
+    private static final PrintStream out = System.out;
+
+    /**
      * Ellipsoid excentricity. Value 0 means that the ellipsoid is spherical.
      */
     private final double excentricity;
@@ -106,6 +112,7 @@ public final class MercatorAlternative {
 
     /**
      * Computes φ using the series expansion given by Geomatics Guidance Note number 7,
part 2.
+     * This is the first part of the {@link GeneralLambert#φ(double)} method.
      *
      * @param  t The {@code expOfSouthing} parameter value.
      * @return The latitude (in radians) for the given parameter.
@@ -120,6 +127,7 @@ public final class MercatorAlternative {
 
     /**
      * Computes φ using the iterative method used by USGS.
+     * This is the second part of the {@link GeneralLambert#φ(double)} method.
      *
      * @param  t The {@code expOfSouthing} parameter value.
      * @return The latitude (in radians) for the given parameter.
@@ -150,75 +158,148 @@ public final class MercatorAlternative {
     }
 
     /**
-     * Performs a comparison between φ values computed by the iterative method
-     * and φ values computed by series expansion.
-     * The result is printed to the standard output stream.
+     * Compares the φ values computed by the two methods (iterative and series expansion)
against the expected φ
+     * values for random numbers. The result is printed to the standard output stream as
the maximum and average errors,
+     * in units of {@link NormalizedProjection#ITERATION_TOLERANCE} (about 0.25 cm on a planet
of the size of Earth).
      *
      * @param  numSamples Number of random sample values.
      * @throws ProjectionException if an error occurred during the calculation of φ.
      */
-    public void compare(final int numSamples) throws ProjectionException {
-        compare(null, numSamples);
+    public void printAccuracyComparison(final int numSamples) throws ProjectionException
{
+        compare(null, numSamples, null);
     }
 
     /**
-     * Implementation of {@link #compare(int)}, optionally with a comparison with {@link
GeneralLambert}.
+     * Implementation of {@link #printAccuracyComparison(int)} and {@link #printErrorForExcentricities(double,double)},
+     * optionally with a comparison with {@link GeneralLambert}.
      */
-    private void compare(final GeneralLambert projection, final int numSamples) throws ProjectionException
{
+    private void compare(final GeneralLambert projection, final int numSamples, final TableAppender
summarize)
+            throws ProjectionException
+    {
         final Statistics iterativeMethodErrors = new Statistics("Iterative method error");
         final Statistics seriesExpansionErrors = new Statistics("Series expansion error");
         final Statistics generalLambertErrors  = new Statistics("'GeneralLambert' error");
-        final Statistics methodDifferences     = new Statistics("Δ (iterative - series)");
         final Random random = new Random();
         for (int i=0; i<numSamples; i++) {
-            final double φ_deg = random.nextDouble() * (Latitude.MAX_VALUE - Latitude.MIN_VALUE)
+ Latitude.MIN_VALUE;
-            final double φ     = toRadians(φ_deg);
-            final double t     = 1 / expOfNorthing(φ);
-            final double byIterativeMethod = toDegrees(byIterativeMethod(t));
-            final double bySeriesExpansion = toDegrees(bySeriesExpansion(t));
-
-            iterativeMethodErrors.accept(abs(φ_deg - byIterativeMethod));
-            seriesExpansionErrors.accept(abs(φ_deg - bySeriesExpansion));
-            methodDifferences.accept(byIterativeMethod - bySeriesExpansion);
+            final double φ = random.nextDouble() * PI - PI/2;
+            final double t = 1 / expOfNorthing(φ);
+            final double byIterativeMethod = byIterativeMethod(t);
+            final double bySeriesExpansion = bySeriesExpansion(t);
+
+            iterativeMethodErrors.accept(abs(φ - byIterativeMethod) / NormalizedProjection.ITERATION_TOLERANCE);
+            seriesExpansionErrors.accept(abs(φ - bySeriesExpansion) / NormalizedProjection.ITERATION_TOLERANCE);
             if (projection != null) {
-                generalLambertErrors.accept(abs(φ_deg - toDegrees(projection.φ(t))));
+                generalLambertErrors.accept(abs(φ - projection.φ(t)) / NormalizedProjection.ITERATION_TOLERANCE);
             }
         }
         /*
-         * At this point we finished to collect the statistics.
+         * At this point we finished to collect the statistics for the excentricity of this
particular
+         * MercatorAlternative instance. If this method call is only part of a longer calculation
for
+         * various excentricty values, print a summary in a single line. Otherwise print
more verbose
+         * results.
          */
-        Statistics[] stats = new Statistics[] {
-            iterativeMethodErrors,
-            seriesExpansionErrors,
-            generalLambertErrors,
-            methodDifferences
-        };
-        if (projection == null) {
-            stats = ArraysExt.remove(stats, 2, 1);
-        }
-        final PrintStream out = System.out;
-        out.println("Comparison of two different way to compute φ for excentricity " + excentricity);
-        out.println("Values are in degrees, ");
-        final StatisticsFormat format = StatisticsFormat.getInstance();
-        format.setBorderWidth(1);
+        if (summarize != null) {
+            summarize.append(String.valueOf(excentricity));                     summarize.nextColumn();
+            summarize.append(String.valueOf(iterativeMethodErrors.mean()));     summarize.nextColumn();
+            summarize.append(String.valueOf(iterativeMethodErrors.maximum()));  summarize.nextColumn();
+            summarize.append(String.valueOf(seriesExpansionErrors.mean()));     summarize.nextColumn();
+            summarize.append(String.valueOf(seriesExpansionErrors.maximum()));  summarize.nextLine();
+        } else {
+            Statistics[] stats = new Statistics[] {
+                iterativeMethodErrors,
+                seriesExpansionErrors,
+                generalLambertErrors
+            };
+            if (projection == null) {
+                stats = ArraysExt.remove(stats, 2, 1);
+            }
+            out.println("Comparison of different ways to compute φ for excentricity " +
excentricity + '.');
+            out.println("Values are in units of " + NormalizedProjection.ITERATION_TOLERANCE
+ " radians (about "
+                    + Math.round(toDegrees(NormalizedProjection.ITERATION_TOLERANCE) * 60
* ReferencingServices.NAUTICAL_MILE * 1000)
+                    + " mm on Earth).");
+            final StatisticsFormat format = StatisticsFormat.getInstance();
+            format.setBorderWidth(1);
+            try {
+                format.format(stats, out);
+            } catch (IOException e) {
+                throw new AssertionError(e);
+            }
+            out.flush();
+        }
+    }
+
+    /**
+     * Prints the error of the two methods for various excentricity values.
+     * The intend of this method is to find an excentricity threshold value where we consider
the errors too high.
+     *
+     * <p>This method is used for determining empirically a value for {@link GeneralLambert#EXCENTRICITY_THRESHOLD}.
+     * The current threshold value is shown by inserting a horizontal line separator in the
table when that threshold
+     * is crossed.</p>
+     *
+     * @param min The first excentricity value to test.
+     * @param max The maximal excentricity value to test.
+     * @throws ProjectionException if an error occurred in {@link #φ(double)}.
+     */
+    public static void printErrorForExcentricities(final double min, final double max) throws
ProjectionException {
+        final TableAppender table = new TableAppender(out);
+        table.appendHorizontalSeparator();
+        table.append("Excentricity");            table.nextColumn();
+        table.append("Mean iterative error");    table.nextColumn();
+        table.append("Maximal iterative error"); table.nextColumn();
+        table.append("Mean series error");       table.nextColumn();
+        table.append("Maximal series error");    table.nextLine();
+        table.appendHorizontalSeparator();
+        boolean crossThreshold = false;
+        final double step = 0.01;
+        double excentricity;
+        for (int i=0; (excentricity = min + step*i) < max; i++) {
+            if (!crossThreshold && excentricity >= GeneralLambert.EXCENTRICITY_THRESHOLD)
{
+                crossThreshold = true;
+                table.appendHorizontalSeparator();
+            }
+            final MercatorAlternative alt = new MercatorAlternative(excentricity * excentricity);
+            alt.compare(null, 10000, table);
+        }
+        table.appendHorizontalSeparator();
         try {
-            format.format(stats, out);
+            table.flush();
         } catch (IOException e) {
             throw new AssertionError(e);
         }
-        out.flush();
     }
 
     /**
-     * Executes {@link #compare(int)} for the excentricity of an imaginary ellipsoid.
      * The result is printed to the standard output stream.
      *
      * @param  args ignored.
      * @throws ProjectionException if an error occurred in {@link #φ(double)}.
      */
     public static void main(String[] args) throws ProjectionException {
-        final GeneralLambert projection = new NoOp(100, 95);
-        final MercatorAlternative alt = new MercatorAlternative(projection);
-        alt.compare(projection, 2000000);
+        out.println("Comparison of the errors of series expension and iterative method for
various excentricity values.");
+        printErrorForExcentricities(0.08, 0.3);
+
+        out.println();
+        out.println("Comparison of the errors for a sphere.");
+        out.println("The errors should be almost zero:");
+        out.println();
+        GeneralLambert projection = new NoOp(false);
+        MercatorAlternative alt = new MercatorAlternative(projection);
+        alt.compare(projection, 10000, null);
+
+        out.println();
+        out.println("Comparison of the errors for the WGS84 excentricity.");
+        out.println("The 'GeneralLambert' errors should be the same than the series expansion
errors:");
+        out.println();
+        projection = new NoOp(true);
+        alt = new MercatorAlternative(projection);
+        alt.compare(projection, 1000000, null);
+
+        out.println();
+        out.println("Comparison of the errors for the excentricity of an imaginary ellipsoid.");
+        out.println("The 'GeneralLambert' errors should be the close to the iterative method
errors:");
+        out.println();
+        projection = new NoOp(100, 95);
+        alt = new MercatorAlternative(projection);
+        alt.compare(projection, 1000000, null);
     }
 }

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=1690623&r1=1690622&r2=1690623&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 13 10:29:21 2015
@@ -46,7 +46,7 @@ import static org.apache.sis.referencing
  * @version 0.6
  * @module
  */
-@DependsOn(NormalizedProjectionTest.class)
+@DependsOn(GeneralLambertTest.class)
 public final strictfp class MercatorTest extends MapProjectionTestCase {
     /**
      * Creates a new instance of {@link Mercator} for a sphere or an ellipsoid.

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=1690623&r1=1690622&r2=1690623&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] Mon Jul 13 10:29:21 2015
@@ -16,7 +16,6 @@
  */
 package org.apache.sis.referencing.operation.projection;
 
-import java.util.Random;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.test.referencing.TransformTestCase;
 import org.apache.sis.referencing.operation.transform.AbstractMathTransform1D;
@@ -49,7 +48,7 @@ public final strictfp class NormalizedPr
     /**
      * Tolerance level for comparing floating point numbers.
      */
-    private static final double TOLERANCE = 1E-12;
+    static final double TOLERANCE = 1E-12;
 
     /**
      * Natural logarithm of the pseudo-infinity as returned by Mercator formulas in the spherical
@@ -80,17 +79,6 @@ public final strictfp class NormalizedPr
     }
 
     /**
-     * Computes {@link GeneralLambert#φ(double)}.
-     *
-     * @param  expOfSouthing The reciprocal of the value returned by {@link #expOfNorthing(double)}.
-     * @return The latitude in radians.
-     * @throws ProjectionException if the iteration does not converge.
-     */
-    private double φ(final double expOfSouthing) throws ProjectionException {
-        return ((GeneralLambert) transform).φ(expOfSouthing);
-    }
-
-    /**
      * Tests the value documented in the javadoc. Those value may be freely changed;
      * those tests exist only to increase the chances that the documented values are right.
      */
@@ -218,81 +206,6 @@ public final strictfp class NormalizedPr
     }
 
     /**
-     * Tests the {@link GeneralLambert#φ(double)} function. We expect it to be
-     * the converse of the {@link NormalizedProjection#expOfNorthing(double, double)} function.
-     * In theory only the [-90° … +90°] range needs to be tested. However the function
is still
-     * consistent in the [-90° … +270°] range so we test that range for tracking this
fact.
-     *
-     * @throws ProjectionException Should never happen.
-     */
-    @Test
-    @DependsOnMethod("testExpOfNorthing")
-    public void test_φ() throws ProjectionException {
-        transform = new NoOp(false);   // Spherical case
-        tolerance = TOLERANCE;
-        doTest_φ();
-        transform = new NoOp(true);    // Ellipsoidal case
-        tolerance = NormalizedProjection.ITERATION_TOLERANCE;
-        doTest_φ();
-    }
-
-    /**
-     * Implementation of {@link #test_φ()}.
-     * The {@link #projection} field must have been set before this method is called.
-     */
-    private void doTest_φ() throws ProjectionException {
-        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.
-         */
-        for (int i=-90; i<=270; i+=5) {
-            final double φ   = toRadians(i);
-            final double t    = 1 / expOfNorthing(φ);
-            final double back = toDegrees(φ(t));
-            if (i <= 90) {
-                assertTrue("φ(t) in valid range should be positive.", t >= 0);
-            } else {
-                assertTrue("φ(t) in invalid range should be negative.", t < 0);
-            }
-            assertEquals("Inverse function does not match.", i, back, tolerance);
-        }
-    }
-
-    /**
-     * Performs a comparison between φ values computed by the iterative method and by series
expansion.
-     * Then compares with the φ values computed by {@link GeneralLambert#φ(double)}, which
uses a mix
-     * of the two methods. See {@link MercatorAlternative} for a discussion.
-     *
-     * @throws ProjectionException if an error occurred during computation of φ.
-     *
-     * @see MercatorAlternative
-     */
-    @Test
-    public void compareWithSeriesExpansion() throws ProjectionException {
-        final GeneralLambert projection = new NoOp(true);
-        final MercatorAlternative comparator = new MercatorAlternative(projection.excentricitySquared);
-        final Random random = TestUtilities.createRandomNumberGenerator();
-        final int numSamples = 2000;
-        for (int i=0; i<numSamples; i++) {
-            final double φ = random.nextDouble() * PI - PI/2;
-            final double t = 1 / comparator.expOfNorthing(φ);
-            final double byIterativeMethod = comparator.byIterativeMethod(t);
-            final double bySeriesExpansion = comparator.bySeriesExpansion(t);
-            assertEquals(bySeriesExpansion, byIterativeMethod, 1E-11);
-            assertEquals(bySeriesExpansion, projection.φ(t),   1E-11);
-        }
-    }
-
-    /**
      * Tests the {@link NormalizedProjection#dy_dφ(double, double)} method.
      *
      * @throws TransformException Should never happen.

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=1690623&r1=1690622&r2=1690623&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] Mon Jul 13 10:29:21 2015
@@ -101,6 +101,7 @@ import org.junit.BeforeClass;
     // Test map projections. Those tests need the providers tested above.
     org.apache.sis.referencing.operation.projection.NormalizedProjectionTest.class,
     org.apache.sis.referencing.operation.projection.EquirectangularTest.class,
+    org.apache.sis.referencing.operation.projection.GeneralLambertTest.class,
     org.apache.sis.referencing.operation.projection.MercatorTest.class,
     org.apache.sis.referencing.operation.projection.LambertConformalTest.class,
 



Mime
View raw message