blob: 67ca991309c6f3f7a073abaa1448b888b40f1653 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.referencing;
import java.util.Map;
import java.util.HashMap;
import java.util.Arrays;
import static java.lang.StrictMath.*;
import org.opengis.geometry.DirectPosition;
import org.opengis.referencing.crs.GeographicCRS;
import org.apache.sis.referencing.privy.ReferencingUtilities;
import org.apache.sis.referencing.privy.Formulas;
import org.apache.sis.math.MathFunctions;
import org.apache.sis.measure.Units;
import static org.apache.sis.metadata.privy.ReferencingServices.NAUTICAL_MILE;
// Test dependencies
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import org.apache.sis.referencing.crs.HardCodedCRS;
/**
* Tests {@link GeodesicsOnEllipsoid}. Some of the tests in this class use data published by
* <a href="https://link.springer.com/content/pdf/10.1007%2Fs00190-012-0578-z.pdf">Algorithms
* for geodesics from Charles F. F. Karney (SRI International)</a>.
*
* @author Matthieu Bastianelli (Geomatys)
* @author Martin Desruisseaux (Geomatys)
*/
public final class GeodesicsOnEllipsoidTest extends GeodeticCalculatorTest {
/**
* Tolerance threshold for comparison of floating point numbers.
*/
private static final double STRICT = 0;
/**
* The {@link GeodesicsOnEllipsoid} instance to be tested.
* A specialized type is used for tracking locale variables.
*/
private Calculator testedEarth;
/**
* Values of local variables in {@link GeodesicsOnEllipsoid} methods. If values for the same key are added
* many times, the new values are appended after the current ones. This happen for example during iterations.
*
* @see GeodesicsOnEllipsoid#STORE_LOCAL_VARIABLES
*/
private Map<String,double[]> localVariables;
/**
* {@code true} if {@link GeodesicsOnEllipsoid#store(String, double)} shall verify consistency instead of
* storing local variables.
*/
private boolean verifyConsistency;
/**
* Creates a new test case.
*/
public GeodesicsOnEllipsoidTest() {
}
/**
* Returns the calculator to use for testing purpose.
*
* @param normalized whether to force (longitude, latitude) axis order.
*/
@Override
GeodeticCalculator create(final boolean normalized) {
testedEarth = new Calculator(normalized ? HardCodedCRS.WGS84 : HardCodedCRS.WGS84_LATITUDE_FIRST);
return testedEarth;
}
/**
* Creates a calculator which will store locale variables in the {@link #localVariables} map.
* This is used for the test cases that compare intermediate calculations with values provided
* in tables of Karney (2013) <cite>Algorithms for geodesics</cite> publication.
*/
private void createTracked() {
localVariables = new HashMap<>();
testedEarth = new Calculator(HardCodedCRS.WGS84) {
/** Replaces a computed value by the value given in Karney table. */
@Override double computedToGiven(final double α1) {
return (abs(TRUNCATED_α1 - toDegrees1)) < 1E-3) ? toRadians(TRUNCATED_α1) : α1;
}
/** Invoked when {@link GeodesicsOnEllipsoid} computed an intermediate value. */
@Override void store(final String name, final double value) {
super.store(name, value);
if (verifyConsistency) {
assertValueEquals(name, 0, value, STRICT, false);
} else {
double[] values = localVariables.putIfAbsent(name, new double[] {value});
if (values != null) {
final int i = values.length;
values = Arrays.copyOf(values, i+1);
values[i] = value;
localVariables.put(name, values);
}
}
}
};
}
/**
* The {@link GeodesicsOnEllipsoid} implementation to use for tests. This implementation compares the
* quartic root computed by {@link GeodesicsOnEllipsoid#μ(double, double)} against the values computed
* by {@link MathFunctions#polynomialRoots(double...)}.
*/
private static class Calculator extends GeodesicsOnEllipsoid {
/**
* {@code true} if iteration stopped before to reach the desired accuracy because of limitation
* in {@code double} precision. This field must be reset to {@code false} before any new point.
*
* @see #relaxIfConfirmed(KnownProblem)
* @see #clear()
*/
private boolean iterationReachedPrecisionLimit;
/** Values needed for computation of μ. */
private double x, y;
/** Creates a new calculator for the given coordinate reference system. */
Calculator(final GeographicCRS crs) {
super(crs, ReferencingUtilities.getEllipsoid(crs));
}
/** Invoked when {@link GeodesicsOnEllipsoid} computed an intermediate value. */
@Override void store(final String name, final double value) {
if (name.equals("dα₁ ≪ α₁")) {
iterationReachedPrecisionLimit = true;
} else if (name.length() == 1) {
switch (name.charAt(0)) {
case 'x': x = value; break;
case 'y': y = value; break;
case 'μ': {
double μ = 0;
final double y2 = y*y;
for (final double r : MathFunctions.polynomialRoots(-y2, -2*y2, (1 - x*x)-y2, 2, 1)) {
if (r > μ) μ = r;
}
if > 0) {
/*
* This assertion fails if y² is too close to zero. We that as a criterion for choosing
* the threshold value that determine when to use Karney (2013) equation 57 instead.
* That threshold is in `GeodesicOnEllipsoid.computeDistance()` method.
*/
assertEquals(μ, value, abs(μ) * 1E-8, "μ(x²,y²)");
}
}
}
}
}
}
/**
* Clears the tested {@link GeodeticCalculator} before to test a new point.
* This is invoked by parent class between two tests using the same calculator.
* The intent is to make sure that data from previous test are not mixed with current test.
*/
@Override
void clear() {
if (localVariables != null) {
localVariables.clear();
}
testedEarth.x = Double.NaN;
testedEarth.y = Double.NaN;
testedEarth.iterationReachedPrecisionLimit = false;
}
/**
* Asserts that variable of the given name is equal to the given value. This is used for comparing an
* intermediate value computed by Apache SIS against the value published in a table of Karney (2013)
* <cite>Algorithms for geodesics</cite>.
*
* @param name name of the variable to verify.
* @param index if the same variable has been set many times, its index with numbering starting at 1. Otherwise 0.
* @param expected the expected value.
* @param tolerance tolerance threshold for comparison.
* @param angular whether the stored value needs to be converted from radians to degrees.
*/
private void assertValueEquals(final String name, int index, final double expected, final double tolerance, final boolean angular) {
final double[] values = localVariables.get(name);
if (values != null) {
if (index > 0) {
index--;
} else if (values.length != 1) {
fail("Expected exactly one value for " + name + " but got " + Arrays.toString(values));
}
double value = values[index];
if (angular) value = toDegrees(value);
assertEquals(expected, value, tolerance, name);
} else if (GeodesicsOnEllipsoid.STORE_LOCAL_VARIABLES) {
fail("Missing value: " + name);
}
}
/**
* Takes a snapshot of some intermediate calculations in {@link GeodesicsOnEllipsoid}.
* If {@link GeodesicsOnEllipsoid#STORE_LOCAL_VARIABLES} is {@code true}, then we already
* have snapshots of those variables and this method will instead verify consistency.
*/
private void snapshot() {
verifyConsistency = true;
testedEarth.snapshot();
}
/**
* Verifies that the constant parameters set by constructor are equal to the ones used by Karney
* for his example. Those values do not depend on the point being tested.
*
* <p><b>Source:</b>Karney (2013) table 1.</p>
*/
private void verifyParametersForWGS84() {
assertEquals(6378137, testedEarth.semiMajorAxis, "a");
assertEquals(6356752.314245, testedEarth.ellipsoid.getSemiMinorAxis(), 1E-6, "b");
assertEquals(298.257223563, testedEarth.ellipsoid.getInverseFlattening(), 1E-9, "1/f");
assertEquals(0.00669437999014132, testedEarth.eccentricitySquared, 1E-17, "ℯ²");
assertEquals(0.00673949674227643, testedEarth.secondEccentricitySquared, 1E-17, "ℯ′²");
assertEquals(0.00167922038638370, testedEarth.thirdFlattening, 1E-16, "n");
assertEquals(Units.METRE, testedEarth.getDistanceUnit());
}
/**
* Tests solving the direct geodesic problem by a call to {@link GeodesicsOnEllipsoid#computeEndPoint()}.
* The data used by this test are provided by the example published in Karney (2013) table 2.
* Input values are:
*
* <ul>
* <li>Starting longitude: λ₁ = any</li>
* <li>Starting latitude: φ₁ = 40°</li>
* <li>Starting azimuth: α₁ = 30°</li>
* <li>Geodesic distance: s₁₂ = 10 000 000 m</li>
* </ul>
*
* About 20 intermediate values are published in Karney table 2 and copied in this method body.
* Those values are verified if {@link GeodesicsOnEllipsoid#STORE_LOCAL_VARIABLES} is {@code true}.
*
* <p><b>Source:</b> Karney (2013), <u>Algorithms for geodesics</u> table 2.</p>
*/
@Test
public void testComputeEndPoint() {
createTracked();
verifyParametersForWGS84();
testedEarth.setStartGeographicPoint(40, 10);
testedEarth.setStartingAzimuth(30);
testedEarth.setGeodesicDistance(10000000);
assertEquals(toDegrees(testedEarth1), 10.0, Formulas.ANGULAR_TOLERANCE, "λ₁");
assertEquals(toDegrees(testedEarth1), 40.0, Formulas.ANGULAR_TOLERANCE, "φ₁");
assertEquals(testedEarth.geodesicDistance, 10000000, "∆s");
/*
* The following method invocation causes calculation of all intermediate values.
* Some intermediate values are verified by `assertValueEquals` statements below.
* If some values are wrong, it is easier to check correctness with step-by-step
* debugging of `computeDistance()` method while keeping table 3 of Karney aside.
*/
final DirectPosition endPoint = testedEarth.getEndPoint();
snapshot();
/*
* Start point on auxiliary sphere:
*
* β₁ — reduced latitude of starting point.
* α₀ — azimuth at equator in the direction given by α₁ azimuth at the β₁ reduced latitude.
* σ₁ — arc length from equatorial point E to starting point on auxiliary sphere.
* ω₁ — spherical longitude of the starting point (from equatorial point E) on the auxiliary sphere.
* s₁ — distance from equatorial point E to starting point along the geodesic.
*/
assertValueEquals("β₁", 0, 39.90527714601, 1E-11, true);
assertValueEquals("α₀", 0, 22.55394020262, 1E-11, true);
assertValueEquals("σ₁", 0, 43.99915364500, 1E-11, true);
assertValueEquals("ω₁", 0, 20.32371827837, 1E-11, true);
/*
* End point on auxiliary sphere.
*
* σ₂ — arc lentgth from equator to the ending point σ₂ on auxiliary sphere.
* β₂ — reduced latitude of ending point.
* ω₂ — spherical longitude of the ending point (from equatorial point E) on the auxiliary sphere.
*/
assertValueEquals("k²", 0, 0.00574802962857, 1E-14, false);
assertValueEquals("ε", 0, 0.00143289220416, 1E-14, false);
assertValueEquals("A₁", 0, 1.00143546236207, 1E-14, false);
assertValueEquals("I₁(σ₁)", 0, 0.76831538886412, 1E-14, false);
assertValueEquals("s₁", 0, 4883990.626232, 1E-6, false);
assertValueEquals("s₂", 0, 14883990.626232, 1E-6, false);
assertValueEquals("τ₂", 0, 133.96266050208, 1E-11, true);
assertValueEquals("σ₂", 0, 133.92164083038, 1E-11, true);
assertValueEquals("α₂", 0, 149.09016931807, 1E-11, true);
assertValueEquals("β₂", 0, 41.69771809250, 1E-11, true);
assertValueEquals("ω₂", 0, 158.28412147112, 1E-11, true);
/*
* Conversion of end point coordinates to geodetic longitude.
*
* λ₁ — longitudes of the starting point, measured from equatorial point E.
* λ₂ — longitudes of the ending point, measured from Equatorial point E.
*/
assertValueEquals("A₃", 0, 0.99928424306, 1E-11, false);
assertValueEquals("I₃(σ)", 1, 0.76773786069, 1E-11, false);
assertValueEquals("I₃(σ)", 2, 2.33534322170, 1E-11, false);
assertValueEquals("λ₁", 0, 20.26715038016, 1E-11, true);
assertValueEquals("λ₂", 0, 158.11205042393, 1E-11, true);
assertValueEquals("Δλ", 0, 137.84490004377, 1E-11, true);
/*
* Final result:
*
* φ₂ — end point latitude.
* λ₂ — end point longitude. Shifted by 10° compared to Karney because of λ₁ = 10°.
* α₂ — azimuth at end point.
*/
assertEquals( 41.79331020506, endPoint.getOrdinate(1), 1E-11, "φ₂");
assertEquals(147.84490004377, endPoint.getOrdinate(0), 1E-11, "λ₂");
assertEquals(149.09016931807, testedEarth.getEndingAzimuth(), 1E-11, "α₂");
}
/**
* Tests solving the inverse geodesic problem by a call to {@link GeodesicsOnEllipsoid#computeDistance()}
* for a short distance. The data used by this test are provided by the example published in Karney (2013)
* table 3. Input values are:
*
* <ul>
* <li>Starting longitude: λ₁ = any</li>
* <li>Starting latitude: φ₁ = -30.12345°</li>
* <li>Ending longitude: λ₂ = λ₁ + 0.00005°</li>
* <li>Ending latitude: φ₂ = -30.12344°</li>
* </ul>
*
* About 5 intermediate values are published in Karney table 3 and copied in this method body.
* Those values are verified if {@link GeodesicsOnEllipsoid#STORE_LOCAL_VARIABLES} is {@code true}.
*
* <p><b>Source:</b> Karney (2013), <u>Algorithms for geodesics</u> table 3.</p>
*/
@Test
public void testComputeShortDistance() {
createTracked();
verifyParametersForWGS84();
testedEarth.setStartGeographicPoint(-30.12345, 2);
testedEarth.setEndGeographicPoint (-30.12344, 2.00005);
/*
* The following method invocation causes calculation of all intermediate values.
* Some intermediate values are verified by `assertValueEquals` statements below.
* If some values are wrong, it is easier to check correctness with step-by-step
* debugging of `computeDistance()` method while keeping table 3 of Karney aside.
*
* β₁ — reduced latitude of starting point.
* β₂ — reduced latitude of ending point.
*/
final double distance = testedEarth.getGeodesicDistance();
assertValueEquals("β₁", 0, -30.03999083821, 1E-11, true);
assertValueEquals("β₂", 0, -30.03998085491, 1E-11, true);
assertValueEquals("ωb", 0, 0.99748847744, 1E-11, false);
assertValueEquals("Δω", 0, 0.00005012589, 1E-11, true);
assertValueEquals("Δσ", 0, 0.00004452641, 1E-11, true);
assertValueEquals("α₁", 1, 77.04353354237, 1E-8, true);
assertValueEquals("α₂", 1, 77.04350844913, 1E-8, true);
/*
* Final result. Values are slightly different than the values published
* in Karney table 3 because GeodeticCalculator has done an iteration.
*/
assertEquals(77.04353354237, testedEarth.getStartingAzimuth(), 1E-8, "α₁"); // Last 3 digits differ.
assertEquals(77.04350844913, testedEarth.getEndingAzimuth(), 1E-8, "α₂");
assertEquals(4.944208, distance, 1E-6, "Δs");
}
/**
* Tests computing a shorter distance than {@link #testComputeShortDistance()}.
* This is based on the empirical observation that for distances short enough,
* the {@literal α₁ -= dα₁} calculation leaves α₁ unchanged when {@literal dα₁ ≪ α₁}.
* {@link GeodesicsOnEllipsoid} shall detect this situation and stop iteration.
* This tests verify that {@link GeodeticException} is not thrown.
*
* @throws GeodeticException if the {@literal dα₁ ≪ α₁} check did not worked.
*/
@Test
public void testComputeShorterDistance() throws GeodeticException {
final GeodeticCalculator c = create(false);
c.setStartGeographicPoint(-0.000014, -29.841548);
c.setEndGeographicPoint (-0.000014, -29.841319);
assertEquals(25.49, c.getGeodesicDistance(), 0.01);
}
/**
* Result of Karney table 4, used as input in Karney table 5. We need to truncated that intermediate result
* to the same number of digits than Karney in order to get the numbers published in table 5 and 6.
* This value is used in {@link #testComputeNearlyAntipodal()} only.
*/
private static final double TRUNCATED_α1 = 161.914;
/**
* Tests solving the inverse geodesic problem by a call to {@link GeodesicsOnEllipsoid#computeDistance()} for nearly
* antipodal points. The data used by this test are provided by the example published in Karney (2013) tables 4 to 6.
* Input values are:
*
* <ul>
* <li>Starting longitude: λ₁ = any</li>
* <li>Starting latitude: φ₁ = -30°</li>
* <li>Ending longitude: λ₂ = λ₁ + 179.8°</li>
* <li>Ending latitude: φ₂ = 29.9°</li>
* </ul>
*
* Some intermediate values are published in Karney tables 4 to 6 and copied in this method body.
* Those values are verified if {@link GeodesicsOnEllipsoid#STORE_LOCAL_VARIABLES} is {@code true}.
*
* <p><b>Source:</b> Karney (2013), <u>Algorithms for geodesics</u> tables 4, 5 and 6.</p>
*/
@Test
public void testComputeNearlyAntipodal() {
createTracked();
verifyParametersForWGS84();
testedEarth.setStartGeographicPoint(-30, 0);
testedEarth.setEndGeographicPoint(29.9, 179.8);
/*
* The following method invocation causes calculation of all intermediate values.
* Values β₁ and β₂ are kept constant during all iterations.
* Other values are given in Karney table 4.
*/
final double distance = testedEarth.getGeodesicDistance();
assertValueEquals("β₁", 0, -29.91674771324, 1E-11, true);
assertValueEquals("β₂", 0, 29.81691642189, 1E-11, true);
assertValueEquals("x", 0, -0.382344, 1E-6, false);
assertValueEquals("y", 0, -0.220189, 1E-6, false);
assertValueEquals("μ", 0, 0.231633, 1E-6, false);
assertValueEquals("α₁", 1, TRUNCATED_α1, 1E-3, true); // Initial value before iteration.
/*
* Following values are updated during iterations. Note that in order to get the same values as the ones
* published in Karney table 5, we need to truncate the α₁ initial value to the same number of digits than
* Karney. This is done automatically if GeodesicsOnEllipsoid.STORE_LOCAL_VARIABLES is true.
*/
assertValueEquals("α₀", 1, 15.60939746414, 1E-11, true);
assertValueEquals("σ₁", 1, -148.81253566596, 1E-11, true);
assertValueEquals("ω₁", 1, -170.74896696128, 1E-11, true);
assertValueEquals("α₂", 1, 18.06728796231, 1E-11, true);
assertValueEquals("σ₂", 1, 31.08244976895, 1E-11, true);
assertValueEquals("ω₂", 1, 9.21345761110, 1E-11, true);
assertValueEquals("k²", 1, 0.00625153791662, 1E-14, false);
assertValueEquals("ε", 1, 0.00155801826780, 1E-14, false);
assertValueEquals("λ₁", 1, -170.61483552458, 1E-11, true);
assertValueEquals("λ₂", 1, 9.18542009839, 1E-11, true);
assertValueEquals("Δλ", 1, 179.80025562297, 1E-11, true);
assertValueEquals("δλ", 1, 0.00025562297, 1E-11, true);
assertValueEquals("J(σ₁)", 1, -0.00948040927640, 1E-14, false);
assertValueEquals("J(σ₂)", 1, 0.00031349128630, 1E-14, false);
assertValueEquals("Δm", 1, 57288.000110, 1E-6, false);
assertValueEquals("dλ/dα", 1, 0.01088931716115, 1E-14, false);
assertValueEquals("δσ₁", 1, -0.02347465519, 1E-11, true);
assertValueEquals("α₁", 2, 161.89052534481, 1E-11, true);
/*
* After second iteration.
*/
assertValueEquals("δλ", 2, 0.00000000663, 1E-11, true);
assertValueEquals("α₁", 3, 161.89052473633, 1E-11, true);
/*
* After third iteration.
*/
assertValueEquals("α₀", 3, 15.62947966537, 1E-11, true);
assertValueEquals("σ₁", 3, -148.80913691776, 1E-11, true);
assertValueEquals("ω₁", 3, -170.73634378066, 1E-11, true);
assertValueEquals("α₂", 3, 18.09073724574, 1E-11, true);
assertValueEquals("σ₂", 3, 31.08583447040, 1E-11, true);
assertValueEquals("ω₂", 3, 9.22602862110, 1E-11, true);
assertValueEquals("s₁", 3, -16539979.064227, 1E-6, false);
assertValueEquals("s₂", 3, 3449853.763383, 1E-6, false);
assertValueEquals("Δs", 3, 19989832.827610, 1E-6, false);
assertValueEquals("λ₁", 3, -170.60204712148, 1E-11, true);
assertValueEquals("λ₂", 3, 9.19795287852, 1E-11, true);
assertValueEquals("Δλ", 3, 179.80000000000, 1E-11, true);
/*
* Final result:
*/
assertEquals(161.89052473633, testedEarth.getStartingAzimuth(), 1E-11, "α₁");
assertEquals( 18.09073724574, testedEarth.getEndingAzimuth(), 1E-11, "α₂");
assertEquals(19989832.827610, distance, 1E-6, "Δs");
}
/**
* Same test as the one defined in parent class, but with expected results modified for ellipsoidal formulas.
* Input points are from <a href="https://en.wikipedia.org/wiki/Great-circle_navigation#Example">Wikipedia</a>.
* Outputs were computed with GeographicLib.
*/
@Test
@Override
public void testGeodesicDistanceAndAzimuths() {
final GeodeticCalculator c = create(false);
c.setStartGeographicPoint(-33.0, -71.6); // Valparaíso
c.setEndGeographicPoint ( 31.4, 121.8); // Shanghai
/*
* α₁ α₂ distance
* Spherical: -94.41° -78.42° 18743 km
* Ellipsoidal: -94.82° -78.29° 18752 km
*/
assertEquals(Units.METRE, c.getDistanceUnit());
assertEquals(-94.82071749, c.getStartingAzimuth(), 1E-8, "α₁");
assertEquals(-78.28609385, c.getEndingAzimuth(), 1E-8, "α₂");
assertEquals(18752.493521, c.getGeodesicDistance() / 1000, 1E-6, "distance");
assertPositionEquals(31.4, 121.8, c.getEndPoint(), 1E-12); // Should be the specified value.
/*
* Keep start point unchanged, but set above azimuth and distance.
* Verify that we get the Shanghai coordinates.
*/
c.setStartingAzimuth(-94.82);
c.setGeodesicDistance(18752494);
assertEquals(-94.82, c.getStartingAzimuth(), 1E-12, "α₁"); // Should be the specified value.
assertEquals(-78.28678389, c.getEndingAzimuth(), 1E-8, "α₂");
assertEquals(18752.494, c.getGeodesicDistance() / 1000, "Δs"); // Should be the specified value.
assertPositionEquals(31.4, 121.8, c.getEndPoint(), 0.0002);
}
/**
* Returns a value {@literal > 1} if iteration stopped before to reach the desired accuracy because of limitation
* in {@code double} precision. This problem may happen in the {@link GeodesicsOnEllipsoid#computeDistance()}
* method when {@literal dα₁ ≪ α₁}. If locale variable storage is enabled, this situation is flagged by the
* {@code "dα₁ ≪ α₁"} key. Otherwise we conservatively assume that this situation occurred.
*/
@Override
double relaxIfConfirmed(final KnownProblem potentialProblem) {
if (potentialProblem == KnownProblem.ITERATION_REACHED_PRECISION_LIMIT) {
if (GeodesicsOnEllipsoid.STORE_LOCAL_VARIABLES) {
if (testedEarth.iterationReachedPrecisionLimit) {
return 2;
}
} else {
// No information about whether the problem really occurred.
return 2;
}
}
return super.relaxIfConfirmed(potentialProblem);
}
/**
* Tests {@link GeodesicsOnEllipsoid#getRhumblineLength()} using the example 1 given in Bennett (1996) appendix.
*/
@Test
@Override
public void testRhumblineLength() {
createTracked();
verifyParametersForWGS84();
/*
* Bennett (1996) example 1. For comparing with values given by Bennett:
*
* M = Ψ * 10800/PI.
* m = m * semiMajor / 1852
*/
testedEarth.setStartGeographicPoint(10+18.4/60, 37+41.7/60);
testedEarth.setEndGeographicPoint (53+29.5/60, 113+17.1/60);
final double distance = testedEarth.getRhumblineLength();
final double scale = testedEarth.semiMajorAxis / NAUTICAL_MILE;
assertValueEquals("Δλ", 0, 75+35.4 / 60, 1E-11, true);
assertValueEquals("ΔΨ", 0, 3176.89 / (10800/PI), 1E-5, false);
assertValueEquals("m₁", 0, 615.43 / scale, 1E-6, false);
assertValueEquals("m₂", 0, 3201.59 / scale, 1E-6, false);
assertValueEquals("Δm", 0, 2586.16 / scale, 1E-6, false);
assertEquals(54.99008056, testedEarth.getConstantAzimuth(), 1E-8, "azimuth");
assertEquals(4507.7 * NAUTICAL_MILE, distance, 0.05 * NAUTICAL_MILE, "distance"); // From Bennett (1996)
assertEquals(8348285.202, distance, Formulas.LINEAR_TOLERANCE, "distance"); // From Karney's online calculator.
}
/**
* Tests {@link GeodesicsOnEllipsoid#getRhumblineLength()} using the example 3 given in Bennett (1996) appendix.
*/
@Test
@Override
public void testRhumblineNearlyEquatorial() {
createTracked();
verifyParametersForWGS84();
testedEarth.setStartGeographicPoint(-52-47.8/60, -97-31.6/60);
testedEarth.setEndGeographicPoint (-53-10.8/60, -41-34.6/60);
final double distance = testedEarth.getRhumblineLength();
assertValueEquals("Δλ", 0, 55+57.0 / 60, 1E-11, true);
assertValueEquals("ΔΨ", 0, -38.12 / (10800/PI), 1E-5, false);
// assertValueEquals("C", 0, 90.6505, 1E-4, true);
assertEquals(90.65049570, testedEarth.getConstantAzimuth(), 1E-8, "azimuth");
assertEquals(2028.9 * NAUTICAL_MILE, distance, 0.05 * NAUTICAL_MILE, "distance"); // From Bennett (1996)
assertEquals(3757550.656, distance, Formulas.LINEAR_TOLERANCE, "distance"); // From Karney's online calculator.
}
/**
* Tests {@link GeodesicsOnEllipsoid#getRhumblineLength()} using the example 4 given in Bennett (1996) appendix.
*/
@Test
@Override
public void testRhumblineEquatorial() {
createTracked();
verifyParametersForWGS84();
testedEarth.setStartGeographicPoint(48+45.0/60, -61-31.1/60);
testedEarth.setEndGeographicPoint (48+45.0/60, 5+13.2/60);
final double distance = testedEarth.getRhumblineLength();
assertValueEquals("Δλ", 0, 4004.3 / 60, 1E-11, true);
assertEquals(90.00000000, testedEarth.getConstantAzimuth(), 1E-8, "azimuth");
assertEquals(2649.9 * NAUTICAL_MILE, distance, 0.1 * NAUTICAL_MILE, "distance"); // From Bennett (1996)
assertEquals(4907757.375, distance, Formulas.LINEAR_TOLERANCE, "distance"); // From Karney's online calculator.
}
}