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 f34cf02b40 Make possible to invoke 
`GridDerivation.subgrid(GridGeometry)` with an Area Of Interest having less 
dimensions that the base, even if the extra dimensions are not a slice.
f34cf02b40 is described below

commit f34cf02b40463c09083a3593a369af67ac08a1b0
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri Aug 1 19:05:56 2025 +0200

    Make possible to invoke `GridDerivation.subgrid(GridGeometry)`
    with an Area Of Interest having less dimensions that the base,
    even if the extra dimensions are not a slice.
---
 .../coverage/grid/CoordinateOperationFinder.java   | 43 +++++++++++++++++--
 .../apache/sis/coverage/grid/GridDerivation.java   | 45 ++++++++++++++++++--
 .../apache/sis/coverage/grid/SliceGeometry.java    |  2 +-
 .../sis/coverage/grid/GridDerivationTest.java      | 49 ++++++++++++++++++++++
 .../operation/CoordinateOperationContext.java      | 28 +++++++++----
 .../operation/CoordinateOperationFinder.java       | 27 ++++++++++--
 .../DefaultCoordinateOperationFactory.java         | 31 ++++++++------
 .../referencing/operation/SubOperationInfo.java    | 13 ++++--
 8 files changed, 202 insertions(+), 36 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/CoordinateOperationFinder.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/CoordinateOperationFinder.java
index ce1971a943..003fc80760 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/CoordinateOperationFinder.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/CoordinateOperationFinder.java
@@ -224,6 +224,15 @@ final class CoordinateOperationFinder implements 
Supplier<double[]> {
      */
     private boolean isWraparoundDisabled;
 
+    /**
+     * Whether the {@link #get()} method should accept dimensions that are not 
slices in a multi-dimensional cube.
+     * If 0 (the default), the dimensions are required to be slices. If 
non-zero, the requirement is relaxed, with
+     * -1 meaning to use the low grid coordinate and +1 meaning to use the 
high grid coordinate.
+     *
+     * @see #relaxSliceRequirement(byte)
+     */
+    private byte relaxSliceRequirement;
+
     /**
      * Creates a new finder initialized to {@link PixelInCell#CELL_CORNER} 
anchor.
      *
@@ -268,7 +277,7 @@ final class CoordinateOperationFinder implements 
Supplier<double[]> {
         anchor = newValue;
         gridToCRS = null;
         crsToGrid = null;
-        if (coordinates != null) {
+        if (hasUsedConstantValues()) {
             coordinates        = null;
             changeOfCRS        = null;
             forwardChangeOfCRS = null;
@@ -278,6 +287,18 @@ final class CoordinateOperationFinder implements 
Supplier<double[]> {
         }
     }
 
+    /**
+     * Sets whether the {@link #get()} method should accept dimensions that 
are not slices in a multi-dimensional cube.
+     *
+     * @param  mode  0 to require slices, -1 for using low grid coordinates, 
or +1 for high grid coordinates.
+     */
+    final void relaxSliceRequirement(final byte mode) {
+        relaxSliceRequirement = mode;
+        if (hasUsedConstantValues()) {
+            setAnchor(anchor);          // For cleaning the cache.
+        }
+    }
+
     /**
      * Disables completely all wraparounds operation.
      *
@@ -720,7 +741,7 @@ apply:          if (forwardChangeOfCRS == null) {
      * be given to the missing dimensions, then those values are returned.
      * Otherwise this method returns {@code null}.
      *
-     * <p>The returned array has a length equals to the number of dimensions 
in the target CRS.
+     * <p>The returned array has a length equals to the number of dimensions 
in the target <abbr>CRS</abbr>.
      * Only coordinates in dimensions without source (<var>t</var> in the 
above example) will be used.
      * All other coordinate values will be ignored.</p>
      *
@@ -736,10 +757,17 @@ apply:          if (forwardChangeOfCRS == null) {
             Arrays.fill(gc, Double.NaN);
             final GridExtent extent = target.getExtent();
             for (int i=0; i<gc.length; i++) {
-                final long low = extent.getLow(i);
-                if (low == extent.getHigh(i)) {
+                long low  = extent.getLow (i);
+                long high = extent.getHigh(i);
+                if (low == high || relaxSliceRequirement < 0) {
                     gc[i] = low;
+                } else if (relaxSliceRequirement > 0) {
+                    if (anchor != PixelInCell.CELL_CENTER) {
+                        high++;     // From inclusive to exclusive.
+                    }
+                    gc[i] = high;
                 }
+                // Otherwise keep the NaN value.
             }
             /*
              * At this point, the only grid coordinates with finite values are 
the ones where the
@@ -757,6 +785,13 @@ apply:          if (forwardChangeOfCRS == null) {
         return coordinates;
     }
 
+    /**
+     * Returns {@code true} if {@link #get()} has been invoked and computed 
coordinates.
+     */
+    final boolean hasUsedConstantValues() {
+        return coordinates != null;
+    }
+
     /**
      * Configures the accuracy hints on the given processor.
      *
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java
index 3bb5d54224..c3087896bc 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java
@@ -569,14 +569,15 @@ public class GridDerivation {
             final MathTransform nowraparound;
             final var finder = new CoordinateOperationFinder(areaOfInterest, 
base);
             finder.verifyPresenceOfCRS(false);
+            finder.relaxSliceRequirement((byte) -1);
             final GridExtent domain = areaOfInterest.extent;
             if (domain == null) {
                 finder.setAnchor(PixelInCell.CELL_CENTER);
                 finder.nowraparound();
-                nowraparound = finder.gridToGrid();
+                nowraparound = finder.gridToGrid();     // We will use only 
the scale factors.
             } else {
                 /*
-                 * Get the transform from the base grid to the grid of 
`areaOfInterest`.
+                 * Get the transform from the grid of `areaOfInterest` to the 
base grid.
                  * There is two variants, depending on whether the user wants 
tight box:
                  *
                  * - Default:   map pixel corners with exclusive upper grid 
coordinate value.
@@ -587,14 +588,50 @@ public class GridDerivation {
                     finder.setAnchor(PixelInCell.CELL_CENTER);
                 } // Else default value is PixelInCell.CELL_CORNER.
                 final MathTransform gridToGrid = finder.gridToGrid();
+                final MathTransform gridToGridHigh;
+                if (finder.hasUsedConstantValues()) {
+                    finder.relaxSliceRequirement((byte) +1);
+                    gridToGridHigh = finder.gridToGrid();
+                } else {
+                    gridToGridHigh = null;
+                }
+                /*
+                 * Compute again the transform from `areaOfInterest` grid to 
base grid,
+                 * but this time with no special treatment for longitude 
wraparound.
+                 * The main intent is to get the scale factors, which are 
hidden when
+                 * a wraparound shift is bundled in the transform.
+                 */
                 finder.setAnchor(tight ? PixelInCell.CELL_CORNER : 
PixelInCell.CELL_CENTER);
                 finder.nowraparound();
-                nowraparound = finder.gridToGrid();   // We will use only the 
scale factors.
+                final MathTransform nowraparoundHigh;
+                if (gridToGridHigh != null) {
+                    nowraparoundHigh = finder.gridToGrid();
+                    finder.relaxSliceRequirement((byte) -1);
+                } else {
+                    nowraparoundHigh = null;
+                }
+                nowraparound = finder.gridToGrid();
+                /*
+                 * Converts the grid extent of the area of interest to the 
grid coordinates of the base.
+                 * We may get one or two envelopes, depending on whether there 
is a longitude wraparound.
+                 * If the area of interest has less dimensions than the base 
grid, we may need to compute
+                 * more envelopes for enclosing the full span of dimensions 
that are not in `areaOfInterest`.
+                 */
                 final GeneralEnvelope[] envelopes;
-                if (gridToGrid.isIdentity()) {
+                if (gridToGrid.isIdentity() && (gridToGridHigh == null || 
gridToGridHigh.isIdentity())) {
                     envelopes = new GeneralEnvelope[] 
{domain.toEnvelope(tight)};
                 } else {
                     envelopes = domain.toEnvelopes(gridToGrid, tight, 
nowraparound, null);
+                    if (gridToGridHigh != null) {
+                        final GeneralEnvelope[] high = 
domain.toEnvelopes(gridToGridHigh, tight, nowraparoundHigh, null);
+                        for (int i = Math.min(envelopes.length, high.length); 
--i >= 0;) {
+                            /*
+                             * TODO: actually, we have no guarantee that the 
envelopes at the same index match.
+                             * We need to find a more reliable algorithm, 
maybe by checking intersection first.
+                             */
+                            envelopes[i].add(high[i]);
+                        }
+                    }
                 }
                 setBaseExtentClipped(tight, envelopes);
                 if (baseExtent != base.extent && baseExtent.equals(domain)) {
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/SliceGeometry.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/SliceGeometry.java
index d27bbd82c8..ec6e73472a 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/SliceGeometry.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/SliceGeometry.java
@@ -316,7 +316,7 @@ final class SliceGeometry implements 
Function<RenderedImage, GridGeometry> {
         }
         numRow += derivative.getNumRow();
         if (dimCRS < 0) {
-            dimCRS = gridDimensions.length;
+            dimCRS = Math.min(gridDimensions.length, 
gridToCRS.getTargetDimensions());
         }
         /*
          * Search for the greatest scale coefficient. For the greatest value, 
take the row as the target
diff --git 
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridDerivationTest.java
 
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridDerivationTest.java
index 86fe67d11e..0ed8c3b205 100644
--- 
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridDerivationTest.java
+++ 
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridDerivationTest.java
@@ -385,6 +385,55 @@ public final class GridDerivationTest extends TestCase {
         assertMatrixEquals(expected, gridToCRS, STRICT, "gridToCRS");
     }
 
+    /**
+     * Tests {@link GridDerivation#subgrid(GridGeometry)} using a grid with 
less dimensions
+     * than the source grid geometry when the extra dimensions are not a slice.
+     *
+     * <h4>Note on cache dependency</h4>
+     * Another difference between this test and {@link 
#testSubgridWithLessDimensions()} is that
+     * this test uses a geographic area covering the world. It has subtile 
implications in the way
+     * that {@link 
org.apache.sis.referencing.operation.CoordinateOperationFinder} uses its cache.
+     * See in particular the {@code canStoreInCache} flag of the latter class.
+     *
+     * @see <a href="https://issues.apache.org/jira/browse/SIS-610";>SIS-610</a>
+     */
+    @Test
+    public void testSubgridWithLessDimensionsNoSlice() {
+        final var envelope = new GeneralEnvelope(HardCodedCRS.WGS84_4D);
+        envelope.setRange(0, -180, 180);
+        envelope.setRange(1,  -90,  90);
+        envelope.setRange(2,  100, 300);
+        envelope.setRange(3,    3,   6);
+        final var extent = new GridExtent(null, null, new long[] {2, 2, 3, 3}, 
false);
+        final var grid   = new GridGeometry(extent, envelope, 
GridOrientation.DISPLAY);
+        final var env2D  = new GeneralEnvelope(HardCodedCRS.WGS84);
+        env2D.setRange(0, -51, 153);
+        env2D.setRange(1,  68,  89);
+        final var aoi = new GridGeometry(new GridExtent(100, 100), env2D, 
GridOrientation.HOMOTHETY);
+        /*
+         * Test with rounding to nearest grid coordinates. The AOI has a range 
of latitude values which
+         * is fully enclosed in the voxel at index y=1, while the range of 
longitude values intersects
+         * voxels at indexes x=[0…1]. However, because the rounding mode is 
nearest and the lower bound
+         * is closer to x=1, the result is x=[1].
+         */
+        GridGeometry slice = grid.derive().subgrid(aoi).build();
+        assertEquals(grid.getGridToCRS(PixelInCell.CELL_CENTER),
+                    slice.getGridToCRS(PixelInCell.CELL_CENTER));
+        assertExtentEquals(new long[] {1, 0, 0, 0},
+                           new long[] {1, 0, 2, 2},
+                           slice.getExtent());
+        /*
+         * Same as above but with rounding mode to "enclosed".
+         * The range in dimension of longitude become x=[0…1].
+         */
+        slice = 
grid.derive().rounding(GridRoundingMode.ENCLOSING).subgrid(aoi).build();
+        assertEquals(grid.getGridToCRS(PixelInCell.CELL_CENTER),
+                    slice.getGridToCRS(PixelInCell.CELL_CENTER));
+        assertExtentEquals(new long[] {0, 0, 0, 0},
+                           new long[] {1, 0, 2, 2},
+                           slice.getExtent());
+    }
+
     /**
      * Tests {@link GridDerivation#subgrid(Envelope, double...)} on a grid 
using a polar projection.
      * The test also uses a geographic envelope with more dimensions than the 
source grid geometry.
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationContext.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationContext.java
index 22936847f6..3f91b17c70 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationContext.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationContext.java
@@ -23,6 +23,7 @@ import org.opengis.metadata.extent.Extent;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.opengis.referencing.operation.CoordinateOperation;
 import org.opengis.referencing.operation.TransformException;
+import org.opengis.referencing.operation.OperationNotFoundException;
 import org.apache.sis.referencing.privy.CoordinateOperations;
 import org.apache.sis.metadata.iso.extent.DefaultExtent;
 import org.apache.sis.metadata.iso.extent.Extents;
@@ -225,16 +226,17 @@ public class CoordinateOperationContext implements 
Serializable {
     }
 
     /**
-     * Invoked when some coordinates in the target CRS cannot be computed from 
coordinates in the source CRS.
-     * For example if the source CRS has (<var>x</var>, <var>y</var>) axes and 
the target CRS has (<var>x</var>,
-     * <var>y</var>, <var>t</var>) axes, then this method is invoked for 
determining which value to assign to the
-     * <var>t</var> coordinate. In some cases the user can tell that the 
coordinate should be set to a constant value.
+     * Invoked when at least one coordinate in the target <abbr>CRS</abbr> 
cannot be computed from the coordinates in
+     * the source <abbr>CRS</abbr>. For example, if the source 
<abbr>CRS</abbr> has (<var>x</var>, <var>y</var>) axes
+     * and the target <abbr>CRS</abbr> has (<var>x</var>, <var>y</var>, 
<var>t</var>) axes,
+     * then this method is invoked for determining which value to assign to 
the <var>t</var> coordinate.
+     * In some cases, the user can tell that the coordinate should be set to a 
constant value.
      *
-     * <p>If this method returns {@code null} (which is the default), then the 
{@link CoordinateOperationFinder} caller
-     * will throw an {@link 
org.opengis.referencing.operation.OperationNotFoundException}. Otherwise the 
returned array
-     * should have a length equals to the number of dimensions in the full 
(usually compound) target CRS.
-     * Only coordinate values in dimensions without source (the <var>t</var> 
dimension in the above example) will be used.
-     * All other coordinate values will be ignored.
+     * <p>If this method returns {@code null}, then the {@link 
CoordinateOperationFinder} caller will throw an
+     * {@link OperationNotFoundException}. Otherwise, the returned array 
should have a length equals to the number
+     * of dimensions in the full (usually compound) target <abbr>CRS</abbr>. 
Only coordinate values in dimensions
+     * without source (the <var>t</var> dimension in the above example) will 
be used.
+     * All other coordinate values will be ignored.</p>
      *
      * @return coordinate values to take as constants for the specified target 
component, or {@code null} if none.
      * @throws TransformException if the coordinates cannot be computed. This 
exception may occur when the constant
@@ -251,4 +253,12 @@ public class CoordinateOperationContext implements 
Serializable {
         }
         return null;
     }
+
+    /**
+     * Returns whether it is safe to use cached operation.
+     * This is {@code false} if the operation result may depend on external 
configuration.
+     */
+    static boolean canReadFromCache() {
+        return CoordinateOperations.CONSTANT_COORDINATES.get() == null;
+    }
 }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
index d28975397b..e127c1d49f 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
@@ -139,7 +139,15 @@ public class CoordinateOperationFinder extends 
CoordinateOperationRegistry {
     /**
      * Whether this finder instance is allowed to use {@link 
DefaultCoordinateOperationFactory#cache}.
      */
-    private final boolean useCache;
+    private final boolean canReadFromCache;
+
+    /**
+     * Whether the operation can be cached. This flag shall be {@code false} if
+     * the operation depends on parameters that may vary between two 
executions.
+     *
+     * @see #canStoreInCache()
+     */
+    private boolean canStoreInCache;
 
     /**
      * Creates a new instance for the given factory and context.
@@ -158,7 +166,8 @@ public class CoordinateOperationFinder extends 
CoordinateOperationRegistry {
         super(registry, factory, context);
         identifierOfStepCRS = new HashMap<>(8);
         previousSearches    = new HashMap<>(8);
-        useCache = (context == null) && (factory == factorySIS);
+        canReadFromCache    = (context == null) && (factory == factorySIS) && 
CoordinateOperationContext.canReadFromCache();
+        canStoreInCache     = (context == null);
     }
 
     /**
@@ -243,7 +252,7 @@ public class CoordinateOperationFinder extends 
CoordinateOperationRegistry {
          * is not in the cache, store the key in our internal map for 
preventing infinite recursion.
          */
         final CRSPair key = new CRSPair(sourceCRS, targetCRS);
-        if (useCache && stopAtFirst && !previousSearches.isEmpty()) {
+        if (canReadFromCache && stopAtFirst && !previousSearches.isEmpty()) {
             final CoordinateOperation op = factorySIS.cache.peek(key);
             if (op != null) return asList(op);      // Must be a modifiable 
list as per this method contract.
         }
@@ -858,6 +867,7 @@ public class CoordinateOperationFinder extends 
CoordinateOperationRegistry {
             final SubOperationInfo          info   = infos[i];
             final CoordinateReferenceSystem source = stepComponents[i];
             final CoordinateReferenceSystem target = 
targetComponents.get(info.targetComponentIndex);
+            canStoreInCache &= info.canStoreInCache;
             /*
              * In order to compute `stepTargetCRS`, replace in-place a single 
element in `stepComponents`.
              * For each step except the last one, `stepTargetCRS` is a mix of 
target CRS and source CRS.
@@ -910,6 +920,9 @@ public class CoordinateOperationFinder extends 
CoordinateOperationRegistry {
                     stepSourceCRS.getCoordinateSystem().getDimension(),
                         targetCRS.getCoordinateSystem().getDimension());
             operation = concatenate(operation, 
createFromAffineTransform(CONSTANTS, stepSourceCRS, targetCRS, null, m));
+            for (int i = stepComponents.length; i < infos.length; i++) {
+                canStoreInCache &= infos[i].canStoreInCache;
+            }
         } catch (TransformException e) {
             throw new FactoryException(notFoundMessage(sourceCRS, targetCRS), 
e);
         }
@@ -1172,4 +1185,12 @@ public class CoordinateOperationFinder extends 
CoordinateOperationRegistry {
     private String canNotInvert(final DerivedCRS crs) {
         return resources().getString(Resources.Keys.NonInvertibleOperation_1, 
label(crs.getConversionFromBase()));
     }
+
+    /**
+     * Returns whether the operation can be cached. This is {@code false} if
+     * the operation depends on parameters that may vary between two 
executions.
+     */
+    final boolean canStoreInCache() {
+        return canStoreInCache;
+    }
 }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
index 7c3a3e067b..8e24b9dc6e 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
@@ -704,20 +704,16 @@ next:   for (SingleCRS component : 
CRS.getSingleComponents(targetCRS)) {
             handler = null;
             op = null;
         }
+        boolean canStoreInCache = true;
         try {
             if (handler == null || (op = handler.peek()) == null) {
-                CoordinateOperationAuthorityFactory registry = null;
-                if (USE_EPSG_FACTORY) {
-                    final AuthorityFactory candidate = 
CRS.getAuthorityFactory(Constants.EPSG);
-                    if (candidate instanceof 
CoordinateOperationAuthorityFactory) {
-                        registry = (CoordinateOperationAuthorityFactory) 
candidate;
-                    }
-                }
-                op = createOperationFinder(registry, 
context).createOperation(sourceCRS, targetCRS);
+                final CoordinateOperationFinder finder = 
createOperationFinder(getFactorySIS(), context);
+                op = finder.createOperation(sourceCRS, targetCRS);
+                canStoreInCache = finder.canStoreInCache();
             }
         } finally {
             if (handler != null) {
-                handler.putAndUnlock(op);
+                handler.putAndUnlock(canStoreInCache ? op : null);
             }
         }
         return op;
@@ -756,9 +752,20 @@ next:   for (SingleCRS component : 
CRS.getSingleComponents(targetCRS)) {
                                                       final 
CoordinateOperationContext context)
             throws OperationNotFoundException, FactoryException
     {
-        final AuthorityFactory registry = USE_EPSG_FACTORY ? 
CRS.getAuthorityFactory(Constants.EPSG) : null;
-        return createOperationFinder((registry instanceof 
CoordinateOperationAuthorityFactory) ?
-                (CoordinateOperationAuthorityFactory) registry : null, 
context).createOperations(sourceCRS, targetCRS);
+        return createOperationFinder(getFactorySIS(), 
context).createOperations(sourceCRS, targetCRS);
+    }
+
+    /**
+     * Returns the Apache <abbr>SIS</abbr> implementation of the 
<abbr>EPSG</abbr> factory, or {@code null} if none.
+     */
+    private static CoordinateOperationAuthorityFactory getFactorySIS() throws 
FactoryException {
+        if (USE_EPSG_FACTORY) {
+            AuthorityFactory registry = 
CRS.getAuthorityFactory(Constants.EPSG);
+            if (registry instanceof CoordinateOperationAuthorityFactory) {
+                return (CoordinateOperationAuthorityFactory) registry;
+            }
+        }
+        return null;
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubOperationInfo.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubOperationInfo.java
index 6b63c2b0d1..767ba53262 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubOperationInfo.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubOperationInfo.java
@@ -116,6 +116,12 @@ final class SubOperationInfo {
      */
     final int targetComponentIndex;
 
+    /**
+     * Whether this operation can be cached. This flag shall be {@code false} 
if
+     * the operation depends on parameters that may vary between two 
executions.
+     */
+    final boolean canStoreInCache;
+
     /**
      * Creates a new instance wrapping the given coordinate operation or 
coordinate constants.
      * Exactly one of {@code operation} or {@code constants} shall be non-null.
@@ -123,7 +129,7 @@ final class SubOperationInfo {
     private SubOperationInfo(final CoordinateOperation operation, final 
double[] constants,
                              final int sourceLowerDimension, final int 
sourceUpperDimension,
                              final int targetLowerDimension, final int 
targetUpperDimension,
-                             final int targetComponentIndex)
+                             final int targetComponentIndex, final boolean 
canStoreInCache)
     {
         this.operation            = operation;
         this.constants            = constants;
@@ -132,6 +138,7 @@ final class SubOperationInfo {
         this.targetLowerDimension = targetLowerDimension;
         this.targetUpperDimension = targetUpperDimension;
         this.targetComponentIndex = targetComponentIndex;
+        this.canStoreInCache      = canStoreInCache;
         assert (operation == null) != (constants == null);
     }
 
@@ -216,7 +223,7 @@ next:   for (int targetComponentIndex = 0; 
targetComponentIndex < infos.length;
                                             operation, null,
                                             sourceLowerDimension, 
sourceUpperDimension,
                                             targetLowerDimension, 
targetUpperDimension,
-                                            targetComponentIndex);
+                                            targetComponentIndex, true);
 
                                     if (failure != null) {
                                         
CoordinateOperationRegistry.recoverableException("decompose", failure);
@@ -253,7 +260,7 @@ next:   for (int targetComponentIndex = 0; 
targetComponentIndex < infos.length;
                     null, constants,
                     sourceUpperDimension, sourceUpperDimension,
                     targetLowerDimension, targetUpperDimension,
-                    targetComponentIndex);
+                    targetComponentIndex, false);
         }
         return infos;
     }

Reply via email to