blob: 164d1acb6316e5743bc03ca9bbe48948fd36128b [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.projection;
import java.util.Arrays;
import java.util.Random;
import java.io.IOException;
import java.io.LineNumberReader;
import org.opengis.util.FactoryException;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.internal.referencing.Formulas;
import org.apache.sis.internal.referencing.provider.TransverseMercatorSouth;
import org.apache.sis.referencing.operation.transform.CoordinateDomain;
import org.apache.sis.parameter.Parameters;
import org.apache.sis.util.CharSequences;
import org.apache.sis.test.OptionalTestData;
import org.apache.sis.test.DependsOnMethod;
import org.apache.sis.test.DependsOn;
import org.junit.Test;
import static java.lang.Double.NaN;
import static java.lang.StrictMath.min;
import static java.lang.StrictMath.toRadians;
import static org.apache.sis.test.Assert.*;
import static org.apache.sis.internal.referencing.provider.TransverseMercator.LATITUDE_OF_ORIGIN;
import org.opengis.test.CalculationType;
/**
* Tests the {@link TransverseMercator} class.
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.0
* @since 0.6
* @module
*/
@DependsOn(NormalizedProjectionTest.class)
public final strictfp class TransverseMercatorTest extends MapProjectionTestCase {
/**
* Creates a new instance of {@link TransverseMercator}.
*
* @param ellipsoidal {@code false} for a sphere, or {@code true} for WGS84 ellipsoid.
*/
private void createNormalizedProjection(final boolean ellipsoidal, final double latitudeOfOrigin) {
final org.apache.sis.internal.referencing.provider.TransverseMercator method =
new org.apache.sis.internal.referencing.provider.TransverseMercator();
final Parameters parameters = parameters(method, ellipsoidal);
parameters.getOrCreate(LATITUDE_OF_ORIGIN).setValue(latitudeOfOrigin);
transform = new TransverseMercator(method, parameters);
tolerance = NORMALIZED_TOLERANCE;
validate();
}
/**
* Tests the <cite>Transverse Mercator</cite> case (EPSG:9807).
* This test is defined in GeoAPI conformance test suite.
*
* @throws FactoryException if an error occurred while creating the map projection.
* @throws TransformException if an error occurred while projecting a coordinate.
*
* @see org.opengis.test.referencing.ParameterizedTransformTest#testTransverseMercator()
*/
@Test
public void testTransverseMercator() throws FactoryException, TransformException {
createGeoApiTest(new org.apache.sis.internal.referencing.provider.TransverseMercator()).testTransverseMercator();
}
/**
* Tests the <cite>Transverse Mercator (South Orientated)</cite> case (EPSG:9808).
* This test is defined in GeoAPI conformance test suite.
*
* @throws FactoryException if an error occurred while creating the map projection.
* @throws TransformException if an error occurred while projecting a coordinate.
*
* @see org.opengis.test.referencing.ParameterizedTransformTest#testTransverseMercatorSouthOrientated()
*/
@Test
@DependsOnMethod("testTransverseMercator")
public void testTransverseMercatorSouthOrientated() throws FactoryException, TransformException {
createGeoApiTest(new TransverseMercatorSouth()).testTransverseMercatorSouthOrientated();
}
/**
* Verifies the consistency of elliptical formulas with the spherical formulas.
* This test compares the results of elliptical formulas with the spherical ones
* for some random points.
*
* @throws FactoryException if an error occurred while creating the map projection.
* @throws TransformException if an error occurred while projecting a coordinate.
*/
@Test
@DependsOnMethod("testTransverseMercator")
public void compareEllipticalWithSpherical() throws FactoryException, TransformException {
createCompleteProjection(new org.apache.sis.internal.referencing.provider.TransverseMercator(),
6371007, // Semi-major axis length
6371007, // Semi-minor axis length
0.5, // Central meridian
2.5, // Latitude of origin
NaN, // Standard parallel 1 (none)
NaN, // Standard parallel 2 (none)
0.997, // Scale factor
200, // False easting
100); // False northing
tolerance = Formulas.LINEAR_TOLERANCE;
compareEllipticalWithSpherical(CoordinateDomain.RANGE_10, 0);
}
/**
* Creates a projection and derivates a few points.
*
* @throws TransformException if an error occurred while projecting a point.
*/
@Test
public void testSphericalDerivative() throws TransformException {
createNormalizedProjection(false, 0);
tolerance = 1E-9;
final double delta = toRadians(100.0 / 60) / 1852; // Approximately 100 metres.
derivativeDeltas = new double[] {delta, delta};
verifyDerivative(toRadians( 0), toRadians( 0));
verifyDerivative(toRadians(-3), toRadians(30));
verifyDerivative(toRadians(+6), toRadians(60));
}
/**
* Creates a projection and derivates a few points.
*
* @throws TransformException if an error occurred while projecting a point.
*/
@Test
public void testEllipsoidalDerivative() throws TransformException {
createNormalizedProjection(true, 0);
tolerance = 1E-9;
final double delta = toRadians(100.0 / 60) / 1852; // Approximately 100 metres.
derivativeDeltas = new double[] {delta, delta};
verifyDerivative(toRadians( 0), toRadians( 0));
verifyDerivative(toRadians(-3), toRadians(30));
verifyDerivative(toRadians(+6), toRadians(60));
}
/**
* Verifies that deserialized projections work as expected. This implies that deserialization
* recomputed the internal transient fields, especially the series expansion coefficients.
*
* @throws FactoryException if an error occurred while creating the map projection.
* @throws TransformException if an error occurred while projecting a coordinate.
*/
@Test
@DependsOnMethod("testTransverseMercator")
public void testSerialization() throws FactoryException, TransformException {
createNormalizedProjection(true, 40);
/*
* Use a fixed seed for the random number generator in this test, because in case of failure this class will not
* report which seed it used. This limitation exists because this test class does not extend the SIS TestCase.
*/
final double[] source = CoordinateDomain.GEOGRAPHIC_RADIANS_HALF_λ.generateRandomInput(new Random(5346144739450824145L), 2, 10);
final double[] target = new double[source.length];
for (int i=0; i<source.length; i+=2) {
// A longitude range of [-90 … +90]° is still too wide for Transverse Mercator. Reduce to [-45 … +45]°.
source[i] /= 2;
}
transform.transform(source, 0, target, 0, 10);
transform = assertSerializedEquals(transform);
tolerance = Formulas.LINEAR_TOLERANCE;
verifyTransform(source, target);
}
/**
* Compares with <cite>Karney (2009) Test data for the transverse Mercator projection</cite>.
* This is an optional test executed only if the {@code $SIS_DATA/Tests/TMcoords.dat} file is found.
*
* @throws IOException if an error occurred while reading the test file.
* @throws FactoryException if an error occurred while creating the map projection.
* @throws TransformException if an error occurred while transforming coordinates.
*/
@Test
public void compareAgainstDataset() throws IOException, FactoryException, TransformException {
try (LineNumberReader reader = OptionalTestData.TRANSVERSE_MERCATOR.reader()) {
createCompleteProjection(new org.apache.sis.internal.referencing.provider.TransverseMercator(),
WGS84_A, // Semi-major axis length
WGS84_B, // Semi-minor axis length
0, // Central meridian
0, // Latitude of origin
NaN, // Standard parallel 1 (none)
NaN, // Standard parallel 2 (none)
0.9996, // Scale factor
0, // False easting
0); // False northing
final double[] source = new double[2];
final double[] target = new double[2];
String line;
while ((line = reader.readLine()) != null) {
Arrays.fill(source, Double.NaN);
final CharSequence[] split = CharSequences.split(line, ' ');
for (int i=min(split.length, 4); --i >= 0;) {
final double value = Double.parseDouble(split[i].toString());
if (i <= 1) source[i ^ 1] = value; // Swap axis order.
else target[i - 2] = value;
}
// Relax tolerance for longitudes very far from central meridian.
final double longitude = StrictMath.abs(source[0]);
if (longitude < TransverseMercator.DOMAIN_OF_VALIDITY) {
if (StrictMath.abs(source[1]) >= 89.9) tolerance = 0.1;
else if (longitude <= 60) tolerance = Formulas.LINEAR_TOLERANCE;
else if (longitude <= 66) tolerance = 0.1;
else if (longitude <= 70) tolerance = 1;
else if (longitude <= 72) tolerance = 2;
else if (longitude <= 74) tolerance = 10;
else if (longitude <= 76) tolerance = 30;
else tolerance = 1000;
transform.transform(source, 0, source, 0, 1);
assertCoordinateEquals(line, target, source, reader.getLineNumber(), CalculationType.DIRECT_TRANSFORM);
}
}
}
}
}