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/07/13 12:29:21 UTC
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/
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,