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,