blob: 96f373a1a16e6293fec0a52cdf8dd10615f83154 [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.datum;
import java.util.HashMap;
import java.util.Locale;
import java.io.InputStream;
import jakarta.xml.bind.JAXBException;
import org.opengis.metadata.extent.Extent;
import org.opengis.util.InternationalString;
import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.datum.GeodeticDatum;
import org.apache.sis.measure.Units;
import org.apache.sis.xml.Namespaces;
import org.apache.sis.io.wkt.Convention;
import org.apache.sis.referencing.operation.matrix.Matrix4;
import org.apache.sis.referencing.internal.AnnotatedMatrix;
import org.apache.sis.referencing.privy.PositionalAccuracyConstant;
import org.apache.sis.metadata.iso.extent.DefaultExtent;
import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
import static org.apache.sis.referencing.GeodeticObjectVerifier.*;
// Test dependencies
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import org.opengis.test.Validators;
import org.apache.sis.test.TestStep;
import org.apache.sis.xml.test.TestCase;
import static org.apache.sis.test.TestUtilities.getScope;
import static org.apache.sis.test.TestUtilities.getSingleton;
import static org.apache.sis.test.Assertions.assertSerializedEquals;
import static org.apache.sis.metadata.Assertions.assertXmlEquals;
import static org.apache.sis.referencing.Assertions.assertWktEquals;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import static org.opengis.test.Assertions.assertMatrixEquals;
/**
* Tests the {@link DefaultGeodeticDatum} class.
*
* @author Martin Desruisseaux (Geomatys)
*/
public final class DefaultGeodeticDatumTest extends TestCase {
/**
* Creates a new test case.
*/
public DefaultGeodeticDatumTest() {
}
/**
* Opens the stream to the XML file in this package containing a geodetic datum definition.
*
* @return stream opened on the XML document to use for testing purpose.
*/
private static InputStream openTestFile() {
// Call to `getResourceAsStream(…)` is caller sensitive: it must be in the same module.
return DefaultGeodeticDatumTest.class.getResourceAsStream("GeodeticDatum.xml");
}
/**
* Tests the creation and serialization of a {@link DefaultGeodeticDatum}.
*/
@Test
public void testCreateAndSerialize() {
final var properties = new HashMap<String,Object>();
assertNull(properties.put(DefaultEllipsoid.NAME_KEY, "Asteroid"));
final var ellipsoid = DefaultEllipsoid.createEllipsoid(properties, 1200, 1000, Units.METRE);
properties.clear();
assertNull(properties.put(DefaultEllipsoid.NAME_KEY, "Somewhere"));
final var primeMeridian = new DefaultPrimeMeridian(properties, 12, Units.DEGREE);
properties.clear();
assertNull(properties.put("name", "This is a name"));
assertNull(properties.put("scope", "This is a scope"));
assertNull(properties.put("scope_fr", "Valide pour tel usage"));
assertNull(properties.put("remarks", "There is remarks"));
assertNull(properties.put("remarks_fr", "Voici des remarques"));
assertNull(properties.put("remarks_ja", "注です。"));
final var datum = new DefaultGeodeticDatum(properties, ellipsoid, primeMeridian);
validate(datum);
validate(assertSerializedEquals(datum));
}
/**
* Compares the properties of the given datum objects with the properties set by the
* {@link #testCreateAndSerialize()} method.
*/
private static void validate(final DefaultGeodeticDatum datum) {
Validators.validate(datum);
InternationalString scope = getSingleton(datum.getDomains()).getScope();
assertEquals("This is a name", datum.getName().getCode());
assertEquals("This is a scope", scope.toString(Locale.ROOT));
assertEquals("Valide pour tel usage", scope.toString(Locale.FRENCH));
assertEquals("There is remarks", datum.getRemarks().toString(Locale.ROOT));
assertEquals("Voici des remarques", datum.getRemarks().toString(Locale.FRENCH));
assertEquals("注です。", datum.getRemarks().toString(Locale.JAPANESE));
}
/**
* Tests {@link DefaultGeodeticDatum#isHeuristicMatchForName(String)}.
*/
@Test
public void testIsHeuristicMatchForName() {
var datum = new DefaultGeodeticDatum(GeodeticDatumMock.WGS84);
assertFalse(datum.isHeuristicMatchForName("WGS72"));
assertTrue (datum.isHeuristicMatchForName("WGS84"));
assertTrue (datum.isHeuristicMatchForName("WGS 84"));
assertTrue (datum.isHeuristicMatchForName("WGS_84"));
assertTrue (datum.isHeuristicMatchForName("D_WGS_84"));
assertFalse(datum.isHeuristicMatchForName("E_WGS_84"));
datum = HardCodedDatum.NTF;
assertFalse(datum.isHeuristicMatchForName("WGS84"));
assertTrue (datum.isHeuristicMatchForName("Nouvelle Triangulation Française"));
assertTrue (datum.isHeuristicMatchForName("Nouvelle Triangulation Francaise"));
assertTrue (datum.isHeuristicMatchForName("Nouvelle Triangulation Française (Paris)"));
assertTrue (datum.isHeuristicMatchForName("Nouvelle Triangulation Francaise (Paris)"));
assertFalse(datum.isHeuristicMatchForName("Nouvelle Triangulation Francaise (Greenwich)"));
}
/**
* Tests {@link DefaultGeodeticDatum#getPositionVectorTransformation(GeodeticDatum, Extent)}.
*/
@Test
public void testGetPositionVectorTransformation() {
final var properties = new HashMap<String,Object>();
assertNull(properties.put(DefaultGeodeticDatum.NAME_KEY, "Invalid dummy datum"));
/*
* Associate two BursaWolfParameters, one valid only in a local area and the other one
* valid globaly. Note that we are building an invalid set of parameters, because the
* source datum are not the same in both case. But for this test we are not interrested
* in datum consistency - we only want any Bursa-Wolf parameters having different area
* of validity.
*/
final BursaWolfParameters local = BursaWolfParametersTest.createED87_to_WGS84(); // Local area (North Sea)
final BursaWolfParameters global = BursaWolfParametersTest.createWGS72_to_WGS84(); // Global area (World)
assertNull(properties.put(DefaultGeodeticDatum.BURSA_WOLF_KEY, new BursaWolfParameters[] {local, global}));
/*
* Build the datum using WGS 72 ellipsoid (so at least one of the BursaWolfParameters is real).
*/
final var datum = new DefaultGeodeticDatum(properties,
GeodeticDatumMock.WGS72.getEllipsoid(),
GeodeticDatumMock.WGS72.getPrimeMeridian());
/*
* Search for BursaWolfParameters around the North Sea area.
*/
final var areaOfInterest = new DefaultGeographicBoundingBox(-2, 8, 55, 60);
final var extent = new DefaultExtent("Around the North Sea", areaOfInterest, null, null);
Matrix matrix = datum.getPositionVectorTransformation(GeodeticDatumMock.NAD83, extent);
assertNull(matrix, "No BursaWolfParameters for NAD83");
matrix = datum.getPositionVectorTransformation(GeodeticDatumMock.WGS84, extent);
assertNotNull(matrix, "BursaWolfParameters for WGS84");
checkTransformationSignature(local, matrix, 0);
/*
* Expand the area of interest to something greater than North Sea, and test again.
*/
areaOfInterest.setWestBoundLongitude(-8);
matrix = datum.getPositionVectorTransformation(GeodeticDatumMock.WGS84, extent);
assertNotNull(matrix, "BursaWolfParameters for WGS84");
checkTransformationSignature(global, matrix, 0);
/*
* Search in the reverse direction.
*/
final var targetDatum = new DefaultGeodeticDatum(GeodeticDatumMock.WGS84);
matrix = targetDatum.getPositionVectorTransformation(datum, extent);
global.invert(); // Expected result is the inverse.
checkTransformationSignature(global, matrix, 1E-6);
}
/**
* Verifies if the given matrix is for the expected Position Vector transformation.
* The easiest way to verify that is to check the translation terms (last matrix column),
* which should have been copied verbatim from the {@code BursaWolfParameters} to the matrix.
* Other terms in the matrix are modified compared to the {@code BursaWolfParameters} ones.
*/
private static void checkTransformationSignature(final BursaWolfParameters expected, final Matrix actual,
final double tolerance)
{
assertEquals(expected.tX, actual.getElement(0, 3), tolerance, "tX");
assertEquals(expected.tY, actual.getElement(1, 3), tolerance, "tY");
assertEquals(expected.tZ, actual.getElement(2, 3), tolerance, "tZ");
}
/**
* Tests {@link DefaultGeodeticDatum#getPositionVectorTransformation(GeodeticDatum, Extent)}
* going through an indirect transformation. The main purpose of this test is to verify that
* the matrix is associated with {@link PositionalAccuracyConstant#INDIRECT_SHIFT_APPLIED}.
*/
@Test
public void testIndirectTransformation() {
final var properties = new HashMap<String,Object>();
assertNull(properties.put(DefaultGeodeticDatum.NAME_KEY, "Invalid dummy datum"));
assertNull(properties.put(DefaultGeodeticDatum.BURSA_WOLF_KEY, BursaWolfParametersTest.createWGS72_to_WGS84()));
final var global = new DefaultGeodeticDatum(properties,
GeodeticDatumMock.WGS72.getEllipsoid(),
GeodeticDatumMock.WGS72.getPrimeMeridian());
/*
* Create a datum valid only in a specific region of the world and with no direct transformation to WGS72.
* However, an indirect transformation to WGS72 is available through WGS84.
*/
properties.put(DefaultGeodeticDatum.BURSA_WOLF_KEY, BursaWolfParametersTest.createED87_to_WGS84());
final var local = new DefaultGeodeticDatum(properties,
GeodeticDatumMock.ED50.getEllipsoid(),
GeodeticDatumMock.ED50.getPrimeMeridian());
/*
* Main test: verify that the transformation found is associated with accuracy information.
*/
final Matrix m = local.getPositionVectorTransformation(global, null);
assertSame(PositionalAccuracyConstant.INDIRECT_SHIFT_APPLIED,
assertInstanceOf(AnnotatedMatrix.class, m, "Should have accuracy information.").accuracy);
/*
* Following is an anti-regression test only (no authoritative values).
* Verified only opportunistically.
*/
assertMatrixEquals(new Matrix4(1, 7.961E-7, 7.287E-7, -82.981,
-7.961E-7, 1, 2.461E-6, -99.719,
-7.287E-7, -2.461E-6, 1, -115.209,
0, 0, 0, 1),
m, 0.01, "getPositionVectorTransformation");
}
/**
* Tests {@link DefaultGeodeticDatum#toWKT()}.
*/
@Test
public void testToWKT() {
final var datum = new DefaultGeodeticDatum(GeodeticDatumMock.WGS84);
assertWktEquals(Convention.WKT2,
"DATUM[“WGS84”,\n" +
" ELLIPSOID[“WGS84”, 6378137.0, 298.257223563, LENGTHUNIT[“metre”, 1]]]",
datum);
assertWktEquals(Convention.WKT2_SIMPLIFIED,
"GeodeticDatum[“WGS84”,\n" +
" Ellipsoid[“WGS84”, 6378137.0, 298.257223563]]",
datum);
}
/**
* Tests marshalling.
*
* @throws JAXBException if an error occurred during marshalling.
*/
@Test
public void testMarshalling() throws JAXBException {
assertXmlEquals(
"<gml:GeodeticDatum xmlns:gml=\"" + Namespaces.GML + "\">\n" +
" <gml:name codeSpace=\"test\">WGS84</gml:name>\n" +
" <gml:primeMeridian>\n" +
" <gml:PrimeMeridian>\n" +
" <gml:name codeSpace=\"test\">Greenwich</gml:name>\n" +
" <gml:greenwichLongitude uom=\"urn:ogc:def:uom:EPSG::9102\">0.0</gml:greenwichLongitude>\n" +
" </gml:PrimeMeridian>\n" +
" </gml:primeMeridian>\n" +
" <gml:ellipsoid>\n" +
" <gml:Ellipsoid>\n" +
" <gml:name codeSpace=\"test\">WGS84</gml:name>\n" +
" <gml:semiMajorAxis uom=\"urn:ogc:def:uom:EPSG::9001\">6378137.0</gml:semiMajorAxis>\n" +
" <gml:secondDefiningParameter>\n" +
" <gml:SecondDefiningParameter>\n" +
" <gml:inverseFlattening uom=\"urn:ogc:def:uom:EPSG::9201\">298.257223563</gml:inverseFlattening>\n" +
" </gml:SecondDefiningParameter>\n" +
" </gml:secondDefiningParameter>\n" +
" </gml:Ellipsoid>\n" +
" </gml:ellipsoid>\n" +
"</gml:GeodeticDatum>",
marshal(new DefaultGeodeticDatum(GeodeticDatumMock.WGS84)), "xmlns:*");
}
/**
* Tests unmarshalling.
*
* <p>This method is part of a chain.
* The next method is {@link #testUnmarshalledWKT()}.</p>
*
* @return the unmarshalled datum.
* @throws JAXBException if an error occurred during unmarshalling.
*/
@TestStep
public DefaultGeodeticDatum testUnmarshalling() throws JAXBException {
final DefaultGeodeticDatum datum = unmarshalFile(DefaultGeodeticDatum.class, openTestFile());
assertIsWGS84(datum, true);
/*
* Values in the following tests are specific to our XML file.
* The actual texts in the EPSG database are more descriptive.
*/
assertEquals("No distinction between the original and subsequent WGS 84 frames.",
datum.getRemarks().toString());
assertEquals("Satellite navigation.",
getScope(datum));
assertEquals("Station coordinates changed by a few centimetres in 1994, 1997, 2002 and 2012.",
datum.getAnchorDefinition().get().toString());
assertEquals(xmlDate("1984-01-01 00:00:00").toInstant(),
datum.getAnchorEpoch().orElse(null));
assertEquals("Defining parameters cited in EPSG database.",
datum.getEllipsoid().getRemarks().toString());
return datum;
}
/**
* Tests the WKT formatting of the datum created by {@link #testUnmarshalling()}.
*
* @throws JAXBException if an error occurred during unmarshalling.
*/
@Test
public void testUnmarshalledWKT() throws JAXBException {
final DefaultGeodeticDatum datum = testUnmarshalling();
assertWktEquals(Convention.WKT1,
"DATUM[“World Geodetic System 1984”,\n" +
" SPHEROID[“WGS 84”, 6378137.0, 298.257223563],\n" +
" AUTHORITY[“EPSG”, “6326”]]",
datum);
assertWktEquals(Convention.WKT2,
"DATUM[“World Geodetic System 1984”,\n" +
" ELLIPSOID[“WGS 84”, 6378137.0, 298.257223563, LENGTHUNIT[“metre”, 1]],\n" +
" ID[“EPSG”, 6326, URI[“urn:ogc:def:datum:EPSG::6326”]]]",
datum);
assertWktEquals(Convention.WKT2_SIMPLIFIED,
"GeodeticDatum[“World Geodetic System 1984”,\n" +
" Ellipsoid[“WGS 84”, 6378137.0, 298.257223563],\n" +
" Id[“EPSG”, 6326, URI[“urn:ogc:def:datum:EPSG::6326”]]]",
datum);
assertWktEquals(Convention.INTERNAL,
"GeodeticDatum[“World Geodetic System 1984”,\n" +
" Ellipsoid[“WGS 84”, 6378137.0, 298.257223563, Id[“EPSG”, 7030],\n" +
" Remark[“Defining parameters cited in EPSG database.”]],\n" +
" Anchor[“Station coordinates changed by a few centimetres in 1994, 1997, 2002 and 2012.”],\n" +
" Scope[“Satellite navigation.”],\n" +
" Area[“World.”],\n" +
" BBox[-90.00, -180.00, 90.00, 180.00],\n" +
" Id[“EPSG”, 6326],\n" +
" Remark[“No distinction between the original and subsequent WGS 84 frames.”]]",
datum);
}
}