sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Move the special case handled by ConcatenatedTransform for PassThroughTransform as an implementation of PassThroughTransform.tryConcatenate(...) instead. The intent is to improve the handling of a chain of transforms in a way that allow GridExtent to reduce the "gridToCRS" transform to the dimensions of interest. Prior this work, we had an issue if the chain of operations included a PassThroughTransform.
Date Wed, 28 Nov 2018 19:29:51 GMT
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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new a9a26ab  Move the special case handled by ConcatenatedTransform for PassThroughTransform as an implementation of PassThroughTransform.tryConcatenate(...) instead. The intent is to improve the handling of a chain of transforms in a way that allow GridExtent to reduce the "gridToCRS" transform to the dimensions of interest. Prior this work, we had an issue if the chain of operations included a PassThroughTransform.
a9a26ab is described below

commit a9a26abdb25b0b446279b73eec92c72b0d85db88
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Wed Nov 28 16:30:43 2018 +0100

    Move the special case handled by ConcatenatedTransform for PassThroughTransform as an implementation of PassThroughTransform.tryConcatenate(...) instead.
    The intent is to improve the handling of a chain of transforms in a way that allow GridExtent to reduce the "gridToCRS" transform to the dimensions of interest.
    Prior this work, we had an issue if the chain of operations included a PassThroughTransform.
---
 .../org/apache/sis/coverage/grid/GridExtent.java   |  10 +-
 .../operation/transform/ConcatenatedTransform.java |  39 +-
 .../transform/MathTransformsOrFactory.java         | 129 +++++++
 .../operation/transform/PassThroughTransform.java  | 395 +++++++++++++++------
 .../operation/transform/TransformSeparator.java    |  18 +-
 .../transform/ConcatenatedTransformTest.java       |  10 +-
 .../operation/transform/MathTransformsTest.java    |   4 +-
 .../operation/transform/PseudoTransform.java       |  11 +-
 .../transform/TransformSeparatorTest.java          |  81 +++--
 9 files changed, 513 insertions(+), 184 deletions(-)

diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
index 61f28de..c133bf4 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
@@ -345,10 +345,18 @@ public class GridExtent implements Serializable {
                     coordinates[i]   = lower;
                     coordinates[i+m] = upper;
                 }
-            } else {
+            } else if (enclosing == null || !Double.isNaN(min) || !Double.isNaN(max)) {
                 throw new IllegalArgumentException(Resources.format(
                         Resources.Keys.IllegalGridEnvelope_3, i, min, max));
             }
+            /*
+             * We do not throw an exception if 'enclosing' is non-null and envelope bounds are NaN
+             * because this case occurs when the gridToCRS transform has a NaN scale factor.  Such
+             * scale factor may occur with ranges like [0 … 0]. With a non-null 'enclosing' extent,
+             * we can still have grid coordinates: they are inherited from 'enclosing'. We require
+             * the two bounds to be NaN, otherwise the reason for those NaN envelope bounds is not
+             * a NaN scale factor.
+             */
         }
         /*
          * At this point we finished to compute coordinate values.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java
index 1583751..5f4c5f1 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java
@@ -295,39 +295,10 @@ class ConcatenatedTransform extends AbstractMathTransform implements Serializabl
                     return MathTransforms.linear(matrix);
                 }
             }
-            /*
-             * If the second transform is a passthrough transform and all passthrough ordinates
-             * are unchanged by the matrix, we can move the matrix inside the passthrough transform.
-             */
-            if (tr2 instanceof PassThroughTransform) {
-                final PassThroughTransform candidate = (PassThroughTransform) tr2;
-                final Matrix sub = candidate.toSubMatrix(matrix1);
-                if (sub != null) {
-                    if (factory != null) {
-                        return factory.createPassThroughTransform(
-                                candidate.firstAffectedOrdinate,
-                                factory.createConcatenatedTransform(factory.createAffineTransform(sub), candidate.subTransform),
-                                candidate.numTrailingOrdinates);
-                    } else {
-                        return MathTransforms.passThrough(
-                                candidate.firstAffectedOrdinate,
-                                create(MathTransforms.linear(sub), candidate.subTransform, factory),
-                                candidate.numTrailingOrdinates);
-                    }
-                }
-            }
-        }
-        /*
-         * If one transform is the inverse of the other, return the identity transform.
-         */
-        if (areInverse(tr1, tr2) || areInverse(tr2, tr1)) {
-            assert tr1.getSourceDimensions() == tr2.getTargetDimensions();
-            assert tr1.getTargetDimensions() == tr2.getSourceDimensions();
-            return MathTransforms.identity(tr1.getSourceDimensions());          // Returns a cached instance.
         }
         /*
          * Give a chance to AbstractMathTransform to returns an optimized object.
-         * The main use case is Logarithmic vs Exponential transforms.
+         * Examples: Logarithmic versus Exponential transforms, PassThrouthTransform.
          */
         if (tr1 instanceof AbstractMathTransform) {
             final MathTransform optimized = ((AbstractMathTransform) tr1).tryConcatenate(false, tr2, factory);
@@ -341,6 +312,14 @@ class ConcatenatedTransform extends AbstractMathTransform implements Serializabl
                 return optimized;
             }
         }
+        /*
+         * If one transform is the inverse of the other, return the identity transform.
+         */
+        if (areInverse(tr1, tr2) || areInverse(tr2, tr1)) {
+            assert tr1.getSourceDimensions() == tr2.getTargetDimensions();
+            assert tr1.getTargetDimensions() == tr2.getSourceDimensions();
+            return MathTransforms.identity(tr1.getSourceDimensions());          // Returns a cached instance.
+        }
         // No optimized case found.
         return null;
     }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransformsOrFactory.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransformsOrFactory.java
new file mode 100644
index 0000000..2bfc5f6
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransformsOrFactory.java
@@ -0,0 +1,129 @@
+/*
+ * 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 org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.MathTransformFactory;
+import org.opengis.referencing.operation.Matrix;
+import org.opengis.util.FactoryException;
+
+
+/**
+ * Proxy to {@link MathTransforms} method which can be redirected to a {@link MathTransformFactory}.
+ * The method signature in this class mirrors the one in {@link MathTransforms}. We do not provide
+ * this functionality as a {@link MathTransformFactory} implementation because we do not override
+ * all methods.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+class MathTransformsOrFactory {
+    /**
+     * The unique instance to use when no {@link MathTransformFactory} is specified.
+     */
+    private static final MathTransformsOrFactory INSTANCE = new MathTransformsOrFactory();
+
+    /**
+     * Do not allow instantiation of this class, except the inner sub-class.
+     */
+    private MathTransformsOrFactory() {
+    }
+
+    /**
+     * Returns the instance to use for the given factory.
+     *
+     * @param  factory  the factory, which may be {@code null}.
+     */
+    static MathTransformsOrFactory wrap(final MathTransformFactory factory) {
+        return (factory != null) ? new Specified(factory) : INSTANCE;
+    }
+
+    /**
+     * Creates an arbitrary linear transform from the specified matrix.
+     *
+     * @param  matrix  the matrix used to define the linear transform.
+     * @return the linear (usually affine) transform.
+     */
+    MathTransform linear(Matrix matrix) throws FactoryException {
+        return MathTransforms.linear(matrix);
+    }
+
+    /**
+     * Creates a transform which passes through a subset of coordinates to another transform.
+     *
+     * @param  firstAffectedOrdinate  index of the first affected ordinate.
+     * @param  subTransform           the sub-transform to apply on modified coordinates.
+     * @param  numTrailingOrdinates   number of trailing ordinates to pass through.
+     * @return a pass-through transform, potentially as a {@link PassThroughTransform} instance but not necessarily.
+     */
+    MathTransform passThrough(int firstAffectedOrdinate, MathTransform subTransform, int numTrailingOrdinates) throws FactoryException {
+        return MathTransforms.passThrough(firstAffectedOrdinate, subTransform, numTrailingOrdinates);
+    }
+
+    /**
+     * Concatenates the two given transforms.
+     *
+     * @param  tr1  the first math transform.
+     * @param  tr2  the second math transform.
+     * @return the concatenated transform.
+     */
+    MathTransform concatenate(MathTransform tr1, MathTransform tr2) throws FactoryException {
+        return MathTransforms.concatenate(tr1, tr2);
+    }
+
+    /**
+     * Concatenates the two given transforms, switching their order if {@code applyOtherFirst} is {@code true}.
+     */
+    final MathTransform concatenate(boolean applyOtherFirst, MathTransform tr, MathTransform other) throws FactoryException {
+        if (applyOtherFirst) {
+            return concatenate(other, tr);
+        } else {
+            return concatenate(tr, other);
+        }
+    }
+
+    /**
+     * A {@link MathTransformsOrFactory} implementation which delegate method calls to a {@link MathTransformFactory}
+     * specified by the user.
+     */
+    private static final class Specified extends MathTransformsOrFactory {
+        /** The factory where to delegate method calls. */
+        private final MathTransformFactory factory;
+
+        /** Creates a new instance delegating transform creations to the given factory. */
+        Specified(final MathTransformFactory factory) {
+            this.factory = factory;
+        }
+
+        /** Delegate to {@link MathTransformFactory#createAffineTransform(Matrix)}. */
+        @Override MathTransform linear(Matrix matrix) throws FactoryException {
+            return factory.createAffineTransform(matrix);
+        }
+
+        /** Delegate to {@link MathTransformFactory#createPassThroughTransform(int, MathTransform, int)}. */
+        @Override MathTransform passThrough(int firstAffectedOrdinate, MathTransform subTransform, int numTrailingOrdinates) throws FactoryException {
+            return factory.createPassThroughTransform(firstAffectedOrdinate, subTransform, numTrailingOrdinates);
+        }
+
+        /** Delegate to {@link MathTransformFactory#createConcatenatedTransform(MathTransform, MathTransform)}. */
+        @Override MathTransform concatenate(MathTransform tr, MathTransform other) throws FactoryException {
+            return factory.createConcatenatedTransform(tr, other);
+        }
+    }
+}
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 4ada705..5fe2ec1 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
@@ -18,9 +18,12 @@ package org.apache.sis.referencing.operation.transform;
 
 import java.util.Arrays;
 import java.io.Serializable;
+import java.lang.reflect.Array;
+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.opengis.referencing.operation.NoninvertibleTransformException;
 import org.apache.sis.referencing.operation.matrix.Matrices;
@@ -184,55 +187,6 @@ public class PassThroughTransform extends AbstractMathTransform implements Seria
     }
 
     /**
-     * If the given matrix to be concatenated to this transform, can be concatenated to the
-     * sub-transform instead, returns the matrix to be concatenated to the sub-transform.
-     * Otherwise returns {@code null}.
-     *
-     * <p>This method assumes that the matrix size is compatible with this transform source dimension.
-     * It is caller responsibility to verify this condition.</p>
-     */
-    final Matrix toSubMatrix(final Matrix matrix) {
-        final int numRow = matrix.getNumRow();
-        final int numCol = matrix.getNumCol();
-        if (numRow != numCol) {
-            // Current implementation requires a square matrix.
-            return null;
-        }
-        final int subDim = subTransform.getSourceDimensions();
-        final MatrixSIS sub = Matrices.createIdentity(subDim + 1);
-        /*
-         * Ensure that every dimensions which are scaled by the affine transform are one
-         * of the dimensions modified by the sub-transform, and not any other dimension.
-         */
-        for (int j=numRow; --j>=0;) {
-            final int sj = j - firstAffectedOrdinate;
-            for (int i=numCol; --i>=0;) {
-                final double element = matrix.getElement(j, i);
-                if (sj >= 0 && sj < subDim) {
-                    final int si;
-                    final boolean pass;
-                    if (i == numCol-1) {                    // Translation term (last column)
-                        si = subDim;
-                        pass = true;
-                    } else {                                // Any term other than translation.
-                        si = i - firstAffectedOrdinate;
-                        pass = (si >= 0 && si < subDim);
-                    }
-                    if (pass) {
-                        sub.setElement(sj, si, element);
-                        continue;
-                    }
-                }
-                if (element != (i == j ? 1 : 0)) {
-                    // Found a dimension which perform some scaling or translation.
-                    return null;
-                }
-            }
-        }
-        return sub;
-    }
-
-    /**
      * Gets the dimension of input points. This the source dimension of the
      * {@linkplain #subTransform sub-transform} plus the number of pass-through dimensions.
      *
@@ -323,39 +277,81 @@ public class PassThroughTransform extends AbstractMathTransform implements Seria
     }
 
     /**
-     * Transforms many coordinates in a list of ordinal values.
+     * Transforms an array of points with overlapping source and target.
      *
-     * @throws TransformException if the {@linkplain #subTransform sub-transform} failed.
+     * @param  points  the point to transform, as a {@code float[]} or {@code double[]} array.
+     * @param  srcOff  the offset to the point to be transformed in the array.
+     * @param  dstOff  the offset to the location of the transformed point that is stored in the destination array.
+     * @param  numPts  number of points to transform.
+     * @return {@code null} on success, or a copy of a range from {@code points} array otherwise. In the later case, callers
+     *          have to perform the transformation itself but can rely on the returned copy to not overlap the original array.
      */
-    @Override
-    public void transform(double[] srcPts, int srcOff, final double[] dstPts, int dstOff, int numPts)
-            throws TransformException
-    {
+    @SuppressWarnings("SuspiciousSystemArraycopy")
+    private Object transformOverlapping(final Object points, int srcOff, int dstOff, int numPts) throws TransformException {
         final int subDimSource = subTransform.getSourceDimensions();
         final int subDimTarget = subTransform.getTargetDimensions();
         int srcStep = numTrailingOrdinates;
         int dstStep = numTrailingOrdinates;
-        if (srcPts == dstPts) {
-            final int add = firstAffectedOrdinate + numTrailingOrdinates;
-            final int dimSource = subDimSource + add;
-            final int dimTarget = subDimTarget + add;
-            switch (IterationStrategy.suggest(srcOff, dimSource, dstOff, dimTarget, numPts)) {
-                case ASCENDING: {
-                    break;
-                }
-                case DESCENDING: {
-                    srcOff += (numPts - 1) * dimSource;
-                    dstOff += (numPts - 1) * dimTarget;
-                    srcStep -= 2*dimSource;
-                    dstStep -= 2*dimTarget;
-                    break;
-                }
-                default: {
-                    srcPts = Arrays.copyOfRange(srcPts, srcOff, srcOff + numPts*dimSource);
-                    srcOff = 0;
-                }
+        final int add = firstAffectedOrdinate + numTrailingOrdinates;
+        final int dimSource = subDimSource + add;
+        final int dimTarget = subDimTarget + add;
+        switch (IterationStrategy.suggest(srcOff, dimSource, dstOff, dimTarget, numPts)) {
+            case ASCENDING: {
+                break;
+            }
+            case DESCENDING: {
+                srcOff += (numPts - 1) * dimSource;
+                dstOff += (numPts - 1) * dimTarget;
+                srcStep -= 2*dimSource;
+                dstStep -= 2*dimTarget;
+                break;
+            }
+            default: {
+                final int length = numPts * dimSource;
+                final Object buffer = Array.newInstance(points.getClass().getComponentType(), length);
+                System.arraycopy(points, srcOff, buffer, 0, length);
+                return buffer;
+            }
+        }
+        final boolean copyTrailingFirst = subDimSource < subDimTarget;
+        while (--numPts >= 0) {
+            System.arraycopy(points, srcOff, points, dstOff, firstAffectedOrdinate);
+            srcOff += firstAffectedOrdinate;
+            dstOff += firstAffectedOrdinate;
+            if (copyTrailingFirst) {
+                System.arraycopy(points, srcOff + subDimSource,
+                                 points, dstOff + subDimTarget, numTrailingOrdinates);
+            }
+            if (points instanceof double[]) {
+                subTransform.transform((double[]) points, srcOff, (double[]) points, dstOff, 1);
+            } else {
+                subTransform.transform((float[]) points, srcOff, (float[]) points, dstOff, 1);
+            }
+            srcOff += subDimSource;
+            dstOff += subDimTarget;
+            if (!copyTrailingFirst) {
+                System.arraycopy(points, srcOff, points, dstOff, numTrailingOrdinates);
             }
+            srcOff += srcStep;
+            dstOff += dstStep;
         }
+        return null;
+    }
+
+    /**
+     * Transforms many coordinates in a list of ordinal values.
+     *
+     * @throws TransformException if the {@linkplain #subTransform sub-transform} failed.
+     */
+    @Override
+    public void transform(double[] srcPts, int srcOff, final double[] dstPts, int dstOff, int numPts) throws TransformException {
+        if (srcPts == dstPts) {
+            srcPts = (double[]) transformOverlapping(srcPts, srcOff, dstOff, numPts);
+            if (srcPts == null) return;
+            srcOff = 0;
+        }
+        final int subDimSource = subTransform.getSourceDimensions();
+        final int subDimTarget = subTransform.getTargetDimensions();
         while (--numPts >= 0) {
             System.arraycopy(      srcPts, srcOff,
                                    dstPts, dstOff,   firstAffectedOrdinate);
@@ -363,8 +359,8 @@ public class PassThroughTransform extends AbstractMathTransform implements Seria
                                    dstPts, dstOff += firstAffectedOrdinate, 1);
             System.arraycopy(      srcPts, srcOff += subDimSource,
                                    dstPts, dstOff += subDimTarget, numTrailingOrdinates);
-            srcOff += srcStep;
-            dstOff += dstStep;
+            srcOff += numTrailingOrdinates;
+            dstOff += numTrailingOrdinates;
         }
     }
 
@@ -374,34 +370,14 @@ public class PassThroughTransform extends AbstractMathTransform implements Seria
      * @throws TransformException if the {@linkplain #subTransform sub-transform} failed.
      */
     @Override
-    public void transform(float[] srcPts, int srcOff, final float[] dstPts, int dstOff, int numPts)
-            throws TransformException
-    {
-        final int subDimSource = subTransform.getSourceDimensions();
-        final int subDimTarget = subTransform.getTargetDimensions();
-        int srcStep = numTrailingOrdinates;
-        int dstStep = numTrailingOrdinates;
+    public void transform(float[] srcPts, int srcOff, final float[] dstPts, int dstOff, int numPts) throws TransformException {
         if (srcPts == dstPts) {
-            final int add = firstAffectedOrdinate + numTrailingOrdinates;
-            final int dimSource = subDimSource + add;
-            final int dimTarget = subDimTarget + add;
-            switch (IterationStrategy.suggest(srcOff, dimSource, dstOff, dimTarget, numPts)) {
-                case ASCENDING: {
-                    break;
-                }
-                case DESCENDING: {
-                    srcOff += (numPts - 1) * dimSource;
-                    dstOff += (numPts - 1) * dimTarget;
-                    srcStep -= 2*dimSource;
-                    dstStep -= 2*dimTarget;
-                    break;
-                }
-                default: {
-                    srcPts = Arrays.copyOfRange(srcPts, srcOff, srcOff + numPts*dimSource);
-                    srcOff = 0;
-                }
-            }
+            srcPts = (float[]) transformOverlapping(srcPts, srcOff, dstOff, numPts);
+            if (srcPts == null) return;
+            srcOff = 0;
         }
+        final int subDimSource = subTransform.getSourceDimensions();
+        final int subDimTarget = subTransform.getTargetDimensions();
         while (--numPts >= 0) {
             System.arraycopy(      srcPts, srcOff,
                                    dstPts, dstOff,   firstAffectedOrdinate);
@@ -409,8 +385,8 @@ public class PassThroughTransform extends AbstractMathTransform implements Seria
                                    dstPts, dstOff += firstAffectedOrdinate, 1);
             System.arraycopy(      srcPts, srcOff += subDimSource,
                                    dstPts, dstOff += subDimTarget, numTrailingOrdinates);
-            srcOff += srcStep;
-            dstOff += dstStep;
+            srcOff += numTrailingOrdinates;
+            dstOff += numTrailingOrdinates;
         }
     }
 
@@ -573,14 +549,221 @@ public class PassThroughTransform extends AbstractMathTransform implements Seria
     @Override
     public synchronized MathTransform inverse() throws NoninvertibleTransformException {
         if (inverse == null) {
-            inverse = new PassThroughTransform(
-                    firstAffectedOrdinate, subTransform.inverse(), numTrailingOrdinates);
+            inverse = new PassThroughTransform(firstAffectedOrdinate, subTransform.inverse(), numTrailingOrdinates);
             inverse.inverse = this;
         }
         return inverse;
     }
 
     /**
+     * If the given matrix to be concatenated to this transform, can be concatenated to the
+     * sub-transform instead, returns the matrix to be concatenated to the sub-transform.
+     * Otherwise returns {@code null}.
+     *
+     * <p>This method does not verify if the matrix size is compatible with this transform dimension.</p>
+     *
+     * @param  applyOtherFirst  {@code true} if the transformation order is {@code matrix} followed by {@code this}, or
+     *                          {@code false} if the transformation order is {@code this} followed by {@code matrix}.
+     */
+    private Matrix toSubMatrix(final boolean applyOtherFirst, final Matrix matrix) {
+        final int numRow = matrix.getNumRow();
+        final int numCol = matrix.getNumCol();
+        if (numRow != numCol) {
+            // Current implementation requires a square matrix.
+            return null;
+        }
+        final int subDim = applyOtherFirst ? subTransform.getSourceDimensions()
+                                           : subTransform.getTargetDimensions();
+        final MatrixSIS sub = Matrices.createIdentity(subDim + 1);
+        /*
+         * Ensure that every dimensions which are scaled by the affine transform are one
+         * of the dimensions modified by the sub-transform, and not any other dimension.
+         */
+        for (int j=numRow; --j>=0;) {
+            final int sj = j - firstAffectedOrdinate;
+            for (int i=numCol; --i>=0;) {
+                final double element = matrix.getElement(j, i);
+                if (sj >= 0 && sj < subDim) {
+                    final int si;
+                    final boolean copy;
+                    if (i == numCol-1) {                    // Translation term (last column)
+                        si = subDim;
+                        copy = true;
+                    } else {                                // Any term other than translation.
+                        si = i - firstAffectedOrdinate;
+                        copy = (si >= 0 && si < subDim);
+                    }
+                    if (copy) {
+                        sub.setElement(sj, si, element);
+                        continue;
+                    }
+                }
+                if (element != (i == j ? 1 : 0)) {
+                    // Found a dimension which perform some scaling or translation.
+                    return null;
+                }
+            }
+        }
+        return sub;
+    }
+
+    /**
+     * Concatenates or pre-concatenates in an optimized way this transform with the given transform, if possible.
+     * This method applies the following special cases:
+     *
+     * <ul>
+     *  <li>If the other transform is also a {@code PassThroughTransform}, then the two transforms may be merged
+     *      in a single {@code PassThroughTransform} instance.</li>
+     *  <li>If the other transform discards some dimensions, verify if we still need a {@code PassThroughTransform}.</li>
+     * </ul>
+     *
+     * @return the simplified transform, or {@code null} if no such optimization is available.
+     * @throws FactoryException if an error occurred while combining the transforms.
+     *
+     * @since 1.0
+     */
+    @Override
+    protected MathTransform tryConcatenate(final boolean applyOtherFirst, final MathTransform other, final MathTransformFactory factory)
+            throws FactoryException
+    {
+        final MathTransformsOrFactory proxy = MathTransformsOrFactory.wrap(factory);
+        if (other instanceof PassThroughTransform) {
+            final PassThroughTransform opt = (PassThroughTransform) other;
+            if (opt.firstAffectedOrdinate == firstAffectedOrdinate && opt.numTrailingOrdinates == numTrailingOrdinates) {
+                final MathTransform sub = proxy.concatenate(applyOtherFirst, subTransform, opt.subTransform);
+                return proxy.passThrough(firstAffectedOrdinate, sub, numTrailingOrdinates);
+            }
+        }
+        final Matrix m = MathTransforms.getMatrix(other);
+        if (m != null) {
+            /*
+             * If the other transform is a linear transform and all passthrough coordinates are unchanged by the matrix,
+             * we can move the matrix inside the passthrough transform. It reduces the number of dimension on which the
+             * linear transform operate, and gives a chance for another optimization in the concatenation between that
+             * linear transform and the sub-transform.
+             */
+            final Matrix sub = toSubMatrix(applyOtherFirst, m);
+            if (sub != null) {
+                MathTransform tr = proxy.linear(sub);
+                tr = proxy.concatenate(applyOtherFirst, subTransform, tr);
+                return proxy.passThrough(firstAffectedOrdinate, tr, numTrailingOrdinates);
+            }
+            /*
+             * If this PassThroughTransform is followed by a matrix discarding some dimensions, identify which dimensions
+             * are discarded. If all dimensions computed by the sub-transform are discarded, then we no longer need it.
+             * If some pass-through dimensions are discarded, then we can reduce the number of pass-through dimensions.
+             */
+            if (!applyOtherFirst) {
+                final int dimension = m.getNumCol() - 1;            // Number of source dimensions (ignore translations column).
+                if (dimension <= Long.SIZE) {                       // Because retained dimensions stored as a mask on 64 bits.
+                    long retainedDimensions = 0;
+                    final int numRows = m.getNumRow();              // Number of target dimensions + 1.
+                    for (int i=0; i<dimension; i++) {
+                        for (int j=0; j<numRows; j++) {
+                            if (m.getElement(j,i) != 0) {
+                                retainedDimensions |= (1L << i);    // Found a source dimension which is required by target dimension.
+                                break;
+                            }
+                        }
+                    }
+                    /*
+                     * Verify if matrix discards the sub-transform. If it does not, then we need to keep all the sub-transform
+                     * dimensions (discarding them is a "all or nothing" operation). Other dimensions (leading and trailing)
+                     * can be keep or discarded on a case-by-case basis.
+                     */
+                    final long    fullTransformMask = maskLowBits(dimension);
+                    final long    subTransformMask  = maskLowBits(subTransform.getTargetDimensions()) << firstAffectedOrdinate;
+                    final boolean keepSubTransform  = (retainedDimensions & subTransformMask) != 0;
+                    if (keepSubTransform) {
+                        retainedDimensions |= subTransformMask;           // Ensure that we keep all sub-transform dimensions.
+                    }
+                    if (retainedDimensions != fullTransformMask) {
+                        final int change = subTransform.getSourceDimensions() - subTransform.getTargetDimensions();
+                        if (change == 0 && !keepSubTransform) {
+                            return other;                                 // Shortcut avoiding creation of new MathTransforms.
+                        }
+                        /*
+                         * We enter in this block if some dimensions can be discarded. We want to discard them before the
+                         * PassThroughTransform instead than after. The matrix for that purpose will be computed later.
+                         * Before that, the loop below modifies a copy of the 'other' matrix as if those dimensions were
+                         * already removed.
+                         */
+                        MatrixSIS reduced = MatrixSIS.castOrCopy(m);
+                        long columnsToRemove = ~retainedDimensions & fullTransformMask;       // Can not be 0 at this point.
+                        do {
+                            final int lower = Long.numberOfTrailingZeros(columnsToRemove);
+                            final int upper = Long.numberOfTrailingZeros(~(columnsToRemove | maskLowBits(lower)));
+                            reduced = reduced.removeColumns(lower, upper);
+                            columnsToRemove &= ~maskLowBits(upper);
+                            columnsToRemove >>>= (upper - lower);
+                        } while (columnsToRemove != 0);
+                        /*
+                         * Expands the 'retainedDimensions' bitmask into a list of indices of dimensions to keep.   However
+                         * those indices are for dimensions to keep after the PassThroughTransform.  Because we rather want
+                         * indices for dimensions to keep before the PassThroughTransform, we need to adjust for difference
+                         * in number of dimensions. This change is represented by the 'change' integer computed above.
+                         * We apply two strategies:
+                         *
+                         *    1) If we keep the sub-transform, then the loop while surely sees the 'firstAffectedOrdinate'
+                         *       dimension since we ensured that we keep all sub-transform dimensions. When it happens, we
+                         *       add or remove bits at that point for the dimensionality changes.
+                         *
+                         *    2) If we do not keep the sub-transform, then code inside 'if (dim == firstAffectedOrdinate)'
+                         *       should not have been executed. Instead we will adjust the indices after the loop.
+                         */
+                        final long leadPassThroughMask = maskLowBits(firstAffectedOrdinate);
+                        final int numKeepAfter  = Long.bitCount(retainedDimensions & ~(leadPassThroughMask | subTransformMask));
+                        final int numKeepBefore = Long.bitCount(retainedDimensions & leadPassThroughMask);
+                        final int[] indices = new int[Long.bitCount(retainedDimensions) + change];
+                        for (int i=0; i<indices.length; i++) {
+                            int dim = Long.numberOfTrailingZeros(retainedDimensions);
+                            if (dim == firstAffectedOrdinate) {
+                                if (change < 0) {
+                                    retainedDimensions >>>= -change;                        // Discard dimensions to skip.
+                                    retainedDimensions &= ~leadPassThroughMask;             // Clear previous dimension flags.
+                                } else {
+                                    retainedDimensions <<= change;                          // Add dimensions.
+                                    retainedDimensions |= maskLowBits(change) << dim;       // Set flags for new dimensions.
+                                }
+                            }
+                            retainedDimensions &= ~(1L << dim);
+                            indices[i] = dim;
+                        }
+                        if (!keepSubTransform) {
+                            for (int i=indices.length; --i >= 0;) {
+                                final int dim = indices[i];
+                                if (dim <= firstAffectedOrdinate) break;
+                                indices[i] = dim - change;
+                            }
+                        }
+                        /*
+                         * Concatenate:
+                         *   1) An affine transform discarding some dimensions (no other operation).
+                         *   2) The passthrough transform with less input and output dimensions.
+                         *   3) The 'other' transform with less input dimensions.
+                         */
+                        MathTransform tr = proxy.linear(Matrices.createDimensionSelect(dimension + change, indices));
+                        if (keepSubTransform) {
+                            tr = proxy.concatenate(tr, proxy.passThrough(numKeepBefore, subTransform, numKeepAfter));
+                        }
+                        tr = proxy.concatenate(tr, proxy.linear(reduced));
+                        return tr;
+                    }
+                }
+            }
+        }
+        return super.tryConcatenate(applyOtherFirst, other, factory);
+    }
+
+    /**
+     * Returns a mask for the {@code n} lowest bits. This is a convenience method for a frequently
+     * used operation in {@link #tryConcatenate(boolean, MathTransform, MathTransformFactory)}.
+     */
+    private static long maskLowBits(final int n) {
+        return (1L << n) - 1;
+    }
+
+    /**
      * {@inheritDoc}
      *
      * @return {@inheritDoc}
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 cf3f613..0222884 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
@@ -23,7 +23,6 @@ import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransformFactory;
 import org.apache.sis.referencing.operation.matrix.MatrixSIS;
 import org.apache.sis.referencing.operation.matrix.Matrices;
-import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.referencing.Resources;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
@@ -94,7 +93,7 @@ public class TransformSeparator {
     /**
      * The factory to use for creating new math transforms.
      */
-    protected final MathTransformFactory factory;
+    private final MathTransformsOrFactory factory;
 
     /**
      * Whether to remove unused source dimensions. If {@code true}, then {@link #separate()} will try to
@@ -110,20 +109,19 @@ public class TransformSeparator {
      * @param transform  the transform to separate.
      */
     public TransformSeparator(final MathTransform transform) {
-        this(transform, DefaultFactories.forBuildin(MathTransformFactory.class));
+        this(transform, null);
     }
 
     /**
      * Constructs a separator for the given transform and using the given factory.
      *
      * @param transform  the transform to separate.
-     * @param factory    the factory to use for creating new math transforms.
+     * @param factory    the factory to use for creating new math transforms, or {@code null} if none.
      */
     public TransformSeparator(final MathTransform transform, final MathTransformFactory factory) {
         ArgumentChecks.ensureNonNull("transform", transform);
-        ArgumentChecks.ensureNonNull("factory", factory);
         this.transform = transform;
-        this.factory   = factory;
+        this.factory   = MathTransformsOrFactory.wrap(factory);
     }
 
     /**
@@ -526,7 +524,7 @@ public class TransformSeparator {
             final ConcatenatedTransform ctr = (ConcatenatedTransform) step;
             final MathTransform step1 = filterSourceDimensions(ctr.transform1, dimensions);
             final MathTransform step2 = filterSourceDimensions(ctr.transform2, targetDimensions);
-            return factory.createConcatenatedTransform(step1, step2);
+            return factory.concatenate(step1, step2);
             // Keep the 'targetDimensions' computed by the last step.
         }
         /*
@@ -584,7 +582,7 @@ public class TransformSeparator {
              * not accept arbitrary index for modified ordinates.
              */
             if (containsAll(dimensions, lower, subLower) && containsAll(dimensions, subUpper, upper)) {
-                return factory.createPassThroughTransform(subLower - lower, subTransform, Math.max(0, upper - subUpper));
+                return factory.passThrough(subLower - lower, subTransform, Math.max(0, upper - subUpper));
             }
         }
         /*
@@ -642,7 +640,7 @@ reduce:     for (int j=0; j <= numTgt; j++) {
                     return MathTransforms.identity(0);
                 }
                 elements = ArraysExt.resize(elements, startOfRow);
-                return factory.createAffineTransform(Matrices.create(startOfRow / numFilteredColumns, numFilteredColumns, elements));
+                return factory.linear(Matrices.create(startOfRow / numFilteredColumns, numFilteredColumns, elements));
             }
             /*
              * In an affine transform, the last row is not supposed to have dependency to any source dimension.
@@ -718,7 +716,7 @@ reduce:     for (int j=0; j <= numTgt; j++) {
             matrix.setElement(j, i, 1);
         }
         matrix.setElement(dimensions.length, numTgt, 1);
-        return factory.createConcatenatedTransform(step, factory.createAffineTransform(matrix));
+        return factory.concatenate(step, factory.linear(matrix));
     }
 
     /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransformTest.java
index fefe495..bf29c78 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransformTest.java
@@ -20,6 +20,7 @@ import org.opengis.util.FactoryException;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
+import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.operation.matrix.Matrix4;
 import org.apache.sis.test.DependsOn;
 import org.junit.Test;
@@ -31,7 +32,7 @@ import static org.opengis.test.Assert.*;
  * Tests the {@link ConcatenatedTransform} class.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
+ * @version 1.0
  * @since   0.5
  * @module
  */
@@ -86,12 +87,9 @@ public final strictfp class ConcatenatedTransformTest extends MathTransformTestC
      * @throws TransformException if an error occurred while transforming the test coordinate.
      */
     @Test
-    @org.junit.Ignore("Missing implementation of DimensionFilter.")
     public void testGeneric() throws FactoryException, TransformException {
-        final MathTransform first = null; //MathTransforms.dimensionFilter(4, new int[] {1,3});
-
-        final AffineTransform2D second = new AffineTransform2D(0.5, 0, 0, 0.25, 0, 0);  // scale(0.5, 0.25);
-
+        final MathTransform first = MathTransforms.linear(Matrices.createDimensionSelect(4, new int[] {1,3}));
+        final AffineTransform2D second = new AffineTransform2D(0.5, 0, 0, 0.25, 0, 0);     // scale(0.5, 0.25);
         transform = new ConcatenatedTransform(first, second);
         isInverseTransformSupported = false;
         validate();
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformsTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformsTest.java
index 1dcaf1c..b500d91 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformsTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformsTest.java
@@ -44,7 +44,7 @@ import static org.opengis.test.Assert.*;
 public final strictfp class MathTransformsTest extends TestCase {
     /**
      * Creates a dummy transform for testing purpose.
-     * The transform has the folowing properties:
+     * The transform has the following properties:
      *
      * <ul>
      *   <li>The source and target dimensions are 3.</li>
@@ -129,7 +129,7 @@ public final strictfp class MathTransformsTest extends TestCase {
     /**
      * Returns a three-dimensional transform which is non-linear in the second dimension.
      * A sample source point is (x, 1.5, y), which interpolates to (x, 8, y) where 8 is
-     * the mid-point between 6 and 14.
+     * the mid-point between 6 and 14. The transform can compute derivatives.
      */
     private static MathTransform nonLinear3D() {
         MathTransform tr = MathTransforms.interpolate(null, new double[] {2, 6, 14, 15});
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PseudoTransform.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PseudoTransform.java
index 1189886..f0743d3 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PseudoTransform.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PseudoTransform.java
@@ -18,7 +18,6 @@ package org.apache.sis.referencing.operation.transform;
 
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.TransformException;
-import org.apache.sis.internal.referencing.DirectPositionView;
 
 import static java.lang.StrictMath.*;
 
@@ -28,7 +27,7 @@ import static java.lang.StrictMath.*;
  * The transformed points are build as below (when formatted in base 10):
  *
  * {@preformat text
- *     [1 digit for dimension] [3 first fraction digits] . [random digits from source]
+ *     [1 digit for dimension] [3 first fraction digits] . [original digits from source]
  * }
  *
  * For example if the first input coordinate is (0.2, 0.5, 0.3), then the transformed coordinate will be:
@@ -39,8 +38,10 @@ import static java.lang.StrictMath.*;
  *     3003.3
  * }
  *
+ * This transform can not compute {@linkplain #derivative derivative}.
+ *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.5
+ * @version 1.0
  * @since   0.5
  * @module
  */
@@ -95,14 +96,12 @@ strictfp class PseudoTransform extends AbstractMathTransform {
                             final double[] dstPts, final int dstOff,
                             final boolean derivate) throws TransformException
     {
-        final Matrix derivative = derivate ? derivative(
-                new DirectPositionView.Double(srcPts, srcOff, getSourceDimensions())) : null;
         System.arraycopy(srcPts, srcOff, buffer, 0, sourceDimension);
         for (int i=0; i<targetDimension; i++) {
             double v = buffer[i % sourceDimension];
             v += (i+1)*1000 + round(v * 1000);
             dstPts[dstOff + i] = v;
         }
-        return derivative;
+        return null;
     }
 }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformSeparatorTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformSeparatorTest.java
index 3c88372..743cfbe 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformSeparatorTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformSeparatorTest.java
@@ -16,18 +16,22 @@
  */
 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.measure.Units;
+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;
@@ -230,12 +234,16 @@ public final strictfp class TransformSeparatorTest extends TestCase {
      * 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 {
-        final MathTransform nonLinear = new EllipsoidToCentricTransform(6378137, 6356752.314245179,
-                Units.METRE, false, EllipsoidToCentricTransform.TargetType.CARTESIAN);
+    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.
@@ -247,7 +255,7 @@ public final strictfp class TransformSeparatorTest extends TestCase {
          * 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 matrix = Matrices.create(4, 9, new double[] {
+        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,
@@ -255,18 +263,16 @@ public final strictfp class TransformSeparatorTest extends TestCase {
         });
         s.clear();
         s.addTargetDimensions(1, 2, 7);
-        MathTransform r = s.separate();
+        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());
-        assertInstanceOf ("separate()", ConcatenatedTransform.class, r);
-        assertSame(s.transform, ((ConcatenatedTransform) r).transform1);
-        assertMatrixEquals("separate().transform2", matrix,
-                ((LinearTransform) (((ConcatenatedTransform) r).transform2)).getMatrix(), STRICT);
+        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.
          */
-        matrix = Matrices.create(4, 8, new double[] {
+        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,
@@ -274,40 +280,69 @@ public final strictfp class TransformSeparatorTest extends TestCase {
         });
         s.clear();
         s.addTargetDimensions(1, 5, 7);
-        r = s.separate();
+        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, r);
-        assertMatrixEquals("separate().transform2", matrix, ((LinearTransform) r).getMatrix(), STRICT);
+        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.
          */
-        matrix = new Matrix3(
+        expected = new Matrix3(
             1, 0, 0,
             0, 1, 0,
             0, 0, 1
         );
         s.clear();
         s.addSourceDimensions(0, 6);
-        r = s.separate();
+        result = s.separate();
         assertArrayEquals ("sourceDimensions", new int[] {0, 6}, s.getSourceDimensions());
         assertArrayEquals ("targetDimensions", new int[] {0, 7}, s.getTargetDimensions());
-        assertInstanceOf  ("separate()", LinearTransform.class, r);
-        assertMatrixEquals("separate().transform2", matrix, ((LinearTransform) r).getMatrix(), STRICT);
+        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);
-        r = s.separate();
+        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, r);
-        assertSame  ("subTransform",  nonLinear, ((PassThroughTransform) r).subTransform);
-        assertEquals("firstAffectedOrdinate", 1, ((PassThroughTransform) r).firstAffectedOrdinate);
-        assertEquals("numTrailingOrdinates",  2, ((PassThroughTransform) r).numTrailingOrdinates);
+        assertInstanceOf ("separate()", PassThroughTransform.class, result);
+        assertSame  ("subTransform",  nonLinear, ((PassThroughTransform) result).subTransform);
+        assertEquals("firstAffectedOrdinate", 1, ((PassThroughTransform) result).firstAffectedOrdinate);
+        assertEquals("numTrailingOrdinates",  2, ((PassThroughTransform) result).numTrailingOrdinates);
+    }
+
+    /**
+     * 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);
+        }
     }
 
     /**


Mime
View raw message