You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2026/02/22 16:31:40 UTC
(sis) 01/01: 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.
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 <ma...@geomatys.com>
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",