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 ba6681f33243bc4a65a55e76f387b471b5999539
Merge: 39ea192688 3bb55654d2
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Tue Aug 5 19:36:51 2025 +0200

    Merge branch 'geoapi-3.1'.
    Contains upgrade of `EPSGFactory` for reading EPSG database version 12.

 .../sis/buildtools/coding/ReorganizeImports.java   |    7 +-
 .../org.apache.sis.feature/main/module-info.java   |    3 +
 .../main/org/apache/sis/coverage/CategoryList.java |    2 +-
 .../org/apache/sis/coverage/SampleDimension.java   |   20 +-
 .../coverage/grid/CoordinateOperationFinder.java   |   43 +-
 .../apache/sis/coverage/grid/GridDerivation.java   |   53 +-
 .../org/apache/sis/coverage/grid/GridExtent.java   |    9 +-
 .../apache/sis/coverage/grid/SliceGeometry.java    |    2 +-
 .../main/org/apache/sis/coverage/package-info.java |    2 +-
 .../org/apache/sis/feature/DefaultFeatureType.java |    2 +-
 .../main/org/apache/sis/feature/Features.java      |    4 +-
 .../main/org/apache/sis/filter/Optimization.java   |    6 +-
 .../main/org/apache/sis/filter/PropertyValue.java  |    2 +-
 .../main/org/apache/sis/filter/TemporalFilter.java |    2 +-
 .../main/org/apache/sis/image/PixelIterator.java   |    2 +-
 .../main/org/apache/sis/image/TransferType.java    |    6 +-
 .../main/org/apache/sis/image/Transferer.java      |    2 +-
 .../sis/coverage/grid/GridDerivationTest.java      |   49 +
 .../org/apache/sis/feature/FeatureTestCase.java    |   10 +-
 .../org/apache/sis/metadata/MetadataStandard.java  |    2 +-
 .../org/apache/sis/metadata/MetadataVisitor.java   |    2 +-
 .../org/apache/sis/metadata/TreeTableView.java     |    2 +-
 .../sis/metadata/internal/CitationConstant.java    |    2 +-
 .../sis/metadata/iso/citation/Citations.java       |    2 +-
 .../metadata/iso/extent/DefaultVerticalExtent.java |   18 +-
 .../apache/sis/metadata/iso/extent/Extents.java    |   14 +-
 .../DefaultRepresentativeFraction.java             |    4 +-
 .../iso/maintenance/DefaultScopeDescription.java   |    3 +-
 .../org/apache/sis/metadata/sql/Dispatcher.java    |    2 +-
 .../org/apache/sis/temporal/LenientDateFormat.java |    4 +-
 .../apache/sis/util/iso/DefaultNameFactory.java    |    6 +-
 .../main/org/apache/sis/util/iso/Types.java        |    2 +-
 .../apache/sis/temporal/LenientDateFormatTest.java |    4 +
 .../apache/sis/profile/japan/netcdf/GCOM_C.java    |    4 +-
 .../sis/geometry/AbstractDirectPosition.java       |    2 +-
 .../org/apache/sis/geometry/AbstractEnvelope.java  |    2 +-
 .../main/org/apache/sis/geometry/Envelopes.java    |  102 +-
 .../apache/sis/geometry/WraparoundInEnvelope.java  |  171 +-
 .../main/org/apache/sis/io/wkt/Formatter.java      |    2 +-
 .../apache/sis/io/wkt/GeodeticObjectParser.java    |   13 +-
 .../sis/parameter/DefaultParameterDescriptor.java  |    2 +
 .../sis/parameter/DefaultParameterValue.java       |    4 +-
 .../main/org/apache/sis/parameter/Parameters.java  |    4 +-
 .../main/org/apache/sis/parameter/Verifier.java    |   81 +-
 .../sis/referencing/AbstractIdentifiedObject.java  |    7 +-
 .../main/org/apache/sis/referencing/Builder.java   |    2 +-
 .../main/org/apache/sis/referencing/CRS.java       |   22 +-
 .../apache/sis/referencing/NamedIdentifier.java    |    2 +-
 .../sis/referencing/crs/DefaultGeodeticCRS.java    |   24 +-
 .../sis/referencing/datum/AbstractDatum.java       |   30 +-
 .../referencing/datum/DefaultDatumEnsemble.java    |  120 +-
 .../sis/referencing/datum/DefaultEllipsoid.java    |    2 +-
 .../referencing/datum/DefaultGeodeticDatum.java    |   16 +-
 .../referencing/datum/DefaultVerticalDatum.java    |    6 +-
 .../referencing/factory/AuthorityFactoryProxy.java |   17 +
 .../factory/ConcurrentAuthorityFactory.java        |   35 +-
 .../factory/GeodeticAuthorityFactory.java          |  148 +-
 .../referencing/factory/GeodeticObjectFactory.java |    4 +
 .../factory/IdentifiedObjectFinder.java            |    2 +-
 .../referencing/factory/IdentifiedObjectSet.java   |   77 +-
 .../referencing/factory/sql/AuthorityCodes.java    |    6 +-
 .../sis/referencing/factory/sql/AxisName.java      |   18 +-
 .../sis/referencing/factory/sql/BursaWolfInfo.java |  210 --
 .../factory/sql/CoordinateOperationSet.java        |   21 +-
 .../referencing/factory/sql/EPSGCodeFinder.java    |   20 +-
 .../referencing/factory/sql/EPSGDataAccess.java    | 2631 +++++++++++---------
 .../sis/referencing/factory/sql/EPSGFactory.java   |    4 +-
 .../sis/referencing/factory/sql/EPSG_Finish.sql    |    2 +-
 .../sis/referencing/factory/sql/EPSG_Prepare.sql   |   18 +-
 .../referencing/factory/sql/ObjectPertinence.java  |  154 ++
 .../sis/referencing/factory/sql/SQLTranslator.java |  424 ++--
 .../sis/referencing/factory/sql/TableInfo.java     |   61 +-
 .../org/apache/sis/referencing/internal/Epoch.java |    2 -
 .../internal/ParameterizedTransformBuilder.java    |    9 +
 .../internal/PositionalAccuracyConstant.java       |   40 +-
 .../referencing/internal/VerticalDatumTypes.java   |   56 +-
 .../operation/CoordinateOperationContext.java      |   28 +-
 .../operation/CoordinateOperationFinder.java       |   27 +-
 .../operation/CoordinateOperationRegistry.java     |    2 +-
 .../DefaultCoordinateOperationFactory.java         |   31 +-
 .../referencing/operation/SubOperationInfo.java    |   13 +-
 .../referencing/operation/gridded/LoadedGrid.java  |    2 +-
 .../operation/matrix/AffineTransforms2D.java       |    6 +-
 .../sis/referencing/operation/matrix/Matrix1.java  |    2 +-
 .../sis/referencing/operation/matrix/Matrix2.java  |    2 +-
 .../sis/referencing/operation/matrix/Matrix3.java  |    2 +-
 .../sis/referencing/operation/matrix/Matrix4.java  |    2 +-
 .../referencing/operation/matrix/MatrixSIS.java    |    2 +-
 .../referencing/operation/provider/Wraparound.java |    2 +-
 .../operation/transform/MolodenskyTransform.java   |    2 +-
 .../operation/transform/WraparoundTransform.java   |    4 +-
 .../referencing/privy/CoordinateOperations.java    |    5 -
 .../org/apache/sis/geometry/EnvelopesTest.java     |   12 +-
 .../datum/DefaultGeodeticDatumTest.java            |    2 +-
 .../referencing/factory/sql/EPSGFactoryTest.java   |   36 +-
 .../sis/referencing/factory/sql/TableInfoTest.java |   10 +-
 .../internal/VerticalDatumTypesTest.java           |   32 +-
 .../InterpolatedGeocentricTransformTest.java       |    2 +-
 .../storage/landsat/LandsatStoreProviderTest.java  |    2 +-
 .../apache/sis/storage/geotiff/GeoTiffStore.java   |    4 +-
 .../org/apache/sis/storage/geotiff/Reader.java     |    2 +-
 .../org/apache/sis/storage/geotiff/writer/ZIP.java |   28 +-
 .../org/apache/sis/storage/geotiff/WriterTest.java |   41 +-
 .../apache/sis/storage/netcdf/base/Convention.java |   30 +-
 .../sis/storage/netcdf/base/GridMapping.java       |  134 +-
 .../org/apache/sis/storage/netcdf/base/Node.java   |    4 +-
 .../apache/sis/io/stream/HyperRectangleWriter.java |   54 +-
 .../sis/io/stream/SubsampledRectangleWriter.java   |   26 +-
 .../org/apache/sis/io/stream/UpdatableWrite.java   |    2 +-
 .../org/apache/sis/storage/StorageConnector.java   |    8 +-
 .../aggregate/BandAggregateGridResource.java       |    2 +-
 .../aggregate/ConcatenatedGridResource.java        |    2 +-
 .../sis/storage/aggregate/CoverageAggregator.java  |    2 +-
 .../sis/storage/base/MemoryGridResource.java       |   29 +-
 .../storage/image/WritableSingleImageStore.java    |    2 +-
 .../org/apache/sis/storage/CoverageSubsetTest.java |    2 +-
 .../aggregate/BandAggregateGridResourceTest.java   |    2 +-
 .../sis/storage/aggregate/OpaqueGridResource.java  |    2 +-
 .../sis/storage/base/MemoryGridResourceTest.java   |    2 +-
 .../apache/sis/storage/csv/StoreProviderTest.java  |    2 +-
 .../sis/storage/esri/AsciiGridStoreTest.java       |    2 +-
 .../sis/storage/image/WorldFileStoreTest.java      |    2 +-
 .../apache/sis/storage/wkt/StoreProviderTest.java  |    2 +-
 .../test/org/apache/sis/storage/wkt/StoreTest.java |    2 +-
 .../apache/sis/storage/xml/StoreProviderTest.java  |    2 +-
 .../test/org/apache/sis/storage/xml/StoreTest.java |    2 +-
 .../main/org/apache/sis/math/ArrayVector.java      |    4 +-
 .../main/org/apache/sis/math/DecimalFunctions.java |    2 +-
 .../main/org/apache/sis/math/Vector.java           |    6 +-
 .../main/org/apache/sis/measure/DerivedScalar.java |    4 +-
 .../org/apache/sis/measure/MeasurementRange.java   |    6 +-
 .../main/org/apache/sis/measure/NumberRange.java   |   14 +-
 .../main/org/apache/sis/measure/Scalar.java        |   10 +-
 .../main/org/apache/sis/system/Semaphores.java     |    2 +-
 .../main/org/apache/sis/util/ArgumentChecks.java   |    2 +-
 .../main/org/apache/sis/util/ArraysExt.java        |   20 +-
 .../main/org/apache/sis/util/Numbers.java          |    6 +-
 .../main/org/apache/sis/util/collection/Cache.java |   11 +-
 .../org/apache/sis/util/collection/Containers.java |    8 +-
 .../sis/util/collection/DefaultTreeTable.java      |    2 +-
 .../main/org/apache/sis/util/privy/Constants.java  |   10 +-
 .../main/org/apache/sis/util/privy/Numerics.java   |    2 +-
 .../org/apache/sis/util/resources/Vocabulary.java  |    5 +
 .../sis/util/resources/Vocabulary.properties       |    1 +
 .../sis/util/resources/Vocabulary_fr.properties    |    1 +
 .../test/org/apache/sis/math/VectorTest.java       |    2 +-
 .../org/apache/sis/measure/SystemUnitTest.java     |    2 +-
 .../coveragejson/CoverageJsonStoreTest.java        |    2 +-
 .../apache/sis/gui/coverage/CoverageCanvas.java    |   10 +-
 .../referencing/factory/sql/epsg/DebugTools.sql    |    2 +-
 .../sis/referencing/factory/sql/epsg/README.md     |    1 +
 151 files changed, 3505 insertions(+), 2120 deletions(-)

diff --cc endorsed/src/org.apache.sis.feature/main/module-info.java
index 0fed268ab6,e557fb23e2..74df0536bb
--- a/endorsed/src/org.apache.sis.feature/main/module-info.java
+++ b/endorsed/src/org.apache.sis.feature/main/module-info.java
@@@ -45,13 -45,12 +45,16 @@@ module org.apache.sis.feature 
              org.apache.sis.storage,
              org.apache.sis.storage.sql,
              org.apache.sis.storage.shapefile,       // In the "incubator" 
sub-project.
 -            org.apache.sis.cql,                     // In the "incubator" 
sub-project.
 -            org.apache.sis.portrayal.map;           // In the "incubator" 
sub-project.
 +            org.apache.sis.portrayal;
 +
 +    exports org.apache.sis.filter.privy to
 +            org.apache.sis.storage,
 +            org.apache.sis.storage.sql,
 +            org.apache.sis.storage.shapefile;       // In the "incubator" 
sub-project.
  
+     exports org.apache.sis.filter.sqlmm to
+             org.apache.sis.geometry;                // In the "incubator" 
sub-project.
+ 
      exports org.apache.sis.feature.privy to
              org.apache.sis.storage,
              org.apache.sis.storage.xml,
diff --cc 
endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/SampleDimension.java
index 97f50a521a,c082297388..a27b44bdde
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/SampleDimension.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/SampleDimension.java
@@@ -75,10 -78,13 +75,10 @@@ import org.apache.sis.util.iso.Names
   *
   * @author  Martin Desruisseaux (IRD, Geomatys)
   * @author  Alexis Manin (Geomatys)
-  * @version 1.2
+  * @version 1.5
 - *
 - * @see org.opengis.metadata.content.SampleDimension
 - *
   * @since 1.0
   */
 -public class SampleDimension implements IdentifiedType, Serializable {
 +public class SampleDimension implements Serializable {
      /**
       * Serial number for inter-operability with different versions.
       */
@@@ -213,6 -220,25 +213,24 @@@
          return name;
      }
  
+     /**
+      * Returns a concise definition of this sample dimensions.
+      * This definition may be shown in user interfaces and should be 
considered as indicative only.
+      * The default implementation may change between different versions of 
Apache <abbr>SIS</abbr>.
+      *
+      * @return concise definition of the sample dimension.
+      *
+      * @since 1.5
+      */
 -    @Override
+     public InternationalString getDefinition() {
+         for (Category category : categories) {
+             if (category.isQuantitative()) {
+                 return category.getName();
+             }
+         }
+         return getName().toInternationalString();
+     }
+ 
      /**
       * Returns all categories in this sample dimension. Note that a {@link 
Category} object may apply to an arbitrary range
       * of sample values. Consequently, the first element in this collection 
may not be directly related to the sample value
diff --cc 
endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/FeatureTestCase.java
index 081fee01c8,e7cdce107f..c080642aad
--- 
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/FeatureTestCase.java
+++ 
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/FeatureTestCase.java
@@@ -287,11 -292,11 +287,11 @@@ public abstract class FeatureTestCase e
      @Test
      public void testCustomAttribute() {
          feature = createFeature(DefaultFeatureTypeTest.city());
--        final var wrong  = SingletonAttributeTest.parliament();
-         final var city   = assertInstanceOf(DefaultAttributeType.class, 
feature.getType().getProperty("city"));
-         final var casted = new CustomAttribute<>(Features.cast(city, 
String.class));
 -        final var city   = assertInstanceOf(AttributeType.class, 
feature.getType().getProperty("city"));
 -        final var cast   = new CustomAttribute<>(Features.cast(city, 
String.class));
++        final var wrong = SingletonAttributeTest.parliament();
++        final var city  = assertInstanceOf(DefaultAttributeType.class, 
feature.getType().getProperty("city"));
++        final var cast  = new CustomAttribute<>(Features.cast(city, 
String.class));
  
-         feature.setProperty(casted);
+         feature.setProperty(cast);
          setAttributeValue("city", "Utopia", "Atlantide");
  
          var exception = assertThrows(IllegalArgumentException.class, () -> 
feature.setProperty(wrong));
diff --cc 
endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/Extents.java
index b425ce2b84,e4a5d16c39..012f9b39d3
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/Extents.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/Extents.java
@@@ -421,13 -418,13 +421,13 @@@ public final class Extents extends Stat
      @OptionalCandidate
      public static MeasurementRange<Double> getVerticalRange(final Extent 
extent) {
          MeasurementRange<Double> range = null;
 -        RealizationMethod selectedMethod = null;
 +        VerticalDatumType selectedMethod = null;
          if (extent != null) try {
              for (final VerticalExtent element : 
nonNull(extent.getVerticalElements())) {
-                 double min = element.getMinimumValue();
-                 double max = element.getMaximumValue();
+                 Double min = element.getMinimumValue();
+                 Double max = element.getMaximumValue();
                  final VerticalCRS crs = element.getVerticalCRS();
 -                RealizationMethod method = null;
 +                VerticalDatumType method = null;
                  Unit<?> unit = null;
                  if (crs != null) {
                      final VerticalDatum datum = crs.getDatum();
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
index d047ec4b8b,e473ae0c0f..4e1266cee8
--- 
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
@@@ -577,7 -567,7 +578,7 @@@ class GeodeticObjectParser extends Math
          final Element anchor = element.pullElement(OPTIONAL, 
WKTKeywords.Anchor);
          final Map<String,Object> properties = parseMetadataAndClose(element, 
name, null);
          if (anchor != null) {
-             properties.put(Datum.ANCHOR_POINT_KEY, 
anchor.pullString("anchorDefinition"));
 -            properties.put(Datum.ANCHOR_DEFINITION_KEY, 
anchor.pullString("anchorDefinition"));
++            properties.put(Datum.ANCHOR_POINT_KEY, 
anchor.pullString(DefaultGeodeticDatum.ANCHOR_DEFINITION_KEY));
              anchor.close(ignoredElements);
          }
          return properties;
@@@ -1458,12 -1444,12 +1459,12 @@@
              return null;
          }
          final String name = element.pullString("name");
 -        RealizationMethod method = null;
 +        VerticalDatumType method = null;
          if (isWKT1) {
-             method = 
VerticalDatumTypes.fromLegacy(element.pullInteger("datum"));
+             method = 
VerticalDatumTypes.fromLegacyCode(element.pullInteger("datum"));
          }
          if (method == null) {
-             method = VerticalDatumTypes.guess(name, null, null);
+             method = VerticalDatumTypes.fromDatum(name, null, null);
          }
          final DatumFactory datumFactory = factories.getDatumFactory();
          try {
@@@ -1950,9 -1939,9 +1951,9 @@@
                   * But sometimes the axis (which was not available when we 
created the datum) provides
                   * more information. Verify if we can have a better type now, 
and if so rebuild the datum.
                   */
 -                if (datum.getRealizationMethod().isEmpty()) {
 +                if (datum.getVerticalDatumType() == 
VerticalDatumType.OTHER_SURFACE) {
-                     var type = 
VerticalDatumTypes.guess(datum.getName().getCode(), datum.getAlias(), 
cs.getAxis(0));
+                     var type = 
VerticalDatumTypes.fromDatum(datum.getName().getCode(), datum.getAlias(), 
cs.getAxis(0));
 -                    if (type != null) {
 +                    if (type != VerticalDatumType.OTHER_SURFACE) {
                          final DatumFactory datumFactory = 
factories.getDatumFactory();
                          datum = 
datumFactory.createVerticalDatum(IdentifiedObjects.getProperties(datum), type);
                      }
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java
index a4bcb99c7e,6cde7480be..f62d17c438
--- 
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
@@@ -183,13 -181,6 +183,15 @@@ public class AbstractIdentifiedObject e
       */
      public static final String DEPRECATED_KEY = "deprecated";
  
 +    /**
 +     * Key for the <code>{@value}</code> property to be given to the
 +     * {@code ObjectFactory.createFoo(Map, ...)} methods.
 +     * This is used for setting the value to be returned by {@link 
#getDomains()}.
++     *
++     * @since 1.5
 +     */
-     static final String DOMAINS_KEY = "domains";
++    public static final String DOMAINS_KEY = "domains";
 +
      /**
       * The name for this object or code. Shall never be {@code null}.
       *
@@@ -601,14 -590,7 +603,15 @@@
       * @since 0.6
       */
      public Optional<InternationalString> getDescription() {
 -        return Optional.ofNullable((name != null) ? name.getDescription() : 
null);
++        @SuppressWarnings("LocalVariableHidesMemberVariable")
 +        final ReferenceIdentifier name = getName();
 +        if (name instanceof ImmutableIdentifier) {
 +            return Optional.ofNullable(((ImmutableIdentifier) 
name).getDescription());
 +        }
 +        if (name instanceof DefaultIdentifier) {
 +            return Optional.ofNullable(((DefaultIdentifier) 
name).getDescription());
 +        }
 +        return Optional.empty();
      }
  
      /**
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/AbstractDatum.java
index cabcc1e865,3bbfaa0340..864983ab28
--- 
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
@@@ -97,24 -95,6 +97,48 @@@ public class AbstractDatum extends Abst
       */
      private static final long serialVersionUID = 5380816794438838309L;
  
++    /**
++     * Key for the <code>{@value}</code> property to be given to the
++     * {@code DatumFactory.createFoo(Map, ...)} methods.
++     * This is used for setting the value to be returned by {@link 
#getAnchorDefinition()}.
++     *
++     * @see DatumFactory
++     * @see #getAnchorDefinition()
++     *
++     * @since 1.5
++     */
++    public static final String ANCHOR_DEFINITION_KEY = "anchorDefinition";
++
++    /**
++     * Key for the <code>{@value}</code> property to be given to the
++     * {@code DatumFactory.createFoo(Map, ...)} methods.
++     * This is used for setting the value to be returned by {@link 
#getAnchorEpoch()}.
++     *
++     * @see DatumFactory
++     * @see #getAnchorEpoch()
++     *
++     * @since 1.5
++     */
++    public static final String ANCHOR_EPOCH_KEY = "anchorEpoch";
++
 +    /**
 +     * Key for the <code>{@value}</code> property to be given to the
 +     * {@code DatumFactory.createFoo(Map, ...)} methods.
 +     * This is used for setting the value to be returned by {@link 
#getPublicationDate()}.
 +     *
 +     * @since 1.5
 +     */
 +    public static final String PUBLICATION_DATE_KEY = "publicationDate";
 +
 +    /**
 +     * Key for the <code>{@value}</code> property to be given to the
 +     * {@code DatumFactory.createFoo(Map, ...)} methods.
 +     * This is used for setting the value to be returned by {@link 
#getConventionalRS()}.
 +     *
 +     * @since 1.5
 +     */
 +    public static final String CONVENTIONAL_RS_KEY = "conventionalRS";
 +
      /**
       * Description, possibly including coordinates, of the point or points 
used to anchor the datum to the Earth.
       * Also known as the "origin", especially for Engineering and Image 
Datums.
@@@ -172,7 -152,7 +196,7 @@@
       *     <td>{@link InternationalString} or {@link String}</td>
       *     <td>{@link #getAnchorDefinition()}</td>
       *   </tr><tr>
-      *     <td>{@code "anchorEpoch"}</td>
 -     *     <td>{@value 
org.opengis.referencing.datum.Datum#ANCHOR_EPOCH_KEY}</td>
++     *     <td>{@value #ANCHOR_EPOCH_KEY}</td>
       *     <td>{@link Temporal}</td>
       *     <td>{@link #getAnchorEpoch()}</td>
       *   </tr><tr>
@@@ -210,9 -190,10 +234,9 @@@
       *
       * @param  properties  the properties to be given to the identified 
object.
       */
 -    @SuppressWarnings("deprecation")
      public AbstractDatum(final Map<String,?> properties) {
          super(properties);
-         anchorDefinition = Types.toInternationalString(properties, 
"anchorDefinition");
+         anchorDefinition = Types.toInternationalString(properties, 
ANCHOR_DEFINITION_KEY);
          if (anchorDefinition == null) {
              anchorDefinition = Types.toInternationalString(properties, 
ANCHOR_POINT_KEY);
          }
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
index 2347f87a0c,064b1e7c62..9ff2aa3232
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
@@@ -143,9 -149,9 +144,9 @@@ public class DefaultDatumEnsemble<D ext
       * @param  properties  the properties to be given to the identified 
object.
       * @param  members     datum or reference frames which are members of 
this ensemble.
       * @param  accuracy    inaccuracy introduced through use of this ensemble 
(mandatory).
-      * @param  type        the base type of datum members contained in this 
ensemble.
-      * @throws ClassCastException if a member is not an instance of {@code 
type}.
+      * @param  memberType  the base type of datum members contained in this 
ensemble.
+      * @throws ClassCastException if a member is not an instance of {@code 
memberType}.
 -     * @throws IllegalArgumentException if a member is an instance of {@link 
DatumEnsemble}, of if at least two
 +     * @throws IllegalArgumentException if a member is an instance of {@code 
DatumEnsemble}, of if at least two
       *         different {@linkplain AbstractDatum#getConventionalRS() 
conventional reference systems} are found.
       *
       * @see #create(Map, Collection, PositionalAccuracy)
@@@ -169,13 -175,15 +170,13 @@@
       *
       * <p>This constructor performs a shallow copy, i.e. the properties are 
not cloned.</p>
       *
-      * @param  ensemble  the ensemble to copy.
-      * @param  type      the base type of datum members contained in this 
ensemble.
-      * @throws ClassCastException if a member is not an instance of {@code 
type}.
+      * @param  ensemble    the ensemble to copy.
+      * @param  memberType  the base type of datum members contained in this 
ensemble.
+      * @throws ClassCastException if a member is not an instance of {@code 
memberType}.
 -     * @throws IllegalArgumentException if a member is an instance of {@link 
DatumEnsemble}, of if at least two
 +     * @throws IllegalArgumentException if a member is an instance of {@code 
DatumEnsemble}, of if at least two
       *         different {@linkplain AbstractDatum#getConventionalRS() 
conventional reference systems} are found.
 -     *
 -     * @see #castOrCopy(DatumEnsemble)
       */
-     protected DefaultDatumEnsemble(final DefaultDatumEnsemble<? extends D> 
ensemble, final Class<? super D> type) {
 -    protected DefaultDatumEnsemble(final DatumEnsemble<? extends D> ensemble, 
final Class<D> memberType) {
++    protected DefaultDatumEnsemble(final DefaultDatumEnsemble<? extends D> 
ensemble, final Class<D> memberType) {
          super(ensemble);
          members = List.copyOf(ensemble.getMembers());
          ensembleAccuracy = 
Objects.requireNonNull(ensemble.getEnsembleAccuracy());
@@@ -184,26 -192,25 +185,26 @@@
  
      /**
       * Verifies this ensemble. All members shall be instances of the 
specified type and shall have
 -     * the same conventional reference system. No member can be an instance 
of {@link DatumEnsemble}.
 +     * the same conventional reference system. No member can be an instance 
of {@code DatumEnsemble}.
       *
-      * @param  type  the base type of datum members contained in this 
ensemble.
-      * @throws ClassCastException if a member is not an instance of {@code 
type}.
+      * @param  memberType  the base type of datum members contained in this 
ensemble.
+      * @throws ClassCastException if a member is not an instance of {@code 
memberType}.
 -     * @throws IllegalArgumentException if a member is an instance of {@link 
DatumEnsemble}, of if at least two
 +     * @throws IllegalArgumentException if a member is an instance of {@code 
DatumEnsemble}, of if at least two
       *         different {@linkplain AbstractDatum#getConventionalRS() 
conventional reference systems} are found.
       */
-     private void validate(final Class<? super D> type) {
+     private void validate(final Class<D> memberType) {
          IdentifiedObject rs = null;
          for (final D datum : members) {
 -            if (datum instanceof DatumEnsemble<?>) {
 +            if (datum instanceof DefaultDatumEnsemble<?>) {
                  throw new IllegalArgumentException(
 -                        
Errors.format(Errors.Keys.IllegalPropertyValueClass_2, "members", 
DatumEnsemble.class));
 +                        
Errors.format(Errors.Keys.IllegalPropertyValueClass_2, "members", 
DefaultDatumEnsemble.class));
              }
-             if (!type.isInstance(datum)) {
+             if (!memberType.isInstance(datum)) {
                  throw new ClassCastException(
-                         Errors.format(Errors.Keys.IllegalClass_2, type, 
datum.getClass()));
+                         Errors.format(Errors.Keys.IllegalClass_2, memberType, 
Classes.getClass(datum)));
              }
 -            final IdentifiedObject dr = 
datum.getConventionalRS().orElse(null);
 +            if (!(datum instanceof AbstractDatum)) continue;
 +            final IdentifiedObject dr = ((AbstractDatum) 
datum).getConventionalRS().orElse(null);
              if (dr != null) {
                  if (rs == null) {
                      rs = dr;
@@@ -233,9 -240,84 +234,38 @@@
              final Collection<? extends D> members,
              final PositionalAccuracy accuracy)
      {
-         return Factory.forMemberType(null, properties, List.copyOf(members), 
accuracy);
+         return Factory.forMemberType(Datum.class, null, properties, 
List.copyOf(members), accuracy);
+     }
+ 
 -    /**
 -     * Returns a SIS ensemble implementation with the values of the given 
arbitrary implementation.
 -     * This method performs the first applicable action in the following 
choices:
 -     *
 -     * <ul>
 -     *   <li>If the given object is {@code null}, then this method returns 
{@code null}.</li>
 -     *   <li>Otherwise, if the given object is already an instance of
 -     *       {@code DefaultDatumEnsemble}, then it is returned unchanged.</li>
 -     *   <li>Otherwise, a new {@code DefaultDatumEnsemble} instance is 
created using the
 -     *       {@linkplain #DefaultDatumEnsemble(DatumEnsemble, Class) copy 
constructor} and returned.
 -     *       Note that this is a <em>shallow</em> copy operation,
 -     *       because the other properties contained in the given object are 
not recursively copied.</li>
 -     * </ul>
 -     *
 -     * The returned ensemble may implement the {@link GeodeticDatum}, {@link 
VerticalDatum}, {@link TemporalDatum},
 -     * {@link ParametricDatum} or {@link EngineeringDatum} interface if all 
members are instances of the same interface.
 -     *
 -     * @param  <D>     the type of datum members contained in the ensemble.
 -     * @param  object  the object to get as a SIS implementation, or {@code 
null} if none.
 -     * @return a SIS implementation containing the values of the given object 
(may be the
 -     *         given object itself), or {@code null} if the argument was null.
 -     */
 -    public static <D extends Datum> DefaultDatumEnsemble<D> castOrCopy(final 
DatumEnsemble<D> object) {
 -        if (object == null || object instanceof DefaultDatumEnsemble<?>) {
 -            return (DefaultDatumEnsemble<D>) object;
 -        }
 -        return Factory.forMemberType(Datum.class, object, null, 
List.copyOf(object.getMembers()), object.getEnsembleAccuracy());
 -    }
 -
+     /**
+      * Returns this datum ensemble as a collection of datum of the given type.
+      * This method casts the datum members, it does not copy or rewrite them.
+      * However, the returned {@code DatumEnsemble} may be a different 
instance.
+      *
+      * @param  <N>         compile-time value of {@code memberType}.
+      * @param  memberType  the new desired type of datum members.
+      * @return an ensemble of datum of the given type.
+      * @throws ClassCastException if at least one member is not an instance 
of the specified type.
+      */
+     public <N extends Datum> DefaultDatumEnsemble<N> cast(final Class<N> 
memberType) {
+         for (final D member : members) {
+             if (!memberType.isInstance(member)) {
+                 throw new 
ClassCastException(Errors.format(Errors.Keys.IllegalClass_2, memberType, 
Classes.getClass(member)));
+             }
+         }
+         /*
+          * At this point, we verified that all members are of the requested 
type.
+          * Now verify if the ensemble as a whole is also of the requested 
type.
+          * This part is not mandatory however.
+          */
+         @SuppressWarnings("unchecked")
+         DefaultDatumEnsemble<N> ensemble = (DefaultDatumEnsemble<N>) this;
+         if (!memberType.isInstance(ensemble)) {
+             ensemble = Factory.forMemberType(memberType, ensemble, null, 
ensemble.members, ensemble.ensembleAccuracy);
+         }
+         return ensemble;
      }
  
 -    /**
 -     * Returns the GeoAPI interface implemented by this class.
 -     * The SIS implementation returns {@code DatumEnsemble.class}.
 -     *
 -     * <h4>Note for implementers</h4>
 -     * Subclasses usually do not need to override this method since GeoAPI 
does not define {@code DatumEnsemble}
 -     * sub-interface. Overriding possibility is left mostly for implementers 
who wish to extend GeoAPI with their
 -     * own set of interfaces.
 -     *
 -     * @return the datum interface implemented by this class.
 -     */
 -    @Override
 -    @SuppressWarnings("unchecked")
 -    public Class<? extends DatumEnsemble<D>> getInterface() {
 -        return (Class) DatumEnsemble.class;
 -    }
 -
      /*
       * NOTE: a previous version provided the following method:
       *
@@@ -252,8 -335,9 +283,8 @@@
       *
       * @return datum or reference frames which are members of this ensemble.
       */
 -    @Override
      @SuppressWarnings("ReturnOfCollectionOrArrayField")     // Collection is 
unmodifiable.
-     public Collection<D> getMembers() {
+     public final Collection<D> getMembers() {               // Must be final 
for type safety. See `forMemberType(…)`
          return members;
      }
  
@@@ -636,7 -832,8 +669,8 @@@ check:  if (it.hasNext()) 
           *         Should never happen if the parameterized type of {@code 
members} is respected.
           */
          static <D extends Datum> DefaultDatumEnsemble<D> forMemberType(
+                 final Class<? extends Datum> memberType,
 -                final DatumEnsemble<? extends D> object,
 +                final DefaultDatumEnsemble<? extends D> object,
                  final Map<String,?> properties,
                  final List<? extends D> members,
                  final PositionalAccuracy accuracy)
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
index eff6a7bb30,8ba7eb177d..5318757721
--- 
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
@@@ -202,25 -229,15 +202,25 @@@ public class DefaultVerticalDatum exten
      }
  
      /**
 -     * Returns the method through which this vertical reference frame is 
realized.
 +     * 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.
       *
 -     * @return method through which this vertical reference frame is realized.
 +     * <p>This method uses heuristic rules and may be changed in any future 
SIS version.</p>
       *
 -     * @since 2.0 (temporary version number until this branch is released)
 +     * <p>No synchronization needed; this is not a problem if this value is 
computed twice.
 +     * This method returns only existing immutable instances.</p>
 +     *
 +     * @see #getVerticalDatumType()
 +     * @see #getTypeElement()
       */
 -    @Override
 -    public Optional<RealizationMethod> getRealizationMethod() {
 -        return Optional.ofNullable(method);
 +    private VerticalDatumType type() {
 +        VerticalDatumType t = type;
 +        if (t == null) {
 +            final ReferenceIdentifier name = super.getName();
-             type = t = VerticalDatumTypes.guess(name != null ? name.getCode() 
: null, super.getAlias(), null);
++            type = t = VerticalDatumTypes.fromDatum(name != null ? 
name.getCode() : null, super.getAlias(), null);
 +        }
 +        return t;
      }
  
      /**
@@@ -394,7 -430,7 +394,7 @@@
      protected String formatTo(final Formatter formatter) {
          super.formatTo(formatter);
          if (formatter.getConvention().majorVersion() == 1) {
-             formatter.append(VerticalDatumTypes.toLegacy(type()));
 -            
formatter.append(VerticalDatumTypes.toLegacyCode(getVerticalDatumType()));
++            formatter.append(VerticalDatumTypes.toLegacyCode(type()));
              return WKTKeywords.Vert_Datum;
          }
          return formatter.shortOrLong(WKTKeywords.VDatum, 
WKTKeywords.VerticalDatum);
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java
index e0ea64a60c,68cfaace7e..20f5dfb954
--- 
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
@@@ -32,6 -32,6 +32,9 @@@ import org.opengis.util.InternationalSt
  import org.apache.sis.util.resources.Errors;
  import org.apache.sis.util.privy.Strings;
  
++// Specific to the main branch:
++import org.apache.sis.referencing.datum.DefaultDatumEnsemble;
++
  
  /**
   * Delegates object creations to one of the {@code create} methods in a 
backing {@code AuthorityFactory}.
@@@ -199,6 -197,17 +202,20 @@@ abstract class AuthorityFactoryProxy<T
              }
      };
  
+     @SuppressWarnings("unchecked")
 -    static final AuthorityFactoryProxy<DatumEnsemble<?>> ENSEMBLE =
 -        new AuthorityFactoryProxy<DatumEnsemble<?>>((Class) 
DatumEnsemble.class, AuthorityFactoryIdentifier.Type.DATUM) {
 -            @Override DatumEnsemble<?> create(GeodeticAuthorityFactory 
factory, String code) throws FactoryException {
++    static final AuthorityFactoryProxy<DefaultDatumEnsemble<?>> ENSEMBLE =
++        new AuthorityFactoryProxy<DefaultDatumEnsemble<?>>((Class) 
DefaultDatumEnsemble.class, AuthorityFactoryIdentifier.Type.DATUM) {
++            @Override DefaultDatumEnsemble<?> create(GeodeticAuthorityFactory 
factory, String code) throws FactoryException {
+                 return factory.createDatumEnsemble(code);
+             }
 -            @Override DatumEnsemble<?> createFromAPI(AuthorityFactory 
factory, String code) throws FactoryException {
 -                return datumFactory(factory).createDatumEnsemble(code);
++            @Override DefaultDatumEnsemble<?> createFromAPI(AuthorityFactory 
factory, String code) throws FactoryException {
++                if (factory instanceof GeodeticAuthorityFactory) {
++                    return ((GeodeticAuthorityFactory) 
factory).createDatumEnsemble(code);
++                }
++                throw new FactoryException("Unsupported factory.");
+             }
+     };
+ 
      static final AuthorityFactoryProxy<Datum> DATUM =
          new AuthorityFactoryProxy<Datum>(Datum.class, 
AuthorityFactoryIdentifier.Type.DATUM) {
              @Override Datum create(GeodeticAuthorityFactory factory, String 
code) throws FactoryException {
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java
index 1b19c2c77b,955389f63f..2649aa60dd
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java
@@@ -65,6 -65,6 +65,9 @@@ import org.apache.sis.system.Shutdown
  // Specific to the main and geoapi-3.1 branches:
  import org.apache.sis.util.collection.BackingStoreException;
  
++// Specific to the main branch:
++import org.apache.sis.referencing.datum.DefaultDatumEnsemble;
++
  
  /**
   * A concurrent authority factory that caches all objects created by another 
factory.
@@@ -1101,7 -1143,33 +1104,37 @@@ public abstract class ConcurrentAuthori
      }
  
      /**
-      * Returns an arbitrary datum from a code. The returned object will 
typically be an
+      * Returns an arbitrary datum ensemble from a code.
+      * The default implementation performs the following steps:
+      * <ul>
+      *   <li>Return the cached instance for the given code if such instance 
already exists.</li>
+      *   <li>Otherwise if the Data Access Object (DAO) overrides the {@code 
createDatumEnsemble(String)}
+      *       method, invoke that method and cache the result for future 
use.</li>
+      *   <li>Otherwise delegate to the {@link 
GeodeticAuthorityFactory#createDatumEnsemble(String)}
+      *       method in the parent class. This allows to check if the more 
generic
+      *       {@link #createObject(String)} method cached a value before to 
try that method.</li>
+      * </ul>
+      *
++     * <div class="warning"><b>Upcoming API change — generalization</b><br>
++     * The element type will be changed to the {@code DatumEnsemble} interface
++     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).</div>
++     *
+      * @return the datum for the given code.
+      * @throws FactoryException if the object creation failed.
+      *
+      * @since 1.5
+      */
+     @Override
 -    public DatumEnsemble<?> createDatumEnsemble(final String code) throws 
FactoryException {
 -        if (isDefault(DatumEnsemble.class)) {
++    public DefaultDatumEnsemble<?> createDatumEnsemble(final String code) 
throws FactoryException {
++        if (isDefault(DefaultDatumEnsemble.class)) {
+             return super.createDatumEnsemble(code);
+         }
+         return create(AuthorityFactoryProxy.ENSEMBLE, code);
+     }
+ 
+     /**
+      * Returns an arbitrary datum from a code. The returned object will 
typically be a
+      * {@link GeodeticDatum}, {@link VerticalDatum}, {@link TemporalDatum} or 
{@link EngineeringDatum}.
       * The default implementation performs the following steps:
       * <ul>
       *   <li>Return the cached instance for the given code if such instance 
already exists.</li>
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
index 0020f59393,c684889ab8..dec2641dbf
--- 
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
@@@ -42,11 -42,6 +42,12 @@@ import org.apache.sis.referencing.privy
  import org.apache.sis.util.iso.AbstractFactory;
  import org.apache.sis.util.resources.Errors;
  
 +// Specific to the main branch:
 +import org.apache.sis.referencing.cs.DefaultParametricCS;
 +import org.apache.sis.referencing.crs.DefaultParametricCRS;
++import org.apache.sis.referencing.datum.DefaultDatumEnsemble;
 +import org.apache.sis.referencing.datum.DefaultParametricDatum;
 +
  
  /**
   * Creates geodetic objects from codes defined by an authority.
@@@ -345,13 -326,9 +348,13 @@@ public abstract class GeodeticAuthority
       *   <tr><td>EPSG:4984</td> <td>World Geodetic System 1972</td></tr>
       * </table>
       *
 +     * <div class="warning"><b>Warning:</b> In a future SIS version, the 
return type may be changed to the
 +     * {@link GeodeticCRS} parent interface. This is because ISO 19111 does 
not defines specific interface
 +     * for the geocentric case. Users should assign the return value to a 
{@code GeodeticCRS} type.</div>
 +     *
       * <h4>Default implementation</h4>
       * The default implementation delegates to {@link 
#createCoordinateReferenceSystem(String)} and casts the result.
-      * If the result cannot be casted, then a {@link 
NoSuchAuthorityCodeException} is thrown.
+      * If the result cannot be cast, then a {@link 
NoSuchAuthorityCodeException} is thrown.
       *
       * @param  code  value allocated by authority.
       * @return the coordinate reference system for the given code.
@@@ -457,11 -452,8 +460,11 @@@
       *
       * <h4>Default implementation</h4>
       * The default implementation delegates to {@link 
#createCoordinateReferenceSystem(String)} and casts the result.
-      * If the result cannot be casted, then a {@link 
NoSuchAuthorityCodeException} is thrown.
+      * If the result cannot be cast, then a {@link 
NoSuchAuthorityCodeException} is thrown.
       *
 +     * <div class="warning"><b>Warning:</b> in a future SIS version, the 
return type may be changed
 +     * to {@code org.opengis.referencing.crs.ParametricCRS}. This change is 
pending GeoAPI revision.</div>
 +     *
       * @param  code  value allocated by authority.
       * @return the coordinate reference system for the given code.
       * @throws NoSuchAuthorityCodeException if the specified {@code code} was 
not found.
@@@ -575,6 -567,46 +578,50 @@@
          return cast(ImageCRS.class, createCoordinateReferenceSystem(code), 
code);
      }
  
+     /**
+      * Creates an arbitrary datum ensemble from a code.
+      * A datum ensemble is a collection of datums which for low accuracy 
requirements
+      * may be considered to be insignificantly different from each other.
+      *
+      * <h4>Examples</h4>
+      * The {@linkplain #getAuthorityCodes(Class) set of available codes} 
depends on the defining
+      * {@linkplain #getAuthority() authority} and the {@code 
GeodeticAuthorityFactory} subclass in use.
+      * A frequently used authority is "EPSG", which includes the following 
codes:
+      *
+      * <table class="sis">
+      * <caption>Authority codes examples</caption>
+      *   <tr><th>Code</th>      <th>Description</th></tr>
+      *   <tr><td>EPSG:6326</td> <td>World Geodetic System 1984</td></tr>
+      *   <tr><td>EPSG:6258</td> <td>European Terrestrial Reference System 
1989</td></tr>
+      * </table>
+      *
+      * <h4>Default implementation</h4>
+      * The default implementation delegates to {@link #createDatum(String)} 
and casts the result.
+      * If the result cannot be cast, then a {@link 
NoSuchAuthorityCodeException} is thrown.
+      * This approach assumes that the datum ensemble implements also the 
{@link Datum} interface.
+      * It is the case of the {@link 
org.apache.sis.referencing.datum.DefaultDatumEnsemble} class.
+      *
+      * <p>This default implementation is unusual, but is convenient for the 
implementation strategy
+      * of Apache <abbr>SIS</abbr> and for the structure of the 
<abbr>EPSG</abbr> geodetic dataset,
+      * which uses the same {@code "Datum"} table for storing the properties 
of the two kinds of object.</p>
+      *
++     * <div class="warning"><b>Upcoming API change — generalization</b><br>
++     * The element type will be changed to the {@code DatumEnsemble} interface
++     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).</div>
++     *
+      * @param  code  value allocated by authority.
+      * @return the datum ensemble for the given code.
+      * @throws NoSuchAuthorityCodeException if the specified {@code code} was 
not found.
+      * @throws FactoryException if the object creation failed for some other 
reason.
+      *
+      * @see org.apache.sis.referencing.datum.DefaultDatumEnsemble
+      *
+      * @since 1.5
+      */
 -    public DatumEnsemble<?> createDatumEnsemble(final String code) throws 
NoSuchAuthorityCodeException, FactoryException {
 -        return cast(DatumEnsemble.class, createDatum(code), code);
++    public DefaultDatumEnsemble<?> createDatumEnsemble(final String code) 
throws NoSuchAuthorityCodeException, FactoryException {
++        return cast(DefaultDatumEnsemble.class, createDatum(code), code);
+     }
+ 
      /**
       * Creates an arbitrary datum from a code. The returned object will 
typically be an
       * instance of {@link GeodeticDatum}, {@link VerticalDatum} or {@link 
TemporalDatum}.
@@@ -704,11 -746,8 +761,11 @@@
       *
       * <h4>Default implementation</h4>
       * The default implementation delegates to {@link #createDatum(String)} 
and casts the result.
-      * If the result cannot be casted, then a {@link 
NoSuchAuthorityCodeException} is thrown.
+      * If the result cannot be cast, then a {@link 
NoSuchAuthorityCodeException} is thrown.
       *
 +     * <div class="warning"><b>Warning:</b> in a future SIS version, the 
return type may be changed
 +     * to {@code org.opengis.referencing.datum.ParametricDatum}. This change 
is pending GeoAPI revision.</div>
 +     *
       * @param  code  value allocated by authority.
       * @return the datum for the given code.
       * @throws NoSuchAuthorityCodeException if the specified {@code code} was 
not found.
@@@ -997,11 -1036,8 +1054,11 @@@
       *
       * <h4>Default implementation</h4>
       * The default implementation delegates to {@link 
#createCoordinateSystem(String)} and casts the result.
-      * If the result cannot be casted, then a {@link 
NoSuchAuthorityCodeException} is thrown.
+      * If the result cannot be cast, then a {@link 
NoSuchAuthorityCodeException} is thrown.
       *
 +     * <div class="warning"><b>Warning:</b> in a future SIS version, the 
return type may be changed
 +     * to {@code org.opengis.referencing.cs.ParametricCS}. This change is 
pending GeoAPI revision.</div>
 +     *
       * @param  code  value allocated by authority.
       * @return the coordinate system for the given code.
       * @throws NoSuchAuthorityCodeException if the specified {@code code} was 
not found.
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
index fe2721d39f,9d7ef4775f..0797fabecc
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
@@@ -657,6 -632,6 +657,10 @@@ public class GeodeticObjectFactory exte
      /**
       * Creates a datum ensemble from a collection of members and an ensemble 
accuracy.
       *
++     * <div class="warning"><b>Upcoming API change — generalization</b><br>
++     * The element type will be changed to the {@code DatumEnsemble} interface
++     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).</div>
++     *
       * @param  <D>         the type of datum contained in the ensemble.
       * @param  properties  name and other properties to give to the new 
object.
       * @param  members     datum or reference frames which are members of the 
datum ensemble.
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/CoordinateOperationSet.java
index f7005d08db,be28e30680..72fca57f7c
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/CoordinateOperationSet.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/CoordinateOperationSet.java
@@@ -109,9 -110,10 +110,9 @@@ final class CoordinateOperationSet exte
      }
  
      /**
-      * Creates a coordinate operation for the specified EPSG code.
+      * Creates a coordinate operation for the specified <abbr>EPSG</abbr> 
code.
       */
      @Override
 -    @SuppressWarnings("deprecation")
      protected CoordinateOperation createObject(final String code) throws 
FactoryException {
          final Integer base = projections.get(code);
          if (base != null) {
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index 5bdef1b29d,cf8ecff2c0..8034b0bb4f
--- 
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
@@@ -87,8 -89,9 +89,8 @@@ import org.apache.sis.referencing.inter
  import org.apache.sis.referencing.internal.ParameterizedTransformBuilder;
  import org.apache.sis.referencing.internal.PositionalAccuracyConstant;
  import org.apache.sis.referencing.internal.SignReversalComment;
+ import org.apache.sis.referencing.internal.VerticalDatumTypes;
  import org.apache.sis.referencing.internal.Resources;
- import static 
org.apache.sis.referencing.internal.ServicesForMetadata.CONNECTION;
 -import org.apache.sis.referencing.internal.ServicesForMetadata;
  import org.apache.sis.parameter.DefaultParameterDescriptor;
  import org.apache.sis.parameter.DefaultParameterDescriptorGroup;
  import org.apache.sis.system.Loggers;
@@@ -121,12 -125,9 +124,14 @@@ import org.apache.sis.measure.NumberRan
  import org.apache.sis.measure.Units;
  import org.apache.sis.pending.jdk.JDK16;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.metadata.Identifier;
 -import org.opengis.referencing.ObjectDomain;
 +// Specific to the main branch:
++import org.opengis.referencing.ObjectFactory;
 +import org.apache.sis.referencing.cs.DefaultParametricCS;
++import org.apache.sis.referencing.datum.AbstractDatum;
 +import org.apache.sis.referencing.datum.DefaultParametricDatum;
 +import org.apache.sis.referencing.factory.GeodeticObjectFactory;
 +import org.apache.sis.referencing.internal.ServicesForMetadata;
 +import org.apache.sis.temporal.TemporalDate;
  
  
  /**
@@@ -1104,9 -1255,42 +1259,42 @@@ search: try (ResultSet result = execute
       * @return the name together with a set of properties.
       */
      @SuppressWarnings("ReturnOfCollectionOrArrayField")
-     private Map<String,Object> createProperties(final String table, String 
name, final Integer code,
-             CharSequence remarks, final boolean deprecated) throws 
SQLException, FactoryException
+     private Map<String,Object> createProperties(final String       table,
+                                                 final Integer      code,
+                                                       String       name,      
  // May be replaced by an alias.
+                                                 final CharSequence 
description,
+                                                 final String       
extentCode,  // Legacy from EPSG version 9.
+                                                 final String       scope,     
  // Legacy from EPSG version 9.
+                                                 final CharSequence remarks,
+                                                 final boolean      deprecated)
+             throws SQLException, FactoryException
      {
+         /*
+          * Request for extent may cause a recursive call to 
`createCoordinateReferenceSystem(…)`,
+          * se we need to fetch and store the extent before to populate the 
`properties` map.
+          */
+         final Extent extent = (extentCode == null) ? null : 
createExtent(extentCode);
+         final String actualTable = translator.toActualTableName(table);
+         /*
+          * Get all domains for the object identified by the given code.
+          * The table used nere is new in version 10 of EPSG database.
+          * We have to create the extents outside the `while` loop for
+          * the same reason as above for `extent`.
+          */
 -        ObjectDomain[] domains = null;
++        DefaultObjectDomain[] domains = null;
+         if (translator.isUsageTableFound()) {
+             final var extents = new ArrayList<String>();
+             final var scopes  = new ArrayList<Integer>();
+             getUsages(actualTable, code, extents, scopes);
+             if (!extents.isEmpty()) {
 -                domains = new ObjectDomain[extents.size()];
++                domains = new DefaultObjectDomain[extents.size()];
+                 for (int i=0; i<domains.length; i++) {
+                     domains[i] = new DefaultObjectDomain(
+                             getScope(scopes.get(i)),
+                             owner.createExtent(extents.get(i)));
+                 }
+             }
+         }
          /*
           * Search for aliases. Note that searching for the object code is not 
sufficient. We also need to check if the
           * record is really from the table we are looking for since different 
tables may have objects with the same ID.
@@@ -1188,34 -1379,15 +1383,15 @@@
          properties.put(IdentifiedObject.REMARKS_KEY, remarks);
          properties.put(AbstractIdentifiedObject.LOCALE_KEY, locale);
          properties.put(ReferencingFactoryContainer.MT_FACTORY, 
owner.mtFactory);
-         return properties;
-     }
- 
-     /**
-      * Returns the name, aliases and domain of validity for the {@link 
IdentifiedObject} to construct.
-      *
-      * @param  table       the table on which a query has been executed.
-      * @param  name        the name for the {@link IdentifiedObject} to 
construct.
-      * @param  code        the EPSG code of the object to construct.
-      * @param  domainCode  the code for the domain of validity, or {@code 
null} if none.
-      * @param  scope       the scope, or {@code null} if none.
-      * @param  remarks     remarks, or {@code null} if none.
-      * @param  deprecated  {@code true} if the object to create is deprecated.
-      * @return the name together with a set of properties.
-      */
-     private Map<String,Object> createProperties(final String table, final 
String name, final Integer code,
-             final String domainCode, String scope, final String remarks, 
final boolean deprecated)
-             throws SQLException, FactoryException
-     {
-         if ("?".equals(scope)) {                // EPSG sometimes uses this 
value for unspecified scope.
-             scope = null;
+         if (domains != null) {
 -            properties.put(IdentifiedObject.DOMAINS_KEY, domains);
++            properties.put(AbstractIdentifiedObject.DOMAINS_KEY, domains);
+         }
+         if (scope != null && !scope.equals(UNKNOWN_SCOPE)) {    // Should be 
always NULL since EPSG version 10.
 -            properties.put(ObjectDomain.SCOPE_KEY, scope);
++            properties.put(Datum.SCOPE_KEY, scope);
          }
-         @SuppressWarnings("LocalVariableHidesMemberVariable")
-         final Map<String,Object> properties = createProperties(table, name, 
code, remarks, deprecated);
-         if (domainCode != null) {
-             properties.put(Datum.DOMAIN_OF_VALIDITY_KEY, 
owner.createExtent(domainCode));
+         if (extent != null) {                                   // Should be 
always NULL since EPSG version 10.
 -            properties.put(ObjectDomain.DOMAIN_OF_VALIDITY_KEY, extent);
++            properties.put(Datum.DOMAIN_OF_VALIDITY_KEY, extent);
          }
-         properties.put(Datum.SCOPE_KEY, scope);
          return properties;
      }
  
@@@ -1312,6 -1486,50 +1490,59 @@@
          throw noSuchAuthorityCode(IdentifiedObject.class, code);
      }
  
+     /**
+      * The {@code CRSFactory.createFoo(…)} or {@code 
DatumFactory.createFoo(…)} method to invoke.
+      * This is a convenience used by some {@link EPSGDataAccess} {@code 
createFoo(String)} methods when
+      * the factory method to invoke has been decided but the properties map 
has not yet been populated.
+      *
+      * @see Proxy
+      */
+     @FunctionalInterface
+     private interface FactoryCall<F extends Factory, R extends 
IdentifiedObject> {
+         /**
+          * Creates a <abbr>CRS</abbr> or datum.
+          *
+          * @param  factory     the factory to use for creating the object.
+          * @param  properties  the properties to give to the object.
+          * @return the object created from the given properties.
+          * @throws FactoryException if the factory cannot create the object.
+          */
+         R create(F factory, Map<String, Object> properties) throws 
FactoryException;
+     }
+ 
+     /**
+      * Invokes the {@code create(…)} method of the given constructor in a 
block which ensures that recursive method
+      * calls will be redirected to this factory. This is needed only when the 
constructor may indirectly invoke the
+      * {@link org.apache.sis.referencing.CRS#getAuthorityFactory(String)} 
method.
+      */
+     private <F extends Factory, R extends IdentifiedObject> R create(
+             final FactoryCall<F, R> constructor,
+             final F factory,
+             final Map<String, Object> properties) throws FactoryException
+     {
+         final ThreadLocal<CRSAuthorityFactory> caller = 
ParameterizedTransformBuilder.CREATOR;
+         final CRSAuthorityFactory old = caller.get();
+         caller.set(owner);
+         try {
+             return constructor.create(factory, properties);
+         } finally {
+             if (old != null) {
+                 caller.set(old);
+             } else {
+                 caller.remove();
+             }
+         }
+     }
+ 
++    /**
++     * Returns the geodetic factory to use by casting the given one if 
possible.
++     */
++    private static GeodeticObjectFactory extended(final ObjectFactory 
factory) {
++        return (factory instanceof GeodeticObjectFactory)
++                ? (GeodeticObjectFactory) factory
++                : GeodeticObjectFactory.provider();
++    }
++
      /**
       * Creates an arbitrary coordinate reference system from a code.
       * The returned object will typically be an instance of {@link 
GeographicCRS}, {@link ProjectedCRS},
@@@ -1376,97 -1600,109 +1613,115 @@@
                   * The following switch statement should have a case for all 
"epsg_crs_kind" values enumerated
                   * in the "EPSG_Prepare.sql" file, except that the values in 
this Java code are in lower cases.
                   */
-                 final CRSFactory crsFactory = owner.crsFactory;
-                 final CoordinateReferenceSystem crs;
                  switch (type.toLowerCase(Locale.US)) {
-                     /* 
----------------------------------------------------------------------
-                      *   GEOGRAPHIC CRS
+                     /* 
──────────────────────────────────────────────────────────────────────
+                      *   GEOCENTRIC CRS
                       *
-                      *   NOTE: `createProperties` MUST be invoked after any 
call to another
-                      *         `createFoo` method. Consequently, do not 
factor out.
-                      * 
---------------------------------------------------------------------- */
+                      *   NOTE: all values must be extracted from the 
`ResultSet`
+                      *         before to invoke any `owner.createFoo(…)` 
method.
+                      * 
────────────────────────────────────────────────────────────────────── */
+                     case "geocentric": {
+                         final String csCode    = getString(code, result, 8);
+                         final String datumCode = getString(code, result, 9);
+                         final CoordinateSystem cs = 
owner.createCoordinateSystem(csCode);  // Do not inline the `getString(…)` 
calls.
+                         final GeodeticDatum datumOrEnsemble = 
owner.createGeodeticDatum(datumCode);
 -                        final DatumEnsemble<GeodeticDatum> ensemble = 
wasDatumEnsemble(datumOrEnsemble, GeodeticDatum.class);
++                        final DefaultDatumEnsemble<GeodeticDatum> ensemble = 
wasDatumEnsemble(datumOrEnsemble, GeodeticDatum.class);
+                         final GeodeticDatum datum = (ensemble == null) ? 
datumOrEnsemble : null;
+                         if (cs instanceof CartesianCS) {
+                             final var c = (CartesianCS) cs;
 -                            constructor = (factory, metadata) -> 
factory.createGeodeticCRS(metadata, datum, ensemble, c);
++                            constructor = (factory, metadata) ->
++                                    (ensemble != null) ? 
extended(factory).createGeodeticCRS(metadata, datum, ensemble, c)
++                                                       : 
factory.createGeocentricCRS(metadata, datum, c);
+                         } else if (cs instanceof SphericalCS) {
+                             final var c = (SphericalCS) cs;
 -                            constructor = (factory, metadata) -> 
factory.createGeodeticCRS(metadata, datum, ensemble, c);
++                            constructor = (factory, metadata) ->
++                                    (ensemble != null) ? 
extended(factory).createGeodeticCRS(metadata, datum, ensemble, c)
++                                                       : 
factory.createGeocentricCRS(metadata, datum, c);
+                         } else {
+                             throw new FactoryDataException(error().getString(
+                                     Errors.Keys.IllegalCoordinateSystem_1, 
cs.getName()));
+                         }
+                         break;
+                     }
+                     /* 
──────────────────────────────────────────────────────────────────────
+                      *   GEOGRAPHIC CRS
+                      * 
────────────────────────────────────────────────────────────────────── */
                      case "geographic 2d":
                      case "geographic 3d": {
-                         Integer csCode = getInteger(code, result, 8);
+                         Integer csCode    = getInteger(code,  result, 8);
+                         String  datumCode = getOptionalString(result, 9);
+                         final GeodeticDatum datumOrEnsemble;
+                         if (datumCode == null) {
+                             String baseCode = getString(code, result, 10, 9);
+                             datumOrEnsemble = 
owner.createGeographicCRS(baseCode).getDatum();
+                         } else {
+                             datumOrEnsemble = 
owner.createGeodeticDatum(datumCode);
+                         }
                          if (replaceDeprecatedCS) {
-                             csCode = DEPRECATED_CS.getOrDefault(csCode, 
csCode);
+                             csCode = replaceDeprecatedCS(csCode);
                          }
                          final EllipsoidalCS cs = 
owner.createEllipsoidalCS(csCode.toString());
-                         final String datumCode = getOptionalString(result, 9);
-                         final GeodeticDatum datum;
-                         if (datumCode != null) {
-                             datum = owner.createGeodeticDatum(datumCode);
-                         } else {
-                             final String geoCode = getString(code, result, 
10, 9);
-                             result.close();     // Must be closed before call 
to createGeographicCRS(String)
-                             ensureNoCycle(GeographicCRS.class, epsg);
-                             try {
-                                 datum = 
owner.createGeographicCRS(geoCode).getDatum();
-                             } finally {
-                                 endOfRecursion(GeographicCRS.class, epsg);
-                             }
-                         }
-                         crs = 
crsFactory.createGeographicCRS(createProperties("Coordinate Reference System",
-                                 name, epsg, area, scope, remarks, 
deprecated), datum, cs);
 -                        final DatumEnsemble<GeodeticDatum> ensemble = 
wasDatumEnsemble(datumOrEnsemble, GeodeticDatum.class);
++                        final DefaultDatumEnsemble<GeodeticDatum> ensemble = 
wasDatumEnsemble(datumOrEnsemble, GeodeticDatum.class);
+                         final GeodeticDatum datum = (ensemble == null) ? 
datumOrEnsemble : null;
 -                        constructor = (factory, metadata) -> 
factory.createGeographicCRS(metadata, datum, ensemble, cs);
++                        constructor = (factory, metadata) ->
++                                (ensemble != null) ? 
extended(factory).createGeographicCRS(metadata, datum, ensemble, cs)
++                                                   : 
factory.createGeographicCRS(metadata, datum, cs);
                          break;
                      }
-                     /* 
----------------------------------------------------------------------
+                     /* 
──────────────────────────────────────────────────────────────────────
                       *   PROJECTED CRS
                       *
-                      *   NOTE: This method invokes itself indirectly, through 
createGeographicCRS.
-                      *         Consequently, we cannot use `result` anymore 
after this block.
-                      * 
---------------------------------------------------------------------- */
+                      *   NOTE: This method may invoke itself for creating the 
base CRS,
+                      *         in which case the `ResultSet` will be closed. 
Therefore,
+                      *         all values must be extracted from the 
`ResultSet` before
+                      *         to invoke any `owner.createFoo(…)` method.
+                      * 
────────────────────────────────────────────────────────────────────── */
                      case "projected": {
-                         final String csCode  = getString(code, result,  8);
-                         final String geoCode = getString(code, result, 10);
-                         final String opCode  = getString(code, result, 11);
-                         result.close();      // Must be closed before call to 
createFoo(String)
-                         ensureNoCycle(ProjectedCRS.class, epsg);
+                         final String csCode   = getString(code, result,  8);
+                         final String baseCode = getString(code, result, 10);
+                         final String opCode   = getString(code, result, 11);
+                         final Conversion fromBase;
                          try {
-                             final CartesianCS cs = 
owner.createCartesianCS(csCode);
-                             final Conversion op;
+                             fromBase = (Conversion) 
owner.createCoordinateOperation(opCode);
+                         } catch (ClassCastException e) {
+                             // Should never happen in a well-formed EPSG 
database.
+                             // If happen anyway, the ClassCastException cause 
will give more hints than just the message.
+                             throw (NoSuchAuthorityCodeException) 
noSuchAuthorityCode(Conversion.class, opCode).initCause(e);
+                         }
+                         final CoordinateReferenceSystem baseCRS;
+                         if (deprecated) {
+                             /*
+                              * If the ProjectedCRS is deprecated, one reason 
among others may be that it uses one of
+                              * the deprecated coordinate systems. Those 
deprecated CS used non-linear units like DMS.
+                              * Apache SIS cannot instantiate a ProjectedCRS 
when the baseCRS uses such units, so we
+                              * set a flag asking to replace the deprecated CS 
by a supported one. Since that baseCRS
+                              * would not be exactly as defined by EPSG, we 
must not cache it because we do not want
+                              * `owner.createGeographicCRS(geoCode)` to return 
that modified CRS. Since the same CRS
+                              * may be recreated every time a deprecated 
ProjectedCRS is created, we temporarily
+                              * shutdown the loggings in order to avoid the 
same warning to be logged many time.
+                              */
+                             final boolean old = quiet;
                              try {
-                                 op = (Conversion) 
owner.createCoordinateOperation(opCode);
-                             } catch (ClassCastException e) {
-                                 // Should never happen in a well-formed EPSG 
database.
-                                 // If happen anyway, the ClassCastException 
cause will give more hints than just the message.
-                                 throw (NoSuchAuthorityCodeException) 
noSuchAuthorityCode(Conversion.class, opCode).initCause(e);
-                             }
-                             final CoordinateReferenceSystem baseCRS;
-                             final boolean suspendParamChecks;
-                             if (!deprecated) {
-                                 baseCRS = 
owner.createCoordinateReferenceSystem(geoCode);
-                                 suspendParamChecks = true;
-                             } else {
-                                 /*
-                                  * If the ProjectedCRS is deprecated, one 
reason among others may be that it uses one of
-                                  * the deprecated coordinate systems. Those 
deprecated CS used non-linear units like DMS.
-                                  * Apache SIS cannot instantiate a 
ProjectedCRS when the baseCRS uses such units, so we
-                                  * set a flag asking to replace the 
deprecated CS by a supported one. Since that baseCRS
-                                  * would not be exactly as defined by EPSG, 
we must not cache it because we do not want
-                                  * `owner.createGeographicCRS(geoCode)` to 
return that modified CRS. Since the same CRS
-                                  * may be recreated every time a deprecated 
ProjectedCRS is created, we temporarily
-                                  * shutdown the loggings in order to avoid 
the same warning to be logged many time.
-                                  */
-                                 final boolean old = quiet;
-                                 try {
-                                     quiet = true;
-                                     replaceDeprecatedCS = true;
-                                     baseCRS = 
createCoordinateReferenceSystem(geoCode);         // Do not cache that CRS.
-                                 } finally {
-                                     replaceDeprecatedCS = false;
-                                     quiet = old;
-                                 }
-                                 /*
-                                  * The crsFactory method calls will 
indirectly create a parameterized MathTransform.
-                                  * Their constructor will try to verify the 
parameter validity. But some deprecated
-                                  * CRS had invalid parameter values (they 
were deprecated precisely for that reason).
-                                  * If and only if we are creating a 
deprecated CRS, temporarily suspend the parameter
-                                  * checks.
-                                  */
-                                 suspendParamChecks = 
Semaphores.queryAndSet(Semaphores.SUSPEND_PARAMETER_CHECK);
-                                 // Try block must be immediately after above 
line (do not insert any code between).
+                                 quiet = true;
+                                 replaceDeprecatedCS = true;
+                                 baseCRS = 
createCoordinateReferenceSystem(baseCode);         // Do not cache that CRS.
+                             } finally {
+                                 replaceDeprecatedCS = false;
+                                 quiet = old;
                              }
+                         } else {
+                             baseCRS = 
owner.createCoordinateReferenceSystem(baseCode);      // Use the cache.
+                         }
+                         final CartesianCS cs = 
owner.createCartesianCS(csCode);
+                         constructor = (factory, metadata) -> {
+                             /*
+                              * The crsFactory method calls will indirectly 
create a parameterized MathTransform.
+                              * Their constructor will try to verify the 
parameter validity. But some deprecated
+                              * CRS had invalid parameter values (they were 
deprecated precisely for that reason).
+                              * If and only if we are creating a deprecated 
CRS, temporarily suspend the parameter
+                              * checks.
+                              */
+                             final boolean old = !deprecated || 
Semaphores.queryAndSet(Semaphores.SUSPEND_PARAMETER_CHECK);
                              try {
                                  /*
                                   * For a ProjectedCRS, the baseCRS is always 
geodetic. So in theory we would not
@@@ -1479,47 -1715,74 +1734,80 @@@
                                   * We need to check if EPSG database 10+ has 
more specific information.
                                   * See 
https://issues.apache.org/jira/browse/SIS-518
                                   */
-                                 
@SuppressWarnings("LocalVariableHidesMemberVariable")
-                                 final Map<String,Object> properties = 
createProperties("Coordinate Reference System",
-                                                                         name, 
epsg, area, scope, remarks, deprecated);
                                  if (baseCRS instanceof GeographicCRS) {
-                                     crs = 
crsFactory.createProjectedCRS(properties, (GeographicCRS) baseCRS, op, cs);
+                                     return 
factory.createProjectedCRS(metadata, (GeographicCRS) baseCRS, fromBase, cs);
                                  } else {
-                                     crs = 
crsFactory.createDerivedCRS(properties, baseCRS, op, cs);
+                                     return factory.createDerivedCRS(metadata, 
baseCRS, fromBase, cs);
                                  }
                              } finally {
-                                 
Semaphores.clear(Semaphores.SUSPEND_PARAMETER_CHECK, suspendParamChecks);
+                                 
Semaphores.clearIfFalse(Semaphores.SUSPEND_PARAMETER_CHECK, old);
                              }
-                         } finally {
-                             endOfRecursion(ProjectedCRS.class, epsg);
-                         }
+                         };
                          break;
                      }
-                     /* 
----------------------------------------------------------------------
+                     /* 
──────────────────────────────────────────────────────────────────────
                       *   VERTICAL CRS
-                      * 
---------------------------------------------------------------------- */
+                      * 
────────────────────────────────────────────────────────────────────── */
                      case "vertical": {
-                         final VerticalCS    cs    = owner.createVerticalCS   
(getString(code, result, 8));
-                         final VerticalDatum datum = 
owner.createVerticalDatum(getString(code, result, 9));
-                         crs = 
crsFactory.createVerticalCRS(createProperties("Coordinate Reference System",
-                                 name, epsg, area, scope, remarks, 
deprecated), datum, cs);
+                         final String csCode    = getString(code, result, 8);
+                         final String datumCode = getString(code, result, 9);
+                         final VerticalCS cs = owner.createVerticalCS(csCode); 
  // Do not inline the `getString(…)` calls.
+                         final VerticalDatum datumOrEnsemble = 
owner.createVerticalDatum(datumCode);
 -                        final DatumEnsemble<VerticalDatum> ensemble = 
wasDatumEnsemble(datumOrEnsemble, VerticalDatum.class);
++                        final DefaultDatumEnsemble<VerticalDatum> ensemble = 
wasDatumEnsemble(datumOrEnsemble, VerticalDatum.class);
+                         final VerticalDatum datum = (ensemble == null) ? 
datumOrEnsemble : null;
 -                        constructor = (factory, metadata) -> 
factory.createVerticalCRS(metadata, datum, ensemble, cs);
++                        constructor = (factory, metadata) ->
++                                (ensemble != null) ? 
extended(factory).createVerticalCRS(metadata, datum, ensemble, cs)
++                                                   : 
factory.createVerticalCRS(metadata, datum, cs);
                          break;
                      }
-                     /* 
----------------------------------------------------------------------
+                     /* 
──────────────────────────────────────────────────────────────────────
                       *   TEMPORAL CRS
                       *
-                      *   NOTE : The original EPSG database does not define 
any temporal CRS.
-                      *          This block is a SIS-specific extension.
-                      * 
---------------------------------------------------------------------- */
+                      *   NOTE : As of version 12, the EPSG database does not 
define temporal CRS.
+                      *          This block is a SIS─specific extension.
+                      * 
────────────────────────────────────────────────────────────────────── */
                      case "time":
                      case "temporal": {
-                         final TimeCS        cs    = owner.createTimeCS       
(getString(code, result, 8));
-                         final TemporalDatum datum = 
owner.createTemporalDatum(getString(code, result, 9));
-                         crs = 
crsFactory.createTemporalCRS(createProperties("Coordinate Reference System",
-                                 name, epsg, area, scope, remarks, 
deprecated), datum, cs);
+                         final String csCode    = getString(code, result, 8);
+                         final String datumCode = getString(code, result, 9);
+                         final TimeCS cs = owner.createTimeCS(csCode);    // 
Do not inline the `getString(…)` calls.
+                         final TemporalDatum datumOrEnsemble = 
owner.createTemporalDatum(datumCode);
 -                        final DatumEnsemble<TemporalDatum> ensemble = 
wasDatumEnsemble(datumOrEnsemble, TemporalDatum.class);
++                        final DefaultDatumEnsemble<TemporalDatum> ensemble = 
wasDatumEnsemble(datumOrEnsemble, TemporalDatum.class);
+                         final TemporalDatum datum = (ensemble == null) ? 
datumOrEnsemble : null;
 -                        constructor = (factory, metadata) -> 
factory.createTemporalCRS(metadata, datum, ensemble, cs);
++                        constructor = (factory, metadata) ->
++                                (ensemble != null) ? 
extended(factory).createTemporalCRS(metadata, datum, ensemble, cs)
++                                                   : 
factory.createTemporalCRS(metadata, datum, cs);
+                         break;
+                     }
+                     /* 
──────────────────────────────────────────────────────────────────────
+                      *   ENGINEERING CRS
+                      * 
────────────────────────────────────────────────────────────────────── */
+                     case "engineering": {
+                         final String csCode    = getString(code, result, 8);
+                         final String datumCode = getString(code, result, 9);
+                         final CoordinateSystem cs = 
owner.createCoordinateSystem(csCode);    // Do not inline the `getString(…)` 
calls.
+                         final EngineeringDatum datumOrEnsemble = 
owner.createEngineeringDatum(datumCode);
 -                        final DatumEnsemble<EngineeringDatum> ensemble = 
wasDatumEnsemble(datumOrEnsemble, EngineeringDatum.class);
++                        final DefaultDatumEnsemble<EngineeringDatum> ensemble 
= wasDatumEnsemble(datumOrEnsemble, EngineeringDatum.class);
+                         final EngineeringDatum datum = (ensemble == null) ? 
datumOrEnsemble : null;
 -                        constructor = (factory, metadata) -> 
factory.createEngineeringCRS(metadata, datum, ensemble, cs);
++                        constructor = (factory, metadata) ->
++                                (ensemble != null) ? 
extended(factory).createEngineeringCRS(metadata, datum, ensemble, cs)
++                                                   : 
factory.createEngineeringCRS(metadata, datum, cs);
                          break;
                      }
-                     /* 
----------------------------------------------------------------------
+                     /* 
──────────────────────────────────────────────────────────────────────
+                      *   PARAMETRIC CRS
+                      * 
────────────────────────────────────────────────────────────────────── */
+                     case "parametric": {
+                         final String csCode    = getString(code, result, 8);
+                         final String datumCode = getString(code, result, 9);
 -                        final ParametricCS cs = 
owner.createParametricCS(csCode);    // Do not inline the `getString(…)` calls.
 -                        final ParametricDatum datumOrEnsemble = 
owner.createParametricDatum(datumCode);
 -                        final DatumEnsemble<ParametricDatum> ensemble = 
wasDatumEnsemble(datumOrEnsemble, ParametricDatum.class);
 -                        final ParametricDatum datum = (ensemble == null) ? 
datumOrEnsemble : null;
 -                        constructor = (factory, metadata) -> 
factory.createParametricCRS(metadata, datum, ensemble, cs);
++                        final DefaultParametricCS cs = 
owner.createParametricCS(csCode);    // Do not inline the `getString(…)` calls.
++                        final DefaultParametricDatum datumOrEnsemble = 
owner.createParametricDatum(datumCode);
++                        final DefaultDatumEnsemble<DefaultParametricDatum> 
ensemble = wasDatumEnsemble(datumOrEnsemble, DefaultParametricDatum.class);
++                        final DefaultParametricDatum datum = (ensemble == 
null) ? datumOrEnsemble : null;
++                        constructor = (factory, metadata) -> 
extended(factory).createParametricCRS(metadata, datum, ensemble, cs);
+                         break;
+                     }
+                     /* 
──────────────────────────────────────────────────────────────────────
                       *   COMPOUND CRS
                       *
                       *   NOTE: This method invokes itself recursively.
@@@ -1602,6 -1827,29 +1852,29 @@@
          return returnValue;
      }
  
+     /**
+      * Returns the given datum as a datum ensemble if it should be considered 
as such.
+      * This method exists because the datum and datum ensemble are stored in 
the same table,
+      * and Apache <abbr>SIS</abbr> creates those two kinds of objects with 
the same method.
+      * The real type is resolved by inspection of the {@link 
#createDatum(String)} return value.
+      *
+      * <h4>Design restriction</h4>
+      * We cannot resolve the type with a private field which would be set by 
{@link #createDatumEnsemble(String)}
+      * because that method will not be invoked if the datum is fetched from 
the cache.
+      *
+      * @param  <D>         compile-time value of {@code memberType}.
+      * @param  datum       the datum to check if it is a datum ensemble.
+      * @param  memberType  the expected type of datum members.
+      * @return the given datum as an ensemble if it should be considered as 
such, or {@code null} otherwise.
+      * @throws ClassCastException if at least one member is not an instance 
of the specified type.
+      */
 -    private static <D extends Datum> DatumEnsemble<D> wasDatumEnsemble(final 
D datum, final Class<D> memberType) {
 -        if (datum instanceof DatumEnsemble<?>) {
 -            return DefaultDatumEnsemble.castOrCopy((DatumEnsemble<?>) 
datum).cast(memberType);
++    private static <D extends Datum> DefaultDatumEnsemble<D> 
wasDatumEnsemble(final D datum, final Class<D> memberType) {
++        if (datum instanceof DefaultDatumEnsemble<?>) {
++            return ((DefaultDatumEnsemble<?>) datum).cast(memberType);
+         }
+         return null;
+     }
+ 
      /**
       * Creates an arbitrary datum from a code. The returned object will 
typically be an
       * instance of {@link GeodeticDatum}, {@link VerticalDatum} or {@link 
TemporalDatum}.
@@@ -1689,27 -1922,23 +1947,23 @@@
                   * The following switch statement should have a case for all 
"epsg_datum_kind" values enumerated
                   * in the "EPSG_Prepare.sql" file, except that the values in 
this Java code are in lower cases.
                   */
-                 final DatumFactory datumFactory = owner.datumFactory;
-                 final Datum datum;
                  switch (type.toLowerCase(Locale.US)) {
-                     /*
-                      * The "geodetic" case invokes createProperties(…) 
indirectly through calls to
-                      * createEllipsoid(String) and 
createPrimeMeridian(String), so we must protect
-                      * the properties map from changes.
-                      */
+                     case "dynamic geodetic":
                      case "geodetic": {
-                         properties = new HashMap<>(properties);         // 
Protect from changes
-                         final Ellipsoid ellipsoid    = owner.createEllipsoid  
  (getString(code, result, 10));
-                         final PrimeMeridian meridian = 
owner.createPrimeMeridian(getString(code, result, 11));
-                         final BursaWolfParameters[] param = 
createBursaWolfParameters(meridian, epsg);
-                         if (param != null) {
-                             
properties.put(DefaultGeodeticDatum.BURSA_WOLF_KEY, param);
-                         }
-                         datum = datumFactory.createGeodeticDatum(properties, 
ellipsoid, meridian);
+                         final String ellipsoidCode   = getString(code, 
result, 12);
+                         final String meridianCode    = getString(code, 
result, 13);
+                         final Ellipsoid ellipsoid    = 
owner.createEllipsoid(ellipsoidCode);  // Do not inline the `getString(…)` 
calls.
+                         final PrimeMeridian meridian = 
owner.createPrimeMeridian(meridianCode);
+                         constructor = (factory, metadata) ->
 -                                (dynamic != null) ? 
factory.createGeodeticDatum(metadata, ellipsoid, meridian, dynamic)
++                                (dynamic != null) ? 
extended(factory).createGeodeticDatum(metadata, ellipsoid, meridian, dynamic)
+                                                   : 
factory.createGeodeticDatum(metadata, ellipsoid, meridian);
                          break;
                      }
                      case "vertical": {
-                         datum = datumFactory.createVerticalDatum(properties, 
VerticalDatumType.GEOIDAL);
 -                        final RealizationMethod method = 
getRealizationMethod(getOptionalInteger(result, 14));
++                        final VerticalDatumType method = 
getRealizationMethod(getOptionalInteger(result, 14));
+                         constructor = (factory, metadata) ->
 -                                (dynamic != null) ? 
factory.createVerticalDatum(metadata, method, dynamic)
++                                (dynamic != null) ? 
extended(factory).createVerticalDatum(metadata, method, dynamic)
+                                                   : 
factory.createVerticalDatum(metadata, method);
                          break;
                      }
                      /*
@@@ -1726,28 -1955,33 +1980,33 @@@
                          } catch (RuntimeException e) {
                              throw new 
FactoryDataException(resources().getString(Resources.Keys.DatumOriginShallBeDate),
 e);
                          }
-                         datum = datumFactory.createTemporalDatum(properties, 
TemporalDate.toDate(originDate));
 -                        constructor = (factory, metadata) -> 
factory.createTemporalDatum(metadata, originDate);
++                        constructor = (factory, metadata) -> 
factory.createTemporalDatum(metadata, TemporalDate.toDate(originDate));
                          break;
                      }
                      /*
-                      * Straightforward case.
+                      * Straightforward cases.
                       */
-                     case "engineering": {
-                         datum = 
datumFactory.createEngineeringDatum(properties);
-                         break;
-                     }
-                     case "parametric": {
-                         datum = 
ServicesForMetadata.createParametricDatum(properties, datumFactory);
-                         break;
-                     }
-                     default: {
-                         throw new 
FactoryDataException(error().getString(Errors.Keys.UnknownType_1, type));
-                     }
+                     case "engineering": constructor = 
DatumFactory::createEngineeringDatum; break;
 -                    case "parametric":  constructor = 
DatumFactory::createParametricDatum;  break;
++                    case "parametric":  constructor = (factory, metadata) -> 
extended(factory).createParametricDatum(metadata);  break;
+                     case "ensemble":    constructor = 
createDatumEnsemble(epsg); break;
+                     default: throw new 
FactoryDataException(error().getString(Errors.Keys.UnknownType_1, type));
                  }
+                 /*
+                  * Map of properties should be populated only after we 
extracted all
+                  * information needed from the `ResultSet`, because it may be 
closed.
+                  */
+                 final IdentifiedObject conventionalRS = 
createConventionalRS(convRSCode);
+                 @SuppressWarnings("LocalVariableHidesMemberVariable")
+                 final Map<String,Object> properties = createProperties(
+                         "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);
-                 if (result.isClosed()) {
-                     break;                  // Because of the recursive call 
done by createBursaWolfParameters(…).
-                 }
+                 if (result.isClosed()) break;   // See createProperties(…) 
for explanation.
              }
          } catch (SQLException exception) {
              throw databaseFailure(Datum.class, code, exception);
@@@ -1807,71 -2023,115 +2048,115 @@@
                  }
              }
          }
-         int size = bwInfos.size();
-         if (size == 0) {
-             return null;
+         final var accuracy = PositionalAccuracyConstant.ensemble(max);
+         final List<Datum> members = createComponents(
+                 GeodeticAuthorityFactory::createDatum,
+                 "DatumEnsembleMember",
+                 "SELECT DATUM_CODE"
+                         + " FROM \"DatumEnsembleMember\""
+                         + " WHERE DATUM_ENSEMBLE_CODE = ?"
+                         + " ORDER BY DATUM_SEQUENCE", code);
 -        return (factory, metadata) -> 
DefaultDatumEnsemble.castOrCopy(factory.createDatumEnsemble(metadata, members, 
accuracy));
++        return (factory, metadata) -> 
extended(factory).createDatumEnsemble(metadata, members, accuracy);
+     }
+ 
+     /**
+      * Creates the members of a geodetic object. This method gets all member 
codes and closes the
+      * result set before to create the members, because the creation of a 
member causes recursive
+      * invocation to some {@code create(…)} methods of this factory.
+      *
+      * @param  <C>          the type of component objects.
+      * @param  constructor  the method to invoke for creating the components.
+      * @param  table        a key uniquely identifying the caller.
+      * @param  sql          the SQL statement to use for creating the {@link 
PreparedStatement} object.
+      * @param  parent       the code of the container object.
+      * @return all components for the given parent.
+      */
+     private <C extends IdentifiedObject> List<C> createComponents(final 
Proxy<C> constructor,
+                                                                   final 
String   table,
+                                                                   final 
String   sql,
+                                                                   final 
Integer  parent)
+             throws SQLException, FactoryException
+     {
+         final var codes = new ArrayList<String>();
+         try (ResultSet result = executeQueryForCodes(table, sql, parent)) {
+             while (result.next()) {
+                 codes.add(getString(parent, result, 1));
+             }
          }
-         /*
-          * Sort the infos in preference order. The "ORDER BY" clause above 
was not enough;
-          * we also need to take the "Supersession" table in account. Once the 
sorting is done,
-          * keep only one Bursa-Wolf parameters for each datum.
-          */
-         if (size > 1) {
-             final BursaWolfInfo[] codes = bwInfos.toArray(new 
BursaWolfInfo[size]);
-             sort("Coordinate_Operation", codes);
-             bwInfos.clear();
-             BursaWolfInfo.filter(owner, codes, bwInfos);
-             size = bwInfos.size();
+         final var members = new ArrayList<C>(codes.size());
+         for (String code : codes) {
+             members.add(constructor.create(owner, code));
          }
-         /*
-          * Now, iterate over the results and fetch the parameter values for 
each BursaWolfParameters object.
+         return members;
+     }
+ 
+     /**
+      * Delegates object creations to one of the {@code create(…)} methods. 
This is used for creating
+      * the components of a geodetic object. Invoking the {@code create(…)} 
method of this interface
+      * will often result in invocation of a public {@code create(…)} method 
of the enclosing class.
+      *
+      * @param  <C>  the type of geodetic objects to create.
+      *
+      * @see FactoryCall
+      * @see org.apache.sis.referencing.factory.AuthorityFactoryProxy
+      */
+     @FunctionalInterface
+     private interface Proxy<C extends IdentifiedObject> {
+         /**
+          * Creates a component from the given code by delegating
+          * (indirectly) to a method of the enclosing class.
+          *
+          * @param  factory  the factory to use for creating the component.
+          * @param  code     authority code of the component to create.
+          * @return the component created from the given code.
+          * @throws FactoryException if an error occurred while creating the 
component.
           */
-         final var parameters = new BursaWolfParameters[size];
-         final Locale locale = getLocale();
-         int count = 0;
-         for (int i=0; i<size; i++) {
-             final BursaWolfInfo info = bwInfos.get(i);
-             final GeodeticDatum datum;
-             ensureNoCycle(BursaWolfParameters.class, code);    // See comment 
at the begining of this method.
-             try {
-                 datum = 
owner.createGeodeticDatum(String.valueOf(info.target));
-             } finally {
-                 endOfRecursion(BursaWolfParameters.class, code);
-             }
-             /*
-              * Accept only Bursa-Wolf parameters between datum that use the 
same prime meridian.
-              * This is for avoiding ambiguity about whether longitude 
rotation should be applied
-              * before or after the datum change. This check is useless for 
EPSG dataset 8.9 since
-              * all datum seen by this method use Greenwich. But we 
nevertheless perform this check
-              * as a safety for future evolution or customized EPSG dataset.
-              */
-             if (!equalsIgnoreMetadata(meridian, datum.getPrimeMeridian())) {
-                 continue;
-             }
-             final var bwp = new BursaWolfParameters(datum, 
info.getDomainOfValidity(owner));
-             try (ResultSet result = executeQuery("BursaWolfParameters",
-                 "SELECT PARAMETER_CODE," +
-                       " PARAMETER_VALUE," +
-                       " UOM_CODE" +
-                 " FROM \"Coordinate_Operation Parameter Value\"" +
-                 " WHERE COORD_OP_CODE = ?" +
-                   " AND COORD_OP_METHOD_CODE = ?", info.operation, 
info.method))
+         C create(GeodeticAuthorityFactory factory, String code) throws 
FactoryException;
+     }
+ 
+     /**
+      * Creates a conventional reference system from a code.
+      * All members of a datum ensemble shall have the same conventional 
reference system.
+      *
+      * @param  code  value allocated by EPSG, or {@code null} if none.
+      * @return the datum for the given code, or {@code null} if not found.
+      */
+     private IdentifiedObject createConventionalRS(final Integer code) throws 
SQLException, FactoryException {
+         assert Thread.holdsLock(this);
+         if (code == null) {
+             return null;
+         }
+         final Long cacheKey = cacheKey(4, code);
+         var returnValue = (IdentifiedObject) localCache.get(cacheKey);
+         if (returnValue == null) {
+             try (ResultSet result = executeQueryForCodes(
+                     "ConventionalRS",
+                     "SELECT"+ /* column 1 */ " CONVENTIONAL_RS_CODE,"
+                             + /* column 2 */ " CONVENTIONAL_RS_NAME,"
+                             + /* column 3 */ " REMARKS,"
+                             + /* column 4 */ " DEPRECATED"
+                             + " FROM \"ConventionalRS\""
+                             + " WHERE CONVENTIONAL_RS_CODE = ?", code))
              {
                  while (result.next()) {
-                     BursaWolfInfo.setBursaWolfParameter(bwp,
-                             getInteger(info.operation, result, 1),
-                             getDouble (info.operation, result, 2),
-                             owner.createUnit(getString(info.operation, 
result, 3)), locale);
+                     final Integer epsg       = getInteger   (code, result, 1);
+                     final String  name       = getString    (code, result, 2);
+                     final String  remarks    = getOptionalString  (result, 3);
+                     final boolean deprecated = getOptionalBoolean (result, 4);
+                     /*
+                      * Map of properties should be populated only after we 
extracted all
+                      * information needed from the `ResultSet`, because it 
may be closed.
+                      */
+                     @SuppressWarnings("LocalVariableHidesMemberVariable")
+                     final Map<String,Object> properties = createProperties(
+                             "ConventionalRS", epsg, name, null, null, null, 
remarks, deprecated);
+                     returnValue = ensureSingleton(new 
AbstractIdentifiedObject(properties), returnValue, code);
+                     if (result.isClosed()) break;   // See 
createProperties(…) for explanation.
                  }
              }
-             if (info.isFrameRotation()) {
-                 // Coordinate frame rotation (9607): same as 9606,
-                 // except for the sign of rotation parameters.
-                 bwp.reverseRotation();
-             }
-             parameters[count++] = bwp;
+             localCache.put(cacheKey, returnValue);
          }
-         return ArraysExt.resize(parameters, count);
+         return returnValue;
      }
  
      /**
@@@ -2178,10 -2529,7 +2554,7 @@@
                      }
                      case WKTKeywords.spherical: {
                          switch (dimension) {
-                             case 2: if (csFactory instanceof 
GeodeticObjectFactory) {
-                                         cs = ((GeodeticObjectFactory) 
csFactory).createSphericalCS(properties, axes[0], axes[1]);
-                                     }
-                                     break;
 -                            case 2: cs = 
csFactory.createSphericalCS(properties, axes[0], axes[1]); break;
++                            case 2: cs = 
extended(csFactory).createSphericalCS(properties, axes[0], axes[1]); break;
                              case 3: cs = 
csFactory.createSphericalCS(properties, axes[0], axes[1], axes[2]); break;
                          }
                          break;
@@@ -2202,7 -2550,7 +2575,7 @@@
                      }
                      case WKTKeywords.parametric: {
                          switch (dimension) {
-                             case 1: cs = 
ServicesForMetadata.createParametricCS(properties, axes[0], csFactory); break;
 -                            case 1: cs = 
csFactory.createParametricCS(properties, axes[0]); break;
++                            case 1: cs = 
extended(csFactory).createParametricCS(properties, axes[0]); break;
                          }
                          break;
                      }
@@@ -2381,7 -2704,40 +2729,42 @@@
              if (returnValue == null) {
                  throw noSuchAuthorityCode(AxisName.class, 
String.valueOf(code));
              }
-             axisNames.put(code, returnValue);
+             localCache.put(cacheKey, returnValue);
+         }
+         return returnValue;
+     }
+ 
+     /**
+      * Returns the realization method for the specified code.
++     * Returned as the legacy {@link VerticalDatumType} because
++     * {@code RealizationMethod} did not existed in GeoAPI 3.0.
+      *
+      * @param  code  code of the realization method, or {@code null} if none.
 -     * @return realization method, or {@code null} if the given code was null.
++     * @return realization method, or {@code GEOIDAL} if the given code was 
null.
+      */
 -    private RealizationMethod getRealizationMethod(final Integer code) throws 
FactoryException, SQLException {
++    private VerticalDatumType getRealizationMethod(final Integer code) throws 
FactoryException, SQLException {
+         assert Thread.holdsLock(this);
+         if (code == null) {
 -            return null;
++            return VerticalDatumType.GEOIDAL;
+         }
+         final Long cacheKey = cacheKey(2, code);
 -        var returnValue = (RealizationMethod) localCache.get(cacheKey);
++        var returnValue = (VerticalDatumType) localCache.get(cacheKey);
+         if (returnValue == null && code != null) {
+             try (ResultSet result = executeQueryForCodes(
+                     "DatumRealizationMethod",
+                     "SELECT REALIZATION_METHOD_NAME"
+                             + " FROM \"DatumRealizationMethod\""
+                             + " WHERE REALIZATION_METHOD_CODE = ?", code))
+             {
+                 while (result.next()) {
+                     final String name = getString(code, result, 1);
+                     returnValue = 
ensureSingleton(VerticalDatumTypes.fromMethod(name), returnValue, code);
+                 }
+             }
+             if (returnValue == null) {
 -                throw noSuchAuthorityCode(RealizationMethod.class, 
String.valueOf(code));
++                throw noSuchAuthorityCode(VerticalDatumType.class, 
String.valueOf(code));
+             }
+             localCache.put(cacheKey, returnValue);
          }
          return returnValue;
      }
@@@ -2598,14 -2970,20 +2997,20 @@@ next:                   while (r.next()
                      case 0:  valueDomain = null; break;
                      default: valueDomain = new EPSGParameterDomain(units); 
break;
                      case 1:  valueDomain = 
MeasurementRange.create(Double.NEGATIVE_INFINITY, false,
-                                     Double.POSITIVE_INFINITY, false, 
CollectionsExt.first(units)); break;
+                                                                    
Double.POSITIVE_INFINITY, false,
+                                                                    
CollectionsExt.first(units)); break;
                  }
+                 /*
+                  * Map of properties should be populated only after we 
extracted all
+                  * information needed from the `ResultSet`, because it may be 
closed.
+                  */
                  @SuppressWarnings("LocalVariableHidesMemberVariable")
-                 final Map<String,Object> properties =
-                         createProperties("Coordinate_Operation Parameter", 
name, epsg, isReversible, deprecated);
+                 final Map<String,Object> properties = createProperties(
+                         "Coordinate_Operation 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.
              }
          } catch (SQLException exception) {
              throw databaseFailure(OperationMethod.class, code, exception);
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java
index 366b8fa587,4ffdbd4f7a..26c70067cf
--- 
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
@@@ -113,10 -110,10 +114,10 @@@ final class TableInfo 
                  "DATUM_NAME",
                  "DATUM_TYPE",
                  new Class<?>[] { GeodeticDatum.class,  VerticalDatum.class,   
EngineeringDatum.class,
 -                                 TemporalDatum.class,  ParametricDatum.class},
 +                                 TemporalDatum.class,  
DefaultParametricDatum.class},
                  new String[]   {"geodetic",           "vertical",            
"engineering",
                                  "temporal",           "parametric"},         
// Same comment as in the CRS case above.
-                 null),
+                 null, true),
  
          ELLIPSOID = new TableInfo(Ellipsoid.class,
                  "\"Ellipsoid\"",
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java
index a78390b5c2,8eef7854ae..2b754a9e0a
--- 
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
@@@ -81,6 -77,17 +81,11 @@@ public final class VerticalDatumTypes i
       */
      static final String BAROMETRIC = "BAROMETRIC";
  
+     /**
+      * A value used in the <abbr>EPSG</abbr> database.
+      */
+     static final String LOCAL = "LOCAL";
+ 
 -    /**
 -     * Do not allow instantiation of this class.
 -     */
 -    private VerticalDatumTypes() {
 -    }
 -
      /**
       * Returns a pseudo-realization method for ellipsoidal heights.
       * <strong>The use of this method is deprecated</strong> as ellipsoidal 
height
@@@ -115,14 -122,14 +120,14 @@@
       * @param  code  the legacy vertical datum code.
       * @return the vertical datum type, or {@code null} if none.
       */
-     public static VerticalDatumType fromLegacy(final int code) {
 -    public static RealizationMethod fromLegacyCode(final int code) {
++    public static VerticalDatumType fromLegacyCode(final int code) {
          switch (code) {
          //  case 2000: return null;                                     // 
CS_VD_Other
 -            case 2001: return RealizationMethod.valueOf(ORTHOMETRIC);   // 
CS_VD_Orthometric
 +            case 2001: return VerticalDatumType.valueOf(ORTHOMETRIC);   // 
CS_VD_Orthometric
              case 2002: return ellipsoidal();                            // 
CS_VD_Ellipsoidal
 -            case 2003: return RealizationMethod.valueOf(BAROMETRIC);    // 
CS_VD_AltitudeBarometric
 -            case 2005: return RealizationMethod.GEOID;                  // 
CS_VD_GeoidModelDerived
 -            case 2006: return RealizationMethod.TIDAL;                  // 
CS_VD_Depth
 +            case 2003: return VerticalDatumType.BAROMETRIC;             // 
CS_VD_AltitudeBarometric
 +            case 2005: return VerticalDatumType.GEOIDAL;                // 
CS_VD_GeoidModelDerived
 +            case 2006: return VerticalDatumType.DEPTH;                  // 
CS_VD_Depth
              default:   return null;
          }
      }
@@@ -131,13 -138,13 +136,12 @@@
       * Returns the legacy code for the datum type, or 2000 (other surface) if 
unknown.
       * This method is used for WKT 1 formatting.
       *
-      * @param  type  the vertical datum type, or {@code null} if unknown.
+      * @param  method  the vertical datum type, or {@code null} if unknown.
       * @return the legacy code for the given datum type, or 0 if unknown.
       */
--    @SuppressWarnings("deprecation")
-     public static int toLegacy(final VerticalDatumType type) {
-         if (type != null) {
-             switch (type.name().toUpperCase(Locale.US)) {
+     public static int toLegacyCode(final VerticalDatumType method) {
+         if (method != null) {
+             switch (method.name().toUpperCase(Locale.US)) {
                  case ORTHOMETRIC: return 2001;      // CS_VD_Orthometric
                  case ELLIPSOIDAL: return 2002;      // CS_VD_Ellipsoidal
                  case BAROMETRIC:  return 2003;      // 
CS_VD_AltitudeBarometric
@@@ -148,6 -155,80 +152,28 @@@
          return 2000;
      }
  
 -    /**
 -     * Returns the vertical datum type from a realization method.
 -     * If the given method cannot be mapped to a legacy type, then this 
method returns "other surface".
 -     * This is because the vertical datum type was a mandatory property in 
legacy OGC/ISO standards.
 -     * This method is used for writing GML documents older than GML 3.2.
 -     *
 -     * <p>Note: this is renamed {@code toLegacyName(RealizationMethod)} on 
the GeoAPI 4.0 branch.</p>
 -     *
 -     * @param  method  the realization method, or {@code null}.
 -     * @return the vertical datum type (never null).
 -     */
 -    @SuppressWarnings("deprecation")
 -    public static VerticalDatumType fromMethod(final RealizationMethod 
method) {
 -        if (method == RealizationMethod.GEOID) return 
VerticalDatumType.GEOIDAL;
 -        if (method == RealizationMethod.TIDAL) return VerticalDatumType.DEPTH;
 -        if (method != null) {
 -            return 
VerticalDatumType.valueOf(method.name().toUpperCase(Locale.US));
 -        }
 -        return VerticalDatumType.OTHER_SURFACE;
 -    }
 -
 -    /**
 -     * Returns the realization method from a vertical datum type.
 -     * This method is used for reading GML documents older than GML 3.2.
 -     *
 -     * <p>Note: this is renamed {@code fromLegacyName(String)} on the GeoAPI 
4.0 branch.</p>
 -     *
 -     * @param  type  the vertical datum type, or {@code null}.
 -     * @return the realization method, or {@code null} if none.
 -     */
 -    @SuppressWarnings("deprecation")
 -    public static RealizationMethod toMethod(final VerticalDatumType type) {
 -        return (type != null) ? fromLegacyName(type.name()) : null;
 -    }
 -
 -    /**
 -     * Returns the realization method from a vertical datum type.
 -     * This method is used for reading GML documents older than GML 3.2.
 -     *
 -     * @param  type  the vertical datum type, or {@code null}.
 -     * @return the realization method, or {@code null} if none.
 -     */
 -    public static RealizationMethod fromLegacyName(final String type) {
 -        if ("geoidal"  .equalsIgnoreCase(type)) return 
RealizationMethod.GEOID;
 -        if ("depth"    .equalsIgnoreCase(type)) return 
RealizationMethod.TIDAL;
 -        if (LOCAL      .equalsIgnoreCase(type)) return 
RealizationMethod.valueOf(LOCAL);
 -        if (BAROMETRIC .equalsIgnoreCase(type)) return 
RealizationMethod.valueOf(BAROMETRIC);
 -        if (ORTHOMETRIC.equalsIgnoreCase(type)) return 
RealizationMethod.valueOf(ORTHOMETRIC);
 -        if (ELLIPSOIDAL.equalsIgnoreCase(type)) return ellipsoidal();
 -        return null;
 -    }
 -
+     /**
+      * Returns the realization method from heuristic rules applied on the 
name.
+      *
+      * <p>Note: this is {@code fromMethod(String)} on the GeoAPI 4.0 
branch.</p>
+      *
+      * @param  name  the realization method name, or {@code null}.
+      * @return the realization method, or {@code null} if the given name was 
null.
+      */
 -    public static RealizationMethod fromMethod(final String name) {
 -        RealizationMethod method = fromLegacyName(name);
++    public static VerticalDatumType fromMethod(final String name) {
++        VerticalDatumType method = fromDatum(name);
+         if (method == null && name != null && !name.isBlank()) {
+             final int s = name.lastIndexOf('-');
+             if (s >= 0 && 
name.substring(s+1).strip().equalsIgnoreCase("based")) {
 -                method = CodeLists.forCodeName(RealizationMethod.class, 
name.substring(0, s));
++                method = fromDatum(name.substring(0, s));
+             }
+             if (method == null) {
 -                method = RealizationMethod.valueOf(name);
++                method = VerticalDatumType.valueOf(name);
+             }
+         }
+         return method;
+     }
+ 
      /**
       * Guesses the realization method of a datum from its name, aliases or a 
given vertical axis.
       * This is sometimes needed after XML unmarshalling or WKT parsing, 
because GML 3.2 and ISO 19162
@@@ -158,12 -239,12 +184,12 @@@
       * @param  name     the name of the datum for which to guess a type, or 
{@code null} if unknown.
       * @param  aliases  the aliases of the datum for which to guess a type, 
or {@code null} if unknown.
       * @param  axis     the vertical axis for which to guess a type, or 
{@code null} if unknown.
 -     * @return a datum type, or {@code null} if none can be guessed.
 +     * @return a datum type, or {@link VerticalDatumType#OTHER_SURFACE} if 
none can be guessed.
       */
-     public static VerticalDatumType guess(final String name, final 
Collection<? extends GenericName> aliases,
 -    public static RealizationMethod fromDatum(final String name, final 
Collection<? extends GenericName> aliases,
++    public static VerticalDatumType fromDatum(final String name, final 
Collection<? extends GenericName> aliases,
              final CoordinateSystemAxis axis)
      {
-         VerticalDatumType method = guess(name);
 -        RealizationMethod method = fromDatum(name);
++        VerticalDatumType method = fromDatum(name);
          if (method != null) {
              return method;
          }
@@@ -205,17 -286,13 +231,23 @@@
       * @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 VerticalDatumType guess(final String name) {
 -    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.contains("geoid")) {
 -                return RealizationMethod.GEOID;
++            if (name.regionMatches(true, 0, "geoid", 0, 5)) {
++                return VerticalDatumType.GEOIDAL;
++            }
++            if (name.equalsIgnoreCase("Tidal")) {
++                return VerticalDatumType.DEPTH;
++            }
 +            for (int i=0; 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/test/org/apache/sis/referencing/internal/VerticalDatumTypesTest.java
index 00de10be23,0a167b5be6..b0692cbd2d
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/internal/VerticalDatumTypesTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/internal/VerticalDatumTypesTest.java
@@@ -52,23 -56,36 +52,35 @@@ public final class VerticalDatumTypesTe
      }
  
      /**
-      * Tests the {@link VerticalDatumTypes#fromLegacy(int)} method.
+      * Tests the {@link VerticalDatumTypes#fromLegacyCode(int)} method.
       */
      @Test
-     public void testFromLegacy() {
-         assertEquals(VerticalDatumTypes.ellipsoidal(), 
VerticalDatumTypes.fromLegacy(2002));
-         assertEquals(VerticalDatumType .GEOIDAL,       
VerticalDatumTypes.fromLegacy(2005));
-         assertEquals(VerticalDatumType .DEPTH,         
VerticalDatumTypes.fromLegacy(2006));
+     public void testFromLegacyCode() {
+         assertEquals(VerticalDatumTypes.ellipsoidal(), 
VerticalDatumTypes.fromLegacyCode(2002));
 -        assertEquals(RealizationMethod .GEOID,         
VerticalDatumTypes.fromLegacyCode(2005));
 -        assertEquals(RealizationMethod .TIDAL,         
VerticalDatumTypes.fromLegacyCode(2006));
++        assertEquals(VerticalDatumType .GEOIDAL,       
VerticalDatumTypes.fromLegacyCode(2005));
++        assertEquals(VerticalDatumType .DEPTH,         
VerticalDatumTypes.fromLegacyCode(2006));
      }
  
      /**
-      * Tests the {@link VerticalDatumTypes#toLegacy(VerticalDatumType)} 
method.
+      * Tests the {@link VerticalDatumTypes#toLegacyCode(VerticalDatumType)} 
method.
       */
      @Test
-     public void testToLegacy() {
-         assertEquals(2002, 
VerticalDatumTypes.toLegacy(VerticalDatumType.valueOf("ELLIPSOIDAL")));
-         assertEquals(2005, 
VerticalDatumTypes.toLegacy(VerticalDatumType.GEOIDAL));
-         assertEquals(2006, 
VerticalDatumTypes.toLegacy(VerticalDatumType.DEPTH));
+     @SuppressWarnings("deprecation")
+     public void testToLegacyCode() {
+         assertEquals(2002, 
VerticalDatumTypes.toLegacyCode(VerticalDatumType.valueOf("ELLIPSOIDAL")));
+         assertEquals(2005, 
VerticalDatumTypes.toLegacyCode(VerticalDatumType.GEOIDAL));
+         assertEquals(2006, 
VerticalDatumTypes.toLegacyCode(VerticalDatumType.DEPTH));
+     }
+ 
+     /**
+      * Tests the {@link VerticalDatumTypes#fromMethod(String)} method
+      * with names from the <abbr>EPSG</abbr> database.
+      */
+     @Test
+     public void testFromMethod() {
 -        assertEquals(RealizationMethod .LEVELLING, 
VerticalDatumTypes.fromMethod("Levelling-based"));
 -        assertEquals(RealizationMethod .GEOID,     
VerticalDatumTypes.fromMethod("Geoid-based"));
 -        assertEquals(RealizationMethod .TIDAL,     
VerticalDatumTypes.fromMethod("Tidal"));
 -        assertEquals(VerticalDatumTypes.LOCAL,     
VerticalDatumTypes.fromMethod("Local").name());
++        assertEquals(VerticalDatumType.GEOIDAL, 
VerticalDatumTypes.fromMethod("Geoid-based"));
++        assertEquals(VerticalDatumType.DEPTH,   
VerticalDatumTypes.fromMethod("Tidal"));
++        
assertTrue(VerticalDatumTypes.LOCAL.equalsIgnoreCase(VerticalDatumTypes.fromMethod("Local").name()));
      }
  
      /**
diff --cc 
endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java
index 5618343620,9c8bc924ba..c6c62b4661
--- 
a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java
+++ 
b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java
@@@ -396,7 -436,10 +432,9 @@@ final class GridMapping 
           */
          if (isSpecified) {
              final Map<String,?> properties = properties(definition, 
Convention.GEOGRAPHIC_CRS_NAME, main, datum);
-             return decoder.getCRSFactory().createGeographicCRS(properties, 
datum,
+             return decoder.getCRSFactory().createGeographicCRS(
+                     properties,
+                     datum,
 -                    ensemble,
                      defaultDefinitions.geographic().getCoordinateSystem());
          } else {
              return defaultDefinitions.geographic();

Reply via email to