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 3ce6fb642bb95199cd8e785487cf37648bd2cccd
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Thu Jul 18 18:51:11 2024 +0200

    Determine the accuracy of a coordinate operation through datum ensemble.
    The method added in this commit still need to be used by 
`CoordinateOperationFinder`.
---
 endorsed/build.gradle.kts                          |   3 +
 .../iso/quality/DefaultEvaluationMethod.java       |  16 ++-
 .../iso/quality/DefaultMeasureReference.java       |  14 +-
 .../{internal => privy}/RecordSchemaSIS.java       |   5 +-
 .../sis/metadata/privy/TransformationAccuracy.java |  90 -------------
 .../org/apache/sis/util/iso/DefaultRecord.java     |   2 +-
 .../org/apache/sis/util/iso/DefaultRecordType.java |   2 +-
 .../iso/quality/DefaultQuantitativeResultTest.java |   2 +-
 .../sis/openoffice/ReferencingFunctionsTest.java   |   2 +-
 .../apache/sis/io/wkt/GeodeticObjectParser.java    |   4 +-
 .../main/org/apache/sis/referencing/CRS.java       |   2 +-
 .../main/org/apache/sis/referencing/CommonCRS.java |  10 +-
 .../sis/referencing/MultiRegisterOperations.java   |  10 +-
 .../referencing/datum/DefaultDatumEnsemble.java    |   4 +-
 .../apache/sis/referencing/datum/PseudoDatum.java  | 132 ++++++++++++++++---
 .../referencing/factory/sql/EPSGDataAccess.java    |  42 +++---
 .../sis/referencing/internal/AnnotatedMatrix.java  |   1 -
 .../PositionalAccuracyConstant.java                | 144 +++++++++++++++------
 .../apache/sis/referencing/internal/Resources.java |  10 ++
 .../sis/referencing/internal/Resources.properties  |   4 +-
 .../referencing/internal/Resources_fr.properties   |   4 +-
 .../operation/AbstractCoordinateOperation.java     |   2 +-
 .../operation/CoordinateOperationContext.java      |   2 +-
 .../operation/CoordinateOperationRegistry.java     |   6 +-
 .../operation/DefaultConcatenatedOperation.java    |   2 +-
 .../referencing/privy/ReferencingUtilities.java    |  24 ----
 .../datum/DefaultGeodeticDatumTest.java            |   2 +-
 .../internal/PositionalAccuracyConstantTest.java   | 111 ++++++++++++++++
 .../operation/CoordinateOperationFinderTest.java   |   4 +-
 .../DefaultCoordinateOperationFactoryTest.java     |   2 +-
 .../privy/PositionalAccuracyConstantTest.java      |  68 ----------
 .../sis/util/collection/WeakValueHashMap.java      |  24 ++++
 netbeans-project/nbproject/project.properties      |   1 +
 33 files changed, 456 insertions(+), 295 deletions(-)

diff --git a/endorsed/build.gradle.kts b/endorsed/build.gradle.kts
index 0872b955bd..c4252f8581 100644
--- a/endorsed/build.gradle.kts
+++ b/endorsed/build.gradle.kts
@@ -175,6 +175,9 @@ fun addExportForTests(args : MutableList<String>) {
     addExport(args, "org.apache.sis.metadata",          
"org.apache.sis.xml.bind.gcx",
                     "org.apache.sis.referencing")
 
+    addExport(args, "org.apache.sis.referencing",       
"org.apache.sis.referencing.internal",
+                    "org.apache.sis.openoffice")
+
     addExport(args, "org.apache.sis.feature",           
"org.apache.sis.feature.privy",
                     "org.apache.sis.storage.sql")
 
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultEvaluationMethod.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultEvaluationMethod.java
index 2d5f6c2e8f..256a17165b 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultEvaluationMethod.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultEvaluationMethod.java
@@ -30,6 +30,7 @@ import org.opengis.util.InternationalString;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.quality.EvaluationMethodType;
 import org.apache.sis.system.Semaphores;
+import org.apache.sis.util.iso.Types;
 import org.apache.sis.util.privy.CloneAccess;
 import org.apache.sis.util.collection.CheckedContainer;
 import org.apache.sis.util.resources.Errors;
@@ -57,7 +58,7 @@ import org.opengis.metadata.quality.AggregationDerivation;
  *
  * @author  Alexis Gaillard (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.4
+ * @version 1.5
  * @since   1.3
  */
 @XmlType(name = "DQ_EvaluationMethod_Type", propOrder = {
@@ -274,6 +275,19 @@ public class DefaultEvaluationMethod extends ISOMetadata 
implements EvaluationMe
     public DefaultEvaluationMethod() {
     }
 
+    /**
+     * Constructs an evaluation method initialized to the given description.
+     *
+     * @param type  the method type, or {@code null} if none.
+     * @param name  the method description as a {@link String} or an {@link 
InternationalString} object,
+     *              or {@code null} if none.
+     * @since 1.5
+     */
+    public DefaultEvaluationMethod(final EvaluationMethodType type, final 
CharSequence description) {
+        evaluationMethodType = type;
+        evaluationMethodDescription = Types.toInternationalString(description);
+    }
+
     /**
      * Constructs a new instance initialized with the values from the 
specified metadata object.
      * This is a <em>shallow</em> copy constructor, because the other metadata 
contained in the
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultMeasureReference.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultMeasureReference.java
index f540568839..cfd4a3e88c 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultMeasureReference.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultMeasureReference.java
@@ -24,6 +24,7 @@ import org.opengis.util.InternationalString;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.quality.Element;
 import org.apache.sis.util.privy.CollectionsExt;
+import org.apache.sis.util.iso.Types;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.metadata.quality.MeasureReference;
@@ -44,7 +45,7 @@ import org.opengis.metadata.quality.MeasureReference;
  *
  * @author  Alexis Gaillard (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.4
+ * @version 1.5
  * @since   1.3
  */
 @XmlType(name = "DQ_MeasureReference_Type", propOrder = {
@@ -83,6 +84,17 @@ public class DefaultMeasureReference extends ISOMetadata 
implements MeasureRefer
     public DefaultMeasureReference() {
     }
 
+    /**
+     * Constructs a measure reference initialized with the given name.
+     *
+     * @param name  the name of the measure as a {@link String} or an {@link 
InternationalString} object,
+     *              or {@code null} if none.
+     * @since 1.5
+     */
+    public DefaultMeasureReference(final CharSequence name) {
+        namesOfMeasure = singleton(Types.toInternationalString(name), 
InternationalString.class);
+    }
+
     /**
      * Constructs a new instance initialized with the values from the 
specified metadata object.
      * This is a <em>shallow</em> copy constructor, because the other metadata 
contained in the
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/RecordSchemaSIS.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/privy/RecordSchemaSIS.java
similarity index 95%
rename from 
endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/RecordSchemaSIS.java
rename to 
endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/privy/RecordSchemaSIS.java
index 6a7180f314..dba0d52b2e 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/RecordSchemaSIS.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/privy/RecordSchemaSIS.java
@@ -14,11 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.metadata.internal;
+package org.apache.sis.metadata.privy;
 
 import java.util.Map;
 import java.io.Serializable;
 import java.io.ObjectStreamException;
+import org.apache.sis.metadata.internal.Resources;
 import org.opengis.util.TypeName;
 import org.opengis.util.InternationalString;
 import org.apache.sis.util.privy.Constants;
@@ -32,7 +33,7 @@ import org.apache.sis.util.resources.Vocabulary;
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-@SuppressWarnings("serial")  // serialVersionUID not needed because of 
writeReplace().
+@SuppressWarnings({"serial", "removal"})  // serialVersionUID not needed 
because of writeReplace().
 public final class RecordSchemaSIS extends DefaultRecordSchema implements 
Serializable {
     /**
      * The schema used in SIS for creating records.
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/privy/TransformationAccuracy.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/privy/TransformationAccuracy.java
deleted file mode 100644
index 99743b8aa8..0000000000
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/privy/TransformationAccuracy.java
+++ /dev/null
@@ -1,90 +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.metadata.privy;
-
-import java.util.List;
-import org.opengis.util.RecordType;
-import org.opengis.util.InternationalString;
-import org.opengis.metadata.quality.PositionalAccuracy;
-import org.opengis.metadata.quality.EvaluationMethodType;
-import org.apache.sis.measure.Units;
-import org.apache.sis.metadata.internal.RecordSchemaSIS;
-import org.apache.sis.metadata.iso.quality.DefaultQuantitativeResult;
-import 
org.apache.sis.metadata.iso.quality.DefaultAbsoluteExternalPositionalAccuracy;
-import org.apache.sis.util.Static;
-import org.apache.sis.util.collection.WeakValueHashMap;
-import org.apache.sis.util.iso.DefaultRecord;
-import org.apache.sis.util.resources.Vocabulary;
-
-
-/**
- * Creates a record reporting coordinate transformation accuracy.
- *
- * @author  Martin Desruisseaux (Geomatys)
- */
-public final class TransformationAccuracy extends Static {
-    /**
-     * The name for the transformation accuracy metadata.
-     */
-    private static final InternationalString TRANSFORMATION_ACCURACY =
-            
Vocabulary.formatInternational(Vocabulary.Keys.TransformationAccuracy);
-
-    /**
-     * Cache the positional accuracies. Most coordinate operation use a small 
set of accuracy values.
-     */
-    private static final WeakValueHashMap<Double,PositionalAccuracy> CACHE = 
new WeakValueHashMap<>(Double.class);
-
-    /**
-     * Do not allow instantiation of this class.
-     */
-    private TransformationAccuracy() {
-    }
-
-    /**
-     * Creates a positional accuracy for the given value, in metres.
-     * This method may return a cached value.
-     *
-     * @param  accuracy  the accuracy in metres.
-     * @return a positional accuracy with the given value.
-     */
-    @SuppressWarnings("deprecation")
-    public static PositionalAccuracy create(final Double accuracy) {
-        PositionalAccuracy p = CACHE.get(accuracy);
-        if (p == null) {
-            final RecordType type = RecordSchemaSIS.REAL;
-            final DefaultRecord record = new DefaultRecord(type);
-            record.setAll(accuracy);
-
-            final DefaultQuantitativeResult result = new 
DefaultQuantitativeResult();
-            result.setValues(List.of(record));
-            result.setValueUnit(Units.METRE);              // In metres by 
definition in the EPSG database.
-            result.setValueType(type);
-
-            final DefaultAbsoluteExternalPositionalAccuracy element =
-                    new DefaultAbsoluteExternalPositionalAccuracy(result);
-            element.setNamesOfMeasure(List.of(TRANSFORMATION_ACCURACY));
-            
element.setEvaluationMethodType(EvaluationMethodType.DIRECT_EXTERNAL);
-            
element.transitionTo(DefaultAbsoluteExternalPositionalAccuracy.State.FINAL);
-
-            p = CACHE.putIfAbsent(accuracy, element);
-            if (p == null) {
-                p = element;
-            }
-        }
-        return p;
-    }
-}
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultRecord.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultRecord.java
index e9a51afd11..a7ef38bd9b 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultRecord.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultRecord.java
@@ -33,7 +33,7 @@ import org.apache.sis.util.Utilities;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.privy.Strings;
 import org.apache.sis.util.privy.AbstractMapEntry;
-import org.apache.sis.metadata.internal.RecordSchemaSIS;
+import org.apache.sis.metadata.privy.RecordSchemaSIS;
 
 
 /**
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultRecordType.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultRecordType.java
index 69646b3a26..55bb94d6ef 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultRecordType.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultRecordType.java
@@ -42,7 +42,7 @@ import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.ObjectConverters;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.converter.SurjectiveConverter;
-import org.apache.sis.metadata.internal.RecordSchemaSIS;
+import org.apache.sis.metadata.privy.RecordSchemaSIS;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.util.NameFactory;
diff --git 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/quality/DefaultQuantitativeResultTest.java
 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/quality/DefaultQuantitativeResultTest.java
index dc180f7a72..84cc4bff45 100644
--- 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/quality/DefaultQuantitativeResultTest.java
+++ 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/quality/DefaultQuantitativeResultTest.java
@@ -26,7 +26,7 @@ import org.opengis.util.RecordType;
 import org.opengis.util.MemberName;
 import org.opengis.metadata.quality.Element;
 import org.opengis.metadata.quality.QuantitativeResult;
-import org.apache.sis.metadata.internal.RecordSchemaSIS;
+import org.apache.sis.metadata.privy.RecordSchemaSIS;
 import org.apache.sis.xml.XML;
 import org.apache.sis.xml.privy.LegacyNamespaces;
 import org.apache.sis.util.SimpleInternationalString;
diff --git 
a/endorsed/src/org.apache.sis.openoffice/test/org/apache/sis/openoffice/ReferencingFunctionsTest.java
 
b/endorsed/src/org.apache.sis.openoffice/test/org/apache/sis/openoffice/ReferencingFunctionsTest.java
index df72e680be..0e78793851 100644
--- 
a/endorsed/src/org.apache.sis.openoffice/test/org/apache/sis/openoffice/ReferencingFunctionsTest.java
+++ 
b/endorsed/src/org.apache.sis.openoffice/test/org/apache/sis/openoffice/ReferencingFunctionsTest.java
@@ -18,7 +18,7 @@ package org.apache.sis.openoffice;
 
 import com.sun.star.lang.IllegalArgumentException;
 import org.apache.sis.referencing.privy.Formulas;
-import org.apache.sis.referencing.privy.PositionalAccuracyConstant;
+import org.apache.sis.referencing.internal.PositionalAccuracyConstant;
 
 // Test dependencies
 import org.junit.jupiter.api.Test;
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
index 7c1d29de28..ecdfa88305 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
@@ -68,6 +68,7 @@ import org.apache.sis.referencing.privy.WKTUtilities;
 import org.apache.sis.referencing.privy.WKTKeywords;
 import org.apache.sis.referencing.internal.Legacy;
 import org.apache.sis.referencing.internal.VerticalDatumTypes;
+import org.apache.sis.referencing.internal.PositionalAccuracyConstant;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.metadata.iso.extent.DefaultExtent;
 import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
@@ -75,7 +76,6 @@ import 
org.apache.sis.metadata.iso.extent.DefaultGeographicDescription;
 import org.apache.sis.metadata.iso.extent.DefaultVerticalExtent;
 import org.apache.sis.metadata.iso.extent.DefaultTemporalExtent;
 import org.apache.sis.metadata.privy.AxisNames;
-import org.apache.sis.metadata.privy.TransformationAccuracy;
 import org.apache.sis.referencing.operation.provider.AbstractProvider;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.privy.Constants;
@@ -2309,7 +2309,7 @@ class GeodeticObjectParser extends MathTransformParser 
implements Comparator<Coo
         final Map<String,Object>        properties       = 
parseParametersAndClose(element, name, method);
         if (accuracy != null) {
             
properties.put(CoordinateOperation.COORDINATE_OPERATION_ACCURACY_KEY,
-                    
TransformationAccuracy.create(accuracy.pullDouble("accuracy")));
+                    
PositionalAccuracyConstant.create(accuracy.pullDouble("accuracy")));
             accuracy.close(ignoredElements);
         }
         try {
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
index 6174b51d20..98c9574f51 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
@@ -61,10 +61,10 @@ import org.apache.sis.xml.bind.Context;
 import org.apache.sis.xml.bind.ScopedIdentifier;
 import org.apache.sis.referencing.privy.AxisDirections;
 import org.apache.sis.referencing.privy.EllipsoidalHeightCombiner;
-import org.apache.sis.referencing.privy.PositionalAccuracyConstant;
 import org.apache.sis.referencing.privy.ReferencingUtilities;
 import org.apache.sis.referencing.privy.DefinitionVerifier;
 import org.apache.sis.referencing.internal.Resources;
+import org.apache.sis.referencing.internal.PositionalAccuracyConstant;
 import org.apache.sis.referencing.cs.AxisFilter;
 import org.apache.sis.referencing.cs.CoordinateSystems;
 import org.apache.sis.referencing.cs.DefaultVerticalCS;
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
index 6d4457e1bd..cb5394aeac 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
@@ -2084,9 +2084,17 @@ public enum CommonCRS {
          */
         public boolean datumUsedBy(final CoordinateReferenceSystem crs) {
             for (final SingleCRS component : CRS.getSingleComponents(crs)) {
-                if (ReferencingUtilities.uses(component, datum)) {
+                if (Utilities.equalsIgnoreMetadata(datum, 
component.getDatum())) {
                     return true;
                 }
+                final var ensemble = component.getDatumEnsemble();
+                if (ensemble != null) {
+                    for (final Datum member : ensemble.getMembers()) {
+                        if (Utilities.equalsIgnoreMetadata(datum, member)) {
+                            return true;
+                        }
+                    }
+                }
             }
             return false;
         }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/MultiRegisterOperations.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/MultiRegisterOperations.java
index 9b8ebfef7e..8eed014ccc 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/MultiRegisterOperations.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/MultiRegisterOperations.java
@@ -44,7 +44,6 @@ import 
org.apache.sis.referencing.factory.MultiAuthoritiesFactory;
 import org.apache.sis.referencing.factory.NoSuchAuthorityFactoryException;
 import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
 import 
org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory;
-import org.apache.sis.util.Utilities;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.iso.AbstractFactory;
@@ -52,7 +51,6 @@ import org.apache.sis.util.iso.AbstractFactory;
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.referencing.RegisterOperations;
 import org.opengis.referencing.crs.SingleCRS;
-import org.apache.sis.referencing.privy.ReferencingUtilities;
 
 
 /**
@@ -350,13 +348,7 @@ public class MultiRegisterOperations extends 
AbstractFactory implements Register
             return false;
         }
         for (int i=0; i<n; i++) {
-            final var crs1 = sources.get(i);
-            final var crs2 = targets.get(i);
-            if 
(!(Utilities.equalsIgnoreMetadata(PseudoDatum.getDatumOrEnsemble(crs1),
-                                                 
PseudoDatum.getDatumOrEnsemble(crs2))
-                    || ReferencingUtilities.uses(crs1, crs2.getDatum())
-                    || ReferencingUtilities.uses(crs2, crs1.getDatum())))
-            {
+            if (PseudoDatum.getOperationAccuracy(sources.get(i), 
targets.get(i)).isEmpty()) {
                 return false;
             }
         }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
index 0b03e1ba43..0da60eed88 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
@@ -24,7 +24,6 @@ import org.apache.sis.io.wkt.Formatter;
 import org.apache.sis.io.wkt.Convention;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.datum.Datum;
-import org.opengis.referencing.datum.DatumEnsemble;
 import org.opengis.metadata.quality.PositionalAccuracy;
 import org.apache.sis.referencing.AbstractIdentifiedObject;
 import org.apache.sis.referencing.internal.Resources;
@@ -34,6 +33,9 @@ import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.resources.Errors;
 
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.referencing.datum.DatumEnsemble;
+
 
 /**
  * Collection of datums which for low accuracy requirements may be considered
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/PseudoDatum.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/PseudoDatum.java
index f905c6c4f6..87c9fe4f43 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/PseudoDatum.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/PseudoDatum.java
@@ -28,6 +28,7 @@ import java.io.Serializable;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.Identifier;
+import org.opengis.metadata.quality.PositionalAccuracy;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.ObjectDomain;
 import org.opengis.referencing.datum.*;
@@ -38,6 +39,7 @@ import org.apache.sis.util.LenientComparable;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.GeodeticException;
+import org.apache.sis.referencing.internal.PositionalAccuracyConstant;
 
 
 /**
@@ -96,26 +98,6 @@ public abstract class PseudoDatum<D extends Datum> 
implements Datum, LenientComp
         this.ensemble = Objects.requireNonNull(ensemble);
     }
 
-    /**
-     * Returns the datum of the given <abbr>CRS</abbr> if presents, or the 
datum ensemble otherwise.
-     * This is an alternative to the {@code of(…)} methods when the caller 
does not need to view the
-     * object as a datum.
-     *
-     * @param  crs  the <abbr>CRS</abbr> from which to get the datum or 
ensemble, or {@code null}.
-     * @return the datum if present, or the datum ensemble otherwise, or 
{@code null}.
-     */
-    public static IdentifiedObject getDatumOrEnsemble(final SingleCRS crs) {
-        if (crs == null) return null;
-        final Datum datum = crs.getDatum();
-        if (datum != null) {
-            if (datum instanceof PseudoDatum<?>) {
-                return ((PseudoDatum) datum).ensemble;
-            }
-            return datum;
-        }
-        return crs.getDatumEnsemble();
-    }
-
     /**
      * Returns the datum or pseudo-datum of the given geodetic 
<abbr>CRS</abbr>.
      * If the given <abbr>CRS</abbr> is associated to a non-null datum, then 
this method returns that datum.
@@ -215,6 +197,116 @@ public abstract class PseudoDatum<D extends Datum> 
implements Datum, LenientComp
         return datum;
     }
 
+    /**
+     * Returns the datum of the given <abbr>CRS</abbr> if presents, or the 
datum ensemble otherwise.
+     * This is an alternative to the {@code of(…)} methods when the caller 
does not need to view the
+     * object as a datum.
+     *
+     * @param  crs  the <abbr>CRS</abbr> from which to get the datum or 
ensemble, or {@code null}.
+     * @return the datum if present, or the datum ensemble otherwise, or 
{@code null}.
+     */
+    public static IdentifiedObject getDatumOrEnsemble(final SingleCRS crs) {
+        if (crs == null) return null;
+        final Datum datum = crs.getDatum();
+        if (datum != null) {
+            if (datum instanceof PseudoDatum<?>) {
+                return ((PseudoDatum) datum).ensemble;
+            }
+            return datum;
+        }
+        return crs.getDatumEnsemble();
+    }
+
+    /**
+     * Returns the inaccuracy that would have an operation using datum 
ensembles.
+     * This method makes the following choice:
+     *
+     * <ul>
+     *   <li>If the two reference systems are associated to the same datum, 
returns an arbitrary value.</li>
+     *   <li>Otherwise, if the datum of one <abbr>CRS</abbr> is a member of 
the datum ensemble of the other
+     *       <abbr>CRS</abbr>, returns the ensemble accuracy.</li>
+     *   <li>OTherwise, if the datum ensemble of one <abbr>CRS</abbr> is fully 
contained in the datum ensemble
+     *       of the other <abbr>CRS</abbr>, returns the accuracy of the larger 
ensemble.</li>
+     *   <li>Otherwise, returns an empty value.</li>
+     * </ul>
+     *
+     * An empty value means that the two <abbr>CRS</abbr> are not compatible 
according the datum and datum ensemble
+     * properties. However, a transformation path may exist in a geodetic 
registry such as <abbr>EPSG</abbr>.
+     *
+     * @param  source  the first <abbr>CRS</abbr> for which to compare the 
datum.
+     * @param  target  the second <abbr>CRS</abbr> for which to compare the 
datum.
+     * @return a non-null value if it is okay, for low accuracy requirements, 
to ignore the datum shift.
+     */
+    public static Optional<PositionalAccuracy> getOperationAccuracy(final 
SingleCRS source, final SingleCRS target) {
+        final Datum sourceDatum = source.getDatum();
+        final Datum targetDatum = target.getDatum();
+        if (sourceDatum != null && targetDatum != null && 
Utilities.equalsIgnoreMetadata(sourceDatum, targetDatum)) {
+            return Optional.of(PositionalAccuracyConstant.SAME_DATUM_ENSEMBLE);
+        }
+        DatumEnsemble<?> sourceEnsemble;
+        DatumEnsemble<?> targetEnsemble;
+        PositionalAccuracy accuracy;
+        if ((accuracy = accuracyIfMember(sourceDatum, targetEnsemble = 
target.getDatumEnsemble())) != null ||
+            (accuracy = accuracyIfMember(targetDatum, sourceEnsemble = 
source.getDatumEnsemble())) != null ||
+            (sourceEnsemble == null || targetEnsemble == null))
+        {
+            return Optional.of(accuracy);
+        }
+        var sources = sourceEnsemble.getMembers();
+        var targets = targetEnsemble.getMembers();
+        if (sources.size() > targets.size()) {
+            var tmp = targets;          // Want as if transforming from 
smaller ensemble to larger ensemble.
+            targets = sources;
+            sources = tmp;
+
+            var te = targetEnsemble;
+            targetEnsemble = sourceEnsemble;
+            sourceEnsemble = te;
+        }
+        final Datum[] remaining = sources.toArray(Datum[]::new);
+        int count = remaining.length;
+        for (final Datum member : targets) {
+            for (int i=0; i<count; i++) {
+                if (Utilities.equalsIgnoreMetadata(member, remaining[i])) {
+                    System.arraycopy(remaining, i+1, remaining, i, --count - 
i);
+                    if (count == 0) {
+                        /*
+                         * Found all members of the smaller ensemble. Take the 
accuracy
+                         * of the larger ensemble, as it contains the smaller 
ensemble.
+                         */
+                        if ((accuracy = targetEnsemble.getEnsembleAccuracy()) 
== null &&
+                            (accuracy = sourceEnsemble.getEnsembleAccuracy()) 
== null) {
+                             accuracy = 
PositionalAccuracyConstant.SAME_DATUM_ENSEMBLE;
+                        }
+                        return Optional.of(accuracy);
+                    }
+                    break;      // For removing only the first match.
+                }
+            }
+        }
+        return Optional.empty();
+    }
+
+    /**
+     * If the given datum is a member of the given ensemble, returns the 
ensemble accuracy.
+     * Otherwise, returns {@code null}.
+     *
+     * @param  datum     the datum to test, or {@code null}.
+     * @param  ensemble  the ensemble to test, or {@code null}.
+     * @return a non-null value if the datum is a member of the given ensemble.
+     */
+    private static PositionalAccuracy accuracyIfMember(final Datum datum, 
final DatumEnsemble<?> ensemble) {
+        if (ensemble != null) {
+            for (final Datum member : ensemble.getMembers()) {
+                if (Utilities.equalsIgnoreMetadata(datum, member)) {
+                    PositionalAccuracy accuracy = 
ensemble.getEnsembleAccuracy();
+                    return (accuracy != null) ? accuracy : 
PositionalAccuracyConstant.SAME_DATUM_ENSEMBLE;
+                }
+            }
+        }
+        return null;
+    }
+
     /**
      * Returns the GeoAPI interface of the ensemble members.
      * It should also be the interface implemented by this class.
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index 1036dd57c1..21cf4bec22 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
@@ -66,21 +66,31 @@ import org.opengis.referencing.cs.*;
 import org.opengis.referencing.crs.*;
 import org.opengis.referencing.datum.*;
 import org.opengis.referencing.operation.*;
-import org.apache.sis.metadata.privy.TransformationAccuracy;
 import org.apache.sis.referencing.NamedIdentifier;
 import org.apache.sis.referencing.ImmutableIdentifier;
 import org.apache.sis.referencing.AbstractIdentifiedObject;
+import org.apache.sis.referencing.cs.CoordinateSystems;
+import org.apache.sis.referencing.datum.BursaWolfParameters;
+import org.apache.sis.referencing.datum.DefaultGeodeticDatum;
+import org.apache.sis.referencing.operation.DefaultOperationMethod;
+import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
+import org.apache.sis.referencing.factory.FactoryDataException;
+import org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
+import org.apache.sis.referencing.factory.IdentifiedObjectFinder;
 import org.apache.sis.referencing.privy.WKTKeywords;
 import org.apache.sis.referencing.privy.CoordinateOperations;
 import org.apache.sis.referencing.privy.ReferencingFactoryContainer;
 import org.apache.sis.referencing.privy.Formulas;
-import org.apache.sis.metadata.sql.privy.SQLUtilities;
 import org.apache.sis.referencing.internal.DeferredCoordinateOperation;
 import org.apache.sis.referencing.internal.DeprecatedCode;
 import org.apache.sis.referencing.internal.EPSGParameterDomain;
+import org.apache.sis.referencing.internal.ParameterizedTransformBuilder;
+import org.apache.sis.referencing.internal.PositionalAccuracyConstant;
 import org.apache.sis.referencing.internal.SignReversalComment;
 import org.apache.sis.referencing.internal.Resources;
-import org.apache.sis.referencing.internal.ParameterizedTransformBuilder;
+import static 
org.apache.sis.referencing.internal.ServicesForMetadata.CONNECTION;
+import org.apache.sis.parameter.DefaultParameterDescriptor;
+import org.apache.sis.parameter.DefaultParameterDescriptorGroup;
 import org.apache.sis.system.Loggers;
 import org.apache.sis.system.Semaphores;
 import org.apache.sis.util.SimpleInternationalString;
@@ -90,36 +100,26 @@ import org.apache.sis.util.Localized;
 import org.apache.sis.util.Version;
 import org.apache.sis.util.Workaround;
 import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.collection.BackingStoreException;
+import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.privy.Constants;
 import org.apache.sis.util.privy.CollectionsExt;
 import org.apache.sis.util.privy.Strings;
 import org.apache.sis.util.privy.URLs;
+import static org.apache.sis.util.privy.Constants.UTC;
+import static org.apache.sis.util.Utilities.equalsIgnoreMetadata;
 import org.apache.sis.temporal.LenientDateFormat;
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
 import org.apache.sis.metadata.iso.citation.DefaultOnlineResource;
 import org.apache.sis.metadata.iso.extent.DefaultExtent;
 import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
-import org.apache.sis.parameter.DefaultParameterDescriptor;
-import org.apache.sis.parameter.DefaultParameterDescriptorGroup;
-import org.apache.sis.referencing.cs.CoordinateSystems;
-import org.apache.sis.referencing.datum.BursaWolfParameters;
-import org.apache.sis.referencing.datum.DefaultGeodeticDatum;
-import org.apache.sis.referencing.operation.DefaultOperationMethod;
-import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
-import org.apache.sis.referencing.factory.FactoryDataException;
-import org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
-import org.apache.sis.referencing.factory.IdentifiedObjectFinder;
-import org.apache.sis.util.collection.BackingStoreException;
-import org.apache.sis.util.resources.Vocabulary;
-import org.apache.sis.util.resources.Errors;
-import org.apache.sis.util.logging.Logging;
+import org.apache.sis.metadata.sql.privy.SQLUtilities;
 import org.apache.sis.measure.MeasurementRange;
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.measure.Units;
 import org.apache.sis.pending.jdk.JDK16;
-import static org.apache.sis.util.privy.Constants.UTC;
-import static org.apache.sis.util.Utilities.equalsIgnoreMetadata;
-import static 
org.apache.sis.referencing.internal.ServicesForMetadata.CONNECTION;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.metadata.Identifier;
@@ -2896,7 +2896,7 @@ next:                   while (r.next()) {
                     
opProperties.put(CoordinateOperation.OPERATION_VERSION_KEY, version);
                     if (!Double.isNaN(accuracy)) {
                         
opProperties.put(CoordinateOperation.COORDINATE_OPERATION_ACCURACY_KEY,
-                                TransformationAccuracy.create(accuracy));
+                                PositionalAccuracyConstant.create(accuracy));
                     }
                     /*
                      * Creates the operation. Conversions should be the only 
operations allowed to have
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/AnnotatedMatrix.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/AnnotatedMatrix.java
index 5b0140f59f..0732b42805 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/AnnotatedMatrix.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/AnnotatedMatrix.java
@@ -19,7 +19,6 @@ package org.apache.sis.referencing.internal;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.metadata.quality.PositionalAccuracy;
 import org.apache.sis.util.privy.CloneAccess;
-import org.apache.sis.referencing.privy.PositionalAccuracyConstant;
 
 
 /**
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/PositionalAccuracyConstant.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/PositionalAccuracyConstant.java
similarity index 66%
rename from 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/PositionalAccuracyConstant.java
rename to 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/PositionalAccuracyConstant.java
index 7370c6c5e6..123aea11c2 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/PositionalAccuracyConstant.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/PositionalAccuracyConstant.java
@@ -14,16 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.referencing.privy;
+package org.apache.sis.referencing.internal;
 
-import java.util.Set;
+import java.util.List;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.io.ObjectStreamException;
 import jakarta.xml.bind.annotation.XmlTransient;
 import javax.measure.Unit;
 import javax.measure.quantity.Length;
 import org.opengis.util.Record;
-import org.opengis.util.InternationalString;
+import org.opengis.util.RecordType;
 import org.opengis.metadata.quality.PositionalAccuracy;
 import org.opengis.metadata.quality.EvaluationMethodType;
 import org.opengis.metadata.quality.QuantitativeResult;
@@ -34,11 +35,16 @@ import 
org.opengis.referencing.operation.CoordinateOperation;
 import org.opengis.referencing.operation.Transformation;
 import org.apache.sis.measure.Units;
 import org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.metadata.iso.quality.DefaultMeasureReference;
+import org.apache.sis.metadata.iso.quality.DefaultEvaluationMethod;
 import org.apache.sis.metadata.iso.quality.DefaultConformanceResult;
 import 
org.apache.sis.metadata.iso.quality.DefaultAbsoluteExternalPositionalAccuracy;
-import org.apache.sis.system.Configuration;
-import org.apache.sis.referencing.internal.Resources;
+import org.apache.sis.metadata.iso.quality.DefaultQuantitativeResult;
+import org.apache.sis.metadata.privy.RecordSchemaSIS;
+import org.apache.sis.util.collection.WeakValueHashMap;
 import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.util.iso.DefaultRecord;
+import org.apache.sis.system.Configuration;
 
 
 /**
@@ -77,7 +83,7 @@ public final class PositionalAccuracyConstant extends 
DefaultAbsoluteExternalPos
      * @see 
org.apache.sis.referencing.operation.AbstractCoordinateOperation#getLinearAccuracy()
      */
     @Configuration
-    private static final double DATUM_SHIFT_ACCURACY = 25;
+    public static final double DATUM_SHIFT_ACCURACY = 25;
 
     /**
      * Default accuracy of datum shifts when using an intermediate datum 
(typically WGS 84).
@@ -92,7 +98,9 @@ public final class PositionalAccuracyConstant extends 
DefaultAbsoluteExternalPos
      * Indicates that a {@linkplain 
org.opengis.referencing.operation.Transformation transformation}
      * requires a datum shift and some method has been applied. Datum shift 
methods often use
      * {@linkplain org.apache.sis.referencing.datum.BursaWolfParameters Bursa 
Wolf parameters},
-     * but other kind of method may have been applied as well.
+     * but other kinds of method may have been applied as well.
+     *
+     * @todo Should use the accuracy defined in {@code BoundCRS} instead.
      */
     public static final PositionalAccuracy DATUM_SHIFT_APPLIED;
 
@@ -110,29 +118,106 @@ public final class PositionalAccuracyConstant extends 
DefaultAbsoluteExternalPos
      * an intermediate datum, typically WGS 84.
      */
     public static final PositionalAccuracy INDIRECT_SHIFT_APPLIED;
+
+    /**
+     * Coordinate operation between reference frames in the same datum 
ensemble.
+     * Should be used only with coordinate operations that are conversion,
+     * but may also be used as a fallback if a datum ensemble didn't specified 
its accuracy.
+     */
+    public static final PositionalAccuracy SAME_DATUM_ENSEMBLE;
+
+    /**
+     * Name for accuracy metadata of coordinate transformations.
+     */
+    private static final DefaultMeasureReference TRANSFORMATION_REFERENCE =
+            new 
DefaultMeasureReference(Vocabulary.formatInternational(Vocabulary.Keys.TransformationAccuracy));
+
+    /**
+     * The evaluation method for coordinate transformations when the accuracy 
is specified in the EPSG database.
+     * Those evaluation method are considered "external" on the assumption 
that the operation results have been
+     * compared by the database maintainers against some results taken as 
true. By contrast, the accuracies that
+     * we have set to conservative values are considered "direct internal".
+     */
+    private static final DefaultEvaluationMethod TRANSFORMATION_METHOD =
+            new DefaultEvaluationMethod(EvaluationMethodType.DIRECT_EXTERNAL,
+                    
Resources.formatInternational(Resources.Keys.AccuracyFromGeodeticDatase));
+
     static {
-        final InternationalString desc = 
Vocabulary.formatInternational(Vocabulary.Keys.TransformationAccuracy);
-        final InternationalString eval = Resources 
.formatInternational(Resources.Keys.ConformanceMeansDatumShift);
-        DATUM_SHIFT_APPLIED    = new PositionalAccuracyConstant(desc, eval, 
true);
-        DATUM_SHIFT_OMITTED    = new PositionalAccuracyConstant(desc, eval, 
false);
-        INDIRECT_SHIFT_APPLIED = new PositionalAccuracyConstant(desc, eval, 
true);
+        
TRANSFORMATION_REFERENCE.transitionTo(DefaultMeasureReference.State.FINAL);
+        TRANSFORMATION_METHOD   
.transitionTo(DefaultEvaluationMethod.State.FINAL);
+        final var desc   = 
Resources.formatInternational(Resources.Keys.ConformanceMeansDatumShift);
+        final var method = new 
DefaultEvaluationMethod(EvaluationMethodType.DIRECT_INTERNAL, desc);
+        final var pass   = new DefaultConformanceResult(Citations.SIS, desc, 
true);
+        final var fail   = new DefaultConformanceResult(Citations.SIS, desc, 
false);
+
+        DATUM_SHIFT_APPLIED    = new 
PositionalAccuracyConstant(TRANSFORMATION_REFERENCE, method, pass, 
DATUM_SHIFT_ACCURACY);
+        DATUM_SHIFT_OMITTED    = new 
PositionalAccuracyConstant(TRANSFORMATION_REFERENCE, method, fail, 
UNKNOWN_ACCURACY);
+        INDIRECT_SHIFT_APPLIED = new 
PositionalAccuracyConstant(TRANSFORMATION_REFERENCE, method, pass, 
INDIRECT_SHIFT_ACCURACY);
+
+        final var reference = new 
DefaultMeasureReference(Resources.formatInternational(Resources.Keys.OperationSameDatumEnsemble));
+        SAME_DATUM_ENSEMBLE = new PositionalAccuracyConstant(reference, null, 
null, null);
     }
 
     /**
-     * Creates an positional accuracy initialized to the given result.
+     * Creates a positional accuracy initialized to the given result.
+     *
+     * @param  reference  description of the positional accuracy.
+     * @param  method     method used for accuracy measurement, or {@code 
null}.
+     * @param  result     qualitative result, or {@code null} if none.
+     * @param  accuracy   the linear accuracy in metres, or {@code null} if 
none.
      */
-    @SuppressWarnings("deprecation")
-    private PositionalAccuracyConstant(final InternationalString 
measureDescription,
-            final InternationalString evaluationMethodDescription, final 
boolean pass)
+    private PositionalAccuracyConstant(final DefaultMeasureReference  
reference,
+                                       final DefaultEvaluationMethod  method,
+                                       final DefaultConformanceResult result,
+                                       final Double accuracy)
     {
-        DefaultConformanceResult result = new 
DefaultConformanceResult(Citations.SIS, evaluationMethodDescription, pass);
-        setResults(Set.of(result));
-        setMeasureDescription(measureDescription);
-        setEvaluationMethodDescription(evaluationMethodDescription);
-        setEvaluationMethodType(EvaluationMethodType.DIRECT_INTERNAL);
+        setMeasureReference(reference);
+        setEvaluationMethod(method);
+        final var results = new ArrayList<Result>(2);
+        if (result != null) {
+            results.add(result);
+        }
+        if (accuracy != null) {
+            final RecordType type = RecordSchemaSIS.REAL;
+            final var record = new DefaultRecord(type);
+            record.setAll(accuracy);
+
+            final var r = new DefaultQuantitativeResult();
+            r.setValues(List.of(record));
+            r.setValueUnit(Units.METRE);        // In metres by definition in 
the EPSG database.
+            r.setValueType(type);
+            results.add(r);
+        }
+        setResults(results);
         transitionTo(State.FINAL);
     }
 
+    /**
+     * Creates a positional accuracy for a value specified in the EPSG 
database.
+     *
+     * @param  accuracy  the linear accuracy in metres.
+     */
+    private PositionalAccuracyConstant(final Double accuracy) {
+        this(TRANSFORMATION_REFERENCE, TRANSFORMATION_METHOD, null, accuracy);
+    }
+
+    /**
+     * Creates a positional accuracy for the given value, in metres.
+     * This method may return a cached value.
+     *
+     * @param  accuracy  the accuracy in metres.
+     * @return a positional accuracy with the given value.
+     */
+    public static PositionalAccuracy create(final Double accuracy) {
+        return CACHE.computeIfAbsent(accuracy, 
PositionalAccuracyConstant::new);
+    }
+
+    /**
+     * Cache the positional accuracies of coordinate transformations.
+     * Most coordinate operations use a small set of accuracy values.
+     */
+    private static final WeakValueHashMap<Double,PositionalAccuracy> CACHE = 
new WeakValueHashMap<>(Double.class);
+
     /**
      * Invoked on deserialization. Replace this instance by one of the 
constants, if applicable.
      *
@@ -143,6 +228,7 @@ public final class PositionalAccuracyConstant extends 
DefaultAbsoluteExternalPos
         if (equals(DATUM_SHIFT_APPLIED))    return DATUM_SHIFT_APPLIED;
         if (equals(DATUM_SHIFT_OMITTED))    return DATUM_SHIFT_OMITTED;
         if (equals(INDIRECT_SHIFT_APPLIED)) return INDIRECT_SHIFT_APPLIED;
+        if (equals(SAME_DATUM_ENSEMBLE))    return SAME_DATUM_ENSEMBLE;
         return this;
     }
 
@@ -207,22 +293,6 @@ public final class PositionalAccuracyConstant extends 
DefaultAbsoluteExternalPos
             if (operation instanceof Conversion) {
                 return 0;
             }
-            /*
-             * If the coordinate operation is actually a transformation, 
checks if Bursa-Wolf parameters
-             * were available for the datum shift. This is SIS-specific. See 
field javadoc for a rational
-             * about the return values chosen.
-             */
-            if (operation instanceof Transformation) {
-                for (final PositionalAccuracy element : accuracies) {
-                    /*
-                     * Really need identity comparisons, not 
Object.equals(Object), because the latter
-                     * does not distinguish between DATUM_SHIFT_APPLIED and 
INDIRECT_SHIFT_APPLIED.
-                     */
-                    if (element == DATUM_SHIFT_APPLIED)    return 
DATUM_SHIFT_ACCURACY;
-                    if (element == DATUM_SHIFT_OMITTED)    return 
UNKNOWN_ACCURACY;
-                    if (element == INDIRECT_SHIFT_APPLIED) return 
INDIRECT_SHIFT_ACCURACY;
-                }
-            }
             /*
              * If the coordinate operation is a compound of other coordinate 
operations, returns the sum of their accuracy,
              * skipping unknown ones. Making the sum is a conservative 
approach (not exactly the "worst case" scenario,
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.java
index 11a8ad71a4..0af68e9f8f 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.java
@@ -54,6 +54,11 @@ public class Resources extends IndexedResourceBundle {
         private Keys() {
         }
 
+        /**
+         * Accuracy declared in a geodetic dataset.
+         */
+        public static final short AccuracyFromGeodeticDatase = 105;
+
         /**
          * Ambiguity between inverse flattening and semi minor axis length for 
“{0}”. Using inverse
          * flattening.
@@ -503,6 +508,11 @@ public class Resources extends IndexedResourceBundle {
          */
         public static final short OperationHasNoTransform_2 = 43;
 
+        /**
+         * Coordinate operation between reference frames in the same datum 
ensemble.
+         */
+        public static final short OperationSameDatumEnsemble = 106;
+
         /**
          * No parameter named “{1}” has been found in “{0}”.
          */
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.properties
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.properties
index a79a91f56e..e7eef826ce 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.properties
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.properties
@@ -21,8 +21,8 @@
 #
 # Information messages or non-fatal warnings
 #
+AccuracyFromGeodeticDatase        = Accuracy declared in a geodetic dataset.
 AmbiguousEllipsoid_1              = Ambiguity between inverse flattening and 
semi minor axis length for \u201c{0}\u201d. Using inverse flattening.
-CannotParseElement_1              = Cannot parse the \u201c{0}\u201d element:
 ConformanceMeansDatumShift        = This result indicates if a datum shift 
method has been applied.
 ConstantProjParameterValue_1      = This parameter is shown for completeness, 
but should never have a value different than {0} for this projection.
 DeprecatedCode_3                  = Code \u201c{0}\u201d is deprecated and 
replaced by code {1}. Reason is: {2}
@@ -43,6 +43,7 @@ NotFormalProjectionParameter_1    = This parameter borrowed 
from the \u201c{0}\u
 NonConformAxes_2                  = The coordinate system axes in the given 
\u201c{0}\u201d description do not conform to the expected axes according 
\u201c{1}\u201d authoritative description.
 NonConformCRS_3                   = The given \u201c{0}\u201d description does 
not conform to the \u201c{1}\u201d authoritative description. \
                                     Differences are found in 
{2,choice,0#conversion method|1#conversion description|2#coordinate 
system|3#datum|4#prime meridian|5#ellipsoid|6#CRS}.
+OperationSameDatumEnsemble        = Coordinate operation between reference 
frames in the same datum ensemble.
 RestrictedToPoleLatitudes         = The only valid entries are \u00b190\u00b0 
or equivalent in alternative angle units.
 
 #
@@ -61,6 +62,7 @@ CanNotInstantiateGeodeticObject_1 = Cannot instantiate 
geodetic object for \u201
 CanNotLinearizeLocalizationGrid   = Cannot linearize the localization grid.
 CanNotMapAxisToDirection_1        = Cannot map an axis from the specified 
coordinate system to the \u201c{0}\u201d direction.
 CanNotParseCombinedReference_2    = Cannot parse component {1} in the combined 
{0,choice,0#URN|1#URL}.
+CannotParseElement_1              = Cannot parse the \u201c{0}\u201d element:
 CanNotSeparateCRS_1               = Cannot separate the \u201c{0}\u201d 
coordinate reference system into sub-components.
 CanNotSeparateTransform_3         = Cannot separate the transform because 
result would have {2} {0,choice,0#source|1#target} dimension{2,choice,1#|2#s} 
instead of {1}.
 CanNotSeparateTargetDimension_1   = Target dimension {0} depends on excluded 
source dimensions.
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources_fr.properties
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources_fr.properties
index 245a08e502..9a58be876f 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources_fr.properties
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources_fr.properties
@@ -26,8 +26,8 @@
 #
 # Information messages or non-fatal warnings
 #
+AccuracyFromGeodeticDatase        = Pr\u00e9cision d\u00e9clar\u00e9e dans une 
base de donn\u00e9es g\u00e9od\u00e9siques.
 AmbiguousEllipsoid_1              = Ambigu\u00eft\u00e9 entre 
l\u2019aplatissement et la longueur du semi-axe mineur pour 
\u00ab\u202f{0}\u202f\u00bb. Utilise l\u2019aplatissement.
-CannotParseElement_1              = Ne peut pas d\u00e9coder 
l\u2019\u00e9l\u00e9ment \u00ab\u202f{0}\u202f\u00bb\u00a0:
 ConformanceMeansDatumShift        = Ce r\u00e9sultat indique si un changement 
de r\u00e9f\u00e9rentiel a \u00e9t\u00e9 appliqu\u00e9.
 ConstantProjParameterValue_1      = Ce param\u00e8tre est montr\u00e9 pour 
\u00eatre plus complet, mais sa valeur ne devrait jamais \u00eatre 
diff\u00e9rente de {0} pour cette projection.
 DeprecatedCode_3                  = Le code \u00ab\u202f{0}\u202f\u00bb est 
d\u00e9pr\u00e9ci\u00e9 et remplac\u00e9 par le code {1}. La raison est\u00a0: 
{2}
@@ -48,6 +48,7 @@ NotFormalProjectionParameter_1    = Ce param\u00e8tre 
emprunt\u00e9 \u00e0 la pr
 NonConformAxes_2                  = Les axes du syst\u00e8me de 
coordonn\u00e9es d\u00e9finis dans \u00ab\u202f{0}\u202f\u00bb ne sont pas 
conformes aux axes attendus d\u2019apr\u00e8s la description officielle de 
\u00ab\u202f{1}\u202f\u00bb.
 NonConformCRS_3                   = La description donn\u00e9e pour 
\u00ab\u202f{0}\u202f\u00bb n\u2019est pas conforme \u00e0 la description 
officielle de \u00ab\u202f{1}\u202f\u00bb. \
                                     Des diff\u00e9rences ont \u00e9t\u00e9 
trouv\u00e9es dans {2,choice,0#la m\u00e9thode de conversion|1#la description 
de la conversion|2#le syst\u00e8me de coordonn\u00e9es|3#le 
r\u00e9f\u00e9rentiel|4#le m\u00e9ridien 
d\u2019origine|5#l\u2019ellipso\u00efde|6#le CRS}.
+OperationSameDatumEnsemble        = Op\u00e9ration sur les coordonn\u00e9es 
entre des r\u00e9f\u00e9rentiels qui sont dans le m\u00eame ensemble de 
r\u00e9f\u00e9rentiels.
 RestrictedToPoleLatitudes         = Les seules valeurs valides sont 
\u00b190\u00b0 ou \u00e9quivalent dans d\u2019autres unit\u00e9s.
 
 #
@@ -69,6 +70,7 @@ CanNotSeparateCRS_1               = Ne peut pas s\u00e9parer 
le syst\u00e8me de
 CanNotSeparateTransform_3         = Ne peut pas s\u00e9parer la transformation 
parce-que le r\u00e9sultat aurait {2} dimension{2,choice,1#|2#s} en 
{0,choice,0#entr\u00e9|1#sortie} au lieu de {1}.
 CanNotSeparateTargetDimension_1   = La dimension de destination {0} 
d\u00e9pend de dimensions sources qui ont \u00e9t\u00e9 exclues.
 CanNotParseCombinedReference_2    = Ne peut pas d\u00e9coder la composante {1} 
dans l\u2019{0,choice,0#URN|1#URL} combin\u00e9.
+CannotParseElement_1              = Ne peut pas d\u00e9coder 
l\u2019\u00e9l\u00e9ment \u00ab\u202f{0}\u202f\u00bb\u00a0:
 CanNotParseWKT_2                  = Ne peut pas lire le \u00ab\u202fWell-Known 
Text\u202f\u00bb \u00e0 la ligne {0}. La cause est\u202f: {1}
 CanNotTransformCoordinates_2      = Ne peut pas transformer les 
coordonn\u00e9es ({0,number}; {1,number}).
 CanNotTransformEnvelopeToGeodetic = Ne peut pas transformer l\u2019enveloppe 
vers un r\u00e9f\u00e9rentiel g\u00e9od\u00e9sique.
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
index 038e256a13..5724153a01 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
@@ -60,11 +60,11 @@ import org.apache.sis.referencing.AbstractIdentifiedObject;
 import org.apache.sis.referencing.cs.CoordinateSystems;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.referencing.operation.transform.PassThroughTransform;
-import org.apache.sis.referencing.privy.PositionalAccuracyConstant;
 import org.apache.sis.referencing.privy.CoordinateOperations;
 import org.apache.sis.referencing.privy.ReferencingUtilities;
 import org.apache.sis.referencing.privy.WKTUtilities;
 import org.apache.sis.referencing.privy.WKTKeywords;
+import org.apache.sis.referencing.internal.PositionalAccuracyConstant;
 import org.apache.sis.referencing.internal.Resources;
 import org.apache.sis.metadata.privy.ImplementationHelper;
 import org.apache.sis.util.privy.Constants;
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 37a4593bc0..22936847f6 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
@@ -126,7 +126,7 @@ public class CoordinateOperationContext implements 
Serializable {
                 areaOfInterest.getWestBoundLongitude() > Longitude.MIN_VALUE ||
                 areaOfInterest.getEastBoundLongitude() < Longitude.MAX_VALUE)
             {
-                final CoordinateOperationContext context = new 
CoordinateOperationContext();
+                final var context = new CoordinateOperationContext();
                 context.setAreaOfInterest(areaOfInterest);
                 return context;
             }
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 136f8b010b..c8bf82dacf 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
@@ -65,9 +65,9 @@ import 
org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
 import org.apache.sis.referencing.factory.NoSuchAuthorityFactoryException;
 import org.apache.sis.referencing.privy.CoordinateOperations;
 import org.apache.sis.referencing.privy.EllipsoidalHeightCombiner;
-import org.apache.sis.referencing.privy.PositionalAccuracyConstant;
 import org.apache.sis.referencing.privy.ReferencingUtilities;
 import org.apache.sis.referencing.internal.ParameterizedTransformBuilder;
+import org.apache.sis.referencing.internal.PositionalAccuracyConstant;
 import org.apache.sis.referencing.internal.DeferredCoordinateOperation;
 import org.apache.sis.referencing.internal.Resources;
 import org.apache.sis.metadata.iso.citation.Citations;
@@ -131,14 +131,14 @@ class CoordinateOperationRegistry {
      * Such "ellipsoid shifts" are approximations and may have 1 kilometre 
error.
      *
      * @see org.apache.sis.referencing.datum.BursaWolfParameters
-     * @see 
org.apache.sis.referencing.privy.PositionalAccuracyConstant#DATUM_SHIFT_OMITTED
+     * @see PositionalAccuracyConstant#DATUM_SHIFT_OMITTED
      */
     static final Identifier ELLIPSOID_CHANGE = 
createIdentifier(Vocabulary.Keys.EllipsoidChange);
 
     /**
      * The identifier for a transformation which is a datum shift.
      *
-     * @see 
org.apache.sis.referencing.privy.PositionalAccuracyConstant#DATUM_SHIFT_APPLIED
+     * @see PositionalAccuracyConstant#DATUM_SHIFT_APPLIED
      */
     static final Identifier DATUM_SHIFT = 
createIdentifier(Vocabulary.Keys.DatumShift);
 
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConcatenatedOperation.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConcatenatedOperation.java
index 9ee0d7ed63..669701949a 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConcatenatedOperation.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConcatenatedOperation.java
@@ -36,9 +36,9 @@ import 
org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.apache.sis.referencing.IdentifiedObjects;
 import 
org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory;
 import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
-import org.apache.sis.referencing.privy.PositionalAccuracyConstant;
 import org.apache.sis.referencing.privy.CoordinateOperations;
 import org.apache.sis.referencing.privy.ReferencingUtilities;
+import org.apache.sis.referencing.internal.PositionalAccuracyConstant;
 import org.apache.sis.referencing.internal.Resources;
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.ComparisonMode;
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingUtilities.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingUtilities.java
index 6cb1e5cbb8..933df84dd8 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingUtilities.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingUtilities.java
@@ -206,30 +206,6 @@ public final class ReferencingUtilities extends Static {
         return (types.length != 0) ? types[0] : type;
     }
 
-    /**
-     * Returns whether the given <abbr>CRS</abbr> uses the given datum.
-     *
-     * @param  crs    the <abbr>CRS</abbr>, or {@code null}.
-     * @param  datum  the datum to compare with the <abbr>CRS</abbr> datum or 
datum ensemble.
-     * @return whether the given CRS <abbr>CRS</abbr> uses the specified datum.
-     */
-    public static boolean uses(final SingleCRS crs, final Datum datum) {
-        if (crs != null && datum != null) {
-            if (Utilities.equalsIgnoreMetadata(crs.getDatum(), datum)) {
-                return true;
-            }
-            final var ensemble = crs.getDatumEnsemble();
-            if (ensemble != null) {
-                for (final Datum member : ensemble.getMembers()) {
-                    if (Utilities.equalsIgnoreMetadata(member, datum)) {
-                        return true;
-                    }
-                }
-            }
-        }
-        return false;
-    }
-
     /**
      * Returns {@code true} if the type of the given datum is ellipsoidal. A 
vertical datum is not allowed
      * to be ellipsoidal according ISO 19111, but Apache SIS relaxes this 
restriction in some limited cases,
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultGeodeticDatumTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultGeodeticDatumTest.java
index 15c196a909..0618af7057 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultGeodeticDatumTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultGeodeticDatumTest.java
@@ -30,7 +30,7 @@ import org.apache.sis.xml.Namespaces;
 import org.apache.sis.io.wkt.Convention;
 import org.apache.sis.referencing.operation.matrix.Matrix4;
 import org.apache.sis.referencing.internal.AnnotatedMatrix;
-import org.apache.sis.referencing.privy.PositionalAccuracyConstant;
+import org.apache.sis.referencing.internal.PositionalAccuracyConstant;
 import org.apache.sis.metadata.iso.extent.DefaultExtent;
 import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
 import static org.apache.sis.referencing.GeodeticObjectVerifier.*;
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/internal/PositionalAccuracyConstantTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/internal/PositionalAccuracyConstantTest.java
new file mode 100644
index 0000000000..0202bf8af5
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/internal/PositionalAccuracyConstantTest.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.referencing.internal;
+
+import java.util.Map;
+import java.util.Iterator;
+import org.opengis.metadata.quality.Result;
+import org.opengis.metadata.quality.ConformanceResult;
+import org.opengis.metadata.quality.QuantitativeResult;
+import org.opengis.metadata.quality.PositionalAccuracy;
+import org.opengis.referencing.operation.CoordinateOperation;
+import org.apache.sis.referencing.operation.AbstractCoordinateOperation;
+
+// Test dependencies
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+import org.apache.sis.test.TestCase;
+
+
+/**
+ * Tests the {@link PositionalAccuracyConstant} class.
+ *
+ * @author  Martin Desruisseaux (IRD, Geomatys)
+ */
+public final class PositionalAccuracyConstantTest extends TestCase {
+    /**
+     * Creates a new test case.
+     */
+    public PositionalAccuracyConstantTest() {
+    }
+
+    /**
+     * Tests {@link PositionalAccuracyConstant#equals(Object)}.
+     */
+    @Test
+    public void testEquals() {
+        assertEquals(PositionalAccuracyConstant.DATUM_SHIFT_APPLIED,
+                     PositionalAccuracyConstant.DATUM_SHIFT_APPLIED);
+
+        assertEquals(PositionalAccuracyConstant.DATUM_SHIFT_OMITTED,
+                     PositionalAccuracyConstant.DATUM_SHIFT_OMITTED);
+
+        assertNotSame(PositionalAccuracyConstant.DATUM_SHIFT_APPLIED,
+                      PositionalAccuracyConstant.DATUM_SHIFT_OMITTED);
+
+        assertNotEquals(PositionalAccuracyConstant.DATUM_SHIFT_APPLIED,
+                        PositionalAccuracyConstant.DATUM_SHIFT_OMITTED);
+    }
+
+    /**
+     * Verifies the property values of some {@link PositionalAccuracyConstant} 
constants.
+     */
+    @Test
+    public void testQualitativeResults() {
+        final Iterator<? extends Result> appliedResults = 
PositionalAccuracyConstant.DATUM_SHIFT_APPLIED.getResults().iterator();
+        final Iterator<? extends Result> omittedResults = 
PositionalAccuracyConstant.DATUM_SHIFT_OMITTED.getResults().iterator();
+        final var applied = assertInstanceOf(ConformanceResult.class, 
appliedResults.next());
+        final var omitted = assertInstanceOf(ConformanceResult.class, 
omittedResults.next());
+        assertNotSame(applied, omitted);
+        assertTrue (applied.pass(), "DATUM_SHIFT_APPLIED");
+        assertFalse(omitted.pass(), "DATUM_SHIFT_OMITTED");
+        assertNotEquals(applied, omitted);
+        assertNotEquals(appliedResults, omittedResults);
+
+       assertNotEquals(assertInstanceOf(QuantitativeResult.class, 
appliedResults.next()),
+                       assertInstanceOf(QuantitativeResult.class, 
omittedResults.next()));
+
+       assertFalse(appliedResults.hasNext());
+       assertFalse(omittedResults.hasNext());
+    }
+
+    /**
+     * tests {@link 
PositionalAccuracyConstant#getLinearAccuracy(CoordinateOperation)}.
+     */
+    @Test
+    public void testQuantitativeResults() {
+        
assertLinearAccuracyEquals(PositionalAccuracyConstant.DATUM_SHIFT_APPLIED,
+                                   
PositionalAccuracyConstant.DATUM_SHIFT_ACCURACY);
+        
assertLinearAccuracyEquals(PositionalAccuracyConstant.DATUM_SHIFT_OMITTED,
+                                   
PositionalAccuracyConstant.UNKNOWN_ACCURACY);
+        
assertLinearAccuracyEquals(PositionalAccuracyConstant.INDIRECT_SHIFT_APPLIED,
+                                   
PositionalAccuracyConstant.INDIRECT_SHIFT_ACCURACY);
+    }
+
+    /**
+     * Asserts that the numerical accuracy associated to the given metadata is 
the expected value.
+     *
+     * @param metadata  the metadata to test.
+     * @param expected  the expected accuracy value.
+     */
+    private static void assertLinearAccuracyEquals(final PositionalAccuracy 
metadata, final double expected) {
+        var properties = Map.of(CoordinateOperation.NAME_KEY, "Dummy",
+                                
CoordinateOperation.COORDINATE_OPERATION_ACCURACY_KEY, metadata);
+        var operation = new AbstractCoordinateOperation(properties, null, 
null, null, null);
+        assertEquals(expected, 
PositionalAccuracyConstant.getLinearAccuracy(operation));
+    }
+}
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java
index 5d0bdc63f8..46fd3b62cf 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java
@@ -40,7 +40,7 @@ import 
org.opengis.referencing.operation.OperationNotFoundException;
 import org.opengis.referencing.operation.Matrix;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.referencing.CRS;
-import org.apache.sis.referencing.privy.PositionalAccuracyConstant;
+import org.apache.sis.referencing.internal.PositionalAccuracyConstant;
 import org.apache.sis.referencing.operation.transform.LinearTransform;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.referencing.operation.matrix.Matrices;
@@ -55,7 +55,7 @@ import org.apache.sis.measure.Units;
 import static org.apache.sis.util.privy.Constants.SECONDS_PER_DAY;
 import static org.apache.sis.referencing.privy.Formulas.LINEAR_TOLERANCE;
 import static org.apache.sis.referencing.privy.Formulas.ANGULAR_TOLERANCE;
-import static 
org.apache.sis.referencing.privy.PositionalAccuracyConstant.DATUM_SHIFT_APPLIED;
+import static 
org.apache.sis.referencing.internal.PositionalAccuracyConstant.DATUM_SHIFT_APPLIED;
 
 // Test dependencies
 import org.junit.jupiter.api.BeforeEach;
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactoryTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactoryTest.java
index 8decba736b..8422c98203 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactoryTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactoryTest.java
@@ -30,7 +30,7 @@ import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.referencing.privy.Formulas;
-import org.apache.sis.referencing.privy.PositionalAccuracyConstant;
+import org.apache.sis.referencing.internal.PositionalAccuracyConstant;
 import org.apache.sis.util.privy.Constants;
 import org.apache.sis.geometry.DirectPosition2D;
 import org.apache.sis.io.wkt.WKTFormat;
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/PositionalAccuracyConstantTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/PositionalAccuracyConstantTest.java
deleted file mode 100644
index ecbf216563..0000000000
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/PositionalAccuracyConstantTest.java
+++ /dev/null
@@ -1,68 +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.referencing.privy;
-
-import java.util.Collection;
-import org.opengis.metadata.quality.ConformanceResult;
-import org.opengis.metadata.quality.Result;
-
-// Test dependencies
-import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.*;
-import org.apache.sis.test.TestUtilities;
-import org.apache.sis.test.TestCase;
-
-
-/**
- * Tests the {@link PositionalAccuracyConstant} class.
- *
- * @author  Martin Desruisseaux (IRD, Geomatys)
- */
-public final class PositionalAccuracyConstantTest extends TestCase {
-    /**
-     * Creates a new test case.
-     */
-    public PositionalAccuracyConstantTest() {
-    }
-
-    /**
-     * Tests {@link PositionalAccuracyConstant} constants.
-     */
-    @Test
-    public void testPositionalAccuracy() {
-        assertEquals(PositionalAccuracyConstant.DATUM_SHIFT_APPLIED,
-                     PositionalAccuracyConstant.DATUM_SHIFT_APPLIED);
-
-        assertEquals(PositionalAccuracyConstant.DATUM_SHIFT_OMITTED,
-                     PositionalAccuracyConstant.DATUM_SHIFT_OMITTED);
-
-        assertNotSame(PositionalAccuracyConstant.DATUM_SHIFT_APPLIED,
-                      PositionalAccuracyConstant.DATUM_SHIFT_OMITTED);
-
-        final Collection<? extends Result> appliedResults = 
PositionalAccuracyConstant.DATUM_SHIFT_APPLIED.getResults();
-        final Collection<? extends Result> omittedResults = 
PositionalAccuracyConstant.DATUM_SHIFT_OMITTED.getResults();
-        final ConformanceResult applied = (ConformanceResult) 
TestUtilities.getSingleton(appliedResults);
-        final ConformanceResult omitted = (ConformanceResult) 
TestUtilities.getSingleton(omittedResults);
-        assertNotSame(applied, omitted);
-        assertTrue (applied.pass(), "DATUM_SHIFT_APPLIED");
-        assertFalse(omitted.pass(), "DATUM_SHIFT_OMITTED");
-        assertNotEquals(applied, omitted);
-        assertNotEquals(appliedResults, omittedResults);
-        assertNotEquals(PositionalAccuracyConstant.DATUM_SHIFT_APPLIED,
-                        PositionalAccuracyConstant.DATUM_SHIFT_OMITTED);
-    }
-}
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/WeakValueHashMap.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/WeakValueHashMap.java
index c340b0bf34..da842d5b1c 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/WeakValueHashMap.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/WeakValueHashMap.java
@@ -569,6 +569,30 @@ public class WeakValueHashMap<K,V> extends 
AbstractMap<K,V> {
         return intern(key, value, Wildcard.NO_VALUE);
     }
 
+    /**
+     * Returns the value associated to the given key, computing the value if 
it does not exist.
+     * This implementation is thread-safe.
+     *
+     * @param  key      key of the value to get.
+     * @param  creator  function to invoke for creating the value if it does 
not already exist.
+     * @return value (potentially newly created) for the given key.
+     * @since 1.5
+     */
+    @Override
+    public V computeIfAbsent(final K key, final Function<? super K, ? extends 
V> creator) {
+        V value = get(key);
+        if (value == null) {
+            V newValue = creator.apply(key);
+            if (newValue != null) {
+                value = putIfAbsent(key, newValue);     // A value may have 
been created concurrently.
+                if (value == null) {
+                    return newValue;
+                }
+            }
+        }
+        return value;
+    }
+
     /**
      * Replaces the entry for the specified key only if it is currently mapped 
to some value.
      *
diff --git a/netbeans-project/nbproject/project.properties 
b/netbeans-project/nbproject/project.properties
index 49c7f7a4a4..46801fed4b 100644
--- a/netbeans-project/nbproject/project.properties
+++ b/netbeans-project/nbproject/project.properties
@@ -110,6 +110,7 @@ test.options = --add-modules jama,GeographicLib.Java,\
                --add-exports 
org.apache.sis.metadata/org.apache.sis.xml.privy=org.apache.sis.storage.geotiff 
\
                --add-exports 
org.apache.sis.metadata/org.apache.sis.xml.bind.gcx=org.apache.sis.referencing \
                --add-exports 
org.apache.sis.metadata/org.apache.sis.metadata.privy=org.apache.sis.referencing.gazetteer
 \
+               --add-exports 
org.apache.sis.referencing/org.apache.sis.referencing.internal=org.apache.sis.openoffice
 \
                --add-exports 
org.apache.sis.feature/org.apache.sis.feature.privy=org.apache.sis.storage.sql \
                --add-exports 
org.apache.sis.feature/org.apache.sis.geometry.wrapper.jts=org.apache.sis.storage.sql,org.apache.sis.portrayal.map
 \
                --add-exports 
org.apache.sis.storage/org.apache.sis.storage.base=org.apache.sis.portrayal.map 
\

Reply via email to