blob: 6793ab5690e71ea1de792f79812bdd876a798868 [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.Random;
import java.util.Iterator;
import org.opengis.util.FactoryException;
import org.opengis.geometry.DirectPosition;
import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransformFactory;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.internal.system.DefaultFactories;
import org.apache.sis.referencing.datum.HardCodedDatum;
import org.apache.sis.referencing.operation.matrix.Matrix2;
import org.apache.sis.referencing.operation.matrix.Matrix3;
import org.apache.sis.referencing.operation.matrix.Matrix4;
import org.apache.sis.referencing.operation.matrix.Matrices;
import org.apache.sis.geometry.GeneralDirectPosition;
import org.apache.sis.test.TestUtilities;
import org.apache.sis.test.DependsOnMethod;
import org.apache.sis.test.DependsOn;
import org.apache.sis.test.TestCase;
import org.junit.Test;
import static java.lang.Double.NaN;
import static org.opengis.test.Assert.*;
/**
* Tests {@link TransformSeparator}.
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.0
* @since 0.7
* @module
*/
@DependsOn({
PassThroughTransformTest.class,
ConcatenatedTransformTest.class
})
public final strictfp class TransformSeparatorTest extends TestCase {
/**
* Verifies the argument checks performed by the {@code add} methods.
*/
@Test
public void testArgumentChecks() {
final TransformSeparator s = new TransformSeparator(MathTransforms.identity(8));
try {
s.getSourceDimensions();
fail("Shall not return unspecified dimensions.");
} catch (IllegalStateException e) {
// This is the expected exception.
}
try {
s.getTargetDimensions();
fail("Shall not return unspecified dimensions.");
} catch (IllegalStateException e) {
// This is the expected exception.
}
s.addSourceDimensionRange(1, 4);
s.addTargetDimensions(0, 3, 4);
assertArrayEquals("sourceDimensions", new int[] {1, 2, 3}, s.getSourceDimensions());
assertArrayEquals("targetDimensions", new int[] {0, 3, 4}, s.getTargetDimensions());
try {
s.addSourceDimensions(3, 4, 5);
fail("Shall not accept non-increasing value.");
} catch (IllegalArgumentException e) {
// This is the expected exception.
assertTrue(e.getMessage().contains("dimensions[0]"));
}
try {
s.addTargetDimensionRange(3, 5);
fail("Shall not accept non-increasing value.");
} catch (IllegalArgumentException e) {
// This is the expected exception.
assertTrue(e.getMessage().contains("lower"));
}
s.addSourceDimensions(4, 6);
s.addTargetDimensionRange(6, 8);
assertArrayEquals("sourceDimensions", new int[] {1, 2, 3, 4, 6}, s.getSourceDimensions());
assertArrayEquals("targetDimensions", new int[] {0, 3, 4, 6, 7}, s.getTargetDimensions());
try {
s.addSourceDimensions(8);
fail("Shall not accept value out of range.");
} catch (IllegalArgumentException e) {
// This is the expected exception.
assertTrue(e.getMessage().contains("dimensions[0]"));
}
try {
s.addTargetDimensions(3, 8);
fail("Shall not accept value out of range.");
} catch (IllegalArgumentException e) {
// This is the expected exception.
assertTrue(e.getMessage().contains("dimensions[0]"));
}
}
/**
* Tests separation of a linear transform.
*
* @throws FactoryException if an error occurred while creating a new transform.
*/
@Test
public void testLinearTransform() throws FactoryException {
Matrix matrix = new Matrix4(
2, 0, 0, 7, // Some random values.
0, 5, 0, 6,
1, 0, 3, 8,
0, 0, 0, 1
);
final TransformSeparator s = new TransformSeparator(MathTransforms.linear(matrix));
/*
* Trivial case: no dimension specified, we should get the transform unchanged.
*/
assertSame("transform", s.transform, s.separate());
assertArrayEquals("sourceDimensions", new int[] {0, 1, 2}, s.getSourceDimensions());
assertArrayEquals("targetDimensions", new int[] {0, 1, 2}, s.getTargetDimensions());
/*
* Filter only target dimensions. This is the easiest non-trivial case since we just
* need to drop some rows. There is no analysis to perform on the matrix values.
*/
matrix = Matrices.create(3, 4, new double[] {
2, 0, 0, 7,
1, 0, 3, 8,
0, 0, 0, 1
});
s.clear();
s.addTargetDimensions(0, 2);
s.addSourceDimensionRange(0, 3);
assertMatrixEquals("transform", matrix, ((LinearTransform) s.separate()).getMatrix(), STRICT);
assertArrayEquals("sourceDimensions", new int[] {0, 1, 2}, s.getSourceDimensions());
assertArrayEquals("targetDimensions", new int[] {0, 2}, s.getTargetDimensions());
/*
* Filter only source dimensions. Do not specify any target dimensions for now.
* TransformSeparator needs to examine the matrix values and drop all target dimensions
* that depend on an excluded source dimensions.
*/
matrix = Matrices.create(2, 3, new double[] {
5, 0, 6,
0, 0, 1
});
s.clear();
s.addSourceDimensions(1, 2);
assertMatrixEquals("transform", matrix, ((LinearTransform) s.separate()).getMatrix(), STRICT);
assertArrayEquals("sourceDimensions", new int[] {1, 2}, s.getSourceDimensions());
assertArrayEquals("targetDimensions", new int[] {1}, s.getTargetDimensions());
/*
* Filter both source and target dimensions. Source dimensions 0 and 2 allow the target dimensions 0 and 2
* (target dimension 1 is discarded because it depends on source dimension 1). Then the target dimensions
* are filtered for retaining only dimension 0.
*/
matrix = Matrices.create(2, 3, new double[] {
2, 0, 7,
0, 0, 1
});
s.clear();
s.addSourceDimensions(0, 2);
s.addTargetDimensions(0);
assertMatrixEquals("transform", matrix, ((LinearTransform) s.separate()).getMatrix(), STRICT);
assertArrayEquals("sourceDimensions", new int[] {0, 2}, s.getSourceDimensions());
assertArrayEquals("targetDimensions", new int[] {0}, s.getTargetDimensions());
/*
* Try again, but with the addition of a target dimension that TransformSeparator can not keep.
* It shall cause an exception to be thrown.
*/
s.addTargetDimensions(1);
try {
s.separate();
fail("Should not have been able to separate that transform.");
} catch (FactoryException e) {
// This is the expected exception.
assertNotNull(e.getMessage());
}
}
/**
* Tests separation of a linear transform containing {@link Double#NaN} values.
*
* @throws FactoryException if an error occurred while creating a new transform.
*/
@Test
public void testIncompleteTransform() throws FactoryException {
Matrix matrix = new Matrix4(
1, 0, 0, 7,
0, 0, 1, 8,
0, NaN, 0, 6,
0, 0, 0, 1
);
TransformSeparator s = new TransformSeparator(MathTransforms.linear(matrix));
s.addSourceDimensions(1);
assertMatrixEquals("transform", new Matrix2(
NaN, 6,
0, 1
), ((LinearTransform) s.separate()).getMatrix(), STRICT);
assertArrayEquals(new int[] {2}, s.getTargetDimensions());
}
/**
* Tests separation of a concatenated transform.
*
* @throws FactoryException if an error occurred while creating a new transform.
*/
@Test
@DependsOnMethod("testLinearTransform")
public void testConcatenatedTransform() throws FactoryException {
final MathTransformFactory factory = DefaultFactories.forBuildin(MathTransformFactory.class);
final TransformSeparator s = new TransformSeparator(EllipsoidToCentricTransform.createGeodeticConversion(
factory, HardCodedDatum.WGS84.getEllipsoid(), false), factory);
s.addSourceDimensions(0, 1);
s.addTargetDimensions(0, 1);
final Iterator<MathTransform> it = MathTransforms.getSteps(s.separate()).iterator();
assertInstanceOf("normalize", LinearTransform.class, it.next());
assertInstanceOf("transform", EllipsoidToCentricTransform.class, it.next());
assertInstanceOf("denormalize", LinearTransform.class, it.next());
assertFalse(it.hasNext());
}
/**
* Tests separation of a pass through transform.
*
* @throws FactoryException if an error occurred while creating a new transform.
* @throws TransformException if an error occurred while transforming coordinates for comparison purpose.
*/
@Test
@DependsOnMethod("testLinearTransform")
public void testPassThroughTransform() throws FactoryException, TransformException {
/*
* This non-linear transform increase the number of dimensions from 2 to 3.
* In addition we let 2 dimensions passthrough before and 3 passtrough after.
*/
final MathTransform nonLinear = new PseudoTransform(2, 3);
final TransformSeparator s = new TransformSeparator(MathTransforms.passThrough(2, nonLinear, 3));
/*
* Trivial case: no dimension specified, we should get the transform unchanged.
*/
assertSame("transform", s.transform, s.separate());
assertArrayEquals("sourceDimensions", new int[] {0, 1, 2, 3, 4, 5, 6}, s.getSourceDimensions());
assertArrayEquals("targetDimensions", new int[] {0, 1, 2, 3, 4, 5, 6, 7}, s.getTargetDimensions());
/*
* Filter only target dimensions. If the requested indices overlap the pass-through transform,
* TransformSeparator will just concatenate a matrix after the transform for dropping dimensions.
*/
Matrix expected = Matrices.create(4, 9, new double[] {
0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1
});
s.clear();
s.addTargetDimensions(1, 2, 7);
s.addSourceDimensionRange(0, 7);
MathTransform result = s.separate();
assertArrayEquals("sourceDimensions", new int[] {0, 1, 2, 3, 4, 5, 6}, s.getSourceDimensions());
assertArrayEquals("targetDimensions", new int[] {1, 2, 7}, s.getTargetDimensions());
final Random random = TestUtilities.createRandomNumberGenerator();
compare(s.transform, MathTransforms.linear(expected), result, random);
/*
* Filter only target dimensions, but with indices that are all outside the pass-through transform.
* TransformSeparator should be able to give us a simple affine transform.
*/
expected = Matrices.create(4, 8, new double[] {
0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 1
});
s.clear();
s.addTargetDimensions(1, 5, 7);
s.addSourceDimensionRange(0, 7);
result = s.separate();
assertArrayEquals ("sourceDimensions", new int[] {0, 1, 2, 3, 4, 5, 6}, s.getSourceDimensions());
assertArrayEquals ("targetDimensions", new int[] {1, 5, 7}, s.getTargetDimensions());
assertInstanceOf ("separate()", LinearTransform.class, result);
assertMatrixEquals("separate().transform2", expected, ((LinearTransform) result).getMatrix(), STRICT);
/*
* Filter source dimensions. If we ask only for dimensions not in the pass-through transform,
* then TransformSeparator should return an affine transform.
*/
expected = new Matrix3(
1, 0, 0,
0, 1, 0,
0, 0, 1
);
s.clear();
s.addSourceDimensions(0, 6);
result = s.separate();
assertArrayEquals ("sourceDimensions", new int[] {0, 6}, s.getSourceDimensions());
assertArrayEquals ("targetDimensions", new int[] {0, 7}, s.getTargetDimensions());
assertInstanceOf ("separate()", LinearTransform.class, result);
assertMatrixEquals("separate().transform2", expected, ((LinearTransform) result).getMatrix(), STRICT);
/*
* Filter source dimensions, now with overlapping in the pass-through transform.
* TransformSeparator is expected to create a new PassThroughTransform.
*/
s.clear();
s.addSourceDimensions(1, 2, 3, 4, 5);
result = s.separate();
assertArrayEquals("sourceDimensions", new int[] {1, 2, 3, 4, 5}, s.getSourceDimensions());
assertArrayEquals("targetDimensions", new int[] {1, 2, 3, 4, 5, 6}, s.getTargetDimensions());
assertInstanceOf ("separate()", PassThroughTransform.class, result);
assertSame ("subTransform", nonLinear, ((PassThroughTransform) result).subTransform);
assertEquals("firstAffectedCoordinate", 1, ((PassThroughTransform) result).firstAffectedCoordinate);
assertEquals("numTrailingCoordinates", 2, ((PassThroughTransform) result).numTrailingCoordinates);
}
/**
* Compares coordinate computed by a reference with coordinates computed by the transform to test.
* We use this method when we can not easily analyze the {@link MathTransform} created by the test
* case, for example because it may have been rearranged in arbitrary ways for optimization purpose
* (e.g. {@link PassThroughTransform#tryConcatenate(boolean, MathTransform, MathTransformFactory)}).
*
* @param tr1 first half of the transform to use as a reference.
* @param tr2 second half of the transform to use as a reference.
* @param test the transform to test.
* @param random random number generator for coordinate values.
*/
private static void compare(final MathTransform tr1, final MathTransform tr2, final MathTransform test, final Random random)
throws TransformException
{
DirectPosition source = new GeneralDirectPosition(tr1.getSourceDimensions());
DirectPosition step = null;
DirectPosition expected = null;
DirectPosition actual = null;
for (int t=0; t<50; t++) {
for (int i=source.getDimension(); --i>=0;) {
source.setOrdinate(i, random.nextDouble());
}
step = tr1 .transform(source, step);
expected = tr2 .transform(step, expected);
actual = test.transform(source, actual);
assertEquals(expected, actual);
}
}
/**
* Tests separation of a concatenated transform containing a pass through transform.
*
* @throws FactoryException if an error occurred while creating a new transform.
* @throws TransformException if an error occurred while transforming coordinates for comparison purpose.
*/
@Test
@DependsOnMethod({"testConcatenatedTransform", "testPassThroughTransform"})
public void testConcatenatedPassThroughTransform() throws FactoryException, TransformException {
final MathTransform linear = MathTransforms.scale(4, 1, 1, 1, 1, 6);
final MathTransform nonLinear = new PseudoTransform(3, 2);
final MathTransform passthrough = MathTransforms.passThrough(2, nonLinear, 1);
final MathTransform concatenated = new ConcatenatedTransform(linear, passthrough); // Bypass 'tryOptimized' method.
final TransformSeparator sep = new TransformSeparator(concatenated);
sep.addSourceDimensionRange(0, 2);
assertMatrixEquals("Leading passthrough dimensions", new Matrix3(4, 0, 0, 0, 1, 0, 0, 0, 1),
MathTransforms.getMatrix(sep.separate()), STRICT);
sep.clear();
sep.addSourceDimensionRange(5, 6);
assertMatrixEquals("Trailing passthrough dimensions", new Matrix2(6, 0, 0, 1),
MathTransforms.getMatrix(sep.separate()), STRICT);
sep.clear();
sep.addSourceDimensionRange(2, 5);
assertSame("subTransform", nonLinear, sep.separate());
sep.clear();
sep.addSourceDimensionRange(1, 5);
MathTransform mt = sep.separate();
assertInstanceOf("separate()", PassThroughTransform.class, mt);
final PassThroughTransform ps = ((PassThroughTransform) mt);
assertEquals("firstAffectedCoordinate", 1, ps.firstAffectedCoordinate);
assertEquals("numTrailingCoordinates", 0, ps.numTrailingCoordinates);
assertSame ("subTransform", nonLinear, ps.subTransform);
}
/**
* Tests separation of a pass through transform containing another pass through transform.
*
* @throws FactoryException if an error occurred while creating a new transform.
* @throws TransformException if an error occurred while transforming coordinates for comparison purpose.
*/
@Test
@DependsOnMethod("testConcatenatedPassThroughTransform")
public void testNestedPassThroughTransform() throws FactoryException, TransformException {
final MathTransform nonLinear = new PseudoTransform(3, 2);
final MathTransform passthrough1 = MathTransforms.passThrough(2, nonLinear, 1);
final MathTransform concatenated = new ConcatenatedTransform(MathTransforms.scale(4, 3, 2, 1, 1, 6), passthrough1);
final MathTransform passthrough2 = new PassThroughTransform(2, concatenated, 3);
final TransformSeparator sep = new TransformSeparator(passthrough2);
sep.addSourceDimensionRange(3, 7);
MathTransform mt = sep.separate();
assertInstanceOf("separate()", ConcatenatedTransform.class, mt);
assertMatrixEquals("Leading passthrough dimensions", Matrices.create(5, 5, new double[] {
3, 0, 0, 0, 0,
0, 2, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0,
0, 0, 0, 0, 1}), MathTransforms.getMatrix(((ConcatenatedTransform) mt).transform1), STRICT);
mt = ((ConcatenatedTransform) mt).transform2;
assertInstanceOf("subTransform", PassThroughTransform.class, mt);
final PassThroughTransform ps = ((PassThroughTransform) mt);
assertEquals("firstAffectedCoordinate", 1, ps.firstAffectedCoordinate);
assertEquals("numTrailingCoordinates", 0, ps.numTrailingCoordinates);
assertSame ("subTransform", nonLinear, ps.subTransform);
}
/**
* Tests {@link TransformSeparator} with removal of unused source dimensions.
*
* @throws FactoryException if an error occurred while creating a new transform.
*/
@Test
@DependsOnMethod("testLinearTransform")
public void testTrimSourceDimensions() throws FactoryException {
MathTransform tr = MathTransforms.linear(Matrices.create(3, 4, new double[] {
0, 0.5, 0, -90,
0.5, 0, 0, -180,
0, 0, 0, 1}));
/*
* Verify that TransformSeparator does not trim anything if not requested so.
*/
TransformSeparator s = new TransformSeparator(tr);
s.addSourceDimensionRange(0, tr.getSourceDimensions());
assertSame("No source dimensions should be trimmed if not requested.", tr, s.separate());
assertArrayEquals(new int[] {0, 1, 2}, s.getSourceDimensions());
assertArrayEquals(new int[] {0, 1 }, s.getTargetDimensions());
/*
* Trim the last dimension (most common case).
*/
final Matrix expected = new Matrix3(
0, 0.5, -90,
0.5, 0, -180,
0, 0, 1);
s.clear();
MathTransform reduced = s.separate();
assertNotEquals("separate()", tr, reduced);
assertArrayEquals(new int[] {0, 1}, s.getSourceDimensions());
assertArrayEquals(new int[] {0, 1}, s.getTargetDimensions());
assertMatrixEquals("separate()", expected, MathTransforms.getMatrix(reduced), STRICT);
/*
* Trim the first dimension.
*/
tr = MathTransforms.linear(Matrices.create(3, 4, new double[] {
0, 0, 0.5, -90,
0, 0.5, 0, -180,
0, 0, 0, 1}));
s = new TransformSeparator(tr);
reduced = s.separate();
assertNotEquals("separate()", tr, reduced);
assertArrayEquals(new int[] {1, 2}, s.getSourceDimensions());
assertArrayEquals(new int[] {0, 1}, s.getTargetDimensions());
assertMatrixEquals("separate()", new Matrix3(
0, 0.5, -90,
0.5, 0, -180,
0, 0, 1), MathTransforms.getMatrix(reduced), STRICT);
}
}