blob: c34e5f30ef38bbe2b8ada4764360b95eefa2e4ae [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.Map;
import java.util.List;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.referencing.privy.WraparoundApplicator;
import org.apache.sis.referencing.operation.AbstractCoordinateOperation;
import org.apache.sis.referencing.operation.matrix.Matrix3;
import org.apache.sis.referencing.operation.matrix.Matrix4;
import org.apache.sis.referencing.cs.AxesConvention;
// Test dependencies
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import org.apache.sis.test.TestCase;
import org.apache.sis.referencing.crs.HardCodedCRS;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import static org.opengis.test.Assertions.assertMatrixEquals;
/**
* Tests {@link WraparoundTransform}.
*
* @author Martin Desruisseaux (Geomatys)
*/
public final class WraparoundTransformTest extends TestCase {
/**
* Creates a new test case.
*/
public WraparoundTransformTest() {
}
/**
* Tests {@link WraparoundTransform#inverse()}.
*
* @throws TransformException if a coordinate cannot be computed.
*/
@Test
public void testInverse() throws TransformException {
/*
* Source range: [ 20 … 380]°.
* Target range: [-90 … 270]°
*/
final MathTransform forward = WraparoundTransform.create(3, 1, 360, 200, 90);
final MathTransform inverse = forward.inverse();
assertSame(forward, inverse.inverse());
assertSame(inverse, forward.inverse()); // Expect cached instance.
final double[] sources = new double[] {
45, -100, 90,
60, -80, 70,
12, 300, 33
};
final double[] targets = new double[9];
forward.transform(sources, 0, targets, 0, 3);
assertArrayEquals(new double[] {
45, -100 + 360, 90, // Wraparound shall be applied.
60, -80, 70, // No wraparound because already in [-90 … 270]° range.
12, 300 - 360, 33 // Wraparound in opposite direction.
}, targets);
inverse.transform(sources, 0, targets, 0, 3);
assertArrayEquals(new double[] {
45, -100 + 360, 90, // Wraparound shall be applied.
60, -80 + 360, 70, // Idem.
12, 300, 33, // No wraparound because already in [ 20 … 380]° range.
}, targets);
}
/**
* Tests wraparound on one axis.
*
* @throws TransformException if a coordinate cannot be computed.
*/
@Test
public void testOneAxis() throws TransformException {
final var op = new AbstractCoordinateOperation(
Map.of(AbstractCoordinateOperation.NAME_KEY, "Wrapper"),
HardCodedCRS.WGS84_LATITUDE_FIRST,
HardCodedCRS.WGS84_LATITUDE_FIRST.forConvention(AxesConvention.POSITIVE_RANGE),
null, MathTransforms.scale(3, 5));
/*
* Transform should be [scale & normalization] → [wraparound] → [denormalization].
* The wraparound is applied on target coordinates, which is why it appears after [scale].
* Wrararound is often (but not always) unnecessary on source coordinates if the operation
* uses trigonometric functions.
*/
final MathTransform wt = WraparoundApplicator.forTargetCRS(op);
final List<MathTransform> steps = MathTransforms.getSteps(wt);
assertEquals(3, steps.size());
assertEquals(1, ((WraparoundTransform) steps.get(1)).wraparoundDimension);
/*
* WraparoundTransform outputs are in [−180 … 180] range, so we expect
* a 180° shift for getting results in the [0 … 360]° range.
*/
assertMatrixEquals(new Matrix3(1, 0, 0, // Latitude (no wrap around)
0, 1, 180, // Longitude in [0 … 360] range.
0, 0, 1),
MathTransforms.getMatrix(steps.get(2)), STRICT, "denormalize");
/*
* The normalization is the inverse of above matrix.
* But we expect the normalization matrix to be concatenated with the (3, 5) scale operation.
*/
assertMatrixEquals(new Matrix3(3, 0, 0, // 3 is a factor in MathTransforms.scale(…).
0, 5, -180, // 5 is (idem).
0, 0, 1),
MathTransforms.getMatrix(steps.get(0)), STRICT, "normalize");
/*
* Test transforming some points.
*/
final double[] pts = {
2, -100/5,
6, -200/5,
9, 200/5,
3, 400/5};
wt.transform(pts, 0, pts, 0, 4);
assertArrayEquals(new double[] {
6, 260,
18, 160,
27, 200,
9, 40}, pts);
}
/**
* Tests wraparound on two axes. We expect two instances of {@link WraparoundTransform} without linear
* transform between them. The absence of separation between the two {@link WraparoundTransform}s is an
* indirect test of {@link WraparoundTransform#tryConcatenate(AbstractMathTransform.Joiner)}.
*
* @throws TransformException if a coordinate cannot be computed.
*/
@Test
public void testTwoAxes() throws TransformException {
final var op = new AbstractCoordinateOperation(
Map.of(AbstractCoordinateOperation.NAME_KEY, "Wrapper"),
HardCodedCRS.WGS84_WITH_TIME.forConvention(AxesConvention.POSITIVE_RANGE),
HardCodedCRS.WGS84_WITH_CYCLIC_TIME, null, MathTransforms.scale(3, 2, 5));
/*
* Transform should be [scale & normalization] → [wraparound 1] → [wraparound 2] → [denormalization].
* At first an affine transform existed between the two [wraparound] operations, but that affine transform
* should have been moved by `WraparoundTransform.tryConcatenate(…)` in order to combine them with initial
* [normalization] and final [denormalization].
*/
final MathTransform wt = WraparoundApplicator.forTargetCRS(op);
final List<MathTransform> steps = MathTransforms.getSteps(wt);
assertEquals(4, steps.size());
assertEquals(0, ((WraparoundTransform) steps.get(1)).wraparoundDimension);
assertEquals(2, ((WraparoundTransform) steps.get(2)).wraparoundDimension);
/*
* WraparoundTransform outputs are in [−180 … 180] range in longitude case,
* so we expect a 180° shift for getting results in the [0 … 360]° range.
*/
assertMatrixEquals(new Matrix4(1, 0, 0, 0, // Longitude in [-180 … 180] range.
0, 1, 0, 0, // Latitude (no wrap around)
0, 0, 1, 183.5, // Day of year in [1 … 366] range.
0, 0, 0, 1),
MathTransforms.getMatrix(steps.get(3)), STRICT, "denormalize");
/*
* The normalization is the inverse of above matrix (when source and target axes have the same span).
* But we expect the normalization matrix to be concatenated with the (3, 2, 5) scale operation.
*/
assertMatrixEquals(new Matrix4(3, 0, 0, 0, // 3 is a factor in MathTransforms.scale(…).
0, 2, 0, 0, // 2 is (idem).
0, 0, 5, -183.5, // 5 is (idem).
0, 0, 0, 1),
MathTransforms.getMatrix(steps.get(0)), 1E-15, "normalize");
}
}