sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 03/04: Build temporary `WraparoundInEnvelope` instances only before to transform an envelope. This change avoid to expose a mutable `MathTransform`, removes the need for synchronization and enable wraparound handling in more situations than only `GridGeometry` operations.
Date Mon, 05 Oct 2020 11:04:28 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

commit 3201ff116e25d66436895ae29537982de7eeb32f
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Oct 5 11:21:57 2020 +0200

    Build temporary `WraparoundInEnvelope` instances only before to transform an envelope.
    This change avoid to expose a mutable `MathTransform`, removes the need for synchronization and enable wraparound handling in more situations than only `GridGeometry` operations.
---
 .../coverage/grid/CoordinateOperationFinder.java   |   8 -
 .../org/apache/sis/coverage/grid/GridExtent.java   |   4 +-
 .../java/org/apache/sis/geometry/Envelopes.java    | 280 +++++++++++----------
 .../apache/sis/geometry/WraparoundInEnvelope.java  | 222 ++++++++++++++++
 .../internal/referencing/WraparoundApplicator.java |  24 +-
 .../internal/referencing/WraparoundInEnvelope.java | 245 ------------------
 .../operation/transform/WraparoundTransform.java   |  92 +++----
 7 files changed, 410 insertions(+), 465 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/CoordinateOperationFinder.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/CoordinateOperationFinder.java
index b71c7f5..5f5554e 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/CoordinateOperationFinder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/CoordinateOperationFinder.java
@@ -354,10 +354,6 @@ final class CoordinateOperationFinder implements Supplier<double[]> {
      * Computes the transform from “grid coordinates of the source” to “grid coordinates of the target”.
      * This is a concatenation of {@link #gridToCRS()} with target "CRS to grid" transform.
      *
-     * <p><b>WARNING:</b> this method may return a mutable transform (unless {@link #nowraparound()} has been invoked).
-     * That transform should be only short lived (e.g. just the time to transform an envelope).
-     * See {@link org.apache.sis.internal.referencing.WraparoundInEnvelope#transform(MathTransform, Envelope)}.</p>
-     *
      * @return operation from source grid indices to target grid indices.
      * @throws FactoryException if no operation can be found between the source and target CRS.
      * @throws TransformException if some coordinates can not be transformed to the specified target.
@@ -390,10 +386,6 @@ final class CoordinateOperationFinder implements Supplier<double[]> {
      *   <li>{@link #forwardChangeOfCRS} — cached for next invocation of this {@code gridToCRS()} method.</li>
      * </ul>
      *
-     * <p><b>WARNING:</b> this method may return a mutable transform (unless {@link #nowraparound()} has been invoked).
-     * That transform should be only short lived (e.g. just the time to transform an envelope).
-     * See {@link org.apache.sis.internal.referencing.WraparoundInEnvelope#transform(MathTransform, Envelope)}.</p>
-     *
      * @return operation from source grid indices to target geospatial coordinates.
      * @throws FactoryException if no operation can be found between the source and target CRS.
      * @throws TransformException if some coordinates can not be transformed to the specified target.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
index ada5ddb..135a68a 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
@@ -40,13 +40,13 @@ import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.collection.WeakValueHashMap;
 import org.apache.sis.internal.referencing.AxisDirections;
 import org.apache.sis.internal.referencing.ExtendedPrecisionMatrix;
-import org.apache.sis.internal.referencing.WraparoundInEnvelope;
 import org.apache.sis.internal.feature.Resources;
 import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.internal.util.Strings;
 import org.apache.sis.internal.util.DoubleDouble;
 import org.apache.sis.geometry.AbstractEnvelope;
 import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.geometry.Envelopes;
 import org.apache.sis.coverage.SubspaceNotSpecifiedException;
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.operation.matrix.MatrixSIS;
@@ -914,7 +914,7 @@ public class GridExtent implements GridEnvelope, LenientComparable, Serializable
             if (high != Long.MAX_VALUE) high++;             // Make the coordinate exclusive before cast.
             envelope.setRange(i, coordinates[i], high);     // Possible loss of precision in cast to `double` type.
         }
-        envelope = WraparoundInEnvelope.transform(cornerToCRS, envelope);
+        envelope = Envelopes.transform(cornerToCRS, envelope);
         if (envelope.isEmpty()) try {
             /*
              * If the envelope contains some NaN values, try to replace them by constant values inferred from the math transform.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java
index cd9f78b..63e59c6 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java
@@ -92,7 +92,7 @@ import static org.apache.sis.util.StringBuilders.trimFractionalPart;
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Johann Sorel (Geomatys)
- * @version 1.0
+ * @version 1.1
  *
  * @see org.apache.sis.metadata.iso.extent.Extents
  * @see CRS
@@ -262,8 +262,8 @@ public final class Envelopes extends Static {
     }
 
     /**
-     * Invoked when a recoverable exception occurred. Those exceptions must be minor enough
-     * that they can be silently ignored in most cases.
+     * Invoked when a recoverable exception occurred.
+     * Those exceptions must be minor enough that they can be silently ignored in most cases.
      */
     static void recoverableException(final Class<? extends Static> caller, final TransformException exception) {
         Logging.recoverableException(Logging.getLogger(Loggers.GEOMETRY), caller, "transform", exception);
@@ -379,9 +379,7 @@ public final class Envelopes extends Static {
      *                   May be {@code null} if this information is not needed.
      */
     @SuppressWarnings("null")
-    private static GeneralEnvelope transform(final MathTransform transform,
-                                             final Envelope      envelope,
-                                             final double[]      targetPt)
+    private static GeneralEnvelope transform(final MathTransform transform, final Envelope envelope, double[] targetPt)
             throws TransformException
     {
         if (transform.isIdentity()) {
@@ -419,154 +417,162 @@ public final class Envelopes extends Static {
         if (sourceDim >= 20) {          // Maximal value supported by Formulas.pow3(int) is 19.
             throw new IllegalArgumentException(Errors.format(Errors.Keys.ExcessiveNumberOfDimensions_1, sourceDim));
         }
-        int             pointIndex            = 0;
-        boolean         isDerivativeSupported = true;
-        GeneralEnvelope transformed           = null;
-        final Matrix[]  derivatives           = new Matrix[Math.toIntExact(MathFunctions.pow(3, sourceDim))];
-        final double[]  coordinates           = new double[derivatives.length * targetDim];
-        final double[]  sourcePt              = new double[sourceDim];
-        for (int i=sourceDim; --i>=0;) {
-            sourcePt[i] = envelope.getMinimum(i);
-        }
+        boolean isDerivativeSupported = true;
+        DirectPosition  temporary     = null;
+        GeneralEnvelope transformed   = null;
+        final Matrix[]  derivatives   = new Matrix[Math.toIntExact(MathFunctions.pow(3, sourceDim))];
+        final double[]  coordinates   = new double[Math.multiplyExact(derivatives.length, targetDim)];
+        final double[]  sourcePt      = new double[sourceDim];
         // A window over a single coordinate in the `coordinates` array.
         final DirectPositionView ordinatesView = new DirectPositionView.Double(coordinates, 0, targetDim);
-        /*
-         * Iterates over every minimal, maximal and median coordinate values (3 points) along each
-         * dimension. The total number of iterations is 3 ^ (number of source dimensions).
-         */
-        transformPoint: while (true) {
-            /*
-             * Compute the derivative (optional operation). If this operation fails, we will
-             * set a flag to `false` so we don't try again for all remaining points. We try
-             * to compute the derivative and the transformed point in a single operation if
-             * we can. If we can not, we will compute those two information separately.
-             *
-             * Note that the very last point to be projected must be the envelope center.
-             * There is usually no need to calculate the derivative for that last point,
-             * but we let it does anyway for safety.
-             */
-            final int offset = pointIndex * targetDim;
-            try {
-                derivatives[pointIndex] = derivativeAndTransform(transform,
-                        sourcePt, coordinates, offset, isDerivativeSupported);
-            } catch (TransformException e) {
-                if (!isDerivativeSupported) {
-                    throw e;                    // Derivative were already disabled, so something went wrong.
-                }
-                isDerivativeSupported = false;
-                transform.transform(sourcePt, 0, coordinates, offset, 1);
-                recoverableException(Envelopes.class, e);       // Log only if the above call was successful.
+        final WraparoundInEnvelope.Controller wc = new WraparoundInEnvelope.Controller(transform);
+        do {
+            for (int i=sourceDim; --i>=0;) {
+                sourcePt[i] = envelope.getMinimum(i);
             }
             /*
-             * The transformed point has been saved for future reuse after the enclosing
-             * `while` loop. Now add the transformed point to the destination envelope.
+             * Iterates over every minimal, maximal and median coordinate values (3 points) along each dimension.
+             * The total number of iterations is 3 ^ (number of source dimensions).
              */
-            if (transformed == null) {
-                transformed = new GeneralEnvelope(targetDim);
-                for (int i=0; i<targetDim; i++) {
-                    final double value = coordinates[offset + i];
-                    transformed.setRange(i, value, value);
+nextPoint:  for (int pointIndex = 0;;) {                // Break condition at the end of this block.
+                /*
+                 * Compute the derivative (optional operation). If this operation fails, we will
+                 * set a flag to `false` so we don't try again for all remaining points. We try
+                 * to compute the derivative and the transformed point in a single operation if
+                 * we can. If we can not, we will compute those two information separately.
+                 *
+                 * Note that the very last point to be projected must be the envelope center.
+                 * There is usually no need to calculate the derivative for that last point,
+                 * but we let it does anyway for safety.
+                 */
+                final int offset = pointIndex * targetDim;
+                try {
+                    derivatives[pointIndex] = derivativeAndTransform(wc.transform,
+                            sourcePt, coordinates, offset, isDerivativeSupported);
+                } catch (TransformException e) {
+                    if (!isDerivativeSupported) {
+                        throw e;                    // Derivative were already disabled, so something went wrong.
+                    }
+                    isDerivativeSupported = false;
+                    wc.transform.transform(sourcePt, 0, coordinates, offset, 1);
+                    recoverableException(Envelopes.class, e);       // Log only if the above call was successful.
+                }
+                /*
+                 * The transformed point has been saved for future reuse after the enclosing
+                 * `for(;;)` loop. Now add the transformed point to the destination envelope.
+                 */
+                if (transformed == null) {
+                    transformed = new GeneralEnvelope(targetDim);
+                    for (int i=0; i<targetDim; i++) {
+                        final double value = coordinates[offset + i];
+                        transformed.setRange(i, value, value);
+                    }
+                } else {
+                    ordinatesView.offset = offset;
+                    transformed.add(ordinatesView);
                 }
-            } else {
-                ordinatesView.offset = offset;
-                transformed.add(ordinatesView);
+                /*
+                 * Get the next point coordinate. The `coordinateIndex` variable is an index in base 3
+                 * having a number of digits equals to the number of source dimensions.  For example a
+                 * 4-D space have indexes ranging from "0000" to "2222" (numbers in base 3). The digits
+                 * are then mapped to minimal (0), maximal (1) or central (2) coordinates. The outer loop
+                 * stops when the counter roll back to "0000". Note that `targetPt` will be set to value
+                 * of the last projected point, which must be the envelope center identified by "2222"
+                 * in the 4-D case.
+                 */
+                int indexBase3 = ++pointIndex;
+                for (int dim = sourceDim; --dim >= 0; indexBase3 /= 3) {
+                    switch (indexBase3 % 3) {
+                        case 0:  sourcePt[dim] = envelope.getMinimum(dim); continue;            // Continue the loop.
+                        case 1:  sourcePt[dim] = envelope.getMaximum(dim); continue nextPoint;
+                        case 2:  sourcePt[dim] = envelope.getMedian (dim); continue nextPoint;
+                        default: throw new AssertionError(indexBase3);                          // Should never happen
+                    }
+                }
+                assert pointIndex == derivatives.length : pointIndex;
+                break;
             }
             /*
-             * Get the next point coordinate. The `coordinateIndex` variable is an index in base 3
-             * having a number of digits equals to the number of source dimensions.  For example a
-             * 4-D space have indexes ranging from "0000" to "2222" (numbers in base 3). The digits
-             * are then mapped to minimal (0), maximal (1) or central (2) coordinates. The outer loop
-             * stops when the counter roll back to "0000". Note that `targetPt` must keep the value
-             * of the last projected point, which must be the envelope center identified by "2222"
-             * in the 4-D case.
+             * At this point we finished to build an envelope from all sampled positions. Now iterate
+             * over all points. For each point, iterate over all line segments from that point to a
+             * neighbor median point.  Use the derivate information for approximating the transform
+             * behavior in that area by a cubic curve. We can then find analytically the curve extremum.
+             *
+             * The same technic is applied in transform(MathTransform, Rectangle2D), except that in
+             * the Rectangle2D case the calculation was bundled right inside the main loop in order
+             * to avoid the need for storage.
              */
-            int indexBase3 = ++pointIndex;
-            for (int dim=sourceDim; --dim>=0; indexBase3 /= 3) {
-                switch (indexBase3 % 3) {
-                    case 0:  sourcePt[dim] = envelope.getMinimum(dim); break;   // Continue the loop.
-                    case 1:  sourcePt[dim] = envelope.getMaximum(dim); continue transformPoint;
-                    case 2:  sourcePt[dim] = envelope.getMedian (dim); continue transformPoint;
-                    default: throw new AssertionError(indexBase3);     // Should never happen
-                }
-            }
-            break;
-        }
-        assert pointIndex == derivatives.length : pointIndex;
-        /*
-         * At this point we finished to build an envelope from all sampled positions. Now iterate
-         * over all points. For each point, iterate over all line segments from that point to a
-         * neighbor median point.  Use the derivate information for approximating the transform
-         * behavior in that area by a cubic curve. We can then find analytically the curve extremum.
-         *
-         * The same technic is applied in transform(MathTransform, Rectangle2D), except that in
-         * the Rectangle2D case the calculation was bundled right inside the main loop in order
-         * to avoid the need for storage.
-         */
-        DirectPosition temporary = null;
-        final DirectPositionView sourceView = new DirectPositionView.Double(sourcePt, 0, sourceDim);
-        final CurveExtremum extremum = new CurveExtremum();
-        for (pointIndex=0; pointIndex < derivatives.length; pointIndex++) {
-            final Matrix D1 = derivatives[pointIndex];
-            if (D1 != null) {
-                int indexBase3 = pointIndex, power3 = 1;
-                for (int i=sourceDim; --i>=0; indexBase3 /= 3, power3 *= 3) {
-                    final int digitBase3 = indexBase3 % 3;
-                    if (digitBase3 != 2) { // Process only if we are not already located on the median along the dimension i.
-                        final int medianIndex = pointIndex + power3 * (2 - digitBase3);
-                        final Matrix D2 = derivatives[medianIndex];
-                        if (D2 != null) {
-                            final double xmin = envelope.getMinimum(i);
-                            final double xmax = envelope.getMaximum(i);
-                            final double x2   = envelope.getMedian (i);
-                            final double x1   = (digitBase3 == 0) ? xmin : xmax;
-                            final int offset1 = targetDim * pointIndex;
-                            final int offset2 = targetDim * medianIndex;
-                            for (int j=0; j<targetDim; j++) {
-                                extremum.resolve(x1, coordinates[offset1 + j], D1.getElement(j,i),
-                                                 x2, coordinates[offset2 + j], D2.getElement(j,i));
-                                boolean isP2 = false;
-                                do { // Executed exactly twice, one for each extremum point.
-                                    final double x = isP2 ? extremum.ex2 : extremum.ex1;
-                                    if (x > xmin && x < xmax) {
-                                        final double y = isP2 ? extremum.ey2 : extremum.ey1;
-                                        if (y < transformed.getMinimum(j) ||
-                                            y > transformed.getMaximum(j))
-                                        {
-                                            /*
-                                             * At this point, we have determined that adding the extremum point
-                                             * would expand the envelope. However we will not add that point
-                                             * directly because its position may not be quite right (since we
-                                             * used a cubic curve approximation). Instead, we project the point
-                                             * on the envelope border which is located vis-à-vis the extremum.
-                                             */
-                                            for (int ib3 = pointIndex, dim = sourceDim; --dim >= 0; ib3 /= 3) {
-                                                final double coordinate;
-                                                if (dim == i) {
-                                                    coordinate = x;                       // Position of the extremum.
-                                                } else switch (ib3 % 3) {
-                                                    case 0:  coordinate = envelope.getMinimum(dim); break;
-                                                    case 1:  coordinate = envelope.getMaximum(dim); break;
-                                                    case 2:  coordinate = envelope.getMedian (dim); break;
-                                                    default: throw new AssertionError(ib3);     // Should never happen.
+            final DirectPositionView sourceView = new DirectPositionView.Double(sourcePt, 0, sourceDim);
+            final CurveExtremum extremum = new CurveExtremum();
+            for (int pointIndex=0; pointIndex < derivatives.length; pointIndex++) {
+                final Matrix D1 = derivatives[pointIndex];
+                if (D1 != null) {
+                    int indexBase3 = pointIndex, power3 = 1;
+                    for (int i = sourceDim; --i >= 0; indexBase3 /= 3, power3 *= 3) {
+                        final int digitBase3 = indexBase3 % 3;
+                        // Process only if we are not already located on the median along the dimension i.
+                        if (digitBase3 != 2) {
+                            final int medianIndex = pointIndex + power3 * (2 - digitBase3);
+                            final Matrix D2 = derivatives[medianIndex];
+                            if (D2 != null) {
+                                final double xmin = envelope.getMinimum(i);
+                                final double xmax = envelope.getMaximum(i);
+                                final double x2   = envelope.getMedian (i);
+                                final double x1   = (digitBase3 == 0) ? xmin : xmax;
+                                final int offset1 = targetDim * pointIndex;
+                                final int offset2 = targetDim * medianIndex;
+                                for (int j=0; j<targetDim; j++) {
+                                    extremum.resolve(x1, coordinates[offset1 + j], D1.getElement(j,i),
+                                                     x2, coordinates[offset2 + j], D2.getElement(j,i));
+                                    boolean isP2 = false;
+                                    do {
+                                        // Executed exactly twice, one for each extremum point.
+                                        final double x = isP2 ? extremum.ex2 : extremum.ex1;
+                                        if (x > xmin && x < xmax) {
+                                            final double y = isP2 ? extremum.ey2 : extremum.ey1;
+                                            if (y < transformed.getMinimum(j) ||
+                                                y > transformed.getMaximum(j))
+                                            {
+                                                /*
+                                                 * At this point, we have determined that adding the extremum point
+                                                 * would expand the envelope. However we will not add that point
+                                                 * directly because its position may not be quite right (since we
+                                                 * used a cubic curve approximation). Instead, we project the point
+                                                 * on the envelope border which is located vis-à-vis the extremum.
+                                                 */
+                                                for (int ib3 = pointIndex, dim = sourceDim; --dim >= 0; ib3 /= 3) {
+                                                    final double coordinate;
+                                                    if (dim == i) {
+                                                        coordinate = x;                       // Position of the extremum.
+                                                    } else switch (ib3 % 3) {
+                                                        case 0:  coordinate = envelope.getMinimum(dim); break;
+                                                        case 1:  coordinate = envelope.getMaximum(dim); break;
+                                                        case 2:  coordinate = envelope.getMedian (dim); break;
+                                                        default: throw new AssertionError(ib3);     // Should never happen.
+                                                    }
+                                                    sourcePt[dim] = coordinate;
                                                 }
-                                                sourcePt[dim] = coordinate;
+                                                temporary = wc.transform.transform(sourceView, temporary);
+                                                transformed.add(temporary);
                                             }
-                                            temporary = transform.transform(sourceView, temporary);
-                                            transformed.add(temporary);
                                         }
-                                    }
-                                } while ((isP2 = !isP2) == true);
+                                    } while ((isP2 = !isP2) == true);
+                                }
                             }
                         }
                     }
+                    derivatives[pointIndex] = null;                 // Let GC do its job earlier.
                 }
-                derivatives[pointIndex] = null;                 // Let GC do its job earlier.
             }
-        }
-        if (targetPt != null) {
-            // Copy the coordinate of the center point.
-            System.arraycopy(coordinates, coordinates.length - targetDim, targetPt, 0, targetDim);
-        }
+            /*
+             * Copy the coordinate of the center point. We take the point of the
+             * first iteration because it is the one before translation is applied.
+             */
+            if (targetPt != null) {
+                System.arraycopy(coordinates, coordinates.length - targetDim, targetPt, 0, targetDim);
+                targetPt = null;
+            }
+        } while (wc.translate());
         return transformed;
     }
 
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/WraparoundInEnvelope.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/WraparoundInEnvelope.java
new file mode 100644
index 0000000..cbced19
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/WraparoundInEnvelope.java
@@ -0,0 +1,222 @@
+/*
+ * 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.geometry;
+
+import java.util.function.UnaryOperator;
+import org.opengis.referencing.operation.MathTransform;
+import org.apache.sis.referencing.operation.transform.WraparoundTransform;
+import org.apache.sis.util.ArraysExt;
+
+
+/**
+ * A {@link WraparoundTransform} where the number of cycles added or removed does not exceed a given limit.
+ * The bound is determined by whether the coordinate to transform is before or after a median point.
+ * If the coordinate is before the median, this class puts a limit on the number of cycles added.
+ * If the coordinate is after  the median, this class puts a limit on the number of cycles removed.
+ * The intent is to avoid that the lower bound of an envelope is shifted by a greater number of cycles
+ * than the upper bound, which may result in lower bound becoming greater than upper bound.
+ *
+ * <p>The median point is {@link #sourceMedian}.
+ * It is used as the source coordinate where the minimum or maximum number of cycles is determined.
+ * This <em>source</em> coordinate is not necessarily the same as the median <em>target</em> coordinate
+ * (which is fixed to zero by application of normalization matrix) because those medians were determined
+ * from source and target envelopes, which do not need to be the same.</p>
+ *
+ * <p>The final result is that envelopes transformed using {@code WraparoundInEnvelope} may be larger
+ * than envelopes transformed using {@link WraparoundTransform} but should never be smaller.</p>
+ *
+ * <h2>Mutability</h2>
+ * <b>This class is mutable.</b> This class records the translations that {@link #shift(double)} wanted to apply
+ * but could not because of the {@linkplain #limit} documented in above paragraph. When such missed translations
+ * are detected, caller should {@linkplain #translate() translate the limit} and transform same envelope again.
+ * We do that because each envelope transformation should use a consistent {@linkplain #limit} for all corners.
+ * Since this strategy breaks usual {@link org.apache.sis.referencing.operation.transform.AbstractMathTransform}
+ * contract about immutability, this class should be used only for temporary transforms to apply on an envelope.
+ *
+ * <h2>Thread-safety</h2>
+ * This class is <strong>not</strong> thread-safe. Each instance shall be used by only one thread.
+ * Temporary instances should be created by the thread doing transformations and discarded immediately after.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+final class WraparoundInEnvelope extends WraparoundTransform {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 4017870982753327584L;
+
+    /**
+     * Number of cycles at the {@linkplain #sourceMedian} position. This is the minimum or maximum number
+     * of cycles to remove to a coordinate, depending if that coordinate is before or after the median.
+     */
+    private double limit;
+
+    /**
+     * The minimum and maximum number of {@linkplain #period period}s that {@link #shift(double)} wanted
+     * to add to the coordinate before to be constrained to the {@link #limit}.
+     */
+    private double minCycles, maxCycles;
+
+    /**
+     * Whether {@link #minCycles} or {@link #maxCycles} has been updated.
+     */
+    private boolean minChanged, maxChanged;
+
+    /**
+     * Creates a new transform initialized to a copy of given instance.
+     * Input and output values in the wraparound dimension shall be normalized
+     * in the [−p/2 … +p/2] range where <var>p</var> is the period (e.g. 360°).
+     */
+    private WraparoundInEnvelope(final WraparoundTransform other) {
+        super(other);
+        minCycles = maxCycles = limit = Math.rint(sourceMedian / period);
+    }
+
+    /**
+     * Applies the wraparound on the given coordinate value. This implementation ensures that coordinates smaller than
+     * {@link #sourceMedian} before wraparound are still smaller than the (possibly shifted) median after wraparound,
+     * and conversely for coordinates greater than the median.
+     *
+     * The final result is that envelopes transformed using {@code WraparoundInEnvelope} may be larger
+     * than envelopes transformed using {@link WraparoundTransform} but should never be smaller.
+     *
+     * @param  x  the value on which to apply wraparound.
+     * @return the value after wraparound.
+     */
+    @Override
+    protected final double shift(final double x) {
+        double n = Math.rint(x / period);
+        if (x < sourceMedian) {
+            if (n < limit) {
+                if (n < minCycles) {
+                    minCycles = n;
+                    minChanged = true;
+                }
+                n = limit;
+            }
+        } else {
+            if (n > limit) {
+                if (n > maxCycles) {
+                    maxCycles = n;
+                    maxChanged = true;
+                }
+                n = limit;
+            }
+        }
+        return x - n * period;
+    }
+
+    /**
+     * Modifies this transform with a translation for enabling the wraparound that could not be applied in previous
+     * {@link #shift(double)} executions. If this method returns {@code true}, then this transform will now compute
+     * different output coordinates for the same input coordinates.
+     *
+     * <h4>Usage</h4>
+     * This method can be invoked after transforming an envelope. If this method returns {@code true}, then
+     * the same envelope should be transformed again and the new result added to the previous result (union).
+     *
+     * @return {@code true} if this transform has been modified.
+     */
+    private boolean translate() {
+        if (minChanged) {
+            minChanged = false;
+            limit = minCycles;
+            return true;
+        }
+        if (maxChanged) {
+            maxChanged = false;
+            limit = maxCycles;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Helper class for transforming an envelope with special checks for wraparounds.
+     * This class provides a translatable {@linkplain #transform} for enabling wraparounds that could not be applied
+     * in previous {@link #shift(double)} executions. The translation is applied by calls to the {@link #translate()}
+     * method, which should be invoked repetitively until it returns {@code false}.
+     */
+    static final class Controller implements UnaryOperator<WraparoundTransform> {
+        /**
+         * The potentially mutable transform to use for transforming envelope corners.
+         * This transform is derived from the transform specified at construction time
+         * and should not live longer than the time needed for transforming an envelope.
+         */
+        final MathTransform transform;
+
+        /**
+         * All wraparound steps found, or {@code null} if none.
+         */
+        private WraparoundInEnvelope[] wraparounds;
+
+        /**
+         * Creates a new instance using the given transform. If the given transform contains wraparound steps,
+         * then the transform stored in the {@link #transform} will be a different transform chains instance.
+         */
+        @SuppressWarnings("ThisEscapedInObjectConstruction")
+        Controller(final MathTransform transform) {
+            this.transform = replace(transform, this);
+        }
+
+        /**
+         * Callback method for replacing {@link WraparoundTransform} instances by {@link WraparoundInEnvelope}
+         * instances in {@link #transform}. This method is public as an implementation side-effect and should
+         * not be invoked directly (it is invoked by {@link WraparoundTransform#replace(MathTransform, Function)}).
+         *
+         * @param  transform  the {@code WraparoundTransform} instance to replace by a translatable instance.
+         * @return same wraparound operation but with a control on translations applied on corner coordinates.
+         */
+        @Override
+        public WraparoundTransform apply(final WraparoundTransform transform) {
+            if (!Double.isFinite(transform.sourceMedian)) {
+                return transform;
+            }
+            final WraparoundInEnvelope w = new WraparoundInEnvelope(transform);
+            if (wraparounds == null) {
+                wraparounds = new WraparoundInEnvelope[] {w};
+            } else {
+                wraparounds = ArraysExt.append(wraparounds, w);
+            }
+            return w;
+        }
+
+        /**
+         * Modifies the {@linkplain #transform} with a translation for enabling wraparounds that could not be applied
+         * in previous {@link #shift(double)} executions. If this method returns {@code true}, then the transform will
+         * compute different output coordinates for the same input coordinates.
+         *
+         * <h4>Usage</h4>
+         * This method can be invoked after transforming an envelope. If this method returns {@code true}, then
+         * the same envelope should be transformed again and the new result added to the previous result (union).
+         *
+         * @return {@code true} if the {@linkplain #transform} has been modified.
+         */
+        final boolean translate() {
+            boolean modified = false;
+            if (wraparounds != null) {
+                for (final WraparoundInEnvelope tr : wraparounds) {
+                    modified |= tr.translate();
+                }
+            }
+            return modified;
+        }
+    }
+}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WraparoundApplicator.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WraparoundApplicator.java
index 9389c67..0d43218 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WraparoundApplicator.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WraparoundApplicator.java
@@ -40,7 +40,7 @@ public final class WraparoundApplicator {
     /**
      * Coordinates at the center of source envelope, or {@code null} if none.
      */
-    final DirectPosition sourceMedian;
+    private final DirectPosition sourceMedian;
 
     /**
      * Coordinates to put at the center of new coordinate ranges,
@@ -54,12 +54,6 @@ public final class WraparoundApplicator {
     private final CoordinateSystem targetCS;
 
     /**
-     * The synchronization lock to give to {@link WraparoundInEnvelope} instances.
-     * This is initially null, then set to the lock of the first instance.
-     */
-    WraparoundInEnvelope lock;
-
-    /**
      * Creates a new applicator.
      *
      * @param  sourceMedian  the coordinates at the center of source envelope, or {@code null} if none.
@@ -155,20 +149,8 @@ public final class WraparoundApplicator {
              */
             m = 0;
         }
-        final int dimension = tr.getTargetDimensions();
-        final double sm = (sourceMedian != null) ? sourceMedian.getOrdinate(wraparoundDimension) - m : Double.NaN;
-        MathTransform wraparound;
-        if (Double.isNaN(sm)) {
-            wraparound = WraparoundTransform.create(dimension, wraparoundDimension, period, sm, m);
-        } else {
-            wraparound = new WraparoundInEnvelope(this, dimension, wraparoundDimension, period, sm);
-            if (m != 0) {
-                final double[] t = new double[dimension];
-                t[wraparoundDimension] = m;
-                final MathTransform denormalize = MathTransforms.translation(t);
-                wraparound = MathTransforms.concatenate(denormalize.inverse(), wraparound, denormalize);
-            }
-        }
+        final MathTransform wraparound = WraparoundTransform.create(tr.getTargetDimensions(), wraparoundDimension,
+                period, (sourceMedian == null) ? Double.NaN : sourceMedian.getOrdinate(wraparoundDimension), m);
         return MathTransforms.concatenate(tr, wraparound);
     }
 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WraparoundInEnvelope.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WraparoundInEnvelope.java
deleted file mode 100644
index f136c86..0000000
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WraparoundInEnvelope.java
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * 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.internal.referencing;
-
-import org.opengis.geometry.Envelope;
-import org.opengis.referencing.operation.MathTransform;
-import org.opengis.referencing.operation.TransformException;
-import org.apache.sis.referencing.operation.transform.WraparoundTransform;
-import org.apache.sis.geometry.Envelopes;
-import org.apache.sis.geometry.GeneralEnvelope;
-import org.apache.sis.internal.util.Numerics;
-import org.apache.sis.util.ComparisonMode;
-
-
-/**
- * A {@link WraparoundTransform} where the number of cycles added or removed does not exceed a given limit.
- * The bound is determined by whether the coordinate to transform is before or after a median point.
- * If the coordinate is before the median, this class puts a limit on the number of cycles added.
- * If the coordinate is after  the median, this class puts a limit on the number of cycles removed.
- * The intent is to avoid that the lower bound of an envelope is shifted by a greater number of cycles
- * than the upper bound, which may result in lower bound becoming greater than upper bound.
- *
- * <p>The median point is {@link #sourceMedian}.
- * It is used as the source coordinate where the minimum or maximum number of cycles is determined.
- * This <em>source</em> coordinate is not necessarily the same as the median <em>target</em> coordinate
- * (which is set to zero by application of normalization matrix) because those medians were determined
- * from source and target envelopes, which do not need to be the same.</p>
- *
- * <p>The final result is that envelopes transformed using {@code WraparoundInEnvelope} may be larger
- * than envelopes transformed using {@link WraparoundTransform} but should never be smaller.</p>
- *
- * <h2>Mutability</h2>
- * <b>This class is mutable.</b> This class records the translations that {@link #shift(double)} wanted to apply
- * but could not because of the {@linkplain #limit} documented in above paragraph. When such missed translations
- * are detected, caller should {@linkplain #translate() translate the limit} and transform same envelope again.
- * We do that because each envelope transformation should use a consistent {@linkplain #limit} for all corners.
- * Since this strategy breaks usual {@link org.apache.sis.referencing.operation.transform.AbstractMathTransform}
- * contract about immutability, this class should be used only for temporary transforms to apply on an envelope.
- *
- * <h2>Initial state</h2>
- * On initialization, the {@linkplain #limit} is not applied and this class behaves like {@link WraparoundTransform}
- * parent class. The limit is enabled when {@link #transform(MathTransform, Envelope)} is invoked.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-public final class WraparoundInEnvelope extends WraparoundTransform {
-    /**
-     * For cross-version compatibility.
-     */
-    private static final long serialVersionUID = 4017870982753327584L;
-
-    /**
-     * Number of cycles at the {@linkplain #sourceMedian} position. This is the minimum or maximum number
-     * of cycles to remove to a coordinate, depending if that coordinate is before or after the median.
-     */
-    private double limit;
-
-    /**
-     * The minimum and maximum number of {@linkplain #period period}s that {@link #shift(double)} wanted
-     * to add to the coordinate before to be constrained to the {@link #limit}.
-     */
-    private double minCycles, maxCycles;
-
-    /**
-     * Whether {@link #minCycles} or {@link #maxCycles} has been updated.
-     */
-    private boolean minChanged, maxChanged;
-
-    /**
-     * The synchronization lock (may be {@code this}).
-     */
-    private final WraparoundInEnvelope lock;
-
-    /**
-     * Creates a new transform with a wraparound behavior in the given dimension.
-     * Input and output values in the wraparound dimension shall be normalized in
-     * the [−p/2 … +p/2] range where <var>p</var> is the period (e.g. 360°).
-     */
-    @SuppressWarnings("ThisEscapedInObjectConstruction")
-    WraparoundInEnvelope(final WraparoundApplicator ap, final int dimension, final int wraparoundDimension,
-                         final double period, final double sourceMedian)
-    {
-        super(dimension, wraparoundDimension, period, sourceMedian);
-        minCycles = maxCycles = limit = Double.NaN;
-        if (ap.lock == null) {
-            ap.lock = this;
-        }
-        lock = ap.lock;
-    }
-
-    /**
-     * Applies the wraparound on the given value. This implementation ensures that coordinates smaller than
-     * {@link #sourceMedian} before wraparound are still smaller than the (possibly shifted) median after wraparound,
-     * and conversely for coordinates greater than the median.
-     *
-     * The final result is that envelopes transformed using {@code WraparoundInEnvelope} may be larger
-     * than envelopes transformed using {@link WraparoundTransform} but should never be smaller.
-     *
-     * @param  x  the value on which to apply wraparound.
-     * @return the value after wraparound.
-     */
-    @Override
-    protected final double shift(final double x) {
-        double n = Math.rint(x / period);
-        synchronized (lock) {
-            if (x < sourceMedian) {
-                if (n < limit) {
-                    if (n < minCycles) {
-                        minCycles = n;
-                        minChanged = true;
-                    }
-                    n = limit;
-                }
-            } else {
-                if (n > limit) {
-                    if (n > maxCycles) {
-                        maxCycles = n;
-                        maxChanged = true;
-                    }
-                    n = limit;
-                }
-            }
-        }
-        return x - n * period;
-    }
-
-    /**
-     * Modifies this transform with a translation for enabling the wraparound that could not be applied in previous
-     * {@link #shift(double)} executions. If this method returns {@code true}, then this transform computes different
-     * output coordinates for the same input coordinates.
-     *
-     * <h4>Usage</h4>
-     * This method can be invoked after transforming an envelope. If this method returns {@code true}, then
-     * the same envelope should be transformed again and the new result added to the previous result (union).
-     *
-     * @return {@code true} if this transform has been modified.
-     */
-    private boolean translate() {
-        synchronized (lock) {
-            if (minChanged) {
-                minChanged = false;
-                limit = minCycles;
-                return true;
-            }
-            if (maxChanged) {
-                maxChanged = false;
-                limit = maxCycles;
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Resets this transform to the {@link #limit} value for an initial transform,
-     * or disable the use of limit.
-     *
-     * @param  enabled  whether to enable the {@linkplain #limit}.
-     */
-    private void reset(final boolean enabled) {
-        synchronized (lock) {
-            minCycles = maxCycles = limit = enabled ? Math.rint(sourceMedian / period) : Double.NaN;
-        }
-    }
-
-    /**
-     * Compares this transform with the given object for equality.
-     *
-     * @param  object  the object to compare with this transform.
-     * @param  mode    ignored, can be {@code null}.
-     */
-    @Override
-    public boolean equals(final Object object, final ComparisonMode mode) {
-        if (super.equals(object, mode)) {
-            return Numerics.equals(sourceMedian, ((WraparoundInEnvelope) object).sourceMedian);
-            // Do not use `limit` is computation because its value may change.
-        }
-        return false;
-    }
-
-    /**
-     * Computes a hash code value for this transform.
-     */
-    @Override
-    protected int computeHashCode() {
-        return super.computeHashCode() + 7*Double.hashCode(sourceMedian);
-        // Do not use `limit` is computation because its value may change.
-    }
-
-    /**
-     * Transforms an envelope using the given math transform with special checks for wraparounds.
-     * The transformation is only approximated: the returned envelope may be bigger than necessary.
-     *
-     * <p>This may method modifies the given transform with translations for enabling wraparounds
-     * that could not be applied in previous {@link #shift(double)} executions.
-     * If the {@link WraparoundInEnvelope#translate()} method returns {@code true}, then the given
-     * transform will compute different output coordinates for the same input coordinates.</p>
-     *
-     * @param  transform  the transform to use.
-     * @param  envelope   envelope to transform. This envelope will not be modified.
-     * @return the transformed envelope.
-     * @throws TransformException if a transform failed.
-     */
-    public static GeneralEnvelope transform(final MathTransform transform, final Envelope envelope) throws TransformException {
-        final WraparoundInEnvelope[] wraparounds = getSteps(WraparoundInEnvelope.class, transform);
-        if (wraparounds == null) {
-            return Envelopes.transform(transform, envelope);
-        }
-        synchronized (wraparounds[0].lock) {
-            for (final WraparoundInEnvelope tr : wraparounds) {
-                tr.reset(true);
-            }
-            final GeneralEnvelope result = Envelopes.transform(transform, envelope);
-            for (;;) {
-                boolean done = false;
-                for (final WraparoundInEnvelope tr : wraparounds) {
-                    done |= tr.translate();
-                }
-                if (!done) break;
-                result.add(Envelopes.transform(transform, envelope));
-            }
-            for (final WraparoundInEnvelope tr : wraparounds) {
-                tr.reset(false);
-            }
-            return result;
-        }
-    }
-}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/WraparoundTransform.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/WraparoundTransform.java
index f597ea7..59dfefc 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/WraparoundTransform.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/WraparoundTransform.java
@@ -17,8 +17,9 @@
 package org.apache.sis.referencing.operation.transform;
 
 import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
 import java.io.Serializable;
-import java.lang.reflect.Array;
 import org.opengis.util.FactoryException;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.parameter.ParameterValueGroup;
@@ -33,7 +34,6 @@ import org.apache.sis.internal.referencing.provider.Wraparound;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.parameter.Parameters;
-import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.logging.Logging;
@@ -87,13 +87,13 @@ public class WraparoundTransform extends AbstractMathTransform implements Serial
     /**
      * The dimension where to apply wraparound.
      */
-    protected final int wraparoundDimension;
+    public final int wraparoundDimension;
 
     /**
-     * Period on wraparound axis. This is 360° for the longitude axis.
+     * Period on wraparound axis, always greater than zero. This is 360° for the longitude axis.
      * Coordinates will be normalized in the [−<var>period</var>/2 … +<var>period</var>/2] range.
      */
-    protected final double period;
+    public final double period;
     /*
      * DESIGN NOTE:
      * A previous version of `WraparoundTransform` had no period. Instead it was expecting coordinates normalized
@@ -119,7 +119,7 @@ public class WraparoundTransform extends AbstractMathTransform implements Serial
      * is related to the arguments given to the {@link #create create(…)} method by
      * {@code this.sourceMeridian = sourceMeridian - targetMeridian}.</div>
      */
-    protected final double sourceMedian;
+    public final double sourceMedian;
 
     /**
      * Inverse of this transform, computed when first needed.
@@ -230,6 +230,39 @@ public class WraparoundTransform extends AbstractMathTransform implements Serial
     }
 
     /**
+     * Replaces all {@code WraparoundTransform} instances in a chain of transform steps.
+     * For each instance found in the {@linkplain MathTransforms#getSteps(MathTransform) list of transform steps},
+     * the given function is invoked with the {@code WraparoundTransform} instance found. If that function returns
+     * a different instance, then this method creates a new chain of transforms with the same steps than the given
+     * {@code transform}, except for the {@code WraparoundTransform} steps that are replaced by the steps returned
+     * by the {@code replacement} function.
+     *
+     * <p>This method allows injection of a specialized type of {@code WraparoundTransform}, for example in order
+     * to override the {@link #shift(double)} method with finer control of wraparound operations.</p>
+     *
+     * @param  transform    the transform in which to replace {@link WraparoundTransform} steps.
+     * @param  replacement  function providing replacements for {@code WraparoundTransform} steps.
+     * @return chain of transforms with {@link WraparoundTransform} steps replaced (if any).
+     */
+    public static MathTransform replace(MathTransform transform,
+            final Function<? super WraparoundTransform, ? extends WraparoundTransform> replacement)
+    {
+        ArgumentChecks.ensureNonNull("transform",   transform);
+        ArgumentChecks.ensureNonNull("replacement", replacement);
+        if (transform instanceof WraparoundTransform) {
+            transform = Objects.requireNonNull(replacement.apply((WraparoundTransform) transform));
+        } else if (transform instanceof ConcatenatedTransform) {
+            final ConcatenatedTransform ct = (ConcatenatedTransform) transform;
+            final MathTransform tr1 = replace(ct.transform1, replacement);
+            final MathTransform tr2 = replace(ct.transform2, replacement);
+            if (tr1 != ct.transform1 || tr2 != ct.transform2) {
+                transform = MathTransforms.concatenate(tr1, tr2);
+            }
+        }
+        return transform;
+    }
+
+    /**
      * Gets the dimension of input points.
      *
      * @return the dimension of input points.
@@ -250,7 +283,7 @@ public class WraparoundTransform extends AbstractMathTransform implements Serial
     }
 
     /**
-     * Applies the wraparound on the given value. This method is invoked by default implementation
+     * Applies the wraparound on the given coordinate value. This method is invoked by default implementation
      * of all {@code transform(…)} methods defined in this {@code WraparoundTransform} class.
      * It provides a single method to override if a different wraparound strategy is desired.
      * The default implementation is:
@@ -570,51 +603,6 @@ public class WraparoundTransform extends AbstractMathTransform implements Serial
     }
 
     /**
-     * Returns all {@link WraparoundTransform} instances of the given type, or {@code null} if none.
-     * Invoking this method is equivalent to invoking {@link MathTransforms#getSteps(MathTransform)}
-     * and filter the list for retaining only instances of the given {@code type}.
-     *
-     * <div class="note"><b>Rational:</b>
-     * this specialized method is provided for efficiency because the returned array, if non-null,
-     * usually contains only one instance. Callers may be interested in a specific subclass of
-     * {@link WraparoundTransform} for finer control of wraparound operations,
-     * for example if using a specialized subclass for envelope transformation.</div>
-     *
-     * @param  <T>        compile-time value of {@code type} argument.
-     * @param  type       {@link WraparoundTransform} subclass to include in the returned array.
-     * @param  transform  the transform for which to get {@link WraparoundTransform} steps.
-     * @return {@link WraparoundTransform} steps in a non-empty array, or {@code null} if none.
-     *
-     * @see MathTransforms#getSteps(MathTransform)
-     */
-    public static <T extends WraparoundTransform> T[] getSteps(final Class<T> type, final MathTransform transform) {
-        ArgumentChecks.ensureNonNull("type", type);
-        return getSteps(type, transform, null);
-    }
-
-    /**
-     * Implementation of {@link #getSteps(Class, MathTransform)} to be invoked recursively.
-     */
-    @SuppressWarnings("unchecked")
-    private static <T extends WraparoundTransform> T[] getSteps(
-            final Class<T> type, final MathTransform transform, T[] wraparounds)
-    {
-        if (type.isInstance(transform)) {
-            if (wraparounds == null) {
-                wraparounds = (T[]) Array.newInstance(type, 1);
-                wraparounds[0] = (T) transform;
-            } else {
-                wraparounds = ArraysExt.append(wraparounds, (T) transform);
-            }
-        } else if (transform instanceof ConcatenatedTransform) {
-            final ConcatenatedTransform ct = (ConcatenatedTransform) transform;
-            wraparounds = getSteps(type, ct.transform1, wraparounds);
-            wraparounds = getSteps(type, ct.transform2, wraparounds);
-        }
-        return wraparounds;
-    }
-
-    /**
      * Returns the parameter descriptors for this math transform.
      *
      * @return the parameter descriptors for this math transform.


Mime
View raw message