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 de19b988f90d84c05efb4ea2f34a1729df5bc0e5
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Sun Feb 22 11:45:45 2026 +0100

    Finer-grain detection of when the `CoordinateOperation` can be cached for a 
pair of CRS.
    In particular, we now detect the case when the Area Of Interest (AOI) has 
not been used.
    It covers a lot of cases, in particular map projections from grid geometry 
to target CRS.
---
 .../operation/CoordinateOperationContext.java      | 31 ++++++++++-
 .../operation/CoordinateOperationFinder.java       | 30 +++--------
 .../operation/CoordinateOperationRegistry.java     | 63 +++++++++++++++++-----
 .../DefaultCoordinateOperationFactory.java         | 58 ++++++++++----------
 .../referencing/operation/SubOperationInfo.java    |  8 +--
 .../sis/referencing/operation/package-info.java    |  2 +-
 6 files changed, 124 insertions(+), 68 deletions(-)

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 1df1f3f0d0..93c24183af 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
@@ -62,7 +62,7 @@ import org.apache.sis.measure.Longitude;
  * to Texas, but not suitable to the rest of North-America.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.6
+ * @version 1.7
  * @since   0.7
  *
  * @todo Should also take the country of a {@link java.util.Locale}. The EPSG 
database contains ISO2 and ISO3
@@ -98,6 +98,15 @@ public class CoordinateOperationContext implements Localized 
{
      */
     private transient Filter logFilter;
 
+    /**
+     * Whether the coordinate operation created with this context depends on 
the context values.
+     * This flag is set to {@code true} when {@link CoordinateOperationFinder} 
has used a contextual information such
+     * as the area of interest, desired accuracy, constant coordinate values 
or operation filter for choosing a result.
+     *
+     * @see #resultWasContextSensitive()
+     */
+    boolean resultWasContextSensitive;
+
     /**
      * Creates a new context with no area of interest and the best accuracy 
available.
      */
@@ -414,4 +423,24 @@ public class CoordinateOperationContext implements 
Localized {
         }
         logger.log(record);
     }
+
+    /**
+     * Returns whether the coordinate operation created with this context 
depends on the context values.
+     * This is {@code true} if the last {@link CoordinateOperationFinder} that 
used this context needed
+     * information such as
+     * the  {@linkplain #getAreaOfInterest() area of interest},
+     * the  {@linkplain #getDesiredAccuracy() desired accuracy},
+     * some {@linkplain #getConstantCoordinates() constant coordinate values} 
or
+     * the  {@linkplain #getOperationFilter() operation filter} for choosing a 
result.
+     * If this flag is {@code false}, then multiple calls to {@code 
createOperation(…)}
+     * with the same pair of <abbr>CRS</abbr> are likely to provide the same 
result no matter the context.
+     * This information information is useful for deciding whether to cache a 
result.
+     *
+     * @return whether the coordinate operation created with this context 
depends on the context values.
+     *
+     * @since 1.7
+     */
+    public boolean resultWasContextSensitive() {
+        return resultWasContextSensitive;
+    }
 }
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 ca2d90fdd4..be099c5216 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
@@ -113,7 +113,7 @@ import org.apache.sis.util.resources.Vocabulary;
  * </ul>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.5
+ * @version 1.7
  *
  * @see 
DefaultCoordinateOperationFactory#createOperation(CoordinateReferenceSystem, 
CoordinateReferenceSystem, CoordinateOperationContext)
  *
@@ -146,14 +146,6 @@ public class CoordinateOperationFinder extends 
CoordinateOperationRegistry {
      */
     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.
      *
@@ -172,7 +164,6 @@ public class CoordinateOperationFinder extends 
CoordinateOperationRegistry {
         identifierOfStepCRS = new HashMap<>(8);
         previousSearches    = new HashMap<>(8);
         canReadFromCache    = (context == null || context.canReadFromCache()) 
&& (factory == factorySIS);
-        canStoreInCache     = (context == null);
     }
 
     /**
@@ -253,13 +244,13 @@ public class CoordinateOperationFinder extends 
CoordinateOperationRegistry {
         /*
          * If this method is invoked recursively, verify if the requested 
operation is already in the cache.
          * We do not perform this verification on the first invocation because 
it was already verified by
-         * DefaultCoordinateOperationFactory.createOperation(…). We do not 
block if the operation is in
+         * `DefaultCoordinateOperationFactory.createOperation(…)`. We do not 
block if the operation is in
          * process of being computed in another thread because of the risk of 
deadlock. If the operation
          * is not in the cache, store the key in our internal map for 
preventing infinite recursion.
          */
         final CRSPair key = new CRSPair(sourceCRS, targetCRS);
         if (canReadFromCache && stopAtFirst && !previousSearches.isEmpty()) {
-            final CoordinateOperation op = factorySIS.cache.peek(key);
+            final CoordinateOperation op = factorySIS.getCachedOperation(key);
             if (op != null) return asList(op);      // Must be a modifiable 
list as per this method contract.
         }
         if (previousSearches.put(key, Boolean.TRUE) != null) {
@@ -559,6 +550,7 @@ public class CoordinateOperationFinder extends 
CoordinateOperationRegistry {
                 final var impl = (DefaultGeodeticDatum) sourceDatum;
                 datumShift = impl.getPositionVectorTransformation(targetDatum, 
areaOfInterest);
                 if (datumShift != null) typeOfChange = DATUM_SHIFT;
+                resultWasContextSensitive(specifiedAOI);
             } else if (targetDatum instanceof DefaultGeodeticDatum) {
                 final var impl = (DefaultGeodeticDatum) targetDatum;
                 Matrix matrix = 
impl.getPositionVectorTransformation(sourceDatum, areaOfInterest);
@@ -570,6 +562,7 @@ public class CoordinateOperationFinder extends 
CoordinateOperationRegistry {
                     matrix = null;
                 }
                 datumShift = matrix;
+                resultWasContextSensitive(specifiedAOI);
             } else {
                 datumShift = null;
             }
@@ -586,6 +579,7 @@ public class CoordinateOperationFinder extends 
CoordinateOperationRegistry {
             }
             method     = builder.getMethod();
             parameters = builder.parametersForMetadata();
+            resultWasContextSensitive(specifiedAccuracy);
         }
         /*
          * Adjust the accuracy information if the datum shift has been 
computed by an indirect path.
@@ -967,7 +961,7 @@ public class CoordinateOperationFinder extends 
CoordinateOperationRegistry {
             final SubOperationInfo          info   = infos[i];
             final CoordinateReferenceSystem source = stepComponents[i];
             final CoordinateReferenceSystem target = info.targetComponent;
-            canStoreInCache &= info.canStoreInCache();
+            resultWasContextSensitive(info.resultWasContextSensitive());
             /*
              * 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.
@@ -1021,7 +1015,7 @@ public class CoordinateOperationFinder extends 
CoordinateOperationRegistry {
                         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();
+                
resultWasContextSensitive(infos[i].resultWasContextSensitive());
             }
         }
         return asList(operation);
@@ -1304,12 +1298,4 @@ public class CoordinateOperationFinder extends 
CoordinateOperationRegistry {
                 Resources.Keys.NonInvertibleOperation_1,
                 CRSPair.label(crs.getConversionFromBase(), locale));
     }
-
-    /**
-     * 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/CoordinateOperationRegistry.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
index bb4f667fdc..c3f05e75e7 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
@@ -230,13 +230,30 @@ class CoordinateOperationRegistry {
      */
     protected Extent areaOfInterest;
 
+    /**
+     * Whether a non-default {@link #areaOfInterest} value was specified 
explicitly by the context.
+     * If this flag is {@code true}, then {@link 
context#operationIsContextSensitive} should be set
+     * to {@code true} when {@link #areaOfInterest} is used.
+     */
+    final boolean specifiedAOI;
+
     /**
      * The desired accuracy in metres, or 0 for the best accuracy available.
+     * When a new {@code CoordinateOperationFinder} instance is created with a 
non-null
+     * {@link CoordinateOperationContext}, the context is used for 
initializing this value.
+     * After initialization, this field may be updated at implementation 
discretion.
      *
      * @see CoordinateOperationContext#getDesiredAccuracy()
      */
     protected double desiredAccuracy;
 
+    /**
+     * Whether a non-default {@link #desiredAccuracy} value was specified 
explicitly by the context.
+     * If this flag is {@code true}, then {@link 
context#operationIsContextSensitive} should be set
+     * to {@code true} when {@link #desiredAccuracy} is used.
+     */
+    final boolean specifiedAccuracy;
+
     /**
      * {@code true} if {@code search(…)} should stop after the first 
coordinate operation found.
      * This field is set to {@code true} when the caller is interested only in 
the "best" operation
@@ -299,6 +316,8 @@ class CoordinateOperationRegistry {
             areaOfInterest  = context.getAreaOfInterest();
             desiredAccuracy = context.getDesiredAccuracy();
         }
+        specifiedAOI = (areaOfInterest != null);
+        specifiedAccuracy = (desiredAccuracy > 0);
     }
 
     /**
@@ -656,15 +675,18 @@ class CoordinateOperationRegistry {
          * then we need to get one from the CRS. This is necessary for 
preventing the transformation from
          * NAD27 to NAD83 in Idaho to select the transform for Alaska (since 
the latter has a larger area).
          */
-        CoordinateOperationSorter.sort(operations, 
Extents.getGeographicBoundingBox(areaOfInterest));
+        if (operations.size() > 1) {
+            resultWasContextSensitive(specifiedAOI);
+            CoordinateOperationSorter.sort(operations, 
Extents.getGeographicBoundingBox(areaOfInterest));
+        }
         final ListIterator<CoordinateOperation> it = operations.listIterator();
         while (it.hasNext()) {
             /*
              * At this point we filtered a CoordinateOperation by looking only 
at its metadata.
              * Code following this point will need the full coordinate 
operation, including its
-             * MathTransform. So if we got a deferred operation, we need to 
resolve it now.
+             * `MathTransform`. So if we got a deferred operation, we need to 
resolve it now.
              * Conversely, we should not use metadata below this point because 
the call to
-             * inverse(CoordinateOperation) is not guaranteed to preserve all 
metadata.
+             * `inverse(CoordinateOperation)` is not guaranteed to preserve 
all metadata.
              */
             CoordinateOperation operation = it.next();
             try {
@@ -705,21 +727,38 @@ class CoordinateOperationRegistry {
              * FactoryException propagate.
              */
             operation = complete(operation, sourceCRS, targetCRS);
-            final Predicate<CoordinateOperation> filter = (context != null) ? 
context.getOperationFilter() : null;
-            if (filter == null || filter.test(operation)) {
-                if (stopAtFirst) {
-                    operations.clear();
-                    operations.add(operation);
-                    break;
+            if (context != null) {
+                final Predicate<CoordinateOperation> filter = 
context.getOperationFilter();
+                if (filter != null) {
+                    resultWasContextSensitive(true);
+                    if (!filter.test(operation)) {
+                        it.remove();
+                        continue;
+                    }
                 }
-                it.set(operation);
-            } else {
-                it.remove();
             }
+            if (stopAtFirst) {
+                operations.clear();
+                operations.add(operation);
+                break;
+            }
+            it.set(operation);
         }
         return operations;
     }
 
+    /**
+     * If the given flag is {@code true}, declares that the search for 
coordinate operations
+     * depends on the values of the {@code context} argument specified at 
construction time.
+     *
+     * @param  flag  whether the coordinate operation depends on the context 
according the caller.
+     */
+    final void resultWasContextSensitive(final boolean flag) {
+        if (flag && context != null) {
+            context.resultWasContextSensitive = true;
+        }
+    }
+
     /**
      * Creates the inverse of the given single operation.
      * If this operation succeed, then the returned coordinate operation has 
the following properties:
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 6efdc5f69c..bd6f8f728f 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
@@ -80,7 +80,7 @@ import org.apache.sis.util.resources.Errors;
  * This class is safe for multi-thread usage if all referenced factories are 
thread-safe.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.5
+ * @version 1.7
  * @since   0.6
  */
 public class DefaultCoordinateOperationFactory extends AbstractFactory 
implements CoordinateOperationFactory {
@@ -124,12 +124,12 @@ public class DefaultCoordinateOperationFactory extends 
AbstractFactory implement
 
     /**
      * The cache of coordinate operations found for a given pair of source and 
target CRS.
-     * If current implementation, we cache only operations found without 
context (otherwise
-     * we would need to take in account the area of interest and desired 
accuracy in the key).
+     * We cache only operations found when {@link CoordinateOperationContext} 
values were
+     * not used (otherwise we would need to store some context values in the 
key).
      *
      * @see #createOperation(CoordinateReferenceSystem, 
CoordinateReferenceSystem, CoordinateOperationContext)
      */
-    final Cache<CRSPair,CoordinateOperation> cache;
+    private final Cache<CRSPair, CoordinateOperation> cache;
 
     /**
      * The default factory instance.
@@ -665,35 +665,37 @@ next:   for (SingleCRS component : 
CRS.getSingleComponents(targetCRS)) {
                                                final 
CoordinateOperationContext context)
             throws OperationNotFoundException, FactoryException
     {
-        final Cache.Handler<CoordinateOperation> handler;
-        CoordinateOperation op;
-        if (context == null) {
-            final CRSPair key = new CRSPair(sourceCRS, targetCRS);
-            op = cache.peek(key);
-            if (op != null) {
-                return op;
-            }
-            handler = cache.lock(key);
-        } else {
-            // We currently do not cache the operation when the result may 
depend on the context (see `this.cache` javadoc).
-            handler = null;
-            op = null;
-        }
-        boolean canStoreInCache = true;
-        try {
-            if (handler == null || (op = handler.peek()) == null) {
-                final CoordinateOperationFinder finder = 
createOperationFinder(getFactorySIS(), context);
-                op = finder.createOperation(sourceCRS, targetCRS);
-                canStoreInCache = finder.canStoreInCache();
-            }
-        } finally {
-            if (handler != null) {
-                handler.putAndUnlock(canStoreInCache ? op : null);
+        final var key = new CRSPair(sourceCRS, targetCRS);
+        CoordinateOperation op = getCachedOperation(key);
+        if (op == null) {
+            boolean resultWasContextSensitive = false;
+            final Cache.Handler<CoordinateOperation> handler = cache.lock(key);
+            try {
+                op = handler.peek();
+                if (op == null) {
+                    final CoordinateOperationFinder finder = 
createOperationFinder(getFactorySIS(), context);
+                    op = finder.createOperation(sourceCRS, targetCRS);
+                    if (context != null) {
+                        resultWasContextSensitive = 
context.resultWasContextSensitive();
+                    }
+                }
+            } finally {
+                handler.putAndUnlock(resultWasContextSensitive ? null : op);
             }
         }
         return op;
     }
 
+    /**
+     * Returns an operation for the given pair of <abbr>CRS</abbr> if that 
operation is already in the cache.
+     *
+     * @param  key  pair of <abbr>CRS</abbr> for which to get an operation.
+     * @return the cached operation for the given pair of <abbr>CRS</abbr>, or 
{@code null} if none.
+     */
+    final CoordinateOperation getCachedOperation(final CRSPair key) {
+        return cache.peek(key);
+    }
+
     /**
      * Finds or creates operations for conversions or transformations between 
two coordinate reference systems.
      * If at least one operation exists, they are returned in preference 
order: the operation having the widest
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 676674b804..1e85a2f4df 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
@@ -479,10 +479,10 @@ otherRow:           for (int j = last.getNumRow() - 1; 
--j >= 0;) {     // Ignor
     }
 
     /**
-     * Whether this operation can be cached. This flag shall be {@code false} 
if
-     * the operation depends on parameters that may vary between two 
executions.
+     * Whether the operation result depends on the values of the user-supplied 
{@link CoordinateOperationContext}.
+     * This flag shall be {@code true} if the operation depends on parameters 
that may vary between two executions.
      */
-    final boolean canStoreInCache() {
-        return constantCoordinates == null;
+    final boolean resultWasContextSensitive() {
+        return constantCoordinates != null;
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/package-info.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/package-info.java
index d9b422b818..ef22720164 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/package-info.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/package-info.java
@@ -72,7 +72,7 @@
  * for example by specifying the area of interest.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.6
+ * @version 1.7
  * @since   0.6
  */
 @XmlSchema(location = 
"http://schemas.opengis.net/gml/3.2.1/coordinateOperations.xsd";,

Reply via email to