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 <martin.desruisse...@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});
+    }
 }

Reply via email to