This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/sis.git

commit a4a443d79e03313f59b1208ea658d18656630b4f
Merge: 0661f9eaf0 4e0bc740bc
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Mon Sep 1 12:13:16 2025 +0200

    Merge branch 'geoapi-3.1'.
    This work improves the support of EPSG version 12.
    The handling of datum and CRS types in `TableInfo` has been improved.
    The search of EPSG code for an existing object has been improved.

 .../org/apache/sis/console/CRSCommandTest.java     |   2 +-
 .../sis/coverage/grid/DimensionalityReduction.java |   2 +-
 .../org/apache/sis/coverage/grid/GridExtent.java   |   2 +-
 .../org/apache/sis/coverage/grid/GridGeometry.java |   2 +-
 .../sis/coverage/grid/ResampledGridCoverage.java   |   7 +-
 .../sis/geometry/wrapper/GeometryWrapper.java      |   4 +-
 .../geometry/wrapper/SpatialOperationContext.java  |   9 +-
 .../org/apache/sis/geometry/wrapper/jts/JTS.java   |   5 +-
 .../coverage/grid/DimensionalityReductionTest.java |   2 +-
 .../org.apache.sis.metadata/main/module-info.java  |   4 +
 .../metadata/iso/extent/DefaultVerticalExtent.java |   3 +-
 .../sis/metadata/privy/NameToIdentifier.java       |   2 +-
 .../sis/metadata/sql/privy/SQLUtilities.java       |  61 +-
 .../org/apache/sis/metadata/sql/privy/Syntax.java  |   2 +-
 .../sis/pending/geoapi/geometry/Geometry.java}     |  28 +-
 .../sis/pending/geoapi/geometry/package-info.java} |  34 +-
 .../main/org/apache/sis/temporal/TemporalDate.java |   2 +-
 .../sis/metadata/sql/privy/SQLUtilitiesTest.java   |   4 +-
 .../sis/openoffice/ReferencingFunctionsTest.java   |   5 +-
 .../org/apache/sis/map/coverage/RenderingData.java |   3 +-
 .../main/org/apache/sis/portrayal/Canvas.java      |  13 +-
 .../gazetteer/AbstractLocationType.java            |   4 +-
 .../gazetteer/GeohashReferenceSystem.java          |   5 +-
 .../gazetteer/MilitaryGridReferenceSystem.java     |   5 +-
 .../gazetteer/ReferencingByIdentifiers.java        |   4 +-
 .../sis/coordinate/DefaultCoordinateMetadata.java  |   4 +-
 .../sis/geometry/AbstractDirectPosition.java       |   6 +-
 .../org/apache/sis/geometry/AbstractEnvelope.java  |   2 +-
 .../org/apache/sis/geometry/EnvelopeReducer.java   |   5 +-
 .../main/org/apache/sis/geometry/Envelopes.java    |   6 +-
 .../apache/sis/geometry/WraparoundAdjustment.java  |   3 +-
 .../main/org/apache/sis/io/wkt/Formatter.java      |   2 +-
 .../apache/sis/io/wkt/GeodeticObjectParser.java    | 125 ++--
 .../sis/parameter/DefaultParameterDescriptor.java  |   6 +-
 .../sis/parameter/DefaultParameterValueGroup.java  |   8 +-
 .../parameter/UnmodifiableParameterValueGroup.java |   8 +-
 .../sis/referencing/AbstractIdentifiedObject.java  |  15 +-
 .../apache/sis/referencing/AuthorityFactories.java |  54 +-
 .../main/org/apache/sis/referencing/Builder.java   |   4 +-
 .../main/org/apache/sis/referencing/CRS.java       |  75 ++-
 .../main/org/apache/sis/referencing/CommonCRS.java |  40 +-
 .../sis/referencing/DefaultObjectDomain.java       |   3 +-
 .../sis/referencing/EPSGFactoryFallback.java       |  41 +-
 .../apache/sis/referencing/IdentifiedObjects.java  |  39 +-
 .../sis/referencing/StandardDefinitions.java       |  70 ++-
 .../apache/sis/referencing/crs/AbstractCRS.java    | 109 ++--
 .../sis/referencing/crs/AbstractDerivedCRS.java    |   4 +-
 .../sis/referencing/crs/AbstractSingleCRS.java     |  56 +-
 .../sis/referencing/crs/DefaultCompoundCRS.java    |  50 +-
 .../sis/referencing/crs/DefaultDerivedCRS.java     |  49 +-
 .../sis/referencing/crs/DefaultEngineeringCRS.java |  10 +
 .../sis/referencing/crs/DefaultGeodeticCRS.java    |  16 +-
 .../sis/referencing/crs/DefaultParametricCRS.java  |  10 +
 .../sis/referencing/crs/DefaultProjectedCRS.java   |  21 +-
 .../sis/referencing/crs/DefaultTemporalCRS.java    |   9 +
 .../sis/referencing/crs/DefaultVerticalCRS.java    |  10 +
 .../org/apache/sis/referencing/cs/AbstractCS.java  |  93 +--
 .../sis/referencing/cs/DefaultCompoundCS.java      |  40 +-
 .../cs/DefaultCoordinateSystemAxis.java            |   4 +-
 .../sis/referencing/datum/AbstractDatum.java       |  10 +-
 .../sis/referencing/datum/DatumOrEnsemble.java     | 244 ++++++--
 .../referencing/datum/DefaultDatumEnsemble.java    |   4 +-
 .../sis/referencing/datum/DefaultEllipsoid.java    |  11 +-
 .../referencing/datum/DefaultGeodeticDatum.java    |   6 +-
 .../sis/referencing/datum/DefaultImageDatum.java   |   4 +-
 .../referencing/datum/DefaultPrimeMeridian.java    |  14 +-
 .../referencing/datum/DefaultTemporalDatum.java    |   4 +-
 .../referencing/datum/DefaultVerticalDatum.java    |  67 +-
 .../factory/AuthorityFactoryIdentifier.java        |  19 +-
 .../referencing/factory/AuthorityFactoryProxy.java |   3 +
 .../factory/ConcurrentAuthorityFactory.java        | 130 +++-
 .../factory/GeodeticAuthorityFactory.java          |   2 +-
 .../factory/IdentifiedObjectFinder.java            | 686 +++++++++++++--------
 .../factory/MultiAuthoritiesFactory.java           | 113 +++-
 .../referencing/factory/sql/AuthorityCodes.java    | 177 ++++--
 .../factory/sql/CloseableReference.java            |   6 +
 .../referencing/factory/sql/EPSGCodeFinder.java    | 396 ++++++++++--
 .../referencing/factory/sql/EPSGDataAccess.java    | 531 ++++++++--------
 .../sis/referencing/factory/sql/EPSGFactory.java   |  32 +-
 .../sis/referencing/factory/sql/EPSGInstaller.java |  27 +-
 .../factory/sql/InstallationScriptProvider.java    | 198 +-----
 .../referencing/factory/sql/ObjectPertinence.java  |  11 +-
 .../sis/referencing/factory/sql/SQLTranslator.java |  34 +-
 .../sis/referencing/factory/sql/TableInfo.java     | 501 +++++++++------
 .../referencing/internal/EPSGParameterDomain.java  |   4 +-
 .../sis/referencing/internal/MergedProperties.java |   4 +-
 .../referencing/internal/PositionTransformer.java  |   6 +-
 .../apache/sis/referencing/internal/RTreeNode.java |   4 +-
 .../referencing/internal/VerticalDatumTypes.java   |  21 +-
 .../operation/AbstractCoordinateOperation.java     |  20 +-
 .../operation/AbstractSingleOperation.java         |   4 +-
 .../apache/sis/referencing/operation/CRSPair.java  |   7 +-
 .../operation/CoordinateOperationFinder.java       |   2 +-
 .../operation/CoordinateOperationRegistry.java     |   9 +-
 .../operation/DefaultConcatenatedOperation.java    |   1 +
 .../operation/DefaultOperationMethod.java          |  10 +-
 .../sis/referencing/operation/matrix/Matrices.java |  15 +-
 .../operation/projection/NormalizedProjection.java |   6 +-
 .../operation/provider/Mercator1SP.java            |   7 +-
 .../operation/provider/ObliqueMercator.java        |  12 +-
 .../operation/provider/PolarStereographicA.java    |   2 +-
 .../operation/provider/PseudoMercator.java         |   7 +-
 .../operation/provider/TransverseMercator.java     |   2 +-
 .../operation/transform/AbstractMathTransform.java |   2 +-
 .../operation/transform/ConcatenatedTransform.java |  15 +-
 .../transform/EllipsoidToRadiusTransform.java      |   6 +-
 .../operation/transform/PoleRotation.java          |   1 +
 .../sis/referencing/privy/AxisDirections.java      |   4 +-
 .../sis/referencing/privy/FilteredIterator.java    | 113 ++++
 .../org/apache/sis/referencing/privy/LazySet.java  |  25 +-
 .../apache/sis/referencing/privy/WKTUtilities.java |  32 -
 .../org/apache/sis/referencing/Assertions.java     |  69 ++-
 .../sis/referencing/AuthorityFactoriesTest.java    |   2 +-
 .../test/org/apache/sis/referencing/CRSTest.java   |   4 +-
 .../org/apache/sis/referencing/CommonCRSTest.java  |   4 +-
 .../sis/referencing/EPSGFactoryFallbackTest.java   |   2 +-
 .../sis/referencing/StandardDefinitionsTest.java   |  29 +
 .../factory/CommonAuthorityFactoryTest.java        |   4 +-
 .../factory/ConcurrentAuthorityFactoryTest.java    |   2 +-
 .../factory/IdentifiedObjectFinderTest.java        |   3 +-
 .../factory/MultiAuthoritiesFactoryTest.java       |   2 +-
 .../referencing/factory/sql/EPSGFactoryTest.java   |  59 +-
 .../referencing/factory/sql/EPSGInstallerTest.java |  20 +-
 .../factory/sql/EPSGScriptProvider.java            | 113 ++++
 .../sis/referencing/factory/sql/TableInfoTest.java |  14 +-
 .../transform/DefaultMathTransformFactoryTest.java |   2 +-
 .../report/CoordinateOperationMethods.java         |   2 +-
 .../sis/test/integration/ConsistencyTest.java      |  63 +-
 .../integration/CoordinateReferenceSystemTest.java |  36 --
 .../sis/storage/geotiff/reader/CRSBuilder.java     |   6 +-
 .../apache/sis/storage/netcdf/base/AxisType.java   |   2 +-
 .../apache/sis/storage/netcdf/base/CRSMerger.java  |   4 +-
 .../sis/storage/sql/feature/InfoStatements.java    |  14 +-
 .../sis/storage/sql/feature/SelectionClause.java   |   4 +-
 .../sis/storage/aggregate/CoverageAggregator.java  |   2 +-
 .../apache/sis/storage/aggregate/GroupByCRS.java   |   4 +-
 .../apache/sis/storage/image/WritableStore.java    |   2 +-
 .../apache/sis/setup/InstallationResources.java    |   2 +-
 .../main/org/apache/sis/util/CharSequences.java    |  66 +-
 .../main/org/apache/sis/util/ComparisonMode.java   | 146 +++--
 .../org/apache/sis/util/LenientComparable.java     |  18 +-
 .../main/org/apache/sis/util/Utilities.java        |   2 +-
 .../org/apache/sis/util/collection/TreeTable.java  |   2 +-
 .../main/org/apache/sis/util/logging/Logging.java  |   2 +-
 .../main/org/apache/sis/util/privy/Constants.java  |   1 +
 .../main/org/apache/sis/util/privy/Numerics.java   |   2 +-
 .../apache/sis/util/privy/SetOfUnknownSize.java    |   6 +-
 .../main/org/apache/sis/util/resources/Errors.java |   7 +-
 .../apache/sis/util/resources/Errors.properties    |   1 -
 .../apache/sis/util/resources/Errors_fr.properties |   1 -
 .../test/org/apache/sis/test/Assertions.java       |   4 +-
 .../org/apache/sis/util/CharSequencesTest.java     |   5 +-
 netbeans-project/nbproject/project.xml             |   1 +
 optional/build.gradle.kts                          |  18 +-
 .../main/org/apache/sis/gui/map/StatusBar.java     |   2 +-
 .../gui/referencing/PositionableProjection.java    |   3 +-
 .../sis/resources/embedded/EmbeddedResources.java  |  14 +-
 .../sis/resources/embedded/package-info.java       |  12 +-
 .../resources/embedded/EmbeddedResourcesTest.java  |  40 +-
 .../apache/sis/resources/embedded/Generator.java   | 117 ++--
 .../sis/referencing/factory/sql/epsg/.gitignore    |   5 +-
 .../sis/referencing/factory/sql/epsg/Finish.sql    |   0
 .../sis/referencing/factory/sql/epsg/Prepare.sql   |   4 +-
 .../sis/referencing/factory/sql/epsg/README.md     |  13 +-
 .../factory/sql/epsg/ScriptProvider.java           |  32 +-
 .../referencing/factory/sql/epsg/DebugTools.sql    |  10 +
 .../factory/sql/epsg/ScriptProviderTest.java       |   4 +-
 167 files changed, 3573 insertions(+), 2217 deletions(-)

diff --cc 
endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/GeometryWrapper.java
index 127f829658,0ef0e3126c..0cb39b8a58
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/GeometryWrapper.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/GeometryWrapper.java
@@@ -24,7 -24,7 +24,6 @@@ import javax.measure.Unit
  import javax.measure.Quantity;
  import javax.measure.IncommensurableException;
  import javax.measure.quantity.Length;
--import org.opengis.geometry.Geometry;
  import org.opengis.geometry.DirectPosition;
  import org.opengis.referencing.crs.CoordinateReferenceSystem;
  import org.opengis.referencing.operation.CoordinateOperation;
@@@ -43,9 -43,14 +42,10 @@@ import org.apache.sis.util.Unconvertibl
  import org.apache.sis.util.collection.BackingStoreException;
  import org.apache.sis.util.resources.Errors;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import java.util.Set;
 -import org.opengis.geometry.Boundary;
 -import org.opengis.geometry.TransfiniteSet;
 -import org.opengis.geometry.complex.Complex;
 -import org.opengis.filter.SpatialOperatorName;
 -import org.opengis.filter.DistanceOperatorName;
 -import org.opengis.coordinate.MismatchedDimensionException;
 +// Specific to the main branch:
++import org.apache.sis.pending.geoapi.geometry.Geometry;
 +import org.apache.sis.pending.geoapi.filter.SpatialOperatorName;
 +import org.apache.sis.pending.geoapi.filter.DistanceOperatorName;
  
  
  /**
diff --cc endorsed/src/org.apache.sis.metadata/main/module-info.java
index 48064df3d8,d160fb9e63..b03e011efd
--- a/endorsed/src/org.apache.sis.metadata/main/module-info.java
+++ b/endorsed/src/org.apache.sis.metadata/main/module-info.java
@@@ -76,9 -76,6 +76,13 @@@ module org.apache.sis.metadata 
      /*
       * Internal API open only to other Apache SIS modules.
       */
++    exports org.apache.sis.pending.geoapi.geometry to
++            org.apache.sis.referencing,
++            org.apache.sis.feature;
++
 +    exports org.apache.sis.pending.geoapi.temporal to
 +            org.apache.sis.feature;
 +
      exports org.apache.sis.temporal to
              org.apache.sis.referencing,
              org.apache.sis.feature,
diff --cc 
endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/geometry/Geometry.java
index e3b30f74d9,ffe6f48b2c..3c29399d9e
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/geometry/Geometry.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/geometry/Geometry.java
@@@ -14,33 -14,27 +14,27 @@@
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
- package org.apache.sis.referencing.factory.sql;
 -package org.apache.sis.geometries.operation.spatialedition;
++package org.apache.sis.pending.geoapi.geometry;
  
- // Test dependencies
- import org.junit.jupiter.api.Test;
- import static org.junit.jupiter.api.Assertions.*;
- import org.apache.sis.test.TestCase;
 -import org.apache.sis.geometries.Geometry;
 -import org.apache.sis.geometries.operation.Operation;
++import org.opengis.geometry.Envelope;
++import org.opengis.referencing.crs.CoordinateReferenceSystem;
  
  
  /**
-  * Tests {@link TableInfo}.
 - * Transform geometry to a Primitive or MultiPrimitive.
++ * Placeholder for a GeoAPI interfaces incomplete in GeoAPI 3.0.
   *
 - * @author Johann Sorel (Geomatys)
 + * @author  Martin Desruisseaux (Geomatys)
++ * @since   1.5
++ * @version 1.5
   */
- public final class TableInfoTest extends TestCase {
 -public class ToPrimitive extends Operation<ToPrimitive> {
 -
 -    public Geometry result;
 -
++public interface Geometry extends org.opengis.geometry.Geometry {
      /**
-      * Creates a new test case.
 -     *
 -     * @param geom geometry to transform
++     * Returns the coordinate reference system.
       */
-     public TableInfoTest() {
 -    public ToPrimitive(Geometry geom) {
 -        super(geom);
--    }
++    CoordinateReferenceSystem getCoordinateReferenceSystem();
  
 +    /**
-      * Tests {@link TableInfo#getObjectClassName(String)}.
++     * Returns the minimum bounding box for this {@code Geometry}.
 +     */
-     @Test
-     public void testgGetObjectClassName() {
-         assertEquals("Datum",                     
TableInfo.getObjectClassName("epsg_datum").orElseThrow());
-         assertEquals("Ellipsoid",                 
TableInfo.getObjectClassName("epsg_ellipsoid").orElseThrow());
-         assertEquals("CoordinateReferenceSystem", 
TableInfo.getObjectClassName("epsg_coordinatereferencesystem").orElseThrow());
-     }
++    Envelope getEnvelope();
  }
diff --cc 
endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/geometry/package-info.java
index e3b30f74d9,4307e9c627..e018f1dd2c
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/geometry/package-info.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/geometry/package-info.java
@@@ -14,33 -14,14 +14,17 @@@
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
- package org.apache.sis.referencing.factory.sql;
- 
- // Test dependencies
- import org.junit.jupiter.api.Test;
- import static org.junit.jupiter.api.Assertions.*;
- import org.apache.sis.test.TestCase;
- 
  
  /**
-  * Tests {@link TableInfo}.
 - * CQL parser.
++ * Placeholder for GeoAPI interfaces not present in GeoAPI 3.0.
+  *
 - * @author  Johann Sorel (Geomatys)
++ * <STRONG>Do not use!</STRONG>
++ *
++ * This package is for internal use by SIS only. Classes in this package
++ * may change in incompatible ways in any future version without notice.
 + *
 + * @author  Martin Desruisseaux (Geomatys)
++ * @since   0.3
++ * @version 1.5
   */
- public final class TableInfoTest extends TestCase {
-     /**
-      * Creates a new test case.
-      */
-     public TableInfoTest() {
-     }
- 
-     /**
-      * Tests {@link TableInfo#getObjectClassName(String)}.
-      */
-     @Test
-     public void testgGetObjectClassName() {
-         assertEquals("Datum",                     
TableInfo.getObjectClassName("epsg_datum").orElseThrow());
-         assertEquals("Ellipsoid",                 
TableInfo.getObjectClassName("epsg_ellipsoid").orElseThrow());
-         assertEquals("CoordinateReferenceSystem", 
TableInfo.getObjectClassName("epsg_coordinatereferencesystem").orElseThrow());
-     }
 -module org.apache.sis.cql {
 -    requires transitive org.apache.sis.feature;
 -    requires org.locationtech.jts;
 -    requires org.antlr.antlr4.runtime;
--}
++package org.apache.sis.pending.geoapi.geometry;
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractDirectPosition.java
index 8d53bf50e5,9624762637..ab836d7d7b
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractDirectPosition.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractDirectPosition.java
@@@ -204,8 -110,8 +204,8 @@@ public abstract class AbstractDirectPos
       * {@code null}, then all coordinate values are set to {@link Double#NaN 
NaN}.
       *
       * <p>If this position and the given position have a non-null CRS, then 
the default implementation
-      * requires the CRS to be {@linkplain Utilities#equalsIgnoreMetadata 
equals (ignoring metadata)},
+      * requires the CRS to be {@linkplain CRS#equivalent equivalent},
 -     * otherwise a {@code MismatchedCoordinateMetadataException} is thrown. 
However, subclass may choose
 +     * otherwise a {@code MismatchedReferenceSystemException} is thrown. 
However, subclass may choose
       * to assign the CRS of this position to the CRS of the given 
position.</p>
       *
       * @param  position  the new position, or {@code null}.
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
index 4e1266cee8,3ebf31d761..8631f90798
--- 
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
@@@ -699,7 -689,9 +700,9 @@@ class GeodeticObjectParser extends Math
       * @throws FactoryException if the factory cannot create the coordinate 
system.
       */
      private CoordinateSystem parseCoordinateSystem(final Element parent, 
String type, int dimension,
-             final boolean isWKT1, final Unit<?> defaultUnit, final Datum 
datum) throws ParseException, FactoryException
+                                                    final boolean isWKT1, 
final Unit<?> defaultUnit,
 -                                                   final boolean geodetic, 
final RealizationMethod vertical)
++                                                   final boolean geodetic, 
final VerticalDatumType vertical)
+             throws ParseException, FactoryException
      {
          axisOrder.clear();
          final boolean is3D = (dimension >= 3);
@@@ -839,20 -831,17 +842,17 @@@
                      z         = "h";
                      nz        = "Height";
                      direction = AxisDirection.UP;
-                     if (datum instanceof VerticalDatum) {
-                         final VerticalDatumType vt = ((VerticalDatum) 
datum).getVerticalDatumType();
-                         if (vt == VerticalDatumType.GEOIDAL) {
-                             nz = AxisNames.GRAVITY_RELATED_HEIGHT;
-                             z  = "H";
-                         } else if (vt == VerticalDatumType.DEPTH) {
-                             direction = AxisDirection.DOWN;
-                             nz = AxisNames.DEPTH;
-                             z  = "D";
-                         } else if (VerticalDatumTypes.ellipsoidal(vt)) {
-                             // Not allowed by ISO 19111 as a standalone axis, 
but SIS is
-                             // tolerant to this case since it is sometimes 
hard to avoid.
-                             nz = AxisNames.ELLIPSOIDAL_HEIGHT;
-                         }
 -                    if (vertical == RealizationMethod.GEOID) {
++                    if (vertical == VerticalDatumType.GEOIDAL) {
+                         nz = AxisNames.GRAVITY_RELATED_HEIGHT;
+                         z  = "H";
 -                    } else if (vertical == RealizationMethod.TIDAL) {
++                    } else if (vertical == VerticalDatumType.DEPTH) {
+                         direction = AxisDirection.DOWN;
+                         nz = AxisNames.DEPTH;
+                         z  = "D";
+                     } else if (VerticalDatumTypes.ellipsoidal(vertical)) {
+                         // Not allowed by ISO 19111 as a standalone axis, but 
SIS is
+                         // tolerant to this case since it is sometimes hard 
to avoid.
+                         nz = AxisNames.ELLIPSOIDAL_HEIGHT;
                      }
                      break;
                  }
@@@ -1939,7 -1928,7 +1940,7 @@@
          }
          final CoordinateSystem cs;
          try {
-             cs = parseCoordinateSystem(element, WKTKeywords.vertical, 1, 
isWKT1, unit, datum);
 -            cs = parseCoordinateSystem(element, WKTKeywords.vertical, 1, 
isWKT1, unit, false, datum.getRealizationMethod().orElse(null));
++            cs = parseCoordinateSystem(element, WKTKeywords.vertical, 1, 
isWKT1, unit, false, datum.getVerticalDatumType());
              final Map<String,?> properties = parseMetadataAndClose(element, 
name, datum);
              if (cs instanceof VerticalCS) {
                  final CRSFactory crsFactory = factories.getCRSFactory();
@@@ -2081,9 -2071,9 +2082,9 @@@
          }
          final CoordinateSystem cs;
          try {
-             cs = parseCoordinateSystem(element, WKTKeywords.parametric, 1, 
false, unit, datum);
+             cs = parseCoordinateSystem(element, WKTKeywords.parametric, 1, 
false, unit, false, null);
              final Map<String,?> properties = parseMetadataAndClose(element, 
name, datum);
 -            if (cs instanceof ParametricCS) {
 +            if (cs != null) {
                  final CRSFactory crsFactory = factories.getCRSFactory();
                  if (baseCRS != null) {
                      return crsFactory.createDerivedCRS(properties, baseCRS, 
fromBase, cs);
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java
index f62d17c438,20250ab122..27dc8e2409
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java
@@@ -806,18 -787,11 +808,11 @@@ public class AbstractIdentifiedObject e
                  return deepEquals(getName(),        that.getName(),        
mode) &&
                         deepEquals(getAlias(),       that.getAlias(),       
mode) &&
                         deepEquals(getIdentifiers(), that.getIdentifiers(), 
mode) &&
 -                       deepEquals(getDomains(),     that.getDomains(),     
mode) &&
 +                       deepEquals(getDomains(),     Legacy.getDomains(that), 
mode) &&
                         deepEquals(getRemarks(),     that.getRemarks(),     
mode);
              }
-             case IGNORE_METADATA:
-             case APPROXIMATE:
-             case ALLOW_VARIANT:
-             case DEBUG: {
-                 return implementsSameInterface(object);
-             }
              default: {
-                 throw new IllegalArgumentException(Errors.format(
-                         Errors.Keys.UnknownEnumValue_2, ComparisonMode.class, 
mode));
+                 return implementsSameInterface(object);
              }
          }
      }
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
index c438930a46,21ffa968fd..3ba6f78225
--- 
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
@@@ -27,6 -27,6 +27,7 @@@ import java.util.logging.LogRecord
  import java.time.temporal.Temporal;
  import org.opengis.util.FactoryException;
  import org.opengis.geometry.Envelope;
++import org.opengis.geometry.Geometry;
  import org.opengis.referencing.NoSuchAuthorityCodeException;
  import org.opengis.referencing.IdentifiedObject;
  import org.opengis.referencing.cs.CartesianCS;
@@@ -51,6 -51,6 +52,8 @@@ import org.opengis.referencing.operatio
  import org.opengis.referencing.operation.TransformException;
  import org.opengis.metadata.citation.Citation;
  import org.opengis.metadata.extent.Extent;
++import org.opengis.metadata.extent.BoundingPolygon;
++import org.opengis.metadata.extent.GeographicExtent;
  import org.opengis.metadata.extent.GeographicBoundingBox;
  import org.apache.sis.measure.Units;
  import org.apache.sis.geometry.Envelopes;
@@@ -96,11 -97,14 +100,13 @@@ import org.apache.sis.util.logging.Logg
  // Specific to the main and geoapi-3.1 branches:
  import org.opengis.referencing.crs.GeneralDerivedCRS;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.geometry.Geometry;
 -import org.opengis.referencing.ObjectDomain;
 -import org.opengis.referencing.crs.DerivedCRS;
 -import org.opengis.referencing.datum.DynamicReferenceFrame;
 -import org.opengis.metadata.extent.BoundingPolygon;
 -import org.opengis.metadata.extent.GeographicExtent;
 -import org.opengis.coordinate.CoordinateMetadata;
 +// Specific to the main branch:
 +import org.apache.sis.coordinate.DefaultCoordinateMetadata;
++import org.apache.sis.referencing.DefaultObjectDomain;
++import org.apache.sis.referencing.crs.AbstractCRS;
 +import org.apache.sis.referencing.datum.DefaultGeodeticDatum;
 +import org.apache.sis.referencing.datum.DefaultVerticalDatum;
 +import static 
org.apache.sis.pending.geoapi.referencing.MissingMethods.getDatumEnsemble;
  
  
  /**
@@@ -877,7 -919,39 +911,40 @@@ public final class CRS extends Static 
      public static Envelope getDomainOfValidity(final 
CoordinateReferenceSystem crs) {
          Envelope envelope = null;
          GeneralEnvelope merged = null;
 -        if (crs != null) {
 -            for (final ObjectDomain domain : crs.getDomains()) {
++        if (crs instanceof AbstractCRS) {
++            for (final DefaultObjectDomain domain : ((AbstractCRS) 
crs).getDomains()) {
+                 final Extent domainOfValidity = domain.getDomainOfValidity();
+                 if (domainOfValidity != null) {
+                     for (final GeographicExtent extent : 
domainOfValidity.getGeographicElements()) {
+                         if (extent instanceof BoundingPolygon && 
!Boolean.FALSE.equals(extent.getInclusion())) {
+                             for (final Geometry geometry : ((BoundingPolygon) 
extent).getPolygons()) {
 -                                final Envelope candidate = 
geometry.getEnvelope();
++                                if (!(geometry instanceof 
org.apache.sis.pending.geoapi.geometry.Geometry)) continue;
++                                final Envelope candidate = 
((org.apache.sis.pending.geoapi.geometry.Geometry) geometry).getEnvelope();
+                                 if (candidate != null) {
+                                     final CoordinateReferenceSystem sourceCRS 
= candidate.getCoordinateReferenceSystem();
+                                     if (sourceCRS == null || 
equivalent(sourceCRS, crs)) {
+                                         if (envelope == null) {
+                                             envelope = candidate;
+                                         } else {
+                                             if (merged == null) {
+                                                 envelope = merged = new 
GeneralEnvelope(envelope);
+                                             }
+                                             merged.add(envelope);
+                                         }
+                                     }
+                                 }
+                             }
+                         }
+                     }
+                 }
+             }
+         }
+         /*
+          * If no envelope was found, uses the geographic bounding box as a 
fallback. We will
+          * need to transform it from WGS84 to the supplied CRS. This step was 
not required in
+          * the previous block because the latter selected only envelopes in 
the right CRS.
+          */
 -        if (envelope == null) {
 +        /* if (envelope == null) */ {   // Condition needed on other branches 
but not on trunk.
              final GeographicBoundingBox bounds = 
getGeographicBoundingBox(crs);
              if (bounds != null && 
!Boolean.FALSE.equals(bounds.getInclusion())) {
                  /*
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EPSGFactoryFallback.java
index dab6091587,5d6f57441a..2a7f97c27c
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EPSGFactoryFallback.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EPSGFactoryFallback.java
@@@ -133,9 -130,10 +130,9 @@@ final class EPSGFactoryFallback extend
          final boolean ellipsoid  = type.isAssignableFrom(Ellipsoid    .class);
          final boolean datum      = type.isAssignableFrom(GeodeticDatum.class);
          final boolean geographic = type.isAssignableFrom(GeographicCRS.class);
 -        @SuppressWarnings("deprecation")
          final boolean geocentric = type.isAssignableFrom(GeocentricCRS.class);
          final boolean projected  = type.isAssignableFrom(ProjectedCRS .class);
-         final Set<String> codes = new LinkedHashSet<>();
+         final var codes = new LinkedHashSet<String>();
          if (pm) codes.add(StandardDefinitions.GREENWICH);
          for (final CommonCRS crs : CommonCRS.values()) {
              if (ellipsoid)  add(codes, crs.ellipsoid);
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
index 2decb802a2,67e0e6a6ae..32b4d34257
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
@@@ -272,7 -271,7 +272,7 @@@ public final class IdentifiedObjects ex
              if (authority instanceof IdentifierSpace<?>) {
                  cs = ((IdentifierSpace<?>) authority).getName();
              }
-             for (final ReferenceIdentifier identifier : 
nonNull(object.getIdentifiers())) {
 -            for (final Identifier identifier : 
CollectionsExt.nonNull(object.getIdentifiers())) {
++            for (final ReferenceIdentifier identifier : 
CollectionsExt.nonNull(object.getIdentifiers())) {
                  if (identifier != null) {                       // Paranoiac 
check.
                      if (cs != null && 
cs.equalsIgnoreCase(identifier.getCodeSpace())) {
                          return identifier;      // Match based on codespace.
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java
index 3afcfb5a39,457b834f88..028ec16420
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java
@@@ -299,25 -294,22 +297,48 @@@ public class AbstractCRS extends Abstra
           * User could provide his own CRS implementation outside this SIS 
package, so we have
           * to check for SingleCRS interface. But all SIS classes override 
this implementation.
           */
-         return (this instanceof SingleCRS) ? ((SingleCRS) this).getDatum() : 
null;
+         if (this instanceof SingleCRS) {
+             final var crs = (SingleCRS) this;
+             final Datum datum = crs.getDatum();
+             if (datum != null) {
+                 return datum;
+             }
+             if (legacy) {
 -                final var ensemble = crs.getDatumEnsemble();
++                final var ensemble = getDatumEnsemble(crs);
+                 if (ensemble instanceof Datum) {
+                     return (Datum) ensemble;
+                 }
+             }
+         }
+         return null;
+     }
+ 
++    /**
++     * Returns the datum ensemble of the given <abbr>CRS</abbr>.
++     *
++     * @param  crs  the <abbr>CRS</abbr> from which to get the datum ensemble.
++     * @return the datum ensemble, or {@code null} if none.
++     */
++    static DefaultDatumEnsemble<?> getDatumEnsemble(final 
CoordinateReferenceSystem crs) {
++        return (crs instanceof AbstractCRS) ? ((AbstractCRS) 
crs).getDatumEnsemble() : null;
 +    }
 +
 +    /**
 +     * Returns the datum ensemble.
 +     *
 +     * @return the datum ensemble, or {@code null} if none.
 +     */
 +    DefaultDatumEnsemble<?> getDatumEnsemble() {
 +        return null;
 +    }
 +
 +    /**
 +     * Initializes the handler for getting datum ensemble of an arbitrary CRS.
 +     */
 +    static {
-         MissingMethods.datumEnsemble = (crs) -> (crs instanceof AbstractCRS) 
? ((AbstractCRS) crs).getDatumEnsemble() : null;
++        MissingMethods.datumEnsemble = AbstractCRS::getDatumEnsemble;
 +    }
 +
      /**
       * Returns the coordinate system.
       *
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractSingleCRS.java
index 16172dd7aa,70690e8e25..d1276f17e8
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractSingleCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractSingleCRS.java
@@@ -31,13 -31,12 +31,12 @@@ import org.apache.sis.util.resources.Er
  import org.apache.sis.referencing.IdentifiedObjects;
  import org.apache.sis.referencing.cs.AbstractCS;
  import org.apache.sis.referencing.internal.Resources;
- import org.apache.sis.metadata.privy.Identifiers;
+ import org.apache.sis.referencing.datum.DatumOrEnsemble;
  import org.apache.sis.metadata.privy.ImplementationHelper;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.referencing.datum.DatumEnsemble;
 -import org.opengis.metadata.Identifier;
 +// Specific to the main branch:
 +import org.opengis.referencing.ReferenceIdentifier;
 +import org.apache.sis.referencing.datum.DefaultDatumEnsemble;
- import org.apache.sis.pending.geoapi.referencing.MissingMethods;
  
  
  /**
@@@ -170,11 -169,11 +169,11 @@@ class AbstractSingleCRS<D extends Datum
      AbstractSingleCRS(final SingleCRS crs) {
          super(crs);
          datum = (D) crs.getDatum();
 -        if (datum instanceof DatumEnsemble<?>) {
 +        if (datum instanceof DefaultDatumEnsemble<?>) {
              throw new IllegalArgumentException(
 -                    Errors.format(Errors.Keys.IllegalPropertyValueClass_2, 
"datum", DatumEnsemble.class));
 +                    Errors.format(Errors.Keys.IllegalPropertyValueClass_2, 
"datum", DefaultDatumEnsemble.class));
          }
-         ensemble = (DefaultDatumEnsemble<D>) 
MissingMethods.getDatumEnsemble(crs);
 -        ensemble = (DatumEnsemble<D>) crs.getDatumEnsemble();
++        ensemble = (DefaultDatumEnsemble<D>) getDatumEnsemble(crs);
          checkDatum(null);
      }
  
@@@ -238,25 -221,24 +221,24 @@@
      @Override
      public boolean equals(final Object object, ComparisonMode mode) {
          if (super.equals(object, mode)) {
-             switch (mode) {
-                 case STRICT: {
-                     final var that = (AbstractSingleCRS<?>) object;
-                     return Objects.equals(datum, that.datum) && 
Objects.equals(ensemble, that.ensemble);
-                 }
-                 default: {
-                     final var that = (SingleCRS) object;
-                     final var d1   = this.getDatum();
-                     final var d2   = that.getDatum();
-                     if (mode == ComparisonMode.DEBUG) {
-                         mode = ComparisonMode.ALLOW_VARIANT;    // For 
avoiding too early `AssertionError`.
-                     }
-                     if (Utilities.deepEquals(d1, d2, mode)) {
-                         return mode.allowsVariant() || 
Utilities.deepEquals(getDatumEnsemble(), MissingMethods.getDatumEnsemble(that), 
mode);
-                     } else if (mode.allowsVariant()) {
-                         return 
isHeuristicMatchForName(this.getDatumEnsemble(), d2, mode) ||
-                                
isHeuristicMatchForName(MissingMethods.getDatumEnsemble(that), d1, mode);
-                     }
+             if (mode == ComparisonMode.STRICT) {
+                 final var that = (AbstractSingleCRS<?>) object;
+                 return Objects.equals(datum, that.datum) && 
Objects.equals(ensemble, that.ensemble);
+             }
+             final var that = (SingleCRS) object;
+             final var d1   = this.getDatum();
+             final var d2   = that.getDatum();
+             if (mode == ComparisonMode.DEBUG) {
+                 mode = ComparisonMode.ALLOW_VARIANT;    // For avoiding too 
early `AssertionError`.
+             }
+             if (Utilities.deepEquals(d1, d2, mode)) {
+                 if (d1 != null && d2 != null && mode.isCompatibility()) {
+                     return true;
                  }
 -                return Utilities.deepEquals(getDatumEnsemble(), 
that.getDatumEnsemble(), mode);
++                return Utilities.deepEquals(getDatumEnsemble(), 
getDatumEnsemble(that), mode);
+             } else if (mode.isCompatibility()) {
+                 return DatumOrEnsemble.isLegacyDatum(this.getDatumEnsemble(), 
d2, mode) ||
 -                       DatumOrEnsemble.isLegacyDatum(that.getDatumEnsemble(), 
d1, mode);
++                       DatumOrEnsemble.isLegacyDatum(getDatumEnsemble(that), 
d1, mode);
              }
          }
          return false;
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
index 657427192c,714f98611d..40f51712e6
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
@@@ -59,12 -60,12 +60,11 @@@ import org.apache.sis.io.wkt.Formatter
  import org.apache.sis.util.ComparisonMode;
  import org.apache.sis.util.collection.Containers;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.referencing.datum.DatumEnsemble;
 -import org.opengis.referencing.datum.ParametricDatum;
 -import org.opengis.referencing.crs.ParametricCRS;
 -import org.opengis.referencing.cs.ParametricCS;
 -import org.opengis.coordinate.MismatchedDimensionException;
 +// Specific to the main branch:
 +import org.opengis.geometry.MismatchedDimensionException;
 +import org.apache.sis.referencing.cs.DefaultParametricCS;
 +import org.apache.sis.referencing.datum.DefaultDatumEnsemble;
 +import org.apache.sis.referencing.datum.DefaultParametricDatum;
- import org.apache.sis.pending.geoapi.referencing.MissingMethods;
  
  
  /**
@@@ -429,8 -430,20 +429,20 @@@ public class DefaultDerivedCRS extends 
       * @since 1.5
       */
      @Override
 -    public DatumEnsemble<?> getDatumEnsemble() {
 -        return getBaseCRS().getDatumEnsemble();
 +    public DefaultDatumEnsemble<?> getDatumEnsemble() {
-         return MissingMethods.getDatumEnsemble(getBaseCRS());
++        return getDatumEnsemble(getBaseCRS());
+     }
+ 
+     /**
+      * Returns the datum or a view of the ensemble as a datum.
+      */
+     @Override
+     Datum getDatumOrEnsemble(final boolean legacy) {
+         final SingleCRS baseCRS = getBaseCRS();
+         if (baseCRS instanceof AbstractCRS) {
+             return ((AbstractCRS) baseCRS).getDatumOrEnsemble(legacy);
+         }
+         return super.getDatumOrEnsemble(legacy);
      }
  
      /**
@@@ -681,6 -692,16 +691,11 @@@
              return (GeodeticDatum) super.getDatum();
          }
  
 -        /** Returns the datum ensemble of the base geodetic CRS. */
 -        @Override public DatumEnsemble<GeodeticDatum> getDatumEnsemble() {
 -            return ((GeodeticCRS) getBaseCRS()).getDatumEnsemble();
 -        }
 -
+         /** Returns the datum or a view of the ensemble as a datum. */
+         @Override GeodeticDatum getDatumOrEnsemble(final boolean legacy) {
+             return legacy ? DatumOrEnsemble.asDatum((GeodeticCRS) 
getBaseCRS()) : getDatum();
+         }
+ 
          /** Returns a coordinate reference system of the same type as this 
CRS but with different axes. */
          @Override AbstractCRS createSameType(final AbstractCS derivedCS) {
              return new Geodetic(this, derivedCS);
@@@ -732,6 -753,16 +747,11 @@@
              return (VerticalDatum) super.getDatum();
          }
  
 -        /** Returns the datum ensemble of the base vertical CRS. */
 -        @Override public DatumEnsemble<VerticalDatum> getDatumEnsemble() {
 -            return ((VerticalCRS) getBaseCRS()).getDatumEnsemble();
 -        }
 -
+         /** Returns the datum or a view of the ensemble as a datum. */
+         @Override VerticalDatum getDatumOrEnsemble(final boolean legacy) {
+             return legacy ? DatumOrEnsemble.asDatum((VerticalCRS) 
getBaseCRS()) : getDatum();
+         }
+ 
          /** Returns the coordinate system given at construction time. */
          @Override public VerticalCS getCoordinateSystem() {
              return (VerticalCS) super.getCoordinateSystem();
@@@ -788,6 -819,16 +808,11 @@@
              return (TemporalDatum) super.getDatum();
          }
  
 -        /** Returns the datum ensemble of the base temporal CRS. */
 -        @Override public DatumEnsemble<TemporalDatum> getDatumEnsemble() {
 -            return ((TemporalCRS) getBaseCRS()).getDatumEnsemble();
 -        }
 -
+         /** Returns the datum or a view of the ensemble as a datum. */
+         @Override TemporalDatum getDatumOrEnsemble(final boolean legacy) {
+             return legacy ? DatumOrEnsemble.asDatum((TemporalCRS) 
getBaseCRS()) : getDatum();
+         }
+ 
          /** Returns the coordinate system given at construction time. */
          @Override public TimeCS getCoordinateSystem() {
              return (TimeCS) super.getCoordinateSystem();
@@@ -840,13 -881,23 +865,18 @@@
          }
  
          /** Returns the datum of the base parametric CRS. */
 -        @Override public ParametricDatum getDatum() {
 -            return (ParametricDatum) super.getDatum();
 -        }
 -
 -        /** Returns the datum ensemble of the base parametric CRS. */
 -        @Override public DatumEnsemble<ParametricDatum> getDatumEnsemble() {
 -            return ((ParametricCRS) getBaseCRS()).getDatumEnsemble();
 +        @Override public DefaultParametricDatum getDatum() {
 +            return (DefaultParametricDatum) super.getDatum();
          }
  
+         /** Returns the datum or a view of the ensemble as a datum. */
 -        @Override ParametricDatum getDatumOrEnsemble(final boolean legacy) {
 -            return legacy ? DatumOrEnsemble.asDatum((ParametricCRS) 
getBaseCRS()) : getDatum();
++        @Override DefaultParametricDatum getDatumOrEnsemble(final boolean 
legacy) {
++            return getDatum();
+         }
+ 
          /** Returns the coordinate system given at construction time. */
 -        @Override public ParametricCS getCoordinateSystem() {
 -            return (ParametricCS) super.getCoordinateSystem();
 +        @Override public DefaultParametricCS getCoordinateSystem() {
 +            return (DefaultParametricCS) super.getCoordinateSystem();
          }
  
          /** Returns a coordinate reference system of the same type as this 
CRS but with different axes. */
@@@ -903,6 -954,16 +933,11 @@@
              return (EngineeringDatum) super.getDatum();
          }
  
 -        /** Returns the datum ensemble of the base engineering CRS. */
 -        @Override public DatumEnsemble<EngineeringDatum> getDatumEnsemble() {
 -            return ((EngineeringCRS) getBaseCRS()).getDatumEnsemble();
 -        }
 -
+         /** Returns the datum or a view of the ensemble as a datum. */
+         @Override EngineeringDatum getDatumOrEnsemble(final boolean legacy) {
+             return legacy ? DatumOrEnsemble.asDatum((EngineeringCRS) 
getBaseCRS()) : getDatum();
+         }
+ 
          /** Returns a coordinate reference system of the same type as this 
CRS but with different axes. */
          @Override AbstractCRS createSameType(final AbstractCS derivedCS) {
              return new Engineering(this, derivedCS);
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
index 06f36fbe55,04ef599736..cf2b16bdf0
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
@@@ -160,14 -160,15 +161,23 @@@ class DefaultGeodeticCRS extends Abstra
          return super.getDatum();
      }
  
 +    /**
 +     * Initializes the handler for getting datum ensemble of an arbitrary CRS.
 +     */
 +    static {
 +        MissingMethods.geodeticDatumEnsemble = (crs) ->
 +                (crs instanceof DefaultGeodeticCRS) ? ((DefaultGeodeticCRS) 
crs).getDatumEnsemble() : null;
 +    }
 +
+     /**
+      * Returns the datum or a view of the ensemble as a datum.
+      * The {@code legacy} argument tells whether this method is invoked for 
formatting in a legacy <abbr>WKT</abbr> format.
+      */
+     @Override
+     final GeodeticDatum getDatumOrEnsemble(final boolean legacy) {
+         return legacy ? DatumOrEnsemble.asDatum(this) : getDatum();
+     }
+ 
      /**
       * Returns a coordinate reference system of the same type as this CRS but 
with different axes.
       * This method shall be overridden by all {@code DefaultGeodeticCRS} 
subclasses in this package.
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultParametricCRS.java
index f637341b31,5fedc4597a..efb669c751
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultParametricCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultParametricCRS.java
@@@ -23,12 -23,14 +23,13 @@@ import jakarta.xml.bind.annotation.XmlT
  import org.apache.sis.referencing.privy.WKTKeywords;
  import org.apache.sis.referencing.cs.AxesConvention;
  import org.apache.sis.referencing.cs.AbstractCS;
+ import org.apache.sis.referencing.datum.DatumOrEnsemble;
  import org.apache.sis.io.wkt.Formatter;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.referencing.cs.ParametricCS;
 -import org.opengis.referencing.crs.ParametricCRS;
 -import org.opengis.referencing.datum.ParametricDatum;
 -import org.opengis.referencing.datum.DatumEnsemble;
 +// Specific to the main branch:
 +import org.apache.sis.referencing.cs.DefaultParametricCS;
 +import org.apache.sis.referencing.datum.DefaultParametricDatum;
 +import org.apache.sis.referencing.datum.DefaultDatumEnsemble;
  
  
  /**
@@@ -193,6 -219,15 +194,15 @@@ public class DefaultParametricCRS exten
          return super.getDatumEnsemble();
      }
  
+     /**
+      * Returns the datum or a view of the ensemble as a datum.
+      * The {@code legacy} argument tells whether this method is invoked for 
formatting in a legacy <abbr>WKT</abbr> format.
+      */
+     @Override
 -    final ParametricDatum getDatumOrEnsemble(final boolean legacy) {
 -        return legacy ? DatumOrEnsemble.asDatum(this) : getDatum();
++    final DefaultParametricDatum getDatumOrEnsemble(final boolean legacy) {
++        return getDatum();
+     }
+ 
      /**
       * Returns the coordinate system.
       *
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
index 346f14ec00,10036913d9..5aa77cc06b
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
@@@ -44,10 -45,9 +45,9 @@@ import org.apache.sis.util.Workaround
  import org.opengis.referencing.crs.GeographicCRS;
  import org.opengis.referencing.operation.Projection;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.referencing.datum.DatumEnsemble;
 -import org.opengis.coordinate.MismatchedDimensionException;
 +// Specific to the main branch:
 +import org.opengis.geometry.MismatchedDimensionException;
 +import org.apache.sis.referencing.datum.DefaultDatumEnsemble;
- import org.apache.sis.pending.geoapi.referencing.MissingMethods;
  
  
  /**
@@@ -247,8 -247,17 +247,17 @@@ public class DefaultProjectedCRS extend
       * @since 1.5
       */
      @Override
 -    public DatumEnsemble<GeodeticDatum> getDatumEnsemble() {
 -        return getBaseCRS().getDatumEnsemble();
 +    public DefaultDatumEnsemble<GeodeticDatum> getDatumEnsemble() {
-         return MissingMethods.getDatumEnsemble(getBaseCRS());
++        return (DefaultDatumEnsemble<GeodeticDatum>) 
getDatumEnsemble(getBaseCRS());
+     }
+ 
+     /**
+      * Returns the datum or a view of the ensemble as a datum.
+      * The {@code legacy} argument tells whether this method is invoked for 
formatting in a legacy <abbr>WKT</abbr> format.
+      */
+     @Override
+     final GeodeticDatum getDatumOrEnsemble(final boolean legacy) {
+         return legacy ? DatumOrEnsemble.asDatum(getBaseCRS()) : getDatum();
      }
  
      /**
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/AbstractDatum.java
index 4e6d19103c,07991ac8b4..b8bb4f3b62
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/AbstractDatum.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/AbstractDatum.java
@@@ -507,15 -434,15 +507,15 @@@ public class AbstractDatum extends Abst
       * and the {@linkplain #getDomains() domains}.
       *
       * <h4>Static versus dynamic datum</h4>
 -     * If this datum implements the {@link DynamicReferenceFrame} interface, 
then the given object needs
 +     * If this datum implements the {@code DynamicReferenceFrame} interface, 
then the given object needs
-      * to also implement that interfaces and provide the same reference epoch 
for being considered equal.
+      * to also implement that interface and provide the same reference epoch 
for being considered equal.
 -     * Conversely, if this datum does not implement {@link 
DynamicReferenceFrame}, then the given object
 +     * Conversely, if this datum does not implement {@code 
DynamicReferenceFrame}, then the given object
       * also need to <em>not</em> implement that interface for being 
considered equal.
+      * This condition is relaxed with {@link ComparisonMode#COMPATIBILITY} if 
the two reference frames have a common identifier
+      * or an {@linkplain 
org.apache.sis.referencing.IdentifiedObjects#isHeuristicMatchForName equivalent 
name}.
       *
       * @param  object  the object to compare to {@code this}.
-      * @param  mode    {@link ComparisonMode#STRICT STRICT} for performing a 
strict comparison, or
-      *                 {@link ComparisonMode#IGNORE_METADATA IGNORE_METADATA} 
for comparing only
-      *                 properties relevant to coordinate transformations.
+      * @param  mode    the strictness level of the comparison.
       * @return {@code true} if both objects are equal.
       */
      @Override
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DatumOrEnsemble.java
index 9823622305,d08ce7f653..ce78a4c244
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DatumOrEnsemble.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DatumOrEnsemble.java
@@@ -60,12 -67,34 +66,35 @@@ import org.apache.sis.util.logging.Logg
   * @version 1.5
   * @since   1.5
   */
 +@SuppressWarnings("unchecked")  // See `getDatumEnsemble(…)`
  public final class DatumOrEnsemble extends Static {
      /**
-      * Do not allow instantiation of this class.
+      * The {@value} keyword which sometime appear at the end of a datum 
ensemble name.
       */
-     private DatumOrEnsemble() {
+     private static final String ENSEMBLE = "ensemble";
+ 
+     /**
+      * The datum of a <abbr>CRS</abbr> or specified by the user, or {@code 
null} if none.
+      */
+     private final Datum datum;
+ 
+     /**
+      * The ensemble of a <abbr>CRS</abbr> or specified by the user, or {@code 
null} if none.
+      */
 -    private final DatumEnsemble<?> ensemble;
++    private final DefaultDatumEnsemble<?> ensemble;
+ 
+     /**
+      * Criterion for deciding if two properties should be considered equal.
+      */
+     private final ComparisonMode mode;
+ 
+     /**
+      * For internal usage only. The fact that we may create instances of this 
class is a hidden implementation details.
+      */
 -    private DatumOrEnsemble(final Datum datum, final DatumEnsemble<?> 
ensemble, final ComparisonMode mode) {
++    private DatumOrEnsemble(final Datum datum, final DefaultDatumEnsemble<?> 
ensemble, final ComparisonMode mode) {
+         this.datum    = datum;
+         this.ensemble = ensemble;
+         this.mode     = mode;
      }
  
      /**
@@@ -279,18 -318,18 +308,18 @@@
       */
      @SuppressWarnings("unchecked")          // Casts are safe because callers 
know the method signature of <D>.
      private static <C extends SingleCRS, D extends Datum, R extends 
IdentifiedObject> Optional<R> asTargetDatum(
-             final C source, final R sourceDatum,
-             final C target, final R targetDatum,
+             final C sourceCRS, final R sourceDatum,
+             final C targetCRS, final R targetDatum,
 -            final Function<DatumEnsemble<D>, R> constructor)
 +            final Function<DefaultDatumEnsemble<D>, R> constructor)
      {
          if (sourceDatum != null && 
Utilities.equalsIgnoreMetadata(sourceDatum, targetDatum)) {
              return Optional.of(targetDatum);
          }
 -        DatumEnsemble<D> sourceEnsemble;
 -        DatumEnsemble<D> targetEnsemble;
 -        DatumEnsemble<D> selected;
 -        if ((isMember(selected = targetEnsemble = (DatumEnsemble<D>) 
targetCRS.getDatumEnsemble(), sourceDatum)) ||
 -            (isMember(selected = sourceEnsemble = (DatumEnsemble<D>) 
sourceCRS.getDatumEnsemble(), targetDatum)))
 +        DefaultDatumEnsemble<D> sourceEnsemble;
 +        DefaultDatumEnsemble<D> targetEnsemble;
 +        DefaultDatumEnsemble<D> selected;
-         if ((isMember(selected = targetEnsemble = (DefaultDatumEnsemble<D>) 
getDatumEnsemble(target), sourceDatum)) ||
-             (isMember(selected = sourceEnsemble = (DefaultDatumEnsemble<D>) 
getDatumEnsemble(source), targetDatum)))
++        if ((isMember(selected = targetEnsemble = (DefaultDatumEnsemble<D>) 
getDatumEnsemble(targetCRS), sourceDatum)) ||
++            (isMember(selected = sourceEnsemble = (DefaultDatumEnsemble<D>) 
getDatumEnsemble(sourceCRS), targetDatum)))
          {
              return Optional.of(constructor.apply(selected));
          }
@@@ -343,15 -382,84 +372,84 @@@
          return false;
      }
  
+     /**
+      * Returns whether a legacy definition of a datum may be considered as 
equivalent to the given datum ensemble.
+      * This is {@code true} if all reference frames (both the specified datum 
and the ensemble members) have the
+      * same properties (ellipsoid and prime meridians in the geodetic case), 
and the datum and datum ensemble either
+      * have a common identifier or an {@linkplain 
IdentifiedObjects#isHeuristicMatchForName(IdentifiedObject, String)
+      * heuristic match of name}.
+      *
+      * <p>This method does not verify if the given datum is a member of the 
given ensemble.
+      * If the datum was a member, then the two objects would <em>not</em> be 
conceptually equal.
+      * We would rather have one object clearly identified as more accurate 
than the other.</p>
+      *
+      * <h4>Use case</h4>
+      * This method is for interoperability between the old and new 
definitions of <abbr>WGS</abbr> 1984 (<abbr>EPSG</abbr>:4326).
+      * Before <abbr>ISO</abbr> 19111:2019, the <i>datum ensemble</i> concept 
did not existed in the <abbr>OGC</abbr>/<abbr>ISO</abbr> standards
+      * and <abbr>WGS</abbr> 1984 was defined as a {@link Datum}.
 -     * In recent standards, <abbr>WGS</abbr> 1984 is defined as a {@link 
DatumEnsemble}, but the old definition is still encountered.
++     * In recent standards, <abbr>WGS</abbr> 1984 is defined as a {@code 
DatumEnsemble}, but the old definition is still encountered.
+      * For example, a <abbr>CRS</abbr> may have been parsed from a <abbr 
title="Geographic Markup Language">GML</abbr> document,
+      * or from a <abbr title="Well-Known Text">WKT</abbr> 1 string, or from a 
<abbr>ISO</abbr> 19162:2015 string, <i>etc.</i>
+      * This method can be used for detecting such situations.
+      * While <abbr>WGS</abbr> 1984 is the main use case, this method can be 
used for any datum in the same situation.
+      *
+      * @param  ensemble  the datum ensemble, or {@code null}.
+      * @param  datum     the datum, or {@code null}.
+      * @param  mode      the criterion for comparing ellipsoids and prime 
meridians.
+      * @return whether the two objects could be considered as equal if the 
concept of datum ensemble did not existed.
+      */
 -    public static boolean isLegacyDatum(final DatumEnsemble<?> ensemble, 
final Datum datum, final ComparisonMode mode) {
++    public static boolean isLegacyDatum(final DefaultDatumEnsemble<?> 
ensemble, final Datum datum, final ComparisonMode mode) {
+         if (ensemble == null || datum == null) {
+             return false;
+         }
+         // Two null values are not considered equal because they are not of 
the same type.
+         if (ensemble == datum) {
+             return true;
+         }
+         final var c = new DatumOrEnsemble(datum, ensemble, mode);
+         if (!(c.isPropertyEqual(GeodeticDatum.class, 
GeodeticDatum::getEllipsoid,         Objects::nonNull) &&
+               c.isPropertyEqual(GeodeticDatum.class, 
GeodeticDatum::getPrimeMeridian,     Objects::nonNull) &&
 -              c.isPropertyEqual(VerticalDatum.class, 
VerticalDatum::getRealizationMethod, Optional::isPresent)))
++              c.isPropertyEqual(VerticalDatum.class, 
VerticalDatum::getVerticalDatumType, Objects::nonNull)))
+         {
+             return false;
+         }
+         final Boolean match = 
Identifiers.hasCommonIdentifier(ensemble.getIdentifiers(), 
datum.getIdentifiers());
+         if (match != null) {
+             return match;
+         }
+         /*
+          * We could not answer the question using identifiers. Try using the 
names.
+          * The primary name is likely to not match, because ensemble names in 
EPSG
+          * dataset often ends with "ensemble" while datum names often do not. 
But
+          * we are more interrested in the ensemble's aliases in the next line.
+          */
+         if (IdentifiedObjects.isHeuristicMatchForName(ensemble, 
datum.getName().getCode())) {
+             return true;
+         }
+         /*
+          * Try to remove the "ensemble" prefix in the datum ensemble name and 
try again.
+          * This time, the comparison will also check `datum` aliases instead 
of `ensemble`.
+          */
+         String name = ensemble.getName().getCode();
+         if (name.endsWith(ENSEMBLE)) {
+             int i = name.length() - ENSEMBLE.length();
+             if (i > (i = CharSequences.skipTrailingWhitespaces(name, 0, i))) {
+                 name = name.substring(0, i);    // Remove the "ensemble" 
suffix.
+             }
+         }
+         return IdentifiedObjects.isHeuristicMatchForName(datum, name);
+     }
+ 
      /**
       * Returns the ellipsoid used by the given coordinate reference system.
-      * More specifically:
+      * This method searches in the following locations:
       *
       * <ul>
       *   <li>If the given <abbr>CRS</abbr> is an instance of {@link 
SingleCRS} and its datum
       *       is a {@link GeodeticDatum}, then this method returns the datum 
ellipsoid.</li>
-      *   <li>Otherwise, if the given <abbr>CRS</abbr> is associated to a 
{@code DatumEnsemble} and all members
-      *       of the ensemble have equal (ignoring metadata) ellipsoid, then 
returns that ellipsoid.</li>
+      *   <li>Otherwise, if the given <abbr>CRS</abbr> is an instance of 
{@link SingleCRS}, is associated to a
 -     *       {@link DatumEnsemble}, and all members of the ensemble have 
equal (ignoring metadata) ellipsoid,
++     *       {@code DatumEnsemble}, and all members of the ensemble have 
equal (ignoring metadata) ellipsoid,
+      *       then returns that ellipsoid.</li>
       *   <li>Otherwise, if the given <abbr>CRS</abbr> is an instance of 
{@link CompoundCRS}, then this method
       *       searches recursively in each component until a geodetic 
reference frame is found.</li>
       *   <li>Otherwise, this method returns an empty value.</li>
@@@ -378,39 -484,98 +474,71 @@@
       * @see org.apache.sis.referencing.CRS#getGreenwichLongitude(GeodeticCRS)
       */
      public static Optional<PrimeMeridian> getPrimeMeridian(final 
CoordinateReferenceSystem crs) {
-         return getGeodeticProperty(crs, GeodeticDatum::getPrimeMeridian);
+         return Optional.ofNullable(getProperty(crs, GeodeticDatum.class, 
GeodeticDatum::getPrimeMeridian, Objects::nonNull));
      }
  
 -    /**
 -     * Returns the realization method used by the given coordinate reference 
system.
 -     * This method searches in the following locations:
 -     *
 -     * <ul>
 -     *   <li>If the given <abbr>CRS</abbr> is an instance of {@link 
SingleCRS} and its datum
 -     *       is a {@link VerticalDatum}, then this method returns the 
realization method.</li>
 -     *   <li>Otherwise, if the given <abbr>CRS</abbr> is an instance of 
{@link SingleCRS}, is associated to a
 -     *       {@link DatumEnsemble}, and all members of the ensemble have 
equal (ignoring metadata) realization
 -     *       methods, then returns that method.</li>
 -     *   <li>Otherwise, if the given <abbr>CRS</abbr> is an instance of 
{@link CompoundCRS}, then this method
 -     *       searches recursively in each component until a vertical 
reference frame is found.</li>
 -     *   <li>Otherwise, this method returns an empty value.</li>
 -     * </ul>
 -     *
 -     * This method may return an empty value if a datum ensemble contains 
different realization methods.
 -     *
 -     * @param  crs  the coordinate reference system for which to get the 
realization method.
 -     * @return the realization method, or an empty value if none or not equal 
for all members.
 -     *
 -     * @since 2.0 (temporary version number until this branch is released)
 -     */
 -    public static Optional<RealizationMethod> getRealizationMethod(final 
CoordinateReferenceSystem crs) {
 -        Optional<RealizationMethod> common = getProperty(crs, 
VerticalDatum.class, VerticalDatum::getRealizationMethod, Optional::isPresent);
 -        return (common != null) ? common : Optional.empty();
 -    }
 -
      /**
       * Implementation of {@code getEllipsoid(CRS)} and {@code 
getPrimeMeridian(CRS)}.
       *
-      * @param  <P>     the type of object to get.
-      * @param  crs     the coordinate reference system for which to get the 
ellipsoid or prime meridian.
-      * @param  getter  the method to invoke on {@link GeodeticDatum} 
instances.
-      * @return the ellipsoid or prime meridian, or an empty value if none of 
inconsistent.
+      * @param  <P>      the type of property to get.
+      * @param  <D>      the type of datum expected by the given {@code 
getter}.
+      * @param  crs      the coordinate reference system for which to get the 
ellipsoid or prime meridian.
+      * @param  getter   the method to invoke on {@link Datum} instances for 
getting the property.
+      * @param  nonNull  test about whether a property value is non-null or 
present.
+      * @return the property value, or {@code null} if none or not equal for 
all members.
       */
-     private static <P> Optional<P> getGeodeticProperty(final 
CoordinateReferenceSystem crs, final Function<GeodeticDatum, P> getter) {
- single: if (crs instanceof SingleCRS) {
+     private static <P, D extends Datum> P getProperty(final 
CoordinateReferenceSystem crs, final Class<D> datumType,
+                                                       final Function<D, P> 
getter, final Predicate<P> nonNull)
+     {
+         if (crs instanceof SingleCRS) {
              final var scrs = (SingleCRS) crs;
              final Datum datum = scrs.getDatum();
-             if (datum instanceof GeodeticDatum) {
-                 P property = getter.apply((GeodeticDatum) datum);
+             if (datumType.isInstance(datum)) {
+                 @SuppressWarnings("unchecked")
+                 P property = getter.apply((D) datum);
+                 if (nonNull.test(property)) {
+                     return property;
+                 }
+             }
 -            final var c = new DatumOrEnsemble(datum, scrs.getDatumEnsemble(), 
ComparisonMode.IGNORE_METADATA);
++            final var c = new DatumOrEnsemble(datum, getDatumEnsemble(scrs), 
ComparisonMode.IGNORE_METADATA);
+             return c.getEnsembleProperty(null, datumType, getter, nonNull);
+         } else if (crs instanceof CompoundCRS) {
+             for (final CoordinateReferenceSystem c : ((CompoundCRS) 
crs).getComponents()) {
+                 final P property = getProperty(c, datumType, getter, nonNull);
                  if (property != null) {
-                     return Optional.of(property);
+                     return property;
                  }
              }
-             final DefaultDatumEnsemble<?> ensemble = getDatumEnsemble(crs);
-             if (ensemble != null) {
-                 P common = null;
-                 for (Datum member : ensemble.getMembers()) {
-                     if (member instanceof GeodeticDatum) {
-                         final P property = getter.apply((GeodeticDatum) 
member);
-                         if (property != null) {
-                             if (common == null) {
-                                 common = property;
-                             } else if 
(!Utilities.equalsIgnoreMetadata(property, common)) {
-                                 break single;
-                             }
+         }
+         return null;
+     }
+ 
+     /**
+      * Returns a property of ensemble member if it is the same for all 
members.
+      * Returns {@code null} if the value is absent or not equal for all 
members.
+      * If {@code common} is non-null, then this method take in account only 
the
+      * property values equal to {@code common}
+      * (i.e., it searches if the value is present).
+      *
+      * @param  <P>      the type of property to get.
+      * @param  <D>      the type of datum expected by the given {@code 
getter}.
+      * @param  common   if non-null, ignore all properties not equal to 
{@code common}.
+      * @param  getter   the method to invoke on {@link Datum} instances for 
getting the property.
+      * @param  nonNull  test about whether a property value is non-null or 
present.
+      * @return the property value, or {@code null} if none or not equal for 
all members.
+      */
+     private <P, D extends Datum> P getEnsembleProperty(P common, final 
Class<D> datumType, final Function<D,P> getter, final Predicate<P> nonNull) {
+         final boolean searching = (common != null);
+         if (ensemble != null) {
+             for (Datum member : ensemble.getMembers()) {
+                 if (datumType.isInstance(member)) {
+                     @SuppressWarnings("unchecked")
+                     final P property = getter.apply((D) member);
+                     if (nonNull.test(property)) {
+                         if (common == null) {
+                             common = property;
+                         } else if (Utilities.deepEquals(property, common, 
mode) == searching) {
+                             return searching ? common : null;
                          }
                      }
                  }
@@@ -434,11 -612,18 +575,18 @@@
       * @param  object  the object from which to get the ensemble accuracy, or 
{@code null}.
       * @return the datum ensemble accuracy if the given object is a datum 
ensemble.
       * @throws NullPointerException if the given object should provide an 
accuracy but didn't.
+      *
+      * @see 
org.apache.sis.referencing.CRS#getLinearAccuracy(CoordinateOperation)
       */
      public static Optional<PositionalAccuracy> getAccuracy(final 
IdentifiedObject object) {
 -        final DatumEnsemble<?> ensemble;
 -        if (object instanceof DatumEnsemble<?>) {
 -            ensemble = (DatumEnsemble<?>) object;
 +        final DefaultDatumEnsemble<?> ensemble;
 +        if (object instanceof DefaultDatumEnsemble<?>) {
 +            ensemble = (DefaultDatumEnsemble<?>) object;
+         } else if (object instanceof SingleCRS) {
 -            ensemble = ((SingleCRS) object).getDatumEnsemble();
++            ensemble = getDatumEnsemble((SingleCRS) object);
+             if (ensemble == null) {
+                 return Optional.empty();
+             }
          } else {
              return Optional.empty();
          }
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
index 2cb7303a74,82a0ae2c47..73bd9c4d48
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
@@@ -93,12 -99,18 +96,11 @@@ public class DefaultVerticalDatum exten
      private static final long serialVersionUID = 380347456670516572L;
  
      /**
 -     * The realization method (geoid, tidal, <i>etc.</i>), or {@code null} if 
unspecified.
 -     *
 -     * @see #getRealizationMethod()
 -     */
 -    private RealizationMethod method;
 -
 -    /**
--     * The type of this vertical datum.
-      * If {@code null}, a value will be inferred from the name by {@link 
#type()}.
++     * The type of this vertical datum, or {@code null} if unspecified.
++     * In the latter case, a default will be inferred when requested.
       *
-      * @see #type()
       * @see #getVerticalDatumType()
       */
 -    @SuppressWarnings("deprecation")
      private VerticalDatumType type;
  
      /**
@@@ -200,28 -231,18 +202,6 @@@
          return VerticalDatum.class;
      }
  
--    /**
-      * Returns the type of this datum, or infers the type from the datum name 
if no type were specified.
-      * The latter case occurs after unmarshalling, since GML 3.2 does not 
contain any attribute for the datum type.
-      * It may also happen if the datum were created using reflection.
-      *
-      * <p>This method uses heuristic rules and may be changed in any future 
SIS version.</p>
 -     * Returns the method through which this vertical reference frame is 
realized.
--     *
-      * <p>No synchronization needed; this is not a problem if this value is 
computed twice.
-      * This method returns only existing immutable instances.</p>
 -     * @return method through which this vertical reference frame is realized.
--     *
-      * @see #getVerticalDatumType()
-      * @see #getTypeElement()
 -     * @since 2.0 (temporary version number until this branch is released)
--     */
-     private VerticalDatumType type() {
-         VerticalDatumType t = type;
-         if (t == null) {
-             final ReferenceIdentifier name = super.getName();
-             type = t = VerticalDatumTypes.fromDatum(name != null ? 
name.getCode() : null, super.getAlias(), null);
-         }
-         return t;
 -    @Override
 -    public Optional<RealizationMethod> getRealizationMethod() {
 -        return Optional.ofNullable(method);
--    }
--
      /**
       * Returns the type of this vertical datum.
       *
@@@ -232,10 -253,33 +212,30 @@@
       * but in a programmatic way more suitable to coordinate transformation 
engines.
       *
       * @return the type of this vertical datum.
 -     *
 -     * @deprecated As of ISO 19111:2019, the {@code VerticalDatumType} 
argument is replaced by {@code RealizationMethod}.
       */
      @Override
 -    @Deprecated(since = "2.0")  // Temporary version number until this branch 
is released.
      public VerticalDatumType getVerticalDatumType() {
-         return type();
++        if (type == null) {
++            // Do not store the value because it depends on the context 
(whether we know the axis).
++            return VerticalDatumTypes.fromDatum(super.getName().getCode(), 
super.getAlias(), null);
++        }
+         return type;
+     }
+ 
+     /**
+      * Returns the datum type if it was explicitly specified, or otherwise 
tries to guess it.
+      * This method may return {@code null} if it cannot guess the method. 
This is used for compatibility
+      * with legacy formats such as WKT 1 or GML 3.1, before realization 
method became a formal property.
+      */
+     private VerticalDatumType getOrGuessMethod(final FormattableObject 
parent) {
 -        @SuppressWarnings("LocalVariableHidesMemberVariable")
 -        final VerticalDatumType type = getVerticalDatumType();
+         if (type != null && type != VerticalDatumType.OTHER_SURFACE) {
+             return type;
+         }
 -        return 
VerticalDatumTypes.fromMethod(getRealizationMethod().orElseGet(() -> {
 -            CoordinateSystemAxis axis = null;
 -            if (parent instanceof CoordinateReferenceSystem) {
 -                axis = ((CoordinateReferenceSystem) 
parent).getCoordinateSystem().getAxis(0);
 -            }
 -            return VerticalDatumTypes.fromDatum(getName().getCode(), 
getAlias(), axis);
 -        }));
++        CoordinateSystemAxis axis = null;
++        if (parent instanceof CoordinateReferenceSystem) {
++            axis = ((CoordinateReferenceSystem) 
parent).getCoordinateSystem().getAxis(0);
++        }
++        return VerticalDatumTypes.fromDatum(getName().getCode(), getAlias(), 
axis);
      }
  
      /**
@@@ -345,7 -402,7 +343,7 @@@
          switch (mode) {
              case STRICT: {
                  final var other = (DefaultVerticalDatum) object;
-                 return type().equals(other.type());
 -                return Objects.equals(method, other.method) && 
Objects.equals(type, other.type);
++                return Objects.equals(type, other.type);
              }
              case BY_CONTRACT: {
                  final var other = (VerticalDatum) object;
@@@ -373,7 -431,7 +371,7 @@@
       */
      @Override
      protected long computeHashCode() {
-         return super.computeHashCode() + type().hashCode();
 -        return super.computeHashCode() + 37 * Objects.hashCode(method);
++        return super.computeHashCode() + Objects.hashCode(type);
      }
  
      /**
@@@ -429,9 -487,13 +427,12 @@@
       *
       * @see <a href="http://issues.apache.org/jira/browse/SIS-160";>SIS-160: 
Need XSLT between GML 3.1 and 3.2</a>
       */
 -    @SuppressWarnings("deprecation")
      @XmlElement(name = "verticalDatumType")
      private VerticalDatumType getTypeElement() {
-         return Context.isGMLVersion(Context.current(), 
LegacyNamespaces.VERSION_3_2) ? null : getVerticalDatumType();
+         if (Context.isGMLVersion(Context.current(), 
LegacyNamespaces.VERSION_3_2)) {
+             return null;
+         }
+         return getOrGuessMethod(null);
      }
  
      /**
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java
index 20f5dfb954,68cfaace7e..e53f7d944c
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java
@@@ -180,12 -177,9 +180,15 @@@ abstract class AuthorityFactoryProxy<T
       */
      static final AuthorityFactoryProxy<InternationalString> description(final 
Class<? extends IdentifiedObject> classe) {
          return new 
AuthorityFactoryProxy<InternationalString>(InternationalString.class, 
AuthorityFactoryIdentifier.Type.ANY) {
 -            @Override InternationalString createFromAPI(AuthorityFactory 
factory, String code) throws FactoryException {
 +            @Override InternationalString create(GeodeticAuthorityFactory 
factory, String code) throws FactoryException {
                  return factory.getDescriptionText(classe, code).orElse(null);
              }
 +            @Override InternationalString createFromAPI(AuthorityFactory 
factory, String code) throws FactoryException {
++                if (factory instanceof GeodeticAuthorityFactory) {
++                    return ((GeodeticAuthorityFactory) 
factory).getDescriptionText(classe, code).orElse(null);
++                }
 +                return factory.getDescriptionText(code);
 +            }
              @Override AuthorityFactoryProxy<InternationalString> 
specialize(String typeName) {
                  return this;
              }
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
index dec2641dbf,c684889ab8..fb9e9c5164
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
@@@ -178,24 -173,6 +178,24 @@@ public abstract class GeodeticAuthority
          return 
Optional.ofNullable(IdentifiedObjects.getDisplayName(createObject(type, code)));
      }
  
 +    /**
 +     * Returns a description of the object corresponding to a code.
 +     *
 +     * @param  code  value allocated by authority.
 +     * @return a description of the object, or {@code null} if the object
 +     *         corresponding to the specified {@code code} has no description.
 +     * @throws NoSuchAuthorityCodeException if the specified {@code code} was 
not found.
 +     * @throws FactoryException if the query failed for some other reason.
 +     *
 +     * @deprecated This method is ambiguous because the EPSG geodetic 
registry may allocate
 +     *             the same code to different kinds of object.
 +     */
 +    @Override
 +    @Deprecated(since = "1.5")
 +    public InternationalString getDescriptionText(final String code) throws 
FactoryException {
-         return getDescriptionText(IdentifiedObject.class, code).orElse(null);
++        return getDescriptionText(CoordinateReferenceSystem.class, 
code).orElse(null);
 +    }
 +
      /**
       * Returns an object of the specified type from a code. This 
implementation forwards
       * the method call to the most specialized methods determined by the 
given type.
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
index 2af9d2d64d,34d842d425..70c4797654
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
@@@ -26,16 -27,26 +27,26 @@@ import org.opengis.util.FactoryExceptio
  import org.opengis.referencing.IdentifiedObject;
  import org.opengis.referencing.AuthorityFactory;
  import org.opengis.referencing.NoSuchAuthorityCodeException;
- import org.apache.sis.referencing.AbstractIdentifiedObject;
+ import org.opengis.referencing.datum.Datum;
  import org.apache.sis.referencing.IdentifiedObjects;
+ import org.apache.sis.referencing.datum.DatumOrEnsemble;
+ import org.apache.sis.referencing.privy.FilteredIterator;
+ import org.apache.sis.referencing.privy.LazySet;
  import org.apache.sis.util.ComparisonMode;
+ import org.apache.sis.util.Disposable;
  import org.apache.sis.util.Utilities;
+ import org.apache.sis.util.OptionalCandidate;
  import org.apache.sis.util.privy.Constants;
- import org.apache.sis.system.Semaphores;
  import org.apache.sis.util.collection.BackingStoreException;
  import org.apache.sis.util.logging.Logging;
+ import org.apache.sis.system.Semaphores;
+ 
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.referencing.datum.DatumEnsemble;
 -
+ // Specific to the main and geoapi-3.1 branches:
+ import org.opengis.referencing.ReferenceIdentifier;
+ 
++// Specific to the main branch:
++import org.apache.sis.referencing.datum.DefaultDatumEnsemble;
 +
  
  /**
   * Searches in an authority factory for objects approximately equal to a 
given object.
@@@ -255,13 -264,45 +264,45 @@@ public class IdentifiedObjectFinder 
      }
  
      /**
-      * Returns {@code true} if a candidate found by {@code 
IdentifiedObjectFinder} should be considered equals to the
-      * requested object. This method invokes the {@code equals(…)} method on 
the {@code candidate} argument instead
-      * than on the user-specified {@code object} on the assumption that 
implementations coming from the factory are
-      * more reliable than user-specified objects.
+      * Returns the comparison mode to use when comparing a candidate against 
the object to search.
+      */
+     private ComparisonMode getComparisonMode() {
+         return ignoreAxes ? ComparisonMode.ALLOW_VARIANT : 
ComparisonMode.APPROXIMATE;
+     }
+ 
+     /**
+      * Returns {@code true} if a candidate created by a factory should be 
considered equal to the object to search.
+      * The {@code mode} and {@code proxy} arguments may be snapshots of the 
{@code IdentifiedObjectFinder}'s state
+      * taken at the time when the {@link Instances} iterable has been created.
+      *
+      * <h4>Implementation note</h4>
+      * This method invokes the {@code equals(…)} method on the {@code 
candidate} argument instead of {@code object}
+      * specified by the user on the assumption that implementations coming 
from the factory are more reliable than
+      * user-specified objects.
+      *
+      * @param  candidate  an object created by an authority factory.
+      * @param  object     the user-specified object to search.
+      * @param  mode       value of {@link #getComparisonMode()} (may be a 
snapshot).
+      * @param  proxy      value of {@link #proxy} (may be a snapshot).
+      * @return whether the given candidate can be considered equal to the 
object to search.
+      *
+      * @see #createAndFilter(AuthorityFactory, String, IdentifiedObject)
       */
-     private boolean match(final IdentifiedObject candidate, final 
IdentifiedObject object) {
-         return Utilities.deepEquals(candidate, object, ignoreAxes ? 
ComparisonMode.ALLOW_VARIANT : COMPARISON_MODE);
+     private static boolean match(final IdentifiedObject candidate, final 
IdentifiedObject object,
+                                  final ComparisonMode mode, final 
AuthorityFactoryProxy<?> proxy)
+     {
+         if (Utilities.deepEquals(candidate, object, mode)) {
+             return true;
+         }
+         if (Datum.class.isAssignableFrom(proxy.type)) {
 -            if (candidate instanceof Datum && object instanceof 
DatumEnsemble<?>) {
 -                return DatumOrEnsemble.isLegacyDatum((DatumEnsemble<?>) 
object, (Datum) candidate, mode);
++            if (candidate instanceof Datum && object instanceof 
DefaultDatumEnsemble<?>) {
++                return 
DatumOrEnsemble.isLegacyDatum((DefaultDatumEnsemble<?>) object, (Datum) 
candidate, mode);
+             }
 -            if (candidate instanceof DatumEnsemble<?> && object instanceof 
Datum) {
 -                return DatumOrEnsemble.isLegacyDatum((DatumEnsemble<?>) 
candidate, (Datum) object, mode);
++            if (candidate instanceof DefaultDatumEnsemble<?> && object 
instanceof Datum) {
++                return 
DatumOrEnsemble.isLegacyDatum((DefaultDatumEnsemble<?>) candidate, (Datum) 
object, mode);
+             }
+         }
+         return false;
      }
  
      /**
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
index b94cbce82e,e113272552..acacea9a0a
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
@@@ -53,6 -64,6 +64,11 @@@ import org.apache.sis.referencing.facto
  import org.apache.sis.referencing.factory.ConcurrentAuthorityFactory;
  import static 
org.apache.sis.metadata.privy.NameToIdentifier.Simplifier.ESRI_DATUM_PREFIX;
  
++// Specific to the main branch:
++import org.opengis.referencing.ReferenceIdentifier;
++import org.apache.sis.referencing.crs.DefaultParametricCRS;
++import org.apache.sis.referencing.datum.DefaultParametricDatum;
++
  // Specific to the main and geoapi-3.1 branches:
  import org.opengis.referencing.crs.GeneralDerivedCRS;
  
@@@ -322,11 -364,15 +365,15 @@@ crs:    if (isInstance(CoordinateRefere
              if (object instanceof GeneralDerivedCRS) {              // No 
need to use isInstance(Class, Object) from here.
                  filter = dependencies("BASE_CRS_CODE", 
CoordinateReferenceSystem.class, ((GeneralDerivedCRS) object).getBaseCRS(), 
true);
              } else if (object instanceof GeodeticCRS) {
-                 filter = dependencies("DATUM_CODE", GeodeticDatum.class, 
((GeodeticCRS) object).getDatum(), true);
+                 filter = dependencies("DATUM_CODE", GeodeticDatum.class, 
DatumOrEnsemble.asDatum((GeodeticCRS) object), true);
              } else if (object instanceof VerticalCRS) {
-                 filter = dependencies("DATUM_CODE", VerticalDatum.class, 
((VerticalCRS) object).getDatum(), true);
+                 filter = dependencies("DATUM_CODE", VerticalDatum.class, 
DatumOrEnsemble.asDatum((VerticalCRS) object), true);
              } else if (object instanceof TemporalCRS) {
-                 filter = dependencies("DATUM_CODE", TemporalDatum.class, 
((TemporalCRS) object).getDatum(), true);
+                 filter = dependencies("DATUM_CODE", TemporalDatum.class, 
DatumOrEnsemble.asDatum((TemporalCRS) object), true);
 -            } else if (object instanceof ParametricCRS) {
 -                filter = dependencies("DATUM_CODE", ParametricDatum.class, 
DatumOrEnsemble.asDatum((ParametricCRS) object), true);
++            } else if (object instanceof DefaultParametricCRS) {
++                filter = dependencies("DATUM_CODE", 
DefaultParametricDatum.class, ((DefaultParametricCRS) object).getDatum(), true);
+             } else if (object instanceof EngineeringCRS) {
+                 filter = dependencies("DATUM_CODE", EngineeringDatum.class, 
DatumOrEnsemble.asDatum((EngineeringCRS) object), true);
              } else if (object instanceof SingleCRS) {
                  filter = dependencies("DATUM_CODE", Datum.class, ((SingleCRS) 
object).getDatum(), true);
              } else {
@@@ -532,4 -583,214 +584,214 @@@
          }
          buffer.append(')');
      }
+ 
+     /**
+      * Returns a set of authority codes that <strong>may</strong> identify 
the same object as the specified one.
+      * This implementation tries to get a smaller set than what {@link 
EPSGDataAccess#getAuthorityCodes(Class)}
+      * would produce. Deprecated objects must be last in iteration order.
+      *
+      * <h4>Exceptions during iteration</h4>
+      * An unchecked {@link BackingStoreException} may be thrown during the 
iteration
+      * if the action of fetching codes from database was delayed and that 
action failed.
+      * The exception cause may be {@link FactoryException} or {@link 
SQLException}.
+      *
+      * @param  object  the object to search in the database.
+      * @return codes of objects that may be the requested ones.
+      * @throws FactoryException if an error occurred while fetching the set 
of code candidates.
+      */
+     @Override
+     protected Iterable<String> getCodeCandidates(final IdentifiedObject 
object) throws FactoryException {
+         for (final TableInfo source : TableInfo.values()) {
+             if (source.isSpecificEnough() && source.type.isInstance(object)) 
try {
+                 return new CodeCandidates(object, source);
+             } catch (SQLException exception) {
+                 throw databaseFailure(exception);
+             }
+         }
+         return Set.of();
+     }
+ 
+     /**
+      * Set of authority codes that <strong>may</strong> identify the same 
object as the specified one.
+      * This collection returns the codes that can be obtained easily before 
the more expensive searches.
+      *
+      * @todo We should not keep a reference to the enclosing finder, because 
the {@link EPSGDataAccess}
+      * may become invalid before the iteration is completed. For now, this is 
not a problem because this
+      * collection is copied by the {@link EPSGFactory} finder. But this is 
suboptimal because it defeats
+      * the purpose of object lazy instantiation.
+      */
+     private final class CodeCandidates implements Iterable<String>, 
Disposable {
+         /** The object to search. */
+         private final IdentifiedObject object;
+ 
+         /** Workaround for a Derby bug (see {@code filterFalsePositive(…)}). 
*/
+         private String name;
+ 
+         /** {@code LIKE} Pattern of the name of the object to search. */
+         private String namePattern;
+ 
+         /** Information about the tables of the object to search. */
+         private final TableInfo source;
+ 
+         /** Cache of codes found so far. */
+         private final Set<Integer> codes;
+ 
+         /** Snapshot of the search domain as it was at collection 
construction time. */
+         private final Domain domain;
+ 
+         /** Sequential number of the algorithm used for filling the {@link 
#codes} collection so far. */
+         private byte searchMethod;
+ 
+         /**
+          * Creates a lazy collection of code candidates.
+          * This constructor loads immediately some codes in order to have an 
exception early in case of problem.
+          *
+          * @param  object  the object to search in the database.
+          * @param  source  information about the table where to search for 
the object.
+          * @throws SQLException if an error occurred while searching for 
codes associated to names.
+          * @throws FactoryException if an error occurred while fetching the 
set of code candidates.
+          */
+         CodeCandidates(final IdentifiedObject object, final TableInfo source) 
throws SQLException, FactoryException {
+             this.object = object;
+             this.source = source;
+             this.domain = getSearchDomain();
+             this.codes  = new LinkedHashSet<>();
+             if (domain != Domain.EXHAUSTIVE_VALID_DATASET) {
 -                for (final Identifier id : object.getIdentifiers()) {
++                for (final ReferenceIdentifier id : object.getIdentifiers()) {
+                     if (Constants.EPSG.equalsIgnoreCase(id.getCodeSpace())) 
try {
+                         codes.add(Integer.valueOf(id.getCode()));
+                     } catch (NumberFormatException exception) {
+                         Logging.ignorableException(EPSGDataAccess.LOGGER, 
IdentifiedObjectFinder.class, "find", exception);
+                     }
+                 }
+             }
+             if (codes.isEmpty()) {
+                 fetchMoreCodes(codes);
+             }
+         }
+ 
+         /**
+          * Invoked when the caller requested to stop the iteration after the 
current group of elements.
+          * A group of elements is either the codes specified by the 
identifiers, or the codes found in
+          * the database. We will avoid to stop in the middle of a group.
+          *
+          * <p>This is an undocumented feature of {@link 
#createFromCodes(IdentifiedObject)}
+          * for stopping an iteration early when at least one match has been 
found.</p>
+          */
+         @Override
+         public void dispose() {
+             searchMethod = 3;   // The value after the last switch in 
`fetchMoreCodes(Collection)`.
+         }
+ 
+         /**
+          * Populates the given collection with code candidates.
+          * This method tries less expansive search methods before to tries 
more expensive search methods.
+          *
+          * @param  addTo  an initially empty collection where to add the 
codes.
+          * @return whether at least one code has been added to the given 
collection.
+          * @throws SQLException if an error occurred while searching for 
codes associated to names.
+          * @throws FactoryException if an error occurred while fetching the 
set of code candidates.
+          */
+         private boolean fetchMoreCodes(final Collection<Integer> addTo) 
throws SQLException, FactoryException {
+             do {
+                 switch (searchMethod) {
+                     case 0: {   // Fetch codes from the name.
+                         if (domain != Domain.EXHAUSTIVE_VALID_DATASET) {
+                             name = getName(object);
+                             if (name != null) {     // Should never be null, 
but we are paranoiac.
+                                 namePattern = dao.toLikePattern(name);
+                                 dao.findCodesFromName(source, 
TableInfo.toCacheKey(object), namePattern, name, addTo);
+                             }
+                         }
+                         break;
+                     }
+                     case 1: {   // Fetch codes from the aliases.
+                         if (domain != Domain.EXHAUSTIVE_VALID_DATASET) {
+                             if (namePattern != null) {
+                                 dao.findCodesFromAlias(source, namePattern, 
name, addTo);
+                             }
+                         }
+                         break;
+                     }
+                     case 2: {   // Search codes based on object properties.
+                         if (domain != Domain.DECLARATION) {
+                             searchCodesFromProperties(object, domain == 
Domain.ALL_DATASET, addTo);
+                         }
+                         break;
+                     }
+                     default: {
+                         return false;
+                     }
+                 }
+                 searchMethod++;
+             } while (addTo.isEmpty());
+             return true;
+         }
+ 
+         /**
+          * Returns additional code candidates which were not yet returned by 
the iteration.
+          * This method uses the next search method which hasn't be tried.
+          *
+          * @return the additional codes.
+          * @throws BackingStoreException if an error occurred while fetching 
the set of code candidates.
+          */
+         private Iterator<Integer> fetchMoreCodes() {
+             final var addTo = new ArrayList<Integer>();
+             do {
+                 try {
+                     if (!fetchMoreCodes(addTo)) break;
+                 } catch (SQLException | FactoryException exception) {
+                     throw new BackingStoreException(exception);
+                 }
+                 for (Iterator<Integer> it = addTo.iterator(); it.hasNext();) {
+                     if (!codes.add(it.next())) {
+                         it.remove();    // Code has already be returned.
+                     }
+                 }
+             } while (addTo.isEmpty());
+             return addTo.iterator();
+         }
+ 
+         /**
+          * Returns an iterator over the code candidates. The codes are cached:
+          * the should not be fetched again if a second iteration is executed.
+          *
+          * <h4>Limitation</h4>
+          * The current implementation does not support concurrent iterations, 
even in the same thread.
+          * This is okay for the usage that Apache <abbr>SIS</abbr> is making 
of this iterator.
+          */
+         @Override
+         public Iterator<String> iterator() {
+             return new Iterator<String>() {
+                 /** Iterator over a subset of the codes. */
+                 private Iterator<Integer> sources = codes.iterator();
+ 
+                 /** Tests whether there is more codes to return. */
+                 @Override public boolean hasNext() {
+                     if (sources.hasNext()) {
+                         return true;
+                     }
+                     sources = fetchMoreCodes();
+                     return sources.hasNext();
+                 }
+ 
+                 /** Returns the next code. */
+                 @Override public String next() {
+                     if (!sources.hasNext()) {
+                         sources = fetchMoreCodes();
+                     }
+                     return sources.next().toString();
+                 }
+             };
+         }
+ 
+         /**
+          * Returns a string representation for debugging purposes.
+          * The {@code "size"} property may change during the iteration.
+          */
+         @Override
+         public String toString() {
+             return Strings.toString(getClass(), "object", getName(object), 
"source", source, "domain", domain, "size", codes.size());
+         }
+     }
  }
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index 8d624ffa7f,edf2c685d9..ec3bc59639
--- 
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
@@@ -1998,11 -1981,11 +2006,11 @@@ search: try (ResultSet result = execute
                  final IdentifiedObject conventionalRS = 
createConventionalRS(convRSCode);
                  @SuppressWarnings("LocalVariableHidesMemberVariable")
                  final Map<String,Object> properties = createProperties(
-                         "Datum", epsg, name, null, area, scope, remarks, 
deprecated);
+                         TableInfo.DATUM, epsg, name, null, area, scope, 
remarks, deprecated);
 -                properties.put(Datum.ANCHOR_DEFINITION_KEY, anchor);
 -                properties.put(Datum.ANCHOR_EPOCH_KEY,      epoch);
 -                properties.put(Datum.PUBLICATION_DATE_KEY,  publish);
 -                properties.put(Datum.CONVENTIONAL_RS_KEY,   conventionalRS);
 +                properties.put(AbstractDatum.ANCHOR_DEFINITION_KEY, anchor);
 +                properties.put(AbstractDatum.ANCHOR_EPOCH_KEY,      epoch);
 +                properties.put(AbstractDatum.PUBLICATION_DATE_KEY,  publish);
 +                properties.put(AbstractDatum.CONVENTIONAL_RS_KEY,   
conventionalRS);
                  properties.values().removeIf(Objects::isNull);
                  final Datum datum = constructor.create(owner.datumFactory, 
properties);
                  returnValue = ensureSingleton(datum, returnValue, code);
@@@ -3006,8 -2977,8 +3004,8 @@@ next:                   while (r.next()
                   */
                  @SuppressWarnings("LocalVariableHidesMemberVariable")
                  final Map<String,Object> properties = createProperties(
-                         "Coordinate_Operation Parameter", epsg, name, null, 
null, null, isReversible, deprecated);
+                         TableInfo.PARAMETER, epsg, name, null, null, null, 
isReversible, deprecated);
 -                properties.put(Identifier.DESCRIPTION_KEY, description);
 +                properties.put(ImmutableIdentifier.DESCRIPTION_KEY, 
description);
                  final var descriptor = new 
DefaultParameterDescriptor<>(properties, 1, 1, type, valueDomain, null, null);
                  returnValue = ensureSingleton(descriptor, returnValue, code);
                  if (result.isClosed()) break;   // See createProperties(…) 
for explanation.
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java
index 39925454cf,835c89fb5e..20bc526972
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java
@@@ -25,139 -26,193 +26,200 @@@ import org.opengis.referencing.datum.*
  import org.opengis.referencing.operation.*;
  import org.opengis.parameter.ParameterDescriptor;
  import org.apache.sis.referencing.privy.WKTKeywords;
- import org.apache.sis.util.CharSequences;
  
 +// Specific to the main branch:
++import org.apache.sis.referencing.datum.DefaultDatumEnsemble;
++import org.apache.sis.referencing.datum.DefaultGeodeticDatum;
 +import org.apache.sis.referencing.crs.DefaultParametricCRS;
 +import org.apache.sis.referencing.cs.DefaultParametricCS;
 +import org.apache.sis.referencing.datum.DefaultParametricDatum;
 +
  
  /**
-  * Information about a specific table. This class uses the mixed-case variant 
of the <abbr>EPSG</abbr> table names.
-  * If needed, those names will be converted by {@link 
SQLTranslator#apply(String)} to the actual table names.
+  * Information (such as columns of particular interest) about a specific 
<abbr>EPSG</abbr> table.
+  * This class uses the mixed-case variant of the <abbr>EPSG</abbr> 
{@linkplain #table table names}.
+  * The {@link #values()} can be tested in preference order for finding the 
table of an object.
+  * Those tables are used by the {@link EPSGDataAccess#createObject(String)} 
method in order to
+  * detect which of the following methods should be invoked for a given code:
+  *
+  * {@link EPSGDataAccess#createCoordinateReferenceSystem(String)}
+  * {@link EPSGDataAccess#createCoordinateSystem(String)}
+  * {@link EPSGDataAccess#createDatum(String)}
+  * {@link EPSGDataAccess#createEllipsoid(String)}
+  * {@link EPSGDataAccess#createUnit(String)}
+  *
+  * <h4>Ambiguity</h4>
+  * As of ISO 19111:2019, we have no standard way to identify the geocentric 
case from a {@link Class} argument
+  * because the standard does not provide the {@code GeocentricCRS} interface. 
This implementation fallbacks on
+  * the GeoAPI-specific geocentric <abbr>CRS</abbr> class. This special case 
is implemented in the
+  * {@link #appendWhere(EPSGDataAccess, Object, StringBuilder)} method.
   *
   * @author  Martin Desruisseaux (IRD, Geomatys)
   */
- final class TableInfo {
+ enum TableInfo {
      /**
-      * The item used for coordinate reference systems.
+      * Information about the "Coordinate Reference System" table.
       */
-     static final TableInfo CRS;
+     CRS(CoordinateReferenceSystem.class,
+             "\"Coordinate Reference System\"",
+             "COORD_REF_SYS_CODE",
+             "COORD_REF_SYS_NAME",
+             "COORD_REF_SYS_KIND",
+             new Class<?>[] { ProjectedCRS.class,   GeographicCRS.class,   
GeocentricCRS.class,
+                              VerticalCRS.class,    CompoundCRS.class,     
EngineeringCRS.class,
 -                             DerivedCRS.class,     TemporalCRS.class,     
ParametricCRS.class},     // See comment below
++                             DerivedCRS.class,     TemporalCRS.class,     
DefaultParametricCRS.class}, // See comment below
+             new String[]   {"projected",          "geographic 2D",       
"geocentric",              // 3D case handled below
+                             "vertical",           "compound",            
"engineering",
+                             "derived",            "temporal",            
"parametric"},             // See comment below
+             "SHOW_CRS", true),
+             /*
+              * Above declaration could omit Derived, Temporal and Parametric 
cases because they are not defined
+              * by the EPSG repository (at least as of version 8.9). In 
particular we are not sure if EPSG would
+              * chose to use "time" or "temporal". However, omitting those 
types slow down a lot the search for
+              * CRS matching an existing one (even if it still work).
+              */
  
      /**
-      * The item used for datums.
+      * Information about the "Datum" table.
       */
-     static final TableInfo DATUM;
+     DATUM(Datum.class,
+             "\"Datum\"",
+             "DATUM_CODE",
+             "DATUM_NAME",
+             "DATUM_TYPE",
 -            new Class<?>[] { DatumEnsemble.class,  // Need to be first 
because Apache SIS uses as mixin interface.
++            new Class<?>[] { DefaultDatumEnsemble.class,  // Need to be first 
because Apache SIS uses as mixin interface.
+                              GeodeticDatum.class,  VerticalDatum.class,   
EngineeringDatum.class,
 -                             TemporalDatum.class,  ParametricDatum.class},
++                             TemporalDatum.class,  
DefaultParametricDatum.class},
+             new String[]   {"ensemble",
+                             "geodetic",           "vertical",            
"engineering",
+                             "temporal",           "parametric"},         // 
Same comment as in the CRS case above.
+             null, true),
  
      /**
-      * The item used for ellipsoids.
+      * Information about the "Conventional RS" table.
+      * This enumeration usually needs to be ignored because the current type 
is too generic.
+      *
+      * @see #isSpecificEnough()
       */
-     static final TableInfo ELLIPSOID;
+     CONVENTIONAL_RS(IdentifiedObject.class,
+             "\"Conventional RS\"",
+             "CONVENTIONAL_RS_CODE",
+             "CONVENTIONAL_RS_NAME",
+             null, null, null, null, false),
  
      /**
-      * List of tables and columns to test for existence of codes values.
-      * Those tables are used by the {@link 
EPSGDataAccess#createObject(String)} method
-      * in order to detect which of the following methods should be invoked 
for a given code:
-      *
-      * {@link EPSGDataAccess#createCoordinateReferenceSystem(String)}
-      * {@link EPSGDataAccess#createCoordinateSystem(String)}
-      * {@link EPSGDataAccess#createDatum(String)}
-      * {@link EPSGDataAccess#createEllipsoid(String)}
-      * {@link EPSGDataAccess#createUnit(String)}
+      * Information about the "Ellipsoid" table.
+      */
+     ELLIPSOID(Ellipsoid.class,
+             "\"Ellipsoid\"",
+             "ELLIPSOID_CODE",
+             "ELLIPSOID_NAME",
+             null, null, null, null, false),
+ 
+     /**
+      * Information about the "Prime Meridian" table.
+      */
+     PRIME_MERIDIAN(PrimeMeridian.class,
+             "\"Prime Meridian\"",
+             "PRIME_MERIDIAN_CODE",
+             "PRIME_MERIDIAN_NAME",
+             null, null, null, null, false),
+ 
+     /**
+      * Information about the "Coordinate_Operation" table.
+      */
+     OPERATION(CoordinateOperation.class,
+             "\"Coordinate_Operation\"",
+             "COORD_OP_CODE",
+             "COORD_OP_NAME",
+             "COORD_OP_TYPE",
+             new Class<?>[] { Conversion.class, Transformation.class},
+             new String[]   {"conversion",     "transformation"},
+             "SHOW_OPERATION", true),
+ 
+     /**
+      * Information about the "Coordinate_Operation Method" table.
+      */
+     METHOD(OperationMethod.class,
+             "\"Coordinate_Operation Method\"",
+             "COORD_OP_METHOD_CODE",
+             "COORD_OP_METHOD_NAME",
+             null, null, null, null, false),
+ 
+     /**
+      * Information about the "Coordinate_Operation Parameter" table.
+      */
+     PARAMETER(ParameterDescriptor.class,
+             "\"Coordinate_Operation Parameter\"",
+             "PARAMETER_CODE",
+             "PARAMETER_NAME",
+             null, null, null, null, false),
+ 
+     /**
+      * Information about the "Extent" table.
+      */
+     EXTENT(Extent.class,
+             "\"Extent\"",
+             "EXTENT_CODE",
+             "EXTENT_NAME",
+             null, null, null, null, false),
+ 
+     /**
+      * Information about the "Coordinate System" table.
+      */
+     CS(CoordinateSystem.class,
+             "\"Coordinate System\"",
+             "COORD_SYS_CODE",
+             "COORD_SYS_NAME",
+             "COORD_SYS_TYPE",
+             new Class<?>[] {CartesianCS.class,      EllipsoidalCS.class,      
VerticalCS.class,      LinearCS.class,
+                             SphericalCS.class,      PolarCS.class,            
CylindricalCS.class,
 -                            TimeCS.class,           ParametricCS.class,       
AffineCS.class},
++                            TimeCS.class,           
DefaultParametricCS.class,AffineCS.class},
+             new String[]   {WKTKeywords.Cartesian,  WKTKeywords.ellipsoidal,  
WKTKeywords.vertical,  WKTKeywords.linear,
+                             WKTKeywords.spherical,  WKTKeywords.polar,        
WKTKeywords.cylindrical,
+                             WKTKeywords.temporal,   WKTKeywords.parametric,   
WKTKeywords.affine},      // Same comment as in the CRS case above.
+             null, false),
+ 
+     /**
+      * Information about the "Coordinate Axis" table.
+      */
+     AXIS(CoordinateSystemAxis.class,
+             "\"Coordinate Axis\" AS CA INNER JOIN \"Coordinate Axis Name\" AS 
CAN " +
+                                 "ON 
CA.COORD_AXIS_NAME_CODE=CAN.COORD_AXIS_NAME_CODE",
+             "COORD_AXIS_CODE",
+             "COORD_AXIS_NAME",
+             null, null, null, null, false),
+ 
+     /**
+      * Information about the "Unit of Measure" table.
+      */
+     UNIT(Unit.class,
+             "\"Unit of Measure\"",
+             "UOM_CODE",
+             "UNIT_OF_MEAS_NAME",
+             null, null, null, null, false);
+ 
+     /**
+      * Types to consider as synonymous for searching purposes. This map 
exists for historical reasons,
+      * because dynamic datum and datum ensemble did not existed in older 
<abbr>ISO</abbr> 19111 standards.
+      * If an object to search is "geodetic", there is a possibility that it 
is defined in the old way and
+      * actually appears as a "dynamic geodetic" or "ensemble" in the 
<abbr>EPSG</abbr> geodetic dataset.
       *
-      * The order is significant: it is the key for a {@code switch} statement.
+      * <p>The "geographic 3D" case is handled in a special way. It is 
considered as a synonymous of
+      * "geographic 2D" only when we don't know the number of dimensions.</p>
+      */
+     private static final Map<String, String[]> SYNONYMOUS_TYPES = Map.of(
+             "geodetic",      new String[] {"dynamic geodetic", "ensemble"},
+             "geographic 2D", new String[] {"geographic 3D"});
+ 
+     /**
+      * Types to replace by specialized types when the user-specified instance 
implements a mixin interface.
+      * For example, {@link DynamicReferenceFrame} means to not search for any 
geodetic datum, but only for
+      * dynamic geodetic datum.
       */
-     static final TableInfo[] EPSG = {
-         CRS = new TableInfo(CoordinateReferenceSystem.class,
-                 "\"Coordinate Reference System\"",
-                 "COORD_REF_SYS_CODE",
-                 "COORD_REF_SYS_NAME",
-                 "COORD_REF_SYS_KIND",
-                 new Class<?>[] { ProjectedCRS.class,   GeographicCRS.class,   
GeocentricCRS.class,
-                                  VerticalCRS.class,    CompoundCRS.class,     
EngineeringCRS.class,
-                                  DerivedCRS.class,     TemporalCRS.class,     
DefaultParametricCRS.class},     // See comment below
-                 new String[]   {"projected",          "geographic",          
"geocentric",
-                                 "vertical",           "compound",            
"engineering",
-                                 "derived",            "temporal",            
"parametric"},             // See comment below
-                 "SHOW_CRS", true),
-                 /*
-                  * Above declaration could omit Derived, Temporal and 
Parametric cases because they are not defined
-                  * by the EPSG repository (at least as of version 8.9). In 
particular we are not sure if EPSG would
-                  * chose to use "time" or "temporal". However, omitting those 
types slow down a lot the search for
-                  * CRS matching an existing one (even if it still work).
-                  */
- 
-         new TableInfo(CoordinateSystem.class,
-                 "\"Coordinate System\"",
-                 "COORD_SYS_CODE",
-                 "COORD_SYS_NAME",
-                 "COORD_SYS_TYPE",
-                 new Class<?>[] {CartesianCS.class,      EllipsoidalCS.class,  
    VerticalCS.class,      LinearCS.class,
-                                 SphericalCS.class,      PolarCS.class,        
    CylindricalCS.class,
-                                 TimeCS.class,           
DefaultParametricCS.class, AffineCS.class},
-                 new String[]   {WKTKeywords.Cartesian,  
WKTKeywords.ellipsoidal,  WKTKeywords.vertical,  WKTKeywords.linear,
-                                 WKTKeywords.spherical,  WKTKeywords.polar,    
    WKTKeywords.cylindrical,
-                                 WKTKeywords.temporal,   
WKTKeywords.parametric,   WKTKeywords.affine},      // Same comment as in the 
CRS case above.
-                 null, false),
- 
-         new TableInfo(CoordinateSystemAxis.class,
-                 "\"Coordinate Axis\" AS CA INNER JOIN \"Coordinate Axis 
Name\" AS CAN " +
-                                     "ON 
CA.COORD_AXIS_NAME_CODE=CAN.COORD_AXIS_NAME_CODE",
-                 "COORD_AXIS_CODE",
-                 "COORD_AXIS_NAME",
-                 null, null, null, null, false),
- 
-         DATUM = new TableInfo(Datum.class,
-                 "\"Datum\"",
-                 "DATUM_CODE",
-                 "DATUM_NAME",
-                 "DATUM_TYPE",
-                 new Class<?>[] { GeodeticDatum.class,  VerticalDatum.class,   
EngineeringDatum.class,
-                                  TemporalDatum.class,  
DefaultParametricDatum.class},
-                 new String[]   {"geodetic",           "vertical",            
"engineering",
-                                 "temporal",           "parametric"},         
// Same comment as in the CRS case above.
-                 null, true),
- 
-         ELLIPSOID = new TableInfo(Ellipsoid.class,
-                 "\"Ellipsoid\"",
-                 "ELLIPSOID_CODE",
-                 "ELLIPSOID_NAME",
-                 null, null, null, null, false),
- 
-         new TableInfo(PrimeMeridian.class,
-                 "\"Prime Meridian\"",
-                 "PRIME_MERIDIAN_CODE",
-                 "PRIME_MERIDIAN_NAME",
-                 null, null, null, null, false),
- 
-         new TableInfo(CoordinateOperation.class,
-                 "\"Coordinate_Operation\"",
-                 "COORD_OP_CODE",
-                 "COORD_OP_NAME",
-                 "COORD_OP_TYPE",
-                 new Class<?>[] { Conversion.class, Transformation.class},
-                 new String[]   {"conversion",     "transformation"},
-                 "SHOW_OPERATION", true),
- 
-         new TableInfo(OperationMethod.class,
-                 "\"Coordinate_Operation Method\"",
-                 "COORD_OP_METHOD_CODE",
-                 "COORD_OP_METHOD_NAME",
-                 null, null, null, null, false),
- 
-         new TableInfo(ParameterDescriptor.class,
-                 "\"Coordinate_Operation Parameter\"",
-                 "PARAMETER_CODE",
-                 "PARAMETER_NAME",
-                 null, null, null, null, false),
- 
-         new TableInfo(Unit.class,
-                 "\"Unit of Measure\"",
-                 "UOM_CODE",
-                 "UNIT_OF_MEAS_NAME",
-                 null, null, null, null, false),
-     };
+     private static final Map<String, String> DYNAMIC_TYPES = Map.of(
+             "geodetic", "dynamic geodetic");
+             // We would expect a "dynamic vertical" as well, but we don't see 
it yet in EPSG database.
  
      /**
       * The class of object to be created (usually a GeoAPI interface).
@@@ -309,16 -404,46 +411,46 @@@
          buffer.append(" WHERE ");
          if (typeColumn != null) {
              for (int i=0; i<subTypes.length; i++) {
-                 final Class<?> candidate = subTypes[i];
-                 if (candidate.isAssignableFrom(userType)) {
-                     if (factory.translator.useEnumerations()) {
-                         buffer.append("CAST(").append(typeColumn).append(" AS 
")
-                                 
.append(EPSGInstaller.ENUM_REPLACEMENT).append(')');
-                     } else {
-                         buffer.append(typeColumn);
+                 final Class<?> subType = subTypes[i];
+                 if (subType.isAssignableFrom(userType)) {
+                     /*
+                      * Found the type to request in the `COORD_REF_SYS_KIND` 
or `DATUM_TYPE` columns.
+                      * The mixin interfaces need to be handled in a special 
way.
+                      */
+                     String typeName = typeNames[i];
 -                    if 
(DynamicReferenceFrame.class.isAssignableFrom(userType)) {
++                    if 
(DefaultGeodeticDatum.Dynamic.class.isAssignableFrom(userType)) {
+                         typeName = DYNAMIC_TYPES.getOrDefault(typeName, 
typeName);
+                     }
+                     /*
+                      * We may need to look for more than one type if some 
information are missing
+                      * (for example, the dimension when EPSG distinguishes 
the 2D and 3D cases).
+                      */
+                     String[] synonymous = SYNONYMOUS_TYPES.get(typeName);
+                     if (synonymous != null && dimension > 0 && dimension <= 
9) {
+                         final String suffix = "2D".replace('2',  (char) ('0' 
+ dimension));
+                         if (typeName.endsWith(suffix)) {
+                             synonymous = null;
+                         } else {
+                             for (String alternative : synonymous) {
+                                 if (alternative.endsWith(suffix)) {
+                                     typeName = alternative;
+                                     synonymous = null;
+                                     break;
+                                 }
+                             }
+                         }
                      }
-                     buffer.append(" LIKE '").append(typeNames[i]).append("%' 
AND ");
-                     return candidate;
+                     /*
+                      * Build the SQL `WHERE` clause.
+                      */
+                     buffer.append('(').append(typeColumn).append(" = 
'").append(typeName).append('\'');
+                     if (synonymous != null) {
+                         for (String alternative : synonymous) {
+                             buffer.append(" OR ").append(typeColumn).append(" 
= '").append(alternative).append('\'');
+                         }
+                     }
+                     buffer.append(") AND ");
+                     return subType;
                  }
              }
          }
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/PositionTransformer.java
index 914cb6be33,40f545525a..7e6de959a8
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/PositionTransformer.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/PositionTransformer.java
@@@ -28,10 -29,10 +29,9 @@@ import org.apache.sis.referencing.CRS
  import org.apache.sis.referencing.MultiRegisterOperations;
  import org.apache.sis.referencing.operation.transform.MathTransforms;
  import org.apache.sis.geometry.GeneralDirectPosition;
- import org.apache.sis.util.Utilities;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.referencing.RegisterOperations;
 -import org.opengis.coordinate.MismatchedDimensionException;
 +// Specific to the main branch:
 +import org.opengis.geometry.MismatchedDimensionException;
  
  
  /**
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java
index bb0c5c49d3,e2aacdd5c1..22994f3543
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java
@@@ -207,9 -265,10 +210,10 @@@ public final class VerticalDatumTypes i
                      AxisDirection dir = AxisDirection.UP;               // 
Expected direction for accepting the type.
                      switch (abbreviation.charAt(0)) {
                          case 'h': method = ellipsoidal(); break;
 -                        case 'H': method = RealizationMethod.GEOID; break;
 +                        case 'H': method = VerticalDatumType.GEOIDAL; break;
+                         case 'd': // Fall through
 -                        case 'D': method = RealizationMethod.TIDAL; dir = 
AxisDirection.DOWN; break;
 +                        case 'D': method = VerticalDatumType.DEPTH; dir = 
AxisDirection.DOWN; break;
-                         default:  return VerticalDatumType.OTHER_SURFACE;
+                         default:  return null;
                      }
                      if (dir.equals(axis.getDirection())) {
                          return method;
@@@ -229,24 -288,18 +233,29 @@@
       * @param  name  name of the datum for which to guess a realization 
method, or {@code null}.
       * @return a realization method, or {@code null} if none can be guessed.
       */
 -    private static RealizationMethod fromDatum(final String name) {
 +    private static VerticalDatumType fromDatum(final String name) {
          if (name != null) {
              if (CharSequences.equalsFiltered("Mean Sea Level", name, 
Characters.Filter.LETTERS_AND_DIGITS, true)) {
 -                return RealizationMethod.TIDAL;
 +                return VerticalDatumType.GEOIDAL;
              }
-             if (name.regionMatches(true, 0, "geoid", 0, 5)) {
-                 return VerticalDatumType.GEOIDAL;
-             }
+             int i = 0;
+             do {
+                 if (name.regionMatches(true, i, "geoid", 0, 5)) {
 -                    return RealizationMethod.GEOID;
++                    return VerticalDatumType.GEOIDAL;
+                 }
+                 i = name.indexOf(' ', i) + 1;
+             } while (i > 0);
 +            if (name.equalsIgnoreCase("Tidal")) {
 +                return VerticalDatumType.DEPTH;
 +            }
-             for (int i=0; i<name.length();) {
++            i = 0;
++            while (i < name.length()) {
 +                final int c = name.codePointAt(i);
 +                if (Character.isLetter(c)) {
 +                    return CodeLists.find(VerticalDatumType.class, new 
VerticalDatumTypes(name));
 +                }
 +                i += Character.charCount(c);
 +            }
          }
          return null;
      }
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
index 12fef44803,7b6a453186..8ad69565f3
--- 
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
@@@ -920,10 -856,10 +918,10 @@@ check:      for (int isTarget=0; ; isTa
                   *   - Scope, domain and accuracy properties only if NOT in 
"ignore metadata" mode.
                   *   - Interpolation CRS in all cases (regardless if ignoring 
metadata or not).
                   */
-                 final CoordinateOperation that = (CoordinateOperation) object;
+                 final var that = (CoordinateOperation) object;
                  if ((mode.isIgnoringMetadata() ||
                      (deepEquals(getCoordinateOperationAccuracy(), 
that.getCoordinateOperationAccuracy(), mode))) &&
 -                     deepEquals(getInterpolationCRS(),            
that.getInterpolationCRS(), mode))
 +                     deepEquals(getInterpolationCRS().orElse(null), 
getInterpolationCRS(that), mode))
                  {
                      /*
                       * At this point all metadata match or can be ignored. 
First, compare the targetCRS.
diff --cc 
endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/Assertions.java
index d7a85f04cd,eade998322..eeb4852a02
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/Assertions.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/Assertions.java
@@@ -108,10 -119,10 +122,10 @@@ public final class Assertions extends S
       */
      public static void assertEpsgIdentifierEquals(final String expected, 
final Identifier actual) {
          assertNotNull(actual);
-         assertEquals(expected,        actual.getCode(), "code");
+         assertLegacyEquals(expected, actual.getCode(), "code");
 -        assertEquals(Constants.EPSG,  actual.getCodeSpace(), "codeSpace");
 +        assertEquals(Constants.EPSG,  (actual instanceof ReferenceIdentifier) 
? ((ReferenceIdentifier) actual).getCodeSpace() : null, "codeSpace");
          assertEquals(Constants.EPSG,  
Citations.toCodeSpace(actual.getAuthority()), "authority");
-         assertEquals(Constants.EPSG + Constants.DEFAULT_SEPARATOR + expected, 
IdentifiedObjects.toString(actual), "identifier");
+         assertLegacyEquals(Constants.EPSG + Constants.DEFAULT_SEPARATOR + 
expected, IdentifiedObjects.toString(actual), "identifier");
      }
  
      /**
diff --cc 
endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/IdentifiedObjectFinderTest.java
index a9e651991e,671fb48832..28584bcf7a
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/IdentifiedObjectFinderTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/IdentifiedObjectFinderTest.java
@@@ -76,9 -72,9 +75,9 @@@ public final class IdentifiedObjectFind
           * Same test as above, using a CRS without identifier.
           * The intent is to force a full scan.
           */
-         final CoordinateReferenceSystem search = new DefaultGeographicCRS(
+         final var search = new DefaultGeographicCRS(
                  Map.of(DefaultGeographicCRS.NAME_KEY, CRS84.getName()),
 -                CRS84.getDatum(), CRS84.getDatumEnsemble(), 
CRS84.getCoordinateSystem());
 +                CRS84.getDatum(), getDatumEnsemble(CRS84), 
CRS84.getCoordinateSystem());
          assertEqualsIgnoreMetadata(CRS84, search);              // Required 
condition for next test.
  
          finder.setSearchDomain(IdentifiedObjectFinder.Domain.DECLARATION);
diff --cc 
endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/MultiAuthoritiesFactoryTest.java
index 99865879a7,99865879a7..87bd14d01f
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/MultiAuthoritiesFactoryTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/MultiAuthoritiesFactoryTest.java
@@@ -196,7 -196,7 +196,7 @@@ public final class MultiAuthoritiesFact
          assertSame(HardCodedDatum.SPHERE,               
factory.createGeodeticDatum("MOCK: 0:6047"));
          assertSame(Extents       .WORLD,                factory.createExtent  
     ("MOCK: 2.3 : 1262"));
          assertSame(Units         .METRE,                factory.createUnit    
     (" MoCK : : 9001 "));
--        assertEquals("Greenwich", 
factory.getDescriptionText(IdentifiedObject.class, 
"MOCK:8901").get().toString());
++        assertEquals("Greenwich", 
factory.getDescriptionText(PrimeMeridian.class, "MOCK:8901").get().toString());
  
          var e = assertThrows(NoSuchAuthorityFactoryException.class,
                  () -> factory.createGeodeticDatum("MOCK2:4326"),
diff --cc 
endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/ConsistencyTest.java
index 714b681bb9,33fdd7fa1a..3850bc2ef1
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/ConsistencyTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/ConsistencyTest.java
@@@ -101,6 -116,26 +116,26 @@@ public final class ConsistencyTest exte
          lookup(parseAndFormat(format, code, crs), crs);
      }
  
+     /**
+      * Returns whether testing the given <abbr>CRS</abbr> requires the 2019 
version of <abbr>WKT</abbr> format.
+      * We skip the vertical datum having the "local" realization method 
because this information is lost during
+      * the roundtrip with WKT or WKT 2 version 2015, and the WKT parser tries 
to guess the method from the axis
+      * abbreviation "d" which result in "tidal".
+      */
+     private static boolean requiresWKT2019(final CoordinateReferenceSystem 
crs) {
+         final VerticalCRS c = CRS.getVerticalComponent(crs, false);
+         if (c != null) {
+             final var datum = c.getDatum();
+             if (datum != null) {
 -                final String method = 
datum.getRealizationMethod().map(CodeList::name).orElse("");
++                final String method = datum.getVerticalDatumType().name();
+                 if (method.equalsIgnoreCase("local")) {
+                     return true;
+                 }
+             }
+         }
+         return false;
+     }
+ 
      /**
       * Verifies the WKT consistency of all CRS instances.
       *
diff --cc 
endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClause.java
index ff31e233f0,79cd823110..cfa89aaad2
--- 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClause.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClause.java
@@@ -34,15 -34,16 +34,15 @@@ import org.apache.sis.geometry.wrapper.
  import org.apache.sis.geometry.wrapper.GeometryWrapper;
  import org.apache.sis.metadata.sql.privy.SQLBuilder;
  import org.apache.sis.filter.privy.WarningEvent;
+ import org.apache.sis.referencing.CRS;
  import org.apache.sis.storage.FeatureSet;
  import org.apache.sis.system.Modules;
- import org.apache.sis.util.Utilities;
  import org.apache.sis.util.Workaround;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.util.CodeList;
 -import org.opengis.feature.Feature;
 -import org.opengis.filter.Filter;
 -import org.opengis.filter.ValueReference;
 +// Specific to the main branch:
 +import org.apache.sis.filter.Filter;
 +import org.apache.sis.feature.AbstractFeature;
 +import org.apache.sis.pending.geoapi.filter.ValueReference;
  
  
  /**
diff --cc 
optional/src/org.apache.sis.referencing.database/test/org/apache/sis/resources/embedded/EmbeddedResourcesTest.java
index 60292eef43,4c8393cdcc..f024ec9597
--- 
a/optional/src/org.apache.sis.referencing.database/test/org/apache/sis/resources/embedded/EmbeddedResourcesTest.java
+++ 
b/optional/src/org.apache.sis.referencing.database/test/org/apache/sis/resources/embedded/EmbeddedResourcesTest.java
@@@ -133,8 -129,8 +129,8 @@@ public final strictfp class EmbeddedRes
       */
      @Test
      public void testCrsforCode() throws FactoryException {
-         assumeDataPresent();
+         assumeContainsEPSG();
 -        CoordinateReferenceSystem crs = CRS.forCode("EPSG:6676");
 +        var crs = assertInstanceOf(AbstractCRS.class, 
CRS.forCode("EPSG:6676"));
          String area = 
TestUtilities.getSingleton(crs.getDomains()).getDomainOfValidity().getDescription().toString();
          assertTrue(area.contains("Japan"), area);
      }

Reply via email to