org.geotoolkit.referencing.operation.projection.Assertions Maven / Gradle / Ivy
/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 1999-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2012, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.referencing.operation.projection;
import org.opengis.referencing.operation.Matrix;
import org.geotoolkit.lang.Static;
import org.geotoolkit.resources.Errors;
import static java.lang.Math.*;
import static java.lang.Double.POSITIVE_INFINITY;
import static org.geotoolkit.referencing.operation.projection.UnitaryProjection.*;
/**
* Static methods for assertions. This is used only when Java 1.4 assertions are enabled.
* When a point has been projected using spherical formulas, compare with the same point
* transformed using spherical formulas and throw an exception if the result differ.
*
* @author Martin Desruisseaux (Geomatys)
* @author Rémi Maréchal (Geomatys)
* @version 3.20
*
* @since 2.0
* @module
*/
final class Assertions extends Static {
/**
* Maximum difference allowed when comparing the result of an inverse projections,
* in radians. A value of 1E-7 radians is approximatively 0.5 kilometres. Note that
* inverse projections are typically less accurate than forward projections. This
* tolerance is set to such high value for avoiding too intrusive assertion errors.
* This is okay only for catching gross programming errors.
*/
private static final double INVERSE_TOLERANCE = 1E-7;
/**
* Maximum difference allowed when comparing the result of forward projections,
* in distance on the unit ellipse. A value of 1E-7 is approximatively 0.1 metres.
*/
private static final double FORWARD_TOLERANCE = 1E-7;
/**
* Maximum difference allowed between spherical and ellipsoidal formulas when
* comparing derivatives. Units are metres.
*/
private static final double DERIVATIVE_TOLERANCE = 1E-1;
/**
* Do not allows instantiation of this class.
*/
private Assertions() {
}
/**
* Checks if transform using spherical formulas produces the same result
* than ellipsoidal formulas. This method is invoked during assertions only.
*
* @param expected The (easting,northing) computed by ellipsoidal formulas.
* @param offset Index of the coordinate in the {@code expected} array.
* @param x The easting computed by spherical formulas on the unit sphere.
* @param y The northing computed by spherical formulas on the unit sphere.
* @param tolerance The tolerance (optional).
* @return Always {@code true} if the {@code tolerance} value is valid.
* @throws ProjectionException if the comparison failed.
*/
static boolean checkTransform(final double[] expected, final int offset,
final double x, final double y, final double tolerance)
throws ProjectionException
{
if (expected != null) {
compare("x", expected[offset ], x, tolerance);
compare("y", expected[offset+1], y, tolerance);
}
return tolerance < POSITIVE_INFINITY;
}
/**
* Default version of {@link #checkTransform(double,double,double[],int)}.
*/
static boolean checkTransform(final double[] expected, final int offset, final double x, final double y)
throws ProjectionException
{
return checkTransform(expected, offset, x, y, FORWARD_TOLERANCE);
}
/**
* Checks if inverse transform using spherical formulas produces the same result
* than ellipsoidal formulas. This method is invoked during assertions only.
*
* {@note This method ignores the longitude if the latitude is at a pole,
* because in such case the longitude is meanless.}
*
* @param expected The (longitude,latitude) computed by ellipsoidal formulas.
* @param offset Index of the coordinate in the {@code expected} array.
* @param λ The longitude computed by spherical formulas, in radians.
* @param φ The latitude computed by spherical formulas, in radians.
* @param tolerance The tolerance (optional).
* @return Always {@code true} if the {@code tolerance} value is valid.
* @throws ProjectionException if the comparison failed.
*/
static boolean checkInverseTransform(final double[] expected, final int offset,
final double λ, final double φ, final double tolerance)
throws ProjectionException
{
compare("latitude", expected[offset+1], φ, tolerance);
if (abs(PI/2 - abs(φ)) > ANGLE_TOLERANCE) {
compare("longitude", expected[offset], λ, tolerance);
}
return tolerance < POSITIVE_INFINITY;
}
/**
* Default version of {@link #checkInverseTransform(double,double,double[],int,double)}.
*/
static boolean checkInverseTransform(final double[] expected, final int offset, final double λ, final double φ)
throws ProjectionException
{
return checkInverseTransform(expected, offset, λ, φ, INVERSE_TOLERANCE);
}
/**
* Checks if derivatives using spherical formulas produces the same result than ellipsoidal
* formulas. This method is invoked during assertions only. The spherical formulas are used
* for the "expected" results since they are simpler than the ellipsoidal formulas.
*
* @since 3.18
*/
static boolean checkDerivative(final Matrix spherical, final Matrix ellipsoidal)
throws ProjectionException
{
if (spherical != null || ellipsoidal != null) { // NullPointerException is ok if only one is null.
compare("m00", spherical.getElement(0,0), ellipsoidal.getElement(0,0), DERIVATIVE_TOLERANCE);
compare("m01", spherical.getElement(0,1), ellipsoidal.getElement(0,1), DERIVATIVE_TOLERANCE);
compare("m10", spherical.getElement(1,0), ellipsoidal.getElement(1,0), DERIVATIVE_TOLERANCE);
compare("m11", spherical.getElement(1,1), ellipsoidal.getElement(1,1), DERIVATIVE_TOLERANCE);
}
return true;
}
/**
* Compares two value for equality up to some tolerance threshold. This is used during
* assertions only. The comparison does not fail if at least one value to compare is
* {@link Double#NaN} or infinity.
*
* Hack: if the {@code variable} name starts by lower-case {@code L}
* (as in "longitude" and "latitude"), then the value is assumed to be an angle in
* radians. This is used for formatting an error message, if needed.
*
* @throws ProjectionException if the comparison failed.
*/
private static void compare(final String variable, double expected, double actual, final double tolerance)
throws ProjectionException
{
final double delta = abs(expected - actual);
if (delta > tolerance) {
if (variable.charAt(0) == 'l') {
actual = toDegrees(actual);
expected = toDegrees(expected);
} else if (abs(actual) > 30 && abs(expected) > 30) {
/*
* If the projected point tend toward infinity, treats the value as if is was
* really infinity. Note that 30 is considered as "close to infinity" because
* of the result we get when projecting 90°N using Mercator spherical formula:
*
* y = log(tan(π/4 + φ/2))
*
* Because there is no exact representation of π/2 in base 2, the tangent
* function gives 1.6E+16 instead of infinity, which leads the logarithmic
* function to give us 37.3.
*
* This behavior is tested in MercatorTest.testSphericalFormulas().
*/
if (signum(actual) == signum(expected)) {
return;
}
}
throw new ProjectionError(Errors.format(Errors.Keys.TEST_FAILURE_$3, variable,
String.valueOf(expected), String.valueOf(actual)) + // Force full precision.
"(Δ" + variable + '=' + delta + " ε=" + tolerance + ')');
}
}
}