You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2023/03/25 15:19:27 UTC
[sis] 04/06: Add test for `PassThroughTransform` with non-consecutive modified coordinates. Allow `DefaultPassThroughOperation` to use non-consecutive coordinates at unmarshalling time.
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit 295965c64a044d5b82c7f74c1295c5e7ff33b854
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sat Mar 25 12:36:05 2023 +0100
Add test for `PassThroughTransform` with non-consecutive modified coordinates.
Allow `DefaultPassThroughOperation` to use non-consecutive coordinates at unmarshalling time.
---
.../operation/DefaultPassThroughOperation.java | 141 +++++++++++----------
.../operation/transform/MathTransforms.java | 10 +-
.../operation/transform/PassThroughTransform.java | 7 +-
.../operation/transform/TransformSeparator.java | 26 ++--
.../transform/PassThroughTransformTest.java | 65 ++++++++--
5 files changed, 156 insertions(+), 93 deletions(-)
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultPassThroughOperation.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultPassThroughOperation.java
index 4ac1387348..fd62fcd1b8 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultPassThroughOperation.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultPassThroughOperation.java
@@ -29,13 +29,11 @@ import org.opengis.referencing.operation.Conversion;
import org.opengis.referencing.operation.CoordinateOperation;
import org.opengis.referencing.operation.PassThroughOperation;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.crs.CompoundCRS;
import org.apache.sis.referencing.GeodeticException;
import org.apache.sis.referencing.operation.transform.MathTransforms;
import org.apache.sis.referencing.operation.transform.PassThroughTransform;
import org.apache.sis.internal.referencing.ReferencingUtilities;
import org.apache.sis.internal.metadata.ImplementationHelper;
-import org.apache.sis.util.UnsupportedImplementationException;
import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.resources.Errors;
@@ -50,7 +48,7 @@ import static org.apache.sis.util.Utilities.deepEquals;
* Specifies that a subset of a coordinate tuple is subject to a specific coordinate operation.
*
* @author Martin Desruisseaux (IRD, Geomatys)
- * @version 1.3
+ * @version 1.4
* @since 0.6
*/
@XmlType(name = "PassThroughOperationType", propOrder = {
@@ -62,7 +60,7 @@ public class DefaultPassThroughOperation extends AbstractCoordinateOperation imp
/**
* Serial number for inter-operability with different versions.
*/
- private static final long serialVersionUID = 4308173919747248695L;
+ private static final long serialVersionUID = 3516394762777350439L;
/**
* The operation to apply on the subset of a coordinate tuple.
@@ -75,6 +73,16 @@ public class DefaultPassThroughOperation extends AbstractCoordinateOperation imp
@SuppressWarnings("serial") // Most SIS implementations are serializable.
private CoordinateOperation operation;
+ /**
+ * Zero-based indices of the modified source coordinates.
+ *
+ * <p><b>Consider this field as final!</b>
+ * This field is modified only at unmarshalling time by {@link #setIndices(int[])}</p>
+ *
+ * @see #getModifiedCoordinates()
+ */
+ private int[] modifiedCoordinates;
+
/**
* Constructs a pass-through operation from a set of properties.
* The properties given in argument follow the same rules than for the
@@ -114,10 +122,27 @@ public class DefaultPassThroughOperation extends AbstractCoordinateOperation imp
final CoordinateOperation operation,
final int firstAffectedCoordinate,
final int numTrailingCoordinates)
+ {
+ this(properties, sourceCRS, targetCRS, operation, operation.getMathTransform(), firstAffectedCoordinate, numTrailingCoordinates);
+ }
+
+ /**
+ * Work around for RFE #4093999 in Sun's bug database
+ * ("Relax constraint on placement of this()/super() call in constructors").
+ */
+ private DefaultPassThroughOperation(final Map<String,?> properties,
+ final CoordinateReferenceSystem sourceCRS,
+ final CoordinateReferenceSystem targetCRS,
+ final CoordinateOperation operation,
+ final MathTransform subTransform,
+ final int firstAffectedCoordinate,
+ final int numTrailingCoordinates)
{
super(properties, sourceCRS, targetCRS, null,
- MathTransforms.passThrough(firstAffectedCoordinate, operation.getMathTransform(), numTrailingCoordinates));
+ MathTransforms.passThrough(firstAffectedCoordinate, subTransform, numTrailingCoordinates));
this.operation = operation;
+ modifiedCoordinates = ArraysExt.range(firstAffectedCoordinate,
+ firstAffectedCoordinate + subTransform.getSourceDimensions());
}
/**
@@ -134,6 +159,7 @@ public class DefaultPassThroughOperation extends AbstractCoordinateOperation imp
protected DefaultPassThroughOperation(final PassThroughOperation operation) {
super(operation);
this.operation = operation.getOperation();
+ modifiedCoordinates = operation.getModifiedCoordinates();
}
/**
@@ -157,10 +183,10 @@ public class DefaultPassThroughOperation extends AbstractCoordinateOperation imp
* Returns the GeoAPI interface implemented by this class.
* The SIS implementation returns {@code PassThroughOperation.class}.
*
- * <div class="note"><b>Note for implementers:</b>
+ * <h4>Note for implementers</h4>
* Subclasses usually do not need to override this method since GeoAPI does not define {@code PassThroughOperation}
* sub-interface. Overriding possibility is left mostly for implementers who wish to extend GeoAPI with their
- * own set of interfaces.</div>
+ * own set of interfaces.
*
* @return {@code PassThroughOperation.class} or a user-defined sub-interface.
*/
@@ -192,33 +218,7 @@ public class DefaultPassThroughOperation extends AbstractCoordinateOperation imp
*/
@Override
public int[] getModifiedCoordinates() {
- final MathTransform transform = super.getMathTransform();
- if (transform instanceof PassThroughTransform) {
- return ((PassThroughTransform) transform).getModifiedCoordinates();
- } else if (operation != null) {
- /*
- * Should not happen with objects created by public methods since the constructor created the transform itself.
- * However, may happen with operations parsed from GML. As a fallback, search in the components of CompoundCRS.
- * This is not a universal fallback, but works for the most straightforward cases.
- */
- final CoordinateReferenceSystem sourceCRS = super.getSourceCRS();
- if (sourceCRS instanceof CompoundCRS) {
- int firstAffectedCoordinate = 0;
- final CoordinateReferenceSystem search = operation.getSourceCRS();
- for (final CoordinateReferenceSystem c : ((CompoundCRS) sourceCRS).getComponents()) {
- final int dim = ReferencingUtilities.getDimension(c);
- if (c == search) {
- final int[] indices = new int[dim];
- for (int i=0; i<dim; i++) {
- indices[i] = firstAffectedCoordinate + i;
- }
- return indices;
- }
- firstAffectedCoordinate += dim;
- }
- }
- }
- throw new UnsupportedImplementationException(transform.getClass());
+ return modifiedCoordinates.clone();
}
/**
@@ -236,9 +236,13 @@ public class DefaultPassThroughOperation extends AbstractCoordinateOperation imp
}
if (super.equals(object, mode)) {
if (mode == ComparisonMode.STRICT) {
- return Objects.equals(operation, ((DefaultPassThroughOperation) object).operation);
+ final var other = (DefaultPassThroughOperation) object;
+ return Arrays.equals(modifiedCoordinates, other.modifiedCoordinates)
+ && Objects.equals(operation, other.operation);
} else {
- return deepEquals(getOperation(), ((PassThroughOperation) object).getOperation(), mode);
+ final var other = (PassThroughOperation) object;
+ return Arrays.equals(getModifiedCoordinates(), other.getModifiedCoordinates())
+ && deepEquals(getOperation(), other.getOperation(), mode);
}
}
return false;
@@ -251,7 +255,7 @@ public class DefaultPassThroughOperation extends AbstractCoordinateOperation imp
*/
@Override
protected long computeHashCode() {
- return super.computeHashCode() + 31 * operation.hashCode();
+ return super.computeHashCode() + 37 * operation.hashCode() + Arrays.hashCode(modifiedCoordinates);
}
/**
@@ -326,6 +330,8 @@ public class DefaultPassThroughOperation extends AbstractCoordinateOperation imp
* Invoked by JAXB at marshalling time for getting the modified coordinates.
* This method converts the zero-based indices to 1-based indices.
*
+ * @return one-based indices of the modified source coordinates.
+ *
* @see #getModifiedCoordinates()
*/
@XmlElement(name = "modifiedCoordinate", required = true)
@@ -339,21 +345,37 @@ public class DefaultPassThroughOperation extends AbstractCoordinateOperation imp
/**
* Invoked by JAXB at unmarshalling time for setting the modified coordinates.
- * This method needs to be invoked last, even if the {@code <gml:modifiedCoordinate>}
- * elements are not last in the GML document. It is the case when using JAXB because
- * multiple occurrences of {@code <gml:modifiedCoordinate>} are aggregated in an array.
+ *
+ * @param indices one-based indices of the modified source coordinates.
+ */
+ private void setIndices(int[] dimensions) {
+ if (modifiedCoordinates == null) {
+ if (dimensions.length != 0) {
+ modifiedCoordinates = dimensions = dimensions.clone();
+ for (int i=0; i<dimensions.length; i++) {
+ dimensions[i]--;
+ }
+ }
+ } else {
+ ImplementationHelper.propertyAlreadySet(DefaultPassThroughOperation.class, "setIndices", "modifiedCoordinates");
+ }
+ }
+
+ /**
+ * Invoked by JAXB after unmarshalling. If needed, this method tries to infer source/target CRS
+ * of the nested operation from the source/target CRS of the enclosing pass-through operation.
*/
- private void setIndices(final int[] dimensions) {
+ @Override
+ final void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
+ super.afterUnmarshal(unmarshaller, parent);
/*
- * Argument and state validation.
+ * State validation. The `missing` string will be used in exception message
+ * at the end of this method if a required component is reported missing.
*/
- String missing = "modifiedCoordinate";
+ final int[] modifiedCoordinates = this.modifiedCoordinates;
FactoryException cause = null;
- final int n = dimensions.length;
- if (n != 0) {
- if (!ArraysExt.isRange(dimensions[0], dimensions)) {
- throw new GeodeticException(Errors.format(Errors.Keys.CanNotAssign_2, missing, Arrays.toString(dimensions)));
- }
+ String missing = "modifiedCoordinate";
+ if (modifiedCoordinates.length != 0) {
missing = "sourceCRS";
final CoordinateReferenceSystem sourceCRS = super.getSourceCRS();
if (sourceCRS != null) {
@@ -364,17 +386,14 @@ public class DefaultPassThroughOperation extends AbstractCoordinateOperation imp
if (operation != null) {
/*
* If the operation is a defining operation, we need to replace it by a full operation.
- * After that, we can store the modified coordinate indices in the transform field.
*/
MathTransform subTransform = operation.getMathTransform();
if (operation instanceof Conversion) {
CoordinateReferenceSystem sourceSub = operation.getSourceCRS();
CoordinateReferenceSystem targetSub = operation.getTargetCRS();
if (subTransform == null || sourceSub == null || targetSub == null) try {
- final int[] zeroBased = dimensions.clone();
- for (int i=0; i<n; i++) zeroBased[i]--;
- if (sourceSub == null) sourceSub = CRS.selectDimensions(sourceCRS, zeroBased);
- if (targetSub == null) targetSub = CRS.selectDimensions(targetCRS, zeroBased);
+ if (sourceSub == null) sourceSub = CRS.selectDimensions(sourceCRS, modifiedCoordinates);
+ if (targetSub == null) targetSub = CRS.selectDimensions(targetCRS, modifiedCoordinates);
operation = DefaultConversion.castOrCopy((Conversion) operation)
.specialize(Conversion.class, sourceSub, targetSub, null);
subTransform = operation.getMathTransform();
@@ -383,8 +402,8 @@ public class DefaultPassThroughOperation extends AbstractCoordinateOperation imp
}
}
if (subTransform != null) {
- transform = MathTransforms.passThrough(dimensions[0] - 1, subTransform,
- ReferencingUtilities.getDimension(sourceCRS) - dimensions[n-1]);
+ transform = MathTransforms.passThrough(modifiedCoordinates, subTransform,
+ ReferencingUtilities.getDimension(sourceCRS));
return;
}
}
@@ -393,16 +412,4 @@ public class DefaultPassThroughOperation extends AbstractCoordinateOperation imp
}
throw new GeodeticException(Errors.format(Errors.Keys.MissingComponentInElement_2, "PassThroughOperation", missing), cause);
}
-
- /**
- * Invoked by JAXB after unmarshalling. If needed, this method tries to infer source/target CRS
- * of the nested operation from the source/target CRS if the enclosing pass-through operation.
- */
- @Override
- void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
- super.afterUnmarshal(unmarshaller, parent);
- if (transform == null) {
- setIndices(ArraysExt.EMPTY_INT); // Cause an exception to be thrown.
- }
- }
}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransforms.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransforms.java
index 27a1f360d5..74c4b9c99c 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransforms.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransforms.java
@@ -364,15 +364,16 @@ public final class MathTransforms extends Static {
/**
* Creates a transform which passes through a subset of coordinates to another transform.
- * The list of modified coordinates is specified by the {@code modifiedCoordinates} argument,
- * which must be in strictly increasing order.
+ * The list of modified coordinates is specified by the {@code modifiedCoordinates} argument.
+ * The array length must be equal to the number of source dimensions of {@code subTransform}
+ * and all array elements must be in strictly increasing order.
*
* @param modifiedCoordinates positions in a source coordinate tuple of the coordinates affected by the transform.
* @param subTransform the sub-transform to apply on modified coordinates.
* @param resultDim total number of source dimensions of the pass-through transform to return.
* @return a pass-through transform for the given set of modified coordinates.
- * @throws MismatchedDimensionException if the bit set cardinality is not equal
- * to the number of source dimensions in {@code subTransform}.
+ * @throws MismatchedDimensionException if the {@code modifiedCoordinates} array length
+ * is not equal to the number of source dimensions in {@code subTransform}.
* @throws IllegalArgumentException if the index of a modified coordinates is invalid.
*
* @see PassThroughTransform#create(BitSet, MathTransform, int, MathTransformFactory)
@@ -380,6 +381,7 @@ public final class MathTransforms extends Static {
* @since 1.4
*/
public static MathTransform passThrough(final int[] modifiedCoordinates, final MathTransform subTransform, final int resultDim) {
+ ArgumentChecks.ensureNonNull("modifiedCoordinates", modifiedCoordinates);
final BitSet bitset = new BitSet();
int previous = -1;
for (int i=0; i < modifiedCoordinates.length; i++) {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/PassThroughTransform.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/PassThroughTransform.java
index d6d8bc5d10..1ff924203f 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/PassThroughTransform.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/PassThroughTransform.java
@@ -222,6 +222,9 @@ public class PassThroughTransform extends AbstractMathTransform implements Seria
* int[] modifiedCoordinates = bitset.stream().toArray();
* }
*
+ * The bitset {@linkplain BitSet#cardinality() cardinality}, which is also the length of above array,
+ * must be equal to the number of source dimensions in the given {@code subTransform}.
+ *
* <h4>Limitation</h4>
* If the modified coordinates are not at consecutive positions in source coordinate tuples,
* then the current implementation of this method adds the following restrictions:
@@ -238,8 +241,8 @@ public class PassThroughTransform extends AbstractMathTransform implements Seria
* @param resultDim total number of source dimensions of the pass-through transform to return.
* @param factory the factory to use for creating transforms, or {@code null} for the default.
* @return a pass-through transform for the given set of modified coordinates.
- * @throws MismatchedDimensionException if the bit set cardinality is not equal
- * to the number of source dimensions in {@code subTransform}.
+ * @throws MismatchedDimensionException if the {@code modifiedCoordinates} bitset cardinality
+ * is not equal to the number of source dimensions in {@code subTransform}.
* @throws IllegalArgumentException if the index of a modified coordinates is out of bounds.
* @throws FactoryException if an error occurred while creating a transform step.
*
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/TransformSeparator.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/TransformSeparator.java
index 4ebfacd07d..67616b04fc 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/TransformSeparator.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/TransformSeparator.java
@@ -143,11 +143,11 @@ public class TransformSeparator {
* Inserts the specified {@code dimension} in the specified sequence at a position that preserve increasing order.
* This method does nothing if the given dimension already exists in the given array.
*
- * <div class="note"><b>Note:</b>
- * we do not provide public API for this method because we rather encourage bulk operations (adding many values
+ * <h4>API note</h4>
+ * We do not provide public API for this method because we rather encourage bulk operations (adding many values
* at once), and because the public API does not allow to specify values in random order (for avoiding risk of
* confusion as some users could expect the separated transform to use the dimensions in the order he specified
- * them).</div>
+ * them).
*
* @param sequence the {@link #sourceDimensions} or {@link #targetDimensions} sequence to update.
* @param dimension the value to add to the given sequence.
@@ -545,7 +545,7 @@ public class TransformSeparator {
final MathTransform step1 = filterSourceDimensions(ctr.transform1, dimensions);
final MathTransform step2 = filterSourceDimensions(ctr.transform2, targetDimensions);
return factory.concatenate(step1, step2);
- // Keep the 'targetDimensions' computed by the last step.
+ // Keep the `targetDimensions` computed by the last step.
}
/*
* Special case for the passthrough transform: if at least one input dimension belong to the pass-
@@ -597,9 +597,11 @@ public class TransformSeparator {
targetDimensions = target;
/*
* If all source dimensions not in the sub-transform are consecutive numbers, we can use our passthrough
- * transform implementation. The "consecutive numbers" requirement (expressed in the 'if' statement below)
+ * transform implementation. The "consecutive numbers" requirement (expressed in the `if` statement below)
* is a consequence of a limitation in our current implementation: our current passthrough transform does
- * not accept arbitrary indices for modified coordinates.
+ * not accept arbitrary indices for modified coordinates. We can not delegate to the static factory method
+ * `MathTransforms.passThrough(int[] modifiedCoordinates, ...)` because that method itself relies on this
+ * `TransformSeparator` for separating the transform components at non-consecutive indices.
*/
if (containsAll(dimensions, lower, subLower) && containsAll(dimensions, subUpper, upper)) {
final int offset = subDimensions[0];
@@ -615,8 +617,8 @@ public class TransformSeparator {
final Matrix matrix = MathTransforms.getMatrix(step);
if (matrix != null) {
targetDimensions = null;
- int startOfRow = 0; // Index of next row to be stored in the 'elements' array.
- boolean isLastRowAccepted = false; // To be set to 'true' if we complete successfully up to last row.
+ int startOfRow = 0; // Index of next row to be stored in the `elements` array.
+ boolean isLastRowAccepted = false; // To be set to `true` if we complete successfully up to last row.
final int numFilteredColumns = (dimensions.length + 1);
double[] elements = new double[(numTgt + 1) * numFilteredColumns];
reduce: for (int j=0; j <= numTgt; j++) {
@@ -632,7 +634,7 @@ reduce: for (int j=0; j <= numTgt; j++) {
elements[startOfRow + filteredColumn++] = element;
} else if (element != 0) {
/*
- * Output dimension 'j' depends on one of discarded input dimension 'i'.
+ * Output dimension `j` depends on one of discarded input dimension `i`.
* The whole row will be discarded.
*/
continue reduce;
@@ -641,10 +643,10 @@ reduce: for (int j=0; j <= numTgt; j++) {
/*
* We reach this point only if we determined that for current matrix row, all dependencies are listed
* in the array of source dimensions to keep. The matrix coefficients for that row are copied in the
- * 'elements' array.
+ * `elements` array.
*/
elements[startOfRow + filteredColumn++] = matrix.getElement(j, numSrc); // Copy the translation term.
- assert filteredColumn == numFilteredColumns : filteredColumn; // We should have used all values in the 'dimensions' array.
+ assert filteredColumn == numFilteredColumns : filteredColumn; // We should have used all values in the `dimensions` array.
startOfRow += numFilteredColumns;
/*
* In an affine transform, the last row is usually [0 0 0 … 1].
@@ -783,7 +785,7 @@ reduce: for (int j=0; j <= numTgt; j++) {
/*
* If we do not retain all dimensions, remove the matrix columns corresponding to the excluded
* source dimensions and create a new transform. We remove consecutive columns in single calls
- * to 'removeColumns', from 'lower' inclusive to 'upper' exclusive.
+ * to `removeColumns`, from `lower` inclusive to `upper` exclusive.
*/
int upper = dimension;
for (int i = retainedCount; --i >= -1;) {
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PassThroughTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PassThroughTransformTest.java
index f5d46d38cd..00185fec10 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PassThroughTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PassThroughTransformTest.java
@@ -18,6 +18,7 @@ package org.apache.sis.referencing.operation.transform;
import java.util.List;
import java.util.Arrays;
+import java.util.BitSet;
import java.util.Random;
import org.opengis.util.FactoryException;
import org.opengis.referencing.operation.Matrix;
@@ -43,7 +44,7 @@ import org.opengis.test.ToleranceModifier;
* Tests {@link PassThroughTransform}.
*
* @author Martin Desruisseaux (IRD, Geomatys)
- * @version 1.0
+ * @version 1.4
* @since 0.5
*/
@DependsOn({
@@ -83,7 +84,7 @@ public final class PassThroughTransformTest extends MathTransformTestCase {
* Tests the pass through transform using an identity transform.
* The "pass-through" of such transform shall be itself the identity transform.
*
- * @throws TransformException should never happen.
+ * @throws TransformException if a test coordinate tuple cannot be transformed.
*/
@Test
public void testIdentity() throws TransformException {
@@ -95,7 +96,7 @@ public final class PassThroughTransformTest extends MathTransformTestCase {
* Tests the pass-through transform using an affine transform.
* The "pass-through" of such transforms are optimized using matrix arithmetic.
*
- * @throws TransformException should never happen.
+ * @throws TransformException if a test coordinate tuple cannot be transformed.
*/
@Test
public void testLinear() throws TransformException {
@@ -110,7 +111,7 @@ public final class PassThroughTransformTest extends MathTransformTestCase {
* Tests the general pass-through transform.
* This test uses a non-linear sub-transform for preventing the factory method to optimize.
*
- * @throws TransformException should never happen.
+ * @throws TransformException if a test coordinate tuple cannot be transformed.
*/
@Test
public void testPassthrough() throws TransformException {
@@ -120,7 +121,7 @@ public final class PassThroughTransformTest extends MathTransformTestCase {
/**
* Tests the general pass-through transform with a sub-transform going from 3D to 2D points.
*
- * @throws TransformException should never happen.
+ * @throws TransformException if a test coordinate tuple cannot be transformed.
*/
@Test
public void testDimensionDecrease() throws TransformException {
@@ -131,7 +132,7 @@ public final class PassThroughTransformTest extends MathTransformTestCase {
/**
* Tests the general pass-through transform with a sub-transform going from 2D to 3D points.
*
- * @throws TransformException should never happen.
+ * @throws TransformException if a test coordinate tuple cannot be transformed.
*/
@Test
public void testDimensionIncrease() throws TransformException {
@@ -141,6 +142,9 @@ public final class PassThroughTransformTest extends MathTransformTestCase {
/**
* Tests a pass-through transform built using the given sub-transform.
+ * This method uses (indirectly) the {@link PassThroughTransform#create(int, MathTransform, int)}
+ * factory method with various {@code firstAffectedCoordinate} and {@code numTrailingCoordinates}
+ * argument values.
*
* @param subTransform the sub-transform to use for building pass-through transform.
* @param expectedClass the expected implementation class of pass-through transforms.
@@ -175,9 +179,10 @@ public final class PassThroughTransformTest extends MathTransformTestCase {
/**
* Tests the current {@linkplain #transform transform} using an array of random coordinate values,
- * and compares the result against the expected ones. This method computes itself the expected results.
+ * and compares the result against the expected ones. This method computes itself the expected results
+ * on the assumption that all modified coordinates are consecutive.
*
- * @param subTransform the sub transform used by the current pass-through transform.
+ * @param subTransform the sub transform used by the current pass-through transform.
* @param firstAffectedCoordinate first input/output dimension used by {@code subTransform}.
* @throws TransformException if a transform failed.
*/
@@ -300,4 +305,48 @@ public final class PassThroughTransformTest extends MathTransformTestCase {
0, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 1}), MathTransforms.getMatrix(steps.get(2)), null);
}
+
+ /**
+ * Tests the creation of a pass-through transform with modified coordinates that are not consecutive.
+ * This is a test of {@link PassThroughTransform#create(BitSet, MathTransform, int, MathTransformFactory)}
+ * factory method.
+ *
+ * @throws FactoryException if an error occurred while combining the transforms.
+ * @throws TransformException if a test coordinate tuple cannot be transformed.
+ */
+ @Test
+ public void testNonConsecutiveModifiedCoordinates() throws FactoryException, TransformException {
+ random = TestUtilities.createRandomNumberGenerator();
+ /*
+ * First, create a pass-through transform from an inseparable `PseudoTransform`.
+ * Because `PseudoTransform` is inseparable, the modified coordinates must be consecutive.
+ * However the `PassThroughTransform` result is partially separable and used in next step.
+ */
+ final var bitset = new BitSet();
+ bitset.set(1, 3, true); // Modified coordinates = {1, 2}.
+ MathTransform subTransform = new PseudoTransform(2, 2);
+ transform = PassThroughTransform.create(bitset, subTransform, 5, null);
+ assertEquals(5, transform.getSourceDimensions());
+ assertEquals(5, transform.getTargetDimensions());
+ assertEquals(1, ((PassThroughTransform) transform).firstAffectedCoordinate);
+ assertEquals(2, ((PassThroughTransform) transform).numTrailingCoordinates);
+ isInverseTransformSupported = false;
+ verifyTransform(subTransform, 1);
+ /*
+ * Now test with non-consecutive coordinates, except for the `PseudoTransform` part
+ * which must still be in consecutive coordinates. We add a linear transform before
+ * for making the work a little bit harder.
+ */
+ bitset.clear();
+ bitset.set(1, true);
+ bitset.set(3, 5, true); // The inseparable `PseudoTransform` part.
+ bitset.set(6, true);
+ bitset.set(9, true);
+ subTransform = MathTransforms.concatenate(MathTransforms.scale(4, 3, 7, 5, -6), transform);
+ transform = PassThroughTransform.create(bitset, subTransform, 10, null);
+ verifyTransform(
+ // _____________[0]_________[1]-____[2]____[3]_______[4] Dimension index in sub-transform.
+ new double[] {2, 1, -1, 0.2, 0.1, 9, 2, 8, 4, -1},
+ new double[] {2, 4, -1, 1600.6, 2700.7, 9, 10, 8, 4, 6});
+ }
}