blob: 0de4a1e0f6961a10b904fd92cac11e8f5c10098c [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.operation.transform;
import java.util.Arrays;
import org.opengis.util.FactoryException;
import org.opengis.referencing.datum.Ellipsoid;
import org.opengis.referencing.operation.MathTransformFactory;
import org.opengis.referencing.operation.TransformException;
import org.opengis.parameter.ParameterValueGroup;
import org.apache.sis.internal.referencing.provider.FranceGeocentricInterpolation;
import org.apache.sis.internal.referencing.provider.Molodensky;
import org.apache.sis.internal.system.DefaultFactories;
import org.apache.sis.internal.referencing.Formulas;
import org.apache.sis.referencing.CommonCRS;
import static java.lang.StrictMath.*;
// Test dependencies
import org.apache.sis.internal.referencing.provider.FranceGeocentricInterpolationTest;
import org.apache.sis.internal.referencing.provider.GeocentricTranslationTest;
import org.apache.sis.referencing.datum.HardCodedDatum;
import org.apache.sis.test.DependsOnMethod;
import org.apache.sis.test.DependsOn;
import org.apache.sis.test.TestUtilities;
import org.junit.Test;
import static org.apache.sis.test.Assert.*;
/**
* Tests {@link MolodenskyTransform}. The {@code compareWithGeocentricTranslation()}
* method uses {@link EllipsoidToCentricTransform} as a reference implementation.
* The errors compared to geocentric translations should not be greater than
* approximately 1 centimetre.
*
* @author Tara Athan
* @author Martin Desruisseaux (Geomatys)
* @author Rémi Maréchal (Geomatys)
* @version 0.7
* @since 0.7
* @module
*/
@DependsOn({
CoordinateDomainTest.class,
ContextualParametersTest.class,
EllipsoidToCentricTransformTest.class // Used as a reference implementation
})
public final strictfp class MolodenskyTransformTest extends MathTransformTestCase {
/**
* Creates a new test case.
*/
public MolodenskyTransformTest() {
final double delta = toRadians(100.0 / 60) / 1852; // Approximately 100 metres
derivativeDeltas = new double[] {delta, delta, 100}; // (Δλ, Δφ, Δh)
λDimension = new int[] {0}; // Dimension for which to ignore ±360° differences.
zDimension = new int[] {2}; // Dimension of h where to apply zTolerance
zTolerance = Formulas.LINEAR_TOLERANCE; // Tolerance for ellipsoidal heights (h)
tolerance = Formulas.ANGULAR_TOLERANCE; // Tolerance for longitude and latitude in degrees
}
/**
* Creates a Molodensky transform for a datum shift from WGS84 to ED50.
* Tolerance thresholds are also initialized.
*
* @throws FactoryException if an error occurred while creating a transform step.
*/
private void create(final boolean abridged) throws FactoryException {
final Ellipsoid source = CommonCRS.WGS84.ellipsoid();
final Ellipsoid target = CommonCRS.ED50.ellipsoid();
transform = MolodenskyTransform.createGeodeticTransformation(
DefaultFactories.forBuildin(MathTransformFactory.class),
source, true, target, true,
GeocentricTranslationTest.TX,
GeocentricTranslationTest.TY,
GeocentricTranslationTest.TZ,
abridged);
tolerance = GeocentricTranslationTest.precision(1); // Half the precision of target sample point
zTolerance = GeocentricTranslationTest.precision(3); // Required precision for h
assertFalse(transform.isIdentity());
validate();
}
/**
* Tests the derivative of the Abridged Molodensky transformation.
*
* @throws FactoryException if an error occurred while creating a transform step.
* @throws TransformException if the transformation failed.
*/
@Test
public void testAbridgedMolodenskyDerivative() throws FactoryException, TransformException {
create(true);
verifyDerivative( 0, 0, 0);
verifyDerivative(-3, 30, 7);
verifyDerivative(+6, 60, 20);
}
/**
* Tests the derivative of the Molodensky transformation.
*
* @throws FactoryException if an error occurred while creating a transform step.
* @throws TransformException if the transformation failed.
*/
@Test
@DependsOnMethod("testAbridgedMolodenskyDerivative")
public void testMolodenskyDerivative() throws FactoryException, TransformException {
create(false);
verifyDerivative( 0, 0, 0);
verifyDerivative(-3, 30, 7);
verifyDerivative(+6, 60, 20);
}
/**
* Tests using the sample point given by the EPSG guide.
*
* <ul>
* <li>Source point in WGS84: 53°48'33.820"N, 02°07'46.380"E, 73.00 metres.</li>
* <li>Target point in ED50: 53°48'36.565"N, 02'07"51.477"E, 28.02 metres.</li>
* <li>Datum shift: dX = +84.87m, dY = +96.49m, dZ = +116.95m.</li>
* </ul>
*
* @throws FactoryException if an error occurred while creating a transform step.
* @throws TransformException if the transformation failed.
*/
@Test
@DependsOnMethod("testAbridgedMolodenskyDerivative")
public void testAbridgedMolodensky() throws FactoryException, TransformException {
create(true);
final double[] sample = GeocentricTranslationTest.samplePoint(1);
final double[] expected = GeocentricTranslationTest.samplePoint(5);
isInverseTransformSupported = false;
tolerance = Formulas.LINEAR_TOLERANCE; // Other SIS branches use a stricter threshold.
verifyTransform(sample, expected);
/*
* When testing the inverse transformation, we need to relax slightly
* the tolerance for the 'h' value.
*/
zTolerance = Formulas.LINEAR_TOLERANCE;
isInverseTransformSupported = true;
verifyTransform(sample, expected);
}
/**
* Tests using the same EPSG example than the one provided in {@link EllipsoidToCentricTransformTest}.
*
* <ul>
* <li>Source point in WGS84: 53°48'33.820"N, 02°07'46.380"E, 73.00 metres.</li>
* <li>Target point in ED50: 53°48'36.565"N, 02'07"51.477"E, 28.02 metres.</li>
* <li>Datum shift: dX = +84.87m, dY = +96.49m, dZ = +116.95m.</li>
* </ul>
*
* @throws FactoryException if an error occurred while creating a transform step.
* @throws TransformException if the transformation failed.
*/
@Test
@DependsOnMethod({"testAbridgedMolodensky", "testMolodenskyDerivative"})
public void testMolodensky() throws FactoryException, TransformException {
create(false);
final double[] sample = GeocentricTranslationTest.samplePoint(1);
final double[] expected = GeocentricTranslationTest.samplePoint(4);
isInverseTransformSupported = false;
tolerance = Formulas.LINEAR_TOLERANCE; // Other SIS branches use a stricter threshold.
verifyTransform(sample, expected);
/*
* When testing the inverse transformation, we need to relax slightly
* the tolerance for the 'h' value.
*/
zTolerance = Formulas.LINEAR_TOLERANCE;
isInverseTransformSupported = true;
verifyTransform(sample, expected);
}
/**
* Tests the point used in {@link FranceGeocentricInterpolationTest}. We use this test for measuring the
* errors induced by the use of the Molodensky approximation instead than a real geocentric translation.
* The error is approximately 1 centimetre, which is about 6 times more than the accuracy of the point
* given in {@code FranceGeocentricInterpolationTest}.
*
* @throws FactoryException if an error occurred while creating the transform.
* @throws TransformException if transformation of a point failed.
*/
@Test
@DependsOnMethod("testMolodensky")
public void testFranceGeocentricInterpolationPoint() throws FactoryException, TransformException {
transform = MolodenskyTransform.createGeodeticTransformation(
DefaultFactories.forBuildin(MathTransformFactory.class),
HardCodedDatum.NTF.getEllipsoid(), true, // Clarke 1880 (IGN)
CommonCRS.ETRS89.ellipsoid(), true, // GRS 1980 ellipsoid
-FranceGeocentricInterpolation.TX,
-FranceGeocentricInterpolation.TY,
-FranceGeocentricInterpolation.TZ,
false);
/*
* Code below is a copy-and-paste of GeocentricTranslationTest.testFranceGeocentricInterpolationPoint(),
* but with the tolerance threshold increased. We do not let the error goes beyond 1 cm however.
*/
tolerance = Formulas.LINEAR_TOLERANCE; // Other SIS branches use a stricter threshold.
final double[] source = Arrays.copyOf(FranceGeocentricInterpolationTest.samplePoint(1), 3);
final double[] expected = Arrays.copyOf(FranceGeocentricInterpolationTest.samplePoint(2), 3);
expected[2] = 43.15; // Anti-regression (this value is not provided in NTG_88 guidance note).
verifyTransform(source, expected);
validate();
}
/**
* Tests conversion of random points. The test is performed with the Molodensky transform,
* not the abridged one, because the errors caused by the abridged Molodensky method are
* too high for this test.
*
* @throws FactoryException if an error occurred while creating a transform step.
* @throws TransformException if a transformation failed.
*/
@Test
@DependsOnMethod("testMolodensky")
public void testRandomPoints() throws FactoryException, TransformException {
create(false);
tolerance = Formulas.LINEAR_TOLERANCE * 3; // To be converted in degrees by ToleranceModifier.GEOGRAPHIC
zTolerance = Formulas.LINEAR_TOLERANCE * 2;
// toleranceModifier = ToleranceModifiers.concatenate(ToleranceModifier.GEOGRAPHIC, toleranceModifier);
verifyInDomain(new double[] {-179, -85, -500},
new double[] {+179, +85, +500},
new int[] { 8, 8, 8},
TestUtilities.createRandomNumberGenerator(208129394));
}
/**
* Tests the creation through the provider.
*
* @throws FactoryException if an error occurred while creating a transform step.
* @throws TransformException if a transformation failed.
*/
@Test
@DependsOnMethod("testRandomPoints")
public void testProvider() throws FactoryException, TransformException {
final MathTransformFactory factory = new MathTransformFactoryMock(new Molodensky());
final ParameterValueGroup parameters = factory.getDefaultParameters("Molodenski");
parameters.parameter("dim").setValue(3);
parameters.parameter("dx").setValue(-3.0);
parameters.parameter("dy").setValue(142.0);
parameters.parameter("dz").setValue(183.0);
parameters.parameter("src_semi_major").setValue(6378206.4);
parameters.parameter("src_semi_minor").setValue(6356583.8);
parameters.parameter("tgt_semi_major").setValue(6378137.0);
parameters.parameter("tgt_semi_minor").setValue(6356752.31414036);
transform = factory.createParameterizedTransform(parameters);
assertEquals(3, transform.getSourceDimensions());
assertEquals(3, transform.getTargetDimensions());
tolerance = Formulas.ANGULAR_TOLERANCE * 5;
zTolerance = Formulas.LINEAR_TOLERANCE * 5;
verifyInDomain(CoordinateDomain.RANGE_10, ORDINATE_COUNT);
}
/**
* Verifies that creating a Molodensky operation with same source and target ellipsoid and zero translation
* results in an identity affine transform.
*
* @throws FactoryException if an error occurred while creating a transform step.
*/
@Test
public void testIdentity() throws FactoryException {
final Ellipsoid source = CommonCRS.WGS84.ellipsoid();
transform = MolodenskyTransform.createGeodeticTransformation(
DefaultFactories.forBuildin(MathTransformFactory.class), source, false, source, false, 0, 0, 0, false);
assertInstanceOf("Expected optimized type.", LinearTransform.class, transform);
assertTrue(transform.isIdentity());
validate();
}
/**
* Tests the standard Well Known Text (version 1) formatting.
* The result is what we show to users, but may quite different than what SIS has in memory.
*
* @throws FactoryException if an error occurred while creating a transform.
* @throws TransformException should never happen.
*/
@Test
public void testWKT() throws FactoryException, TransformException {
create(true);
assertWktEquals("PARAM_MT[“Abridged_Molodenski”,\n" +
" PARAMETER[“dim”, 3],\n" +
" PARAMETER[“src_semi_major”, 6378137.0],\n" +
" PARAMETER[“src_semi_minor”, 6356752.314245179],\n" +
" PARAMETER[“tgt_semi_major”, 6378388.0],\n" +
" PARAMETER[“tgt_semi_minor”, 6356911.9461279465],\n" +
" PARAMETER[“dx”, 84.87],\n" +
" PARAMETER[“dy”, 96.49],\n" +
" PARAMETER[“dz”, 116.95],\n" +
" PARAMETER[“Semi-major axis length difference”, 251.0],\n" +
" PARAMETER[“Flattening difference”, 1.4192702255886284E-5]]");
transform = transform.inverse();
assertWktEquals("PARAM_MT[“Abridged_Molodenski”,\n" +
" PARAMETER[“dim”, 3],\n" +
" PARAMETER[“src_semi_major”, 6378388.0],\n" +
" PARAMETER[“src_semi_minor”, 6356911.9461279465],\n" +
" PARAMETER[“tgt_semi_major”, 6378137.0],\n" +
" PARAMETER[“tgt_semi_minor”, 6356752.314245179],\n" +
" PARAMETER[“dx”, -84.87],\n" +
" PARAMETER[“dy”, -96.49],\n" +
" PARAMETER[“dz”, -116.95],\n" +
" PARAMETER[“Semi-major axis length difference”, -251.0],\n" +
" PARAMETER[“Flattening difference”, -1.4192702255886284E-5]]");
}
/**
* Tests the internal Well Known Text formatting.
* This WKT shows what SIS has in memory for debugging purpose.
* This is normally not what we show to users.
*
* @throws FactoryException if an error occurred while creating a transform.
* @throws TransformException should never happen.
*/
@Test
public void testInternalWKT() throws FactoryException, TransformException {
create(true);
assertInternalWktEquals(
"Concat_MT[\n" +
" Param_MT[“Affine”,\n" +
" Parameter[“num_row”, 4],\n" +
" Parameter[“num_col”, 4],\n" +
" Parameter[“elt_0_0”, 0.017453292519943295],\n" + // Degrees to radians conversion
" Parameter[“elt_1_1”, 0.017453292519943295]],\n" +
" Param_MT[“Molodensky (radians domain)”,\n" +
" Parameter[“src_semi_major”, 6378137.0],\n" +
" Parameter[“src_semi_minor”, 6356752.314245179],\n" +
" Parameter[“Semi-major axis length difference”, 251.0, Id[“EPSG”, 8654]],\n" +
" Parameter[“Flattening difference”, 1.4192702255886284E-5, Id[“EPSG”, 8655]],\n" +
" Parameter[“X-axis translation”, 84.87, Id[“EPSG”, 8605]],\n" +
" Parameter[“Y-axis translation”, 96.49, Id[“EPSG”, 8606]],\n" +
" Parameter[“Z-axis translation”, 116.95, Id[“EPSG”, 8607]],\n" +
" Parameter[“abridged”, TRUE],\n" +
" Parameter[“dim”, 3]],\n" +
" Param_MT[“Affine”,\n" +
" Parameter[“num_row”, 4],\n" +
" Parameter[“num_col”, 4],\n" +
" Parameter[“elt_0_0”, 57.29577951308232],\n" + // Radians to degrees conversion
" Parameter[“elt_1_1”, 57.29577951308232]]]");
}
}