blob: 0658839e2ff4b3762e6a60cb7a391ed65a87089c [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;
import java.util.List;
import java.text.ParseException;
import org.opengis.util.FactoryException;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.ConcatenatedOperation;
import org.opengis.referencing.operation.CoordinateOperation;
import org.opengis.referencing.operation.CoordinateOperationAuthorityFactory;
import org.opengis.referencing.operation.SingleOperation;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.internal.referencing.Formulas;
import org.apache.sis.internal.referencing.PositionalAccuracyConstant;
import org.apache.sis.internal.util.Constants;
import org.apache.sis.geometry.DirectPosition2D;
import org.apache.sis.io.wkt.WKTFormat;
import org.apache.sis.referencing.CRS;
import org.apache.sis.referencing.CommonCRS;
// Test dependencies
import org.apache.sis.referencing.operation.transform.MathTransformTestCase;
import org.apache.sis.test.DependsOnMethod;
import org.apache.sis.test.DependsOn;
import org.junit.BeforeClass;
import org.junit.AfterClass;
import org.junit.Test;
import static org.apache.sis.test.ReferencingAssert.*;
/**
* Tests {@link DefaultCoordinateOperationFactory}, with or without EPSG geodetic dataset.
*
* <p><b>Relationship with other tests:</b></p>
* <ul>
* <li>{@link CoordinateOperationRegistryTest} requires an EPSG geodetic dataset (otherwise tests are skipped).</li>
* <li>{@link CoordinateOperationFinderTest} do not use any EPSG geodetic dataset.</li>
* <li>{@code DefaultCoordinateOperationFactoryTest} is a mix of both.</li>
* </ul>
*
* @author Martin Desruisseaux (Geomatys)
* @version 0.8
* @since 0.7
* @module
*/
@DependsOn({
CoordinateOperationRegistryTest.class,
CoordinateOperationFinderTest.class
})
public final strictfp class DefaultCoordinateOperationFactoryTest extends MathTransformTestCase {
/**
* Tolerance threshold for strict comparisons of floating point numbers.
* This constant can be used like below, where {@code expected} and {@code actual} are {@code double} values:
*
* {@preformat java
* assertEquals(expected, actual, STRICT);
* }
*/
private static final double STRICT = 0;
/**
* The transformation factory to use for testing.
*/
private static DefaultCoordinateOperationFactory factory;
/**
* The parser to use for WKT strings used in this test.
*/
private static WKTFormat parser;
/**
* Creates a new {@link DefaultCoordinateOperationFactory} to use for testing purpose.
* The same factory will be used for all tests in this class.
*
* @throws ParseException if an error occurred while preparing the WKT parser.
*/
@BeforeClass
public static void createFactory() throws ParseException {
factory = new DefaultCoordinateOperationFactory();
parser = new WKTFormat(null, null);
parser.addFragment("NTF",
"ProjectedCRS[“NTF (Paris) / Lambert zone II”,\n" +
" BaseGeodCRS[“NTF (Paris)”,\n" +
" Datum[“Nouvelle Triangulation Française (Paris)”,\n" +
" Ellipsoid[“Clarke 1880 (IGN)”, 6378249.2, 293.4660212936269]],\n" +
" PrimeMeridian[“Paris”, 2.5969213],\n" +
" Unit[“grad”, 0.015707963267948967]]\n," +
" Conversion[“Lambert zone II”,\n" +
" Method[“Lambert Conic Conformal (1SP)”],\n" +
" Parameter[“Latitude of natural origin”, 52.0],\n" +
" Parameter[“Scale factor at natural origin”, 0.99987742],\n" +
" Parameter[“False easting”, 600000.0],\n" +
" Parameter[“False northing”, 2200000.0]],\n" +
" CS[Cartesian, 2]\n," +
" Axis[“Easting (X)”, east],\n" +
" Axis[“Northing (Y)”, north],\n" +
" Unit[“metre”, 1],\n" +
" Id[“EPSG”, 27572]]");
parser.addFragment("Mercator",
"ProjectedCRS[“WGS 84 / World Mercator”,\n" +
" BaseGeodCRS[“WGS 84”,\n" +
" Datum[“World Geodetic System 1984”,\n" +
" Ellipsoid[“WGS 84”, 6378137.0, 298.257223563]],\n" +
" Unit[“degree”, 0.017453292519943295]],\n" +
" Conversion[“World Mercator”,\n" +
" Method[“Mercator (variant A)”]],\n" +
" CS[Cartesian, 2]\n," +
" Axis[“Easting (X)”, east],\n" +
" Axis[“Northing (Y)”, north],\n" +
" Unit[“metre”, 1],\n" +
" Id[“EPSG”, 3395]]");
}
/**
* Disposes the factory created by {@link #createFactory()} after all tests have been executed.
*/
@AfterClass
public static void disposeFactory() {
factory = null;
parser = null;
}
/**
* Returns the CRS for the given Well Known Text.
*/
private static CoordinateReferenceSystem parse(final String wkt) throws ParseException {
return (CoordinateReferenceSystem) parser.parseObject(wkt);
}
/**
* Returns {@code true} if {@link #factory} is expected to use the EPSG factory.
*/
private static boolean isUsingEpsgFactory() throws FactoryException {
return DefaultCoordinateOperationFactory.USE_EPSG_FACTORY &&
CRS.getAuthorityFactory(Constants.EPSG) instanceof CoordinateOperationAuthorityFactory;
}
/**
* Tests a transformation between 2D projected CRS which implies a change of prime meridian.
*
* @throws ParseException if a CRS used in this test can not be parsed.
* @throws FactoryException if the operation can not be created.
* @throws TransformException if an error occurred while converting the test points.
*/
@Test
public void testProjectionAndLongitudeRotation() throws ParseException, FactoryException, TransformException {
final CoordinateReferenceSystem sourceCRS = parse("$NTF");
final CoordinateReferenceSystem targetCRS = parse("$Mercator");
final CoordinateOperation operation = factory.createOperation(sourceCRS, targetCRS);
assertSame ("sourceCRS", sourceCRS, operation.getSourceCRS());
assertSame ("targetCRS", targetCRS, operation.getTargetCRS());
assertInstanceOf("operation", ConcatenatedOperation.class, operation);
/*
* The accuracy of the coordinate operation depends on whether a path has been found with the help
* of the EPSG database (in which case the reported accuracy is 2 metres) or if we had to find an
* operation by ourselves (in which case we conservatively report an accuracy of 3000 metres, but
* in practice observe an error between 80 and 515 metres for this test depending on the operation
* method used). By comparison, the translation declared in EPSG database is about 370 metres in
* geocentric coordinates.
*/
final boolean isUsingEpsgFactory = verifyParametersNTF(((ConcatenatedOperation) operation).getOperations(), 1);
assertEquals("linearAccuracy", isUsingEpsgFactory ? 2 : PositionalAccuracyConstant.UNKNOWN_ACCURACY,
CRS.getLinearAccuracy(operation), STRICT);
tolerance = isUsingEpsgFactory ? Formulas.LINEAR_TOLERANCE : 600;
transform = operation.getMathTransform();
/*
* Test using the location of Paris (48.856578°N, 2.351828°E) first,
* then using a coordinate different than the prime meridian.
*/
verifyTransform(new double[] {
601124.99, 2428693.45,
600000.00, 2420000.00
}, new double[] {
261804.30, 6218365.73,
260098.74, 6205194.95
});
validate();
}
/**
* Tests a transformation from a 4D projection to a 2D projection which imply a change of
* prime meridian. This is the same test than {@link #testProjectionAndLongitudeRotation()},
* with extra dimension which should be just dropped.
*
* <p>This tests requires the EPSG database, because it requires the coordinate operation
* path which is defined there.</p>
*
* @throws ParseException if a CRS used in this test can not be parsed.
* @throws FactoryException if the operation can not be created.
* @throws TransformException if an error occurred while converting the test points.
*/
@Test
@DependsOnMethod("testProjectionAndLongitudeRotation")
public void testCompoundAndLongitudeRotation() throws ParseException, FactoryException, TransformException {
final CoordinateReferenceSystem sourceCRS = parse(
"CompoundCRS[“NTF 4D”," +
" $NTF,\n" +
" VerticalCRS[“Geoidal height”,\n" +
" VerticalDatum[“Geoid”],\n" +
" CS[vertical, 1],\n" +
" Axis[“Geoidal height (H)”, up],\n" +
" Unit[“metre”, 1]],\n" +
" TimeCRS[“Modified Julian”,\n" +
" TimeDatum[“Modified Julian”, TimeOrigin[1858-11-17T00:00:00.0Z]],\n" +
" CS[temporal, 1],\n" +
" Axis[“Time (t)”, future],\n" +
" TimeUnit[“day”, 86400]]]");
final CoordinateReferenceSystem targetCRS = parse("$Mercator");
final CoordinateOperation operation = factory.createOperation(sourceCRS, targetCRS);
assertSame ("sourceCRS", sourceCRS, operation.getSourceCRS());
assertSame ("targetCRS", targetCRS, operation.getTargetCRS());
assertInstanceOf("operation", ConcatenatedOperation.class, operation);
/*
* The accuracy of the coordinate operation depends on whether a path has been found with the help
* of the EPSG database. See testProjectionAndLongitudeRotation() for more information.
*/
final boolean isUsingEpsgFactory = verifyParametersNTF(((ConcatenatedOperation) operation).getOperations(), 2);
assertEquals("linearAccuracy", isUsingEpsgFactory ? 2 : PositionalAccuracyConstant.UNKNOWN_ACCURACY,
CRS.getLinearAccuracy(operation), STRICT);
tolerance = isUsingEpsgFactory ? Formulas.LINEAR_TOLERANCE : 600;
transform = operation.getMathTransform();
isInverseTransformSupported = false;
/*
* Same coordinates than testProjectionAndLongitudeRotation(),
* but with random elevation and time which should be dropped.
*/
verifyTransform(new double[] {
601124.99, 2428693.45, 400, 1000,
600000.00, 2420000.00, 400, 1000
}, new double[] {
261804.30, 6218365.73,
260098.74, 6205194.95
});
validate();
}
/**
* Verifies the datum shift parameters in the <cite>"NTF to WGS 84 (1)"</cite> transformation.
* Those parameters depends on whether an EPSG database have been used or not.
*
* @param steps the list returned by {@link DefaultConcatenatedOperation#getOperations()}.
* @param datumShiftIndex index of the datum shift operations in the {@code steps} list.
* @return the {@link #isUsingEpsgFactory()} value, returned for convenience.
*/
private static boolean verifyParametersNTF(final List<? extends CoordinateOperation> steps, final int datumShiftIndex)
throws FactoryException
{
if (isUsingEpsgFactory()) {
final SingleOperation step1 = (SingleOperation) steps.get(datumShiftIndex);
final SingleOperation step2 = (SingleOperation) steps.get(datumShiftIndex + 1);
assertEpsgNameAndIdentifierEqual("NTF (Paris) to NTF (1)", 1763, step1);
assertEpsgNameAndIdentifierEqual("NTF to WGS 84 (1)", 1193, step2);
final ParameterValueGroup p1 = step1.getParameterValues();
final ParameterValueGroup p2 = step2.getParameterValues();
assertEquals("Longitude offset", 2.5969213, p1.parameter("Longitude offset") .doubleValue(), STRICT);
assertEquals("X-axis translation", -168, p2.parameter("X-axis translation").doubleValue(), STRICT);
assertEquals("Y-axis translation", -60, p2.parameter("Y-axis translation").doubleValue(), STRICT);
assertEquals("Z-axis translation", 320, p2.parameter("Z-axis translation").doubleValue(), STRICT);
return true;
} else {
assertSame(CoordinateOperationFinder.ELLIPSOID_CHANGE, steps.get(datumShiftIndex).getName());
return false;
}
}
/**
* Tests the conversion from Mercator projection to the Google projection. The referencing module
* should detects that the conversion is something more complex that an identity transform.
*
* @throws ParseException if a CRS used in this test can not be parsed.
* @throws FactoryException if the operation can not be created.
* @throws TransformException if an error occurred while converting the test points.
*/
@Test
public void testMercatorToGoogle() throws ParseException, FactoryException, TransformException {
final CoordinateReferenceSystem sourceCRS = parse("$Mercator");
final CoordinateReferenceSystem targetCRS = parse(
"ProjectedCRS[“WGS 84 / Pseudo-Mercator”,\n" +
" BaseGeodCRS[“WGS 84”,\n" +
" Datum[“World Geodetic System 1984”,\n" +
" Ellipsoid[“WGS 84”, 6378137.0, 298.257223563]],\n" +
" Unit[“degree”, 0.017453292519943295]],\n" +
" Conversion[“Popular Visualisation Pseudo-Mercator”,\n" +
" Method[“Popular Visualisation Pseudo Mercator”]],\n" +
" CS[Cartesian, 2],\n" +
" Axis[“Easting (X)”, east],\n" +
" Axis[“Northing (Y)”, north],\n" +
" Unit[“metre”, 1],\n" +
" Id[“EPSG”, 3857]]");
final CoordinateOperation operation = factory.createOperation(sourceCRS, targetCRS);
assertSame ("sourceCRS", sourceCRS, operation.getSourceCRS());
assertSame ("targetCRS", targetCRS, operation.getTargetCRS());
assertInstanceOf("operation", ConcatenatedOperation.class, operation);
transform = operation.getMathTransform();
tolerance = 1;
assertFalse("Mercator to Google should not be an identity transform.", transform.isIdentity());
final DirectPosition2D sourcePt = new DirectPosition2D(334000, 4840000); // Approximately 40°N 3°W
final DirectPosition2D targetPt = new DirectPosition2D();
assertSame(targetPt, transform.transform(sourcePt, targetPt));
assertEquals("Easting should be unchanged", sourcePt.getX(), targetPt.getX(), STRICT);
assertEquals("Expected 27 km shift", 27476, targetPt.getY() - sourcePt.getY(), tolerance);
}
/**
* Tests a datum shift applied as a position vector transformation in geocentric domain. This method performs
* the same test than {@link CoordinateOperationFinderTest#testPositionVectorTransformation()} except that the
* EPSG geodetic dataset may be used. The result however should be the same because of the {@code TOWGS84}
* parameter in the WKT used for the test.
*
* @throws ParseException if a CRS used in this test can not be parsed.
* @throws FactoryException if the operation can not be created.
* @throws TransformException if an error occurred while converting the test points.
*
* @see CoordinateOperationFinderTest#testPositionVectorTransformation()
* @see <a href="https://issues.apache.org/jira/browse/SIS-364">SIS-364</a>
*
* @since 0.8
*/
@Test
public void testPositionVectorTransformation() throws ParseException, FactoryException, TransformException {
final CoordinateReferenceSystem sourceCRS = CommonCRS.WGS84.geographic();
final CoordinateReferenceSystem targetCRS = parse(CoordinateOperationFinderTest.AGD66());
final CoordinateOperation operation = factory.createOperation(sourceCRS, targetCRS);
transform = operation.getMathTransform();
tolerance = Formulas.LINEAR_TOLERANCE;
λDimension = new int[] {0};
verifyTransform(CoordinateOperationFinderTest.expectedAGD66(true),
CoordinateOperationFinderTest.expectedAGD66(false));
validate();
}
}