You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2025/09/08 17:18:45 UTC

(sis) branch main updated (23f2e78a1a -> 92cb82585e)

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

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


    from 23f2e78a1a Merge branch 'geoapi-3.1'.
     add bf613f9a3f Remove the links to the old version (2015) of WKT 2 specification. Do not replace by newer version (2019) for avoiding maintenance cost, especially since the links were often referencing specific sections.
     add 67df589956 Fix the existing JUnit tests to the 2015 version of WKT2. It allows us to start implementing the 2019 version without breaking the tests.
     add 70af867f4a Complete the support of WKT 2 `USAGE` element and update the tests accordingly. This first simple WKT2:2019 element gives us a way to test the distinction between the two versions of WKT 2.
     add 062a8de71d Add the support of `AXISMINVALUE`, `AXISMAXVALUE` and `RANGEMEANING` from ISO 19162:2019.
     add c08fea432c Supports the `ENSEMBLE` element at WKT parsing and formatting time.
     add c65d0e793d Add support for `FrameEpoch` element inside the `DYNAMIC` element.
     add 94cdd0cb3b Complete the support of the new WKT keywords for `GeographicCRS` and other CRS types. Change the default unit of measurement of `PARAMETER` as clarified in ISO 19162:2019. Replace usage of `Date` by `java.time` in WKT parsing of temporal CRS.
     add 11f6114609 Merge branch 'geoapi-4.0' into geoapi-3.1. This merge complete the upgrade of the CRS part of WKT 2 to ISO 19162:2019, except for a some advanced elements such as `GeoidModel` and `Calendar[…]`. This commit does not include the upgrade for the coordinate operation part.
     new 92cb82585e Merge branch 'geoapi-3.1'

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../main/org/apache/sis/console/CommandRunner.java |   2 +-
 .../apache/sis/console/FormattedOutputCommand.java |   6 +
 .../org/apache/sis/console/CRSCommandTest.java     |  15 +-
 .../sis/metadata/iso/citation/Citations.java       |   2 +-
 .../org/apache/sis/temporal/LenientDateFormat.java |   4 +-
 .../sis/metadata/iso/citation/CitationsTest.java   |   2 +-
 .../sis/coordinate/DefaultCoordinateMetadata.java  |   2 +-
 .../main/org/apache/sis/io/wkt/AbstractParser.java |  82 +++
 .../main/org/apache/sis/io/wkt/Convention.java     | 122 ++---
 .../main/org/apache/sis/io/wkt/Element.java        |  18 +-
 .../org/apache/sis/io/wkt/FormattableObject.java   |  10 +-
 .../main/org/apache/sis/io/wkt/Formatter.java      | 102 ++--
 .../apache/sis/io/wkt/GeodeticObjectParser.java    | 559 ++++++++++++++-------
 .../org/apache/sis/io/wkt/MathTransformParser.java |  27 +-
 .../main/org/apache/sis/io/wkt/Transliterator.java |  21 +-
 .../main/org/apache/sis/io/wkt/WKTFormat.java      |  12 +-
 .../main/org/apache/sis/io/wkt/package-info.java   |  11 +-
 .../sis/parameter/DefaultParameterValue.java       | 167 +++---
 .../org/apache/sis/parameter/TensorParameters.java |   4 +-
 .../pending/geoapi/referencing/MissingMethods.java | 167 +++++-
 .../sis/referencing/AbstractIdentifiedObject.java  |  20 +-
 .../main/org/apache/sis/referencing/CRS.java       |  12 +-
 .../sis/referencing/DefaultObjectDomain.java       |   1 +
 .../sis/referencing/ImmutableIdentifier.java       |   2 -
 .../apache/sis/referencing/crs/AbstractCRS.java    |  88 ++--
 .../sis/referencing/crs/AbstractDerivedCRS.java    |  14 +
 .../sis/referencing/crs/DefaultCompoundCRS.java    |  14 +-
 .../sis/referencing/crs/DefaultDerivedCRS.java     |  44 +-
 .../sis/referencing/crs/DefaultEngineeringCRS.java |  20 +-
 .../sis/referencing/crs/DefaultGeocentricCRS.java  |   2 -
 .../sis/referencing/crs/DefaultGeodeticCRS.java    |  43 +-
 .../sis/referencing/crs/DefaultGeographicCRS.java  |   6 +-
 .../sis/referencing/crs/DefaultImageCRS.java       |   2 -
 .../sis/referencing/crs/DefaultParametricCRS.java  |  12 -
 .../sis/referencing/crs/DefaultProjectedCRS.java   |  14 +-
 .../sis/referencing/crs/DefaultTemporalCRS.java    |  20 +-
 .../sis/referencing/crs/DefaultVerticalCRS.java    |  20 +-
 .../org/apache/sis/referencing/crs/DynamicCRS.java |  77 +++
 .../org/apache/sis/referencing/cs/AbstractCS.java  |   2 -
 .../sis/referencing/cs/CoordinateSystems.java      |   3 +-
 .../cs/DefaultCoordinateSystemAxis.java            | 109 ++--
 .../sis/referencing/cs/DirectionAlongMeridian.java |   2 -
 .../sis/referencing/datum/AbstractDatum.java       |  32 +-
 .../referencing/datum/DefaultDatumEnsemble.java    |  76 ++-
 .../sis/referencing/datum/DefaultEllipsoid.java    |   2 -
 .../referencing/datum/DefaultEngineeringDatum.java |  16 +-
 .../referencing/datum/DefaultGeodeticDatum.java    |  12 +-
 .../sis/referencing/datum/DefaultImageDatum.java   |  23 +-
 .../referencing/datum/DefaultParametricDatum.java  |  18 +-
 .../referencing/datum/DefaultPrimeMeridian.java    |   4 +-
 .../referencing/datum/DefaultTemporalDatum.java    |  16 +-
 .../referencing/datum/DefaultVerticalDatum.java    |  11 +-
 .../referencing/factory/GeodeticObjectFactory.java |   6 +-
 .../referencing/factory/sql/EPSGDataAccess.java    |  13 +-
 .../org/apache/sis/referencing/internal/Epoch.java |  69 ++-
 .../internal/PositionalAccuracyConstant.java       |  58 ++-
 .../operation/AbstractCoordinateOperation.java     |  12 +-
 .../operation/DefaultOperationMethod.java          |   2 -
 .../provider/MercatorAuxiliarySphere.java          |   3 +-
 .../referencing/operation/provider/Mollweide.java  |   3 +-
 .../provider/ObliqueMercatorTwoPoints.java         |   5 +-
 .../transform/DefaultMathTransformFactory.java     |   3 +-
 .../apache/sis/referencing/privy/WKTKeywords.java  |  54 +-
 .../apache/sis/referencing/privy/WKTUtilities.java |  87 +---
 .../org/apache/sis/geometry/ArrayEnvelopeTest.java |  11 +-
 .../apache/sis/geometry/GeneralEnvelopeTest.java   |   3 +-
 .../test/org/apache/sis/io/wkt/ElementTest.java    |   4 +-
 .../sis/io/wkt/GeodeticObjectParserTest.java       | 143 +++++-
 .../test/org/apache/sis/io/wkt/WKTFormatTest.java  |   1 +
 .../DefaultParameterDescriptorGroupTest.java       |   7 +-
 .../parameter/DefaultParameterDescriptorTest.java  |   6 +-
 .../sis/parameter/DefaultParameterValueTest.java   |   3 +-
 .../org/apache/sis/parameter/TensorValuesTest.java |   5 +-
 .../referencing/AbstractReferenceSystemTest.java   |  36 +-
 .../org/apache/sis/referencing/Assertions.java     |  18 +-
 .../referencing/crs/DefaultCompoundCRSTest.java    |   9 +-
 .../sis/referencing/crs/DefaultDerivedCRSTest.java |   4 +-
 .../referencing/crs/DefaultEngineeringCRSTest.java |   2 +-
 .../referencing/crs/DefaultGeocentricCRSTest.java  |   5 +-
 .../referencing/crs/DefaultGeographicCRSTest.java  |  27 +-
 .../sis/referencing/crs/DefaultImageCRSTest.java   |   2 +-
 .../referencing/crs/DefaultProjectedCRSTest.java   |  25 +-
 .../referencing/crs/DefaultTemporalCRSTest.java    |   2 +-
 .../referencing/crs/DefaultVerticalCRSTest.java    |   2 +-
 .../cs/DefaultCoordinateSystemAxisTest.java        |  52 +-
 .../referencing/datum/DefaultEllipsoidTest.java    |   5 +-
 .../datum/DefaultGeodeticDatumTest.java            |  12 +-
 .../datum/DefaultVerticalDatumTest.java            |   4 +-
 .../factory/CommonAuthorityFactoryTest.java        |   2 +-
 .../apache/sis/referencing/internal/EpochTest.java |  22 +-
 .../operation/CoordinateOperationFinderTest.java   |   6 +-
 .../operation/CoordinateOperationRegistryTest.java |  10 +-
 .../DefaultConcatenatedOperationTest.java          |   4 +-
 .../DefaultCoordinateOperationFactoryTest.java     |   6 +-
 .../operation/DefaultOperationMethodTest.java      |   2 +-
 .../operation/DefaultTransformationTest.java       |   2 +-
 .../operation/HardCodedConversions.java            |   5 +-
 .../referencing/operation/provider/AffineTest.java |   7 +-
 .../operation/provider/LongitudeRotationTest.java  |   3 +-
 .../report/CoordinateOperationMethods.java         |   8 +-
 .../sis/test/integration/ConsistencyTest.java      |   7 +-
 .../org/apache/sis/storage/base/PRJDataStore.java  |  14 +-
 .../apache/sis/storage/esri/WritableStoreTest.java |   2 +-
 .../main/org/apache/sis/setup/GeometryLibrary.java |   3 +-
 .../main/org/apache/sis/util/Characters.java       |   1 -
 .../main/org/apache/sis/util/privy/Constants.java  |   5 +
 .../main/org/apache/sis/util/privy/URLs.java       |   5 -
 .../org/apache/sis/gui/referencing/WKTPane.java    |   2 +
 108 files changed, 1817 insertions(+), 1074 deletions(-)
 create mode 100644 endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DynamicCRS.java


(sis) 01/01: Merge branch 'geoapi-3.1'

Posted by de...@apache.org.
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 92cb82585e83a19671af5cd91b810669b8b33e58
Merge: 23f2e78a1a 11f6114609
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Mon Sep 8 19:18:23 2025 +0200

    Merge branch 'geoapi-3.1'

 .../main/org/apache/sis/console/CommandRunner.java |   2 +-
 .../apache/sis/console/FormattedOutputCommand.java |   6 +
 .../org/apache/sis/console/CRSCommandTest.java     |  15 +-
 .../sis/metadata/iso/citation/Citations.java       |   2 +-
 .../org/apache/sis/temporal/LenientDateFormat.java |   4 +-
 .../sis/metadata/iso/citation/CitationsTest.java   |   2 +-
 .../sis/coordinate/DefaultCoordinateMetadata.java  |   2 +-
 .../main/org/apache/sis/io/wkt/AbstractParser.java |  82 +++
 .../main/org/apache/sis/io/wkt/Convention.java     | 122 ++---
 .../main/org/apache/sis/io/wkt/Element.java        |  18 +-
 .../org/apache/sis/io/wkt/FormattableObject.java   |  10 +-
 .../main/org/apache/sis/io/wkt/Formatter.java      | 102 ++--
 .../apache/sis/io/wkt/GeodeticObjectParser.java    | 559 ++++++++++++++-------
 .../org/apache/sis/io/wkt/MathTransformParser.java |  27 +-
 .../main/org/apache/sis/io/wkt/Transliterator.java |  21 +-
 .../main/org/apache/sis/io/wkt/WKTFormat.java      |  12 +-
 .../main/org/apache/sis/io/wkt/package-info.java   |  11 +-
 .../sis/parameter/DefaultParameterValue.java       | 167 +++---
 .../org/apache/sis/parameter/TensorParameters.java |   4 +-
 .../pending/geoapi/referencing/MissingMethods.java | 167 +++++-
 .../sis/referencing/AbstractIdentifiedObject.java  |  20 +-
 .../main/org/apache/sis/referencing/CRS.java       |  12 +-
 .../sis/referencing/DefaultObjectDomain.java       |   1 +
 .../sis/referencing/ImmutableIdentifier.java       |   2 -
 .../apache/sis/referencing/crs/AbstractCRS.java    |  88 ++--
 .../sis/referencing/crs/AbstractDerivedCRS.java    |  14 +
 .../sis/referencing/crs/DefaultCompoundCRS.java    |  14 +-
 .../sis/referencing/crs/DefaultDerivedCRS.java     |  44 +-
 .../sis/referencing/crs/DefaultEngineeringCRS.java |  20 +-
 .../sis/referencing/crs/DefaultGeocentricCRS.java  |   2 -
 .../sis/referencing/crs/DefaultGeodeticCRS.java    |  43 +-
 .../sis/referencing/crs/DefaultGeographicCRS.java  |   6 +-
 .../sis/referencing/crs/DefaultImageCRS.java       |   2 -
 .../sis/referencing/crs/DefaultParametricCRS.java  |  12 -
 .../sis/referencing/crs/DefaultProjectedCRS.java   |  14 +-
 .../sis/referencing/crs/DefaultTemporalCRS.java    |  20 +-
 .../sis/referencing/crs/DefaultVerticalCRS.java    |  20 +-
 .../org/apache/sis/referencing/crs/DynamicCRS.java |  77 +++
 .../org/apache/sis/referencing/cs/AbstractCS.java  |   2 -
 .../sis/referencing/cs/CoordinateSystems.java      |   3 +-
 .../cs/DefaultCoordinateSystemAxis.java            | 109 ++--
 .../sis/referencing/cs/DirectionAlongMeridian.java |   2 -
 .../sis/referencing/datum/AbstractDatum.java       |  32 +-
 .../referencing/datum/DefaultDatumEnsemble.java    |  76 ++-
 .../sis/referencing/datum/DefaultEllipsoid.java    |   2 -
 .../referencing/datum/DefaultEngineeringDatum.java |  16 +-
 .../referencing/datum/DefaultGeodeticDatum.java    |  12 +-
 .../sis/referencing/datum/DefaultImageDatum.java   |  23 +-
 .../referencing/datum/DefaultParametricDatum.java  |  18 +-
 .../referencing/datum/DefaultPrimeMeridian.java    |   4 +-
 .../referencing/datum/DefaultTemporalDatum.java    |  16 +-
 .../referencing/datum/DefaultVerticalDatum.java    |  11 +-
 .../referencing/factory/GeodeticObjectFactory.java |   6 +-
 .../referencing/factory/sql/EPSGDataAccess.java    |  13 +-
 .../org/apache/sis/referencing/internal/Epoch.java |  69 ++-
 .../internal/PositionalAccuracyConstant.java       |  58 ++-
 .../operation/AbstractCoordinateOperation.java     |  12 +-
 .../operation/DefaultOperationMethod.java          |   2 -
 .../provider/MercatorAuxiliarySphere.java          |   3 +-
 .../referencing/operation/provider/Mollweide.java  |   3 +-
 .../provider/ObliqueMercatorTwoPoints.java         |   5 +-
 .../transform/DefaultMathTransformFactory.java     |   3 +-
 .../apache/sis/referencing/privy/WKTKeywords.java  |  54 +-
 .../apache/sis/referencing/privy/WKTUtilities.java |  87 +---
 .../org/apache/sis/geometry/ArrayEnvelopeTest.java |  11 +-
 .../apache/sis/geometry/GeneralEnvelopeTest.java   |   3 +-
 .../test/org/apache/sis/io/wkt/ElementTest.java    |   4 +-
 .../sis/io/wkt/GeodeticObjectParserTest.java       | 143 +++++-
 .../test/org/apache/sis/io/wkt/WKTFormatTest.java  |   1 +
 .../DefaultParameterDescriptorGroupTest.java       |   7 +-
 .../parameter/DefaultParameterDescriptorTest.java  |   6 +-
 .../sis/parameter/DefaultParameterValueTest.java   |   3 +-
 .../org/apache/sis/parameter/TensorValuesTest.java |   5 +-
 .../referencing/AbstractReferenceSystemTest.java   |  36 +-
 .../org/apache/sis/referencing/Assertions.java     |  18 +-
 .../referencing/crs/DefaultCompoundCRSTest.java    |   9 +-
 .../sis/referencing/crs/DefaultDerivedCRSTest.java |   4 +-
 .../referencing/crs/DefaultEngineeringCRSTest.java |   2 +-
 .../referencing/crs/DefaultGeocentricCRSTest.java  |   5 +-
 .../referencing/crs/DefaultGeographicCRSTest.java  |  27 +-
 .../sis/referencing/crs/DefaultImageCRSTest.java   |   2 +-
 .../referencing/crs/DefaultProjectedCRSTest.java   |  25 +-
 .../referencing/crs/DefaultTemporalCRSTest.java    |   2 +-
 .../referencing/crs/DefaultVerticalCRSTest.java    |   2 +-
 .../cs/DefaultCoordinateSystemAxisTest.java        |  52 +-
 .../referencing/datum/DefaultEllipsoidTest.java    |   5 +-
 .../datum/DefaultGeodeticDatumTest.java            |  12 +-
 .../datum/DefaultVerticalDatumTest.java            |   4 +-
 .../factory/CommonAuthorityFactoryTest.java        |   2 +-
 .../apache/sis/referencing/internal/EpochTest.java |  22 +-
 .../operation/CoordinateOperationFinderTest.java   |   6 +-
 .../operation/CoordinateOperationRegistryTest.java |  10 +-
 .../DefaultConcatenatedOperationTest.java          |   4 +-
 .../DefaultCoordinateOperationFactoryTest.java     |   6 +-
 .../operation/DefaultOperationMethodTest.java      |   2 +-
 .../operation/DefaultTransformationTest.java       |   2 +-
 .../operation/HardCodedConversions.java            |   5 +-
 .../referencing/operation/provider/AffineTest.java |   7 +-
 .../operation/provider/LongitudeRotationTest.java  |   3 +-
 .../report/CoordinateOperationMethods.java         |   8 +-
 .../sis/test/integration/ConsistencyTest.java      |   7 +-
 .../org/apache/sis/storage/base/PRJDataStore.java  |  14 +-
 .../apache/sis/storage/esri/WritableStoreTest.java |   2 +-
 .../main/org/apache/sis/setup/GeometryLibrary.java |   3 +-
 .../main/org/apache/sis/util/Characters.java       |   1 -
 .../main/org/apache/sis/util/privy/Constants.java  |   5 +
 .../main/org/apache/sis/util/privy/URLs.java       |   5 -
 .../org/apache/sis/gui/referencing/WKTPane.java    |   2 +
 108 files changed, 1817 insertions(+), 1074 deletions(-)

diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java
index a6dda98fbb,55c5408246..5928ff070e
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java
@@@ -88,12 -91,9 +90,11 @@@ import org.apache.sis.geometry.Abstract
  import org.apache.sis.geometry.AbstractEnvelope;
  import org.apache.sis.xml.NilObject;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.util.ControlledVocabulary;
 -import org.opengis.referencing.ObjectDomain;
 +// Specific to the main branch:
 +import org.opengis.util.CodeList;
 +import org.opengis.referencing.ReferenceIdentifier;
 +import org.apache.sis.referencing.DefaultObjectDomain;
- import org.apache.sis.referencing.internal.Legacy;
 +import org.apache.sis.referencing.datum.AbstractDatum;
  
  
  /**
@@@ -868,11 -883,8 +884,8 @@@ public class Formatter implements Local
                          }
                      }
                  }
 -                for (Identifier id : identifiers) {
 +                for (ReferenceIdentifier id : identifiers) {
-                     if (!(id instanceof FormattableObject)) {
-                         id = ImmutableIdentifier.castOrCopy(id);
-                     }
-                     append((FormattableObject) id);
+                     appendFormattable(id, ImmutableIdentifier::castOrCopy);
                      if (filterID) break;
                  }
              }
@@@ -889,21 -901,29 +902,32 @@@
       * {@link ReferenceSystem} and {@link CoordinateOperation} objects.
       */
      private void appendForSubtypes(final IdentifiedObject object) {
-         InternationalString anchor = null, scope = null;
-         Extent area = null;
 -        if (object instanceof Datum) {
 -            final var datum = (Datum) object;
 +        if (object instanceof AbstractDatum) {
-             anchor = ((AbstractDatum) object).getAnchorPoint();
++            final var datum = (AbstractDatum) object;
+             appendOnNewLine(WKTKeywords.Anchor, datum.getAnchorDefinition().orElse(null), null);
+             datum.getAnchorEpoch().ifPresent((epoch) -> append(new Epoch(epoch, WKTKeywords.AnchorEpoch)));
          } else if (!(object instanceof ReferenceSystem || object instanceof CoordinateOperation)) {
              return;
          }
-         for (final DefaultObjectDomain domain : Legacy.getDomains(object)) {
-             scope = domain.getScope();
-             area = domain.getDomainOfValidity();
-             if (area != null) break;
-             // TODO: in 2019 revision we need to format all USAGE[…] elements, not only the first one.
++        if (!(object instanceof AbstractIdentifiedObject)) {
++            return;
++        }
+         final boolean usage = convention.supports(Convention.WKT2_2019);
 -        for (final ObjectDomain domain : object.getDomains()) {
++        for (final DefaultObjectDomain domain : ((AbstractIdentifiedObject) object).getDomains()) {
+             if (usage) {
+                 // ISO 19162:2019
+                 newLine();
 -                appendFormattable(domain, DefaultObjectDomain::castOrCopy);
++                append(domain);
+             } else {
+                 // ISO 19162:2015
+                 InternationalString scope = domain.getScope();
+                 Extent area = domain.getDomainOfValidity();
+                 if (scope != null || area != null) {
+                     append(scope, area);
+                     break;
+                 }
+             }
          }
-         appendOnNewLine(WKTKeywords.Anchor, anchor, null);
-         append(scope, area);
      }
  
      /**
diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
index 1291af32e8,94eb593acc..186903334b
--- 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
@@@ -87,13 -91,8 +91,16 @@@ import org.apache.sis.util.iso.Types
  // Specific to the main and geoapi-3.1 branches:
  import org.opengis.referencing.ReferenceIdentifier;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.referencing.ObjectDomain;
 +// Specific to the main branch:
 +import org.opengis.referencing.ReferenceSystem;
 +import org.apache.sis.pending.geoapi.referencing.MissingMethods;
 +import org.apache.sis.referencing.cs.DefaultParametricCS;
++import org.apache.sis.referencing.datum.AbstractDatum;
++import org.apache.sis.referencing.datum.DefaultDatumEnsemble;
++import org.apache.sis.referencing.datum.DefaultVerticalDatum;
 +import org.apache.sis.referencing.datum.DefaultParametricDatum;
 +import org.apache.sis.referencing.factory.GeodeticObjectFactory;
 +import org.apache.sis.temporal.TemporalDate;
  
  
  /**
@@@ -476,94 -462,45 +478,45 @@@ class GeodeticObjectParser extends Math
              // REMINDER: values associated to IDENTIFIERS_KEY shall be recognized by `toIdentifier(Object)`.
          }
          /*
-          * Other metadata (SCOPE, AREA, etc.).  ISO 19162 said that at most one of each type shall be present,
-          * but our parser accepts an arbitrary number of some kinds of metadata. They can be recognized by the
-          * `while` loop.
+          * SCOPE, AREA, BBOX, VERTICALEXTENT and TIMEEXTENT. Since ISO 19162:2019,
+          * all those properties are grouped inside an USAGE element. Example:
           *
-          * Most WKT do not contain any of those metadata, so we perform an `isEmpty()` check as an optimization
-          * for those common cases.
+          *     USAGE[
+          *       SCOPE["Large scale mapping."],
+          *       AREA["UK - Northern Ireland - onshore"]]
           */
-         if (!parent.isEmpty()) {
-             /*
-              * Example: SCOPE["Large scale topographic mapping and cadastre."]
-              */
-             element = parent.pullElement(OPTIONAL, WKTKeywords.Scope);
-             if (element != null) {
-                 properties.put(ReferenceSystem.SCOPE_KEY, element.pullString("scope"));  // Other types like Datum use the same key.
-                 element.close(ignoredElements);
-             }
-             /*
-              * Example: AREA["Netherlands offshore."]
-              */
-             DefaultExtent extent = null;
-             while ((element = parent.pullElement(OPTIONAL, WKTKeywords.Area)) != null) {
-                 final String area = element.pullString("area");
-                 element.close(ignoredElements);
-                 if (extent == null) {
-                     extent = new DefaultExtent(area, null, null, null);
-                 } else {
-                     extent.getGeographicElements().add(new DefaultGeographicDescription(area));
-                 }
-             }
-             /*
-              * Example: BBOX[51.43, 2.54, 55.77, 6.40]
-              */
-             while ((element = parent.pullElement(OPTIONAL, WKTKeywords.BBox)) != null) {
-                 final double southBoundLatitude = element.pullDouble("southBoundLatitude");
-                 final double westBoundLongitude = element.pullDouble("westBoundLongitude");
-                 final double northBoundLatitude = element.pullDouble("northBoundLatitude");
-                 final double eastBoundLongitude = element.pullDouble("eastBoundLongitude");
-                 element.close(ignoredElements);
-                 if (extent == null) extent = new DefaultExtent();
-                 extent.getGeographicElements().add(new DefaultGeographicBoundingBox(
-                         westBoundLongitude, eastBoundLongitude, southBoundLatitude, northBoundLatitude));
-             }
-             /*
-              * Example: VERTICALEXTENT[-1000, 0, LENGTHUNIT[“metre”, 1]]
-              *
-              * Units are optional, default to metres (no "contextual units" here).
-              */
-             while ((element = parent.pullElement(OPTIONAL, WKTKeywords.VerticalExtent)) != null) {
-                 final double minimum = element.pullDouble("minimum");
-                 final double maximum = element.pullDouble("maximum");
-                 Unit<Length> unit = parseScaledUnit(element, WKTKeywords.LengthUnit, Units.METRE);
-                 element.close(ignoredElements);
-                 if (unit   == null) unit   = Units.METRE;
-                 if (extent == null) extent = new DefaultExtent();
-                 verticalElements = new VerticalInfo(verticalElements, extent, minimum, maximum, unit).resolve(verticalCRS);
 -        final var domains = new ArrayList<ObjectDomain>();
++        final var domains = new ArrayList<DefaultObjectDomain>();
+         while ((element = parent.pullElement(OPTIONAL, WKTKeywords.Usage)) != null) {
+             final String scope = pullElementAsString(element, WKTKeywords.Scope);
+             final DefaultExtent extent = parseExtent(element);
+             if (scope != null || extent != null) {
+                 domains.add(new DefaultObjectDomain(Types.toInternationalString(scope), extent));
              }
+             element.close(ignoredElements);
+         }
+         if (domains.isEmpty()) {
              /*
-              * Example: TIMEEXTENT[2013-01-01, 2013-12-31]
-              *
-              * TODO: syntax like TIMEEXTENT[“Jurassic”, “Quaternary”] is not yet supported.
-              * See https://issues.apache.org/jira/browse/SIS-163
+              * Legacy (ISO 19162:2015) way to declare scope and extent:
+              * directly inside de CRS element, without USAGE wrapper.
               */
-             while ((element = parent.pullElement(OPTIONAL, WKTKeywords.TimeExtent)) != null) {
-                 if (element.peekValue() instanceof String) {
-                     element.pullString("startTime");
-                     element.pullString("endTime");
-                     element.close(ignoredElements);
-                     warning(parent, element, Errors.formatInternational(Errors.Keys.UnsupportedType_1, "TimeExtent[String,String]"), null);
-                 } else {
-                     final Instant startTime = element.pullDate("startTime");
-                     final Instant endTime   = element.pullDate("endTime");
-                     element.close(ignoredElements);
-                     final var t = new DefaultTemporalExtent(startTime, endTime);
-                     if (extent == null) extent = new DefaultExtent();
-                     extent.getTemporalElements().add(t);
-                 }
+             final String scope = pullElementAsString(parent, WKTKeywords.Scope);
+             if (scope != null) {
 -                properties.put(ObjectDomain.SCOPE_KEY, scope);
++                properties.put(ReferenceSystem.SCOPE_KEY, scope);
              }
+             final DefaultExtent extent = parseExtent(parent);
              if (extent != null) {
 -                properties.put(ObjectDomain.DOMAIN_OF_VALIDITY_KEY, extent);
 +                properties.put(ReferenceSystem.DOMAIN_OF_VALIDITY_KEY, extent);
              }
-             /*
-              * Example: REMARK["Замечание на русском языке"]
-              */
-             element = parent.pullElement(OPTIONAL, WKTKeywords.Remark);
-             if (element != null) {
-                 properties.put(IdentifiedObject.REMARKS_KEY, element.pullString("remarks"));
-                 element.close(ignoredElements);
-             }
+         } else {
 -            properties.put(IdentifiedObject.DOMAINS_KEY, domains.toArray(ObjectDomain[]::new));
++            properties.put(AbstractDatum.DOMAINS_KEY, domains.toArray(DefaultObjectDomain[]::new));
+         }
+         /*
+          * Example: REMARK["Замечание на русском языке"]
+          */
+         element = parent.pullElement(OPTIONAL, WKTKeywords.Remark);
+         if (element != null) {
+             properties.put(IdentifiedObject.REMARKS_KEY, element.pullString("remarks"));
+             element.close(ignoredElements);
          }
          parent.close(ignoredElements);
          if (parent.isRoot) {
@@@ -577,15 -514,94 +530,94 @@@
       * String, IdentifiedObject)} method. If an anchor has been found, its value is stored in the returned map.
       */
      private Map<String,Object> parseAnchorAndClose(final Element element, final String name) throws ParseException {
-         final Element anchor = element.pullElement(OPTIONAL, WKTKeywords.Anchor);
+         String   anchor = pullElementAsString(element, WKTKeywords.Anchor);
+         Temporal epoch  = Epoch.fromYear(pullElementAsDouble(element, WKTKeywords.AnchorEpoch, OPTIONAL), 0);
          final Map<String,Object> properties = parseMetadataAndClose(element, name, null);
-         if (anchor != null) {
-             properties.put(Datum.ANCHOR_POINT_KEY, anchor.pullString(DefaultGeodeticDatum.ANCHOR_DEFINITION_KEY));
-             anchor.close(ignoredElements);
-         }
 -        if (anchor != null) properties.put(Datum.ANCHOR_DEFINITION_KEY, anchor);
 -        if (epoch  != null) properties.put(Datum.ANCHOR_EPOCH_KEY, epoch);
++        if (anchor != null) properties.put(AbstractDatum.ANCHOR_DEFINITION_KEY, anchor);
++        if (epoch  != null) properties.put(AbstractDatum.ANCHOR_EPOCH_KEY, epoch);
          return properties;
      }
  
+     /**
+      * Parses the {@code AREA}, {@code BBOX}, {@code VERTICALEXTENT} and {@code TIMEEXTENT} elements if present.
+      * These elements were directly inside the <abbr>CRS</abbr> element in <abbr>ISO</abbr> 19162:2015, but became
+      * wrapped inside an {@code USAGE} element in <abbr>ISO</abbr> 19162:2019.
+      *
+      * <h4>Extension to <abbr>ISO</abbr> 19162 specification</h4>
 -     * The specification saids that at most one extent of each type can appear in the same {@code USAGE}.
++     * The specification said that at most one extent of each type can appear in the same {@code USAGE}.
+      * However, Apache <abbr>SIS</abbr> puts no limit on the number of occurrence of each extent type.
+      *
+      * <h4>Limitations</h4>
+      * Temporal extents with a syntax such as {@code TIMEEXTENT[“Jurassic”, “Quaternary”]} are not yet supported.
+      * See <a href="https://issues.apache.org/jira/browse/SIS-163">SIS-163</a>.
+      *
+      * @param  parent  the parent element.
+      * @return an extent containing all the above-cited elements, or {@code null} if none.
+      * @throws ParseException if an element cannot be parsed.
+      */
+     private DefaultExtent parseExtent(final Element parent) throws ParseException {
+         DefaultExtent extent = null;
+         Element element;
+         /*
+          * Example: AREA["Netherlands offshore."]
+          */
+         while ((element = parent.pullElement(OPTIONAL, WKTKeywords.Area)) != null) {
+             final String area = element.pullString("area");
+             element.close(ignoredElements);
+             if (extent == null) {
+                 extent = new DefaultExtent(area, null, null, null);
+             } else {
+                 extent.getGeographicElements().add(new DefaultGeographicDescription(area));
+             }
+         }
+         /*
+          * Example: BBOX[51.43, 2.54, 55.77, 6.40]
+          */
+         while ((element = parent.pullElement(OPTIONAL, WKTKeywords.BBox)) != null) {
+             final double southBoundLatitude = element.pullDouble("southBoundLatitude");
+             final double westBoundLongitude = element.pullDouble("westBoundLongitude");
+             final double northBoundLatitude = element.pullDouble("northBoundLatitude");
+             final double eastBoundLongitude = element.pullDouble("eastBoundLongitude");
+             element.close(ignoredElements);
+             if (extent == null) extent = new DefaultExtent();
+             extent.getGeographicElements().add(new DefaultGeographicBoundingBox(
+                     westBoundLongitude, eastBoundLongitude, southBoundLatitude, northBoundLatitude));
+         }
+         /*
+          * Example: VERTICALEXTENT[-1000, 0, LENGTHUNIT[“metre”, 1]]
+          *
+          * Units are optional, default to metres (no "contextual units" here).
+          */
+         while ((element = parent.pullElement(OPTIONAL, WKTKeywords.VerticalExtent)) != null) {
+             final double minimum = element.pullDouble("minimum");
+             final double maximum = element.pullDouble("maximum");
+             Unit<Length> unit = parseScaledUnit(element, WKTKeywords.LengthUnit, Units.METRE);
+             element.close(ignoredElements);
+             if (unit   == null) unit   = Units.METRE;
+             if (extent == null) extent = new DefaultExtent();
+             verticalElements = new VerticalInfo(verticalElements, extent, minimum, maximum, unit).resolve(verticalCRS);
+         }
+         /*
+          * Example: TIMEEXTENT[2013-01-01, 2013-12-31]
+          */
+         while ((element = parent.pullElement(OPTIONAL, WKTKeywords.TimeExtent)) != null) {
+             if (element.peekValue() instanceof String) {
+                 element.pullString("startTime");
+                 element.pullString("endTime");
+                 element.close(ignoredElements);
+                 warning(parent, element, Errors.formatInternational(Errors.Keys.UnsupportedType_1, "TimeExtent[String,String]"), null);
+             } else {
+                 final Temporal startTime = element.pullTime("startTime");
+                 final Temporal endTime   = element.pullTime("endTime");
+                 element.close(ignoredElements);
+                 final var t = new DefaultTemporalExtent(startTime, endTime);
+                 if (extent == null) extent = new DefaultExtent();
+                 extent.getTemporalElements().add(t);
+             }
+         }
+         return extent;
+     }
+ 
      /**
       * Parses an optional {@code "UNIT"} element of a known dimension.
       * This element has the following pattern:
@@@ -1395,8 -1420,93 +1440,93 @@@
      }
  
      /**
-      * Parses a {@code "Datum"} (WKT 2) element. The syntax is given by
-      * <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#54">WKT 2 specification §8.2.4</a>.
+      * Parses a {@code "FrameEoch"} (WKT 2) element.
+      *
+      * @param  parent  the parent element.
+      * @return the frame epoch, or {@code null} if none.
+      * @throws ParseException if the {@code "FrameEoch"} element cannot be parsed.
+      */
+     private Temporal parseDynamic(final Element parent) throws ParseException {
+         final Element element = parent.pullElement(OPTIONAL, WKTKeywords.Dynamic);
+         if (element == null) {
+             return null;
+         }
+         Temporal epoch = Epoch.fromYear(pullElementAsDouble(element, WKTKeywords.FrameEpoch, MANDATORY), 0);
+         element.close(ignoredElements);
+         return epoch;
+     }
+ 
+     /**
+      * Parses an {@code "Ensemble"} (WKT 2) element.
+      *
+      * @param  mode       {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
+      * @param  parent     the parent element.
+      * @param  datumType  GeoAPI interface of the type of datum to create.
+      * @param  meridian   the prime meridian, or {@code null} if the ensemble is not geodetic.
 -     * @return the {@code "Ensemble"} element as a {@link DatumEnsemble} object.
++     * @return the {@code "Ensemble"} element as a {@code DatumEnsemble} object.
+      * @throws ParseException if the {@code "Ensemble"} element cannot be parsed.
+      *
+      * @see org.apache.sis.referencing.datum.DefaultDatumEnsemble#formatTo(Formatter)
+      */
 -    private <D extends Datum> DatumEnsemble<D> parseEnsemble(final int mode, final Element parent,
++    private <D extends Datum> DefaultDatumEnsemble<D> parseEnsemble(final int mode, final Element parent,
+             final Class<D> datumType, final PrimeMeridian meridian) throws ParseException
+     {
+         final Element ensemble = parent.pullElement(mode, WKTKeywords.Ensemble);
+         if (ensemble == null) {
+             return null;
+         }
+         final String ensembleName = ensemble.pullString("name");
+         final Ellipsoid ellipsoid;
+         final boolean vertical;
+         if (datumType == GeodeticDatum.class) {
+             ellipsoid = parseEllipsoid(MANDATORY, ensemble);
+             vertical  = false;
+         } else if (datumType == Datum.class) {
+             // Unspecified datum type, use the presence of `ELLIPSOID` for detecting the geodetic case.
+             ellipsoid = parseEllipsoid(OPTIONAL, ensemble);
+             vertical  = (ellipsoid == null);
+         } else {
+             ellipsoid = null;
+             vertical  = (datumType == VerticalDatum.class);
+         }
+         final DatumFactory datumFactory = factories.getDatumFactory();
+         final var members = new ArrayList<D>();
+         try {
+             Element element = ensemble.pullElement(MANDATORY, WKTKeywords.Member);
+             do {
+                 final Datum member;
+                 try {
+                     final String name = element.pullString("name");
+                     final Map<String,Object> properties = parseAnchorAndClose(element, name);
+                     if (ellipsoid != null) {    // `memberType` may be `Datum` or `GeodeticDatum`
+                         member = datumFactory.createGeodeticDatum(properties, ellipsoid, meridian);
+                     } else if (vertical) {
 -                        member = datumFactory.createVerticalDatum(properties, (RealizationMethod) null);
++                        member = datumFactory.createVerticalDatum(properties, VerticalDatumTypes.fromDatum(name, null, null));
+                     } else if (datumType == TemporalDatum.class) {
 -                        member = datumFactory.createTemporalDatum(properties, (Temporal) null);
 -                    } else if (datumType == ParametricDatum.class) {
 -                        member = datumFactory.createParametricDatum(properties);
++                        member = datumFactory.createTemporalDatum(properties, null);
++                    } else if (datumType == DefaultParametricDatum.class) {
++                        member = MissingMethods.createParametricDatum(properties, datumFactory);
+                     } else if (datumType == EngineeringDatum.class) {
+                         member = datumFactory.createEngineeringDatum(properties);
+                     } else {
+                         // Should never happen because this method is private and we checked all calls.
+                         throw new AssertionError(datumType);
+                     }
+                 } catch (FactoryException exception) {
+                     throw element.parseFailed(exception);
+                 }
+                 members.add(datumType.cast(member));
+                 element = ensemble.pullElement(OPTIONAL, WKTKeywords.Member);
+             } while (element != null);
+             var accuracy = PositionalAccuracyConstant.ensemble(pullElementAsDouble(ensemble, WKTKeywords.EnsembleAccuracy, MANDATORY));
 -            return datumFactory.createDatumEnsemble(parseAnchorAndClose(ensemble, ensembleName), members, accuracy);
++            return MissingMethods.createDatumEnsemble(parseAnchorAndClose(ensemble, ensembleName), members, accuracy, datumFactory);
+         } catch (FactoryException exception) {
+             throw ensemble.parseFailed(exception);
+         }
+     }
+ 
+     /**
+      * Parses a {@code "Datum"} (WKT 2) element.
       *
       * The legacy WKT 1 pattern was:
       *
@@@ -1429,7 -1542,10 +1562,10 @@@
          }
          final DatumFactory datumFactory = factories.getDatumFactory();
          try {
-             return datumFactory.createGeodeticDatum(properties, ellipsoid, meridian);
+             if (epoch == null) {
+                 return datumFactory.createGeodeticDatum(properties, ellipsoid, meridian);
+             }
 -            return datumFactory.createGeodeticDatum(properties, ellipsoid, meridian, epoch);
++            return MissingMethods.createGeodeticDatum(properties, ellipsoid, meridian, epoch, datumFactory);
          } catch (FactoryException exception) {
              throw element.parseFailed(exception);
          }
@@@ -1469,9 -1586,13 +1606,13 @@@
          if (method == null) {
              method = VerticalDatumTypes.fromDatum(name, null, null);
          }
+         final Map<String, Object> properties = parseAnchorAndClose(element, name);
          final DatumFactory datumFactory = factories.getDatumFactory();
          try {
-             return datumFactory.createVerticalDatum(parseAnchorAndClose(element, name), method);
+             if (epoch == null) {
+                 return datumFactory.createVerticalDatum(properties, method);
+             }
 -            return datumFactory.createVerticalDatum(properties, method, epoch);
++            return MissingMethods.createVerticalDatum(properties, method, epoch, datumFactory);
          } catch (FactoryException exception) {
              throw element.parseFailed(exception);
          }
@@@ -1494,13 -1617,19 +1637,19 @@@
          if (element == null) {
              return null;
          }
-         final String  name   = element.pullString ("name");
-         final Element origin = element.pullElement(MANDATORY, WKTKeywords.TimeOrigin);
-         final Instant epoch  = origin .pullDate("origin");
-         origin.close(ignoredElements);
+         final String   name = element.pullString ("name");
+         final Element  origin = element.pullElement(OPTIONAL, WKTKeywords.TimeOrigin);
+         final Temporal epoch;
+         if (origin != null) {
+             epoch = origin .pullTime("origin");
+             origin.close(ignoredElements);
+         } else {
+             // Default to the date of signing of the Convention du Mètre (from ISO 19162:2019 specification)
+             epoch = LocalDate.of(1875, 5, 20);
+         }
          final DatumFactory datumFactory = factories.getDatumFactory();
          try {
 -            return datumFactory.createTemporalDatum(parseAnchorAndClose(element, name), epoch);
 +            return datumFactory.createTemporalDatum(parseAnchorAndClose(element, name), TemporalDate.toDate(epoch));
          } catch (FactoryException exception) {
              throw element.parseFailed(exception);
          }
@@@ -1629,6 -1756,7 +1775,7 @@@
           * An EngineeringCRS can be either a "normal" one (with a non-null datum), or a DerivedCRS.
           * In the latter case, the datum is null and we have instead DerivingConversion element from a base CRS.
           */
 -        DatumEnsemble<EngineeringDatum> ensemble = null;
++        DefaultDatumEnsemble<EngineeringDatum> ensemble = null;
          EngineeringDatum datum    = null;
          SingleCRS        baseCRS  = null;
          Conversion       fromBase = null;
@@@ -1670,7 -1800,7 +1819,7 @@@
                  properties.put(Legacy.DERIVED_TYPE_KEY, EngineeringCRS.class);
                  return crsFactory.createDerivedCRS(properties, baseCRS, fromBase, cs);
              }
-             return crsFactory.createEngineeringCRS(properties, datum, cs);
 -            return crsFactory.createEngineeringCRS(properties, datum, ensemble, cs);
++            return MissingMethods.createEngineeringCRS(properties, datum, ensemble, cs, crsFactory);
          } catch (FactoryException exception) {
              throw element.parseFailed(exception);
          }
@@@ -1865,18 -1994,24 +2012,24 @@@
                  warning(element, WKTKeywords.AngleUnit, Errors.formatInternational(
                          Errors.Keys.InconsistentUnitsForCS_1, angularUnit), null);
              }
-             final PrimeMeridian meridian = parsePrimeMeridian(OPTIONAL, element, isWKT1, longitudeUnit);
-             final GeodeticDatum datum = parseDatum(MANDATORY, element, meridian);
-             final Map<String,?> properties = parseMetadataAndClose(element, name, datum);
+             PrimeMeridian meridian = parsePrimeMeridian(OPTIONAL, element, isWKT1, longitudeUnit);
+             if (meridian == null) {
+                 meridian = greenwich();
+             }
+             final Temporal epoch = parseDynamic(element);
 -            final DatumEnsemble<GeodeticDatum> ensemble = parseEnsemble(OPTIONAL, element, GeodeticDatum.class, meridian);
++            final DefaultDatumEnsemble<GeodeticDatum> ensemble = parseEnsemble(OPTIONAL, element, GeodeticDatum.class, meridian);
+             final GeodeticDatum datum = parseDatum(ensemble == null ? MANDATORY : OPTIONAL, element, meridian, epoch);
+             final IdentifiedObject datumOrEnsemble = (datum != null) ? datum : ensemble;
+             final Map<String,?> properties = parseMetadataAndClose(element, name, datumOrEnsemble);
              if (cs instanceof EllipsoidalCS) {                                  // By far the most frequent case.
-                 return crsFactory.createGeographicCRS(properties, datum, (EllipsoidalCS) cs);
 -                return crsFactory.createGeographicCRS(properties, datum, ensemble, (EllipsoidalCS) cs);
++                return MissingMethods.createGeographicCRS(properties, datum, ensemble, (EllipsoidalCS) cs, crsFactory);
              }
              if (cs instanceof CartesianCS) {                                    // The second most frequent case.
-                 return crsFactory.createGeocentricCRS(properties, datum,
 -                return crsFactory.createGeodeticCRS(properties, datum, ensemble,
--                        Legacy.forGeocentricCRS((CartesianCS) cs, false));
++                return MissingMethods.createGeodeticCRS(properties, datum, ensemble,
++                        Legacy.forGeocentricCRS((CartesianCS) cs, false), crsFactory);
              }
              if (cs instanceof SphericalCS) {                                    // Not very common case.
-                 return crsFactory.createGeocentricCRS(properties, datum, (SphericalCS) cs);
 -                return crsFactory.createGeodeticCRS(properties, datum, ensemble, (SphericalCS) cs);
++                return MissingMethods.createGeodeticCRS(properties, datum, ensemble, (SphericalCS) cs, crsFactory);
              }
          } catch (FactoryException exception) {
              throw element.parseFailed(exception);
@@@ -1918,6 -2052,7 +2070,7 @@@
           * A VerticalCRS can be either a "normal" one (with a non-null datum), or a DerivedCRS of kind VerticalCRS.
           * In the latter case, the datum is null and we have instead DerivingConversion element from a BaseVertCRS.
           */
 -        DatumEnsemble<VerticalDatum> ensemble = null;
++        DefaultDatumEnsemble<VerticalDatum> ensemble = null;
          VerticalDatum datum    = null;
          SingleCRS     baseCRS  = null;
          Conversion    fromBase = null;
@@@ -1937,13 -2072,18 +2090,17 @@@
                  baseCRS = parseVerticalCRS(MANDATORY, element, true);
              }
          }
-         if (baseCRS == null) {                                                  // The most usual case.
-             datum = parseVerticalDatum(MANDATORY, element, isWKT1);
+         if (baseCRS == null) {      // The most usual case.
+             final Temporal epoch = parseDynamic(element);
+             ensemble = parseEnsemble(OPTIONAL, element, VerticalDatum.class, null);
+             datum = parseVerticalDatum(ensemble == null ? MANDATORY : OPTIONAL, element, epoch, isWKT1);
          }
+         final IdentifiedObject datumOrEnsemble = (datum != null) ? datum : ensemble;
          final CoordinateSystem cs;
          try {
-             cs = parseCoordinateSystem(element, WKTKeywords.vertical, 1, isWKT1, unit, false, datum.getVerticalDatumType());
-             final Map<String,?> properties = parseMetadataAndClose(element, name, datum);
 -            final RealizationMethod method = (datumOrEnsemble instanceof VerticalDatum)
 -                    ? ((VerticalDatum) datumOrEnsemble).getRealizationMethod().orElse(null) : null;
++            final var method = ((VerticalDatum) datumOrEnsemble).getVerticalDatumType();
+             cs = parseCoordinateSystem(element, WKTKeywords.vertical, 1, isWKT1, unit, false, method);
+             final Map<String,?> properties = parseMetadataAndClose(element, name, datumOrEnsemble);
              if (cs instanceof VerticalCS) {
                  final CRSFactory crsFactory = factories.getCRSFactory();
                  if (baseCRS != null) {
@@@ -1953,15 -2093,18 +2110,18 @@@
                   * The `parseVerticalDatum(…)` method may have been unable to resolve the realization method.
                   * 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.
+                  *
+                  * Note: we exclude the reconstruction of dynamic datum for simplicity in the use of the factory.
+                  * This block is a dirty trick anyway (a workaround for a metadata excluded from the WKT standard).
                   */
-                 if (datum.getVerticalDatumType() == VerticalDatumType.OTHER_SURFACE) {
 -                if (method == null && datum != null && !(datum instanceof DynamicReferenceFrame)) {
++                if (method == VerticalDatumType.OTHER_SURFACE && datum != null && !(datum instanceof DefaultVerticalDatum.Dynamic)) {
                      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);
                      }
                  }
-                 verticalCRS = crsFactory.createVerticalCRS(properties, datum, (VerticalCS) cs);
 -                verticalCRS = crsFactory.createVerticalCRS(properties, datum, ensemble, (VerticalCS) cs);
++                verticalCRS = MissingMethods.createVerticalCRS(properties, datum, ensemble, (VerticalCS) cs, crsFactory);
                  /*
                   * Some DefaultVerticalExtent objects may be waiting for the VerticalCRS before to complete
                   * their construction. If this is the case, try to complete them now.
@@@ -1999,6 -2142,7 +2159,7 @@@
           * A TemporalCRS can be either a "normal" one (with a non-null datum), or a DerivedCRS of kind TemporalCRS.
           * In the latter case, the datum is null and we have instead DerivingConversion element from a BaseTimeCRS.
           */
 -        DatumEnsemble<TemporalDatum> ensemble = null;
++        DefaultDatumEnsemble<TemporalDatum> ensemble = null;
          TemporalDatum datum    = null;
          SingleCRS     baseCRS  = null;
          Conversion    fromBase = null;
@@@ -2030,7 -2176,7 +2193,7 @@@
                  if (baseCRS != null) {
                      return crsFactory.createDerivedCRS(properties, baseCRS, fromBase, cs);
                  }
-                 return crsFactory.createTemporalCRS(properties, datum, (TimeCS) cs);
 -                return crsFactory.createTemporalCRS(properties, datum, ensemble, (TimeCS) cs);
++                return MissingMethods.createTemporalCRS(properties, datum, ensemble, (TimeCS) cs, crsFactory);
              }
          } catch (FactoryException exception) {
              throw element.parseFailed(exception);
@@@ -2060,9 -2206,10 +2223,10 @@@
           * A ParametricCRS can be either a "normal" one (with a non-null datum), or a DerivedCRS of kind ParametricCRS.
           * In the latter case, the datum is null and we have instead DerivingConversion element from a BaseParametricCRS.
           */
 -        DatumEnsemble<ParametricDatum> ensemble = null;
 -        ParametricDatum datum    = null;
++        DefaultDatumEnsemble<DefaultParametricDatum> ensemble = null;
 +        DefaultParametricDatum datum = null;
-         SingleCRS baseCRS = null;
-         Conversion fromBase = null;
+         SingleCRS       baseCRS  = null;
+         Conversion      fromBase = null;
          if (!isBaseCRS) {
              /*
               * UNIT[…] in DerivedCRS parameters are mandatory according ISO 19162 and the specification does not said
@@@ -2079,19 -2226,21 +2243,21 @@@
                  baseCRS = parseParametricCRS(MANDATORY, element, true);
              }
          }
-         if (baseCRS == null) {                                                  // The most usual case.
-             datum = parseParametricDatum(MANDATORY, element);
+         if (baseCRS == null) {      // The most usual case.
 -            ensemble = parseEnsemble(OPTIONAL, element, ParametricDatum.class, null);
++            ensemble = parseEnsemble(OPTIONAL, element, DefaultParametricDatum.class, null);
+             datum = parseParametricDatum(ensemble == null ? MANDATORY : OPTIONAL, element);
          }
+         final IdentifiedObject datumOrEnsemble = (datum != null) ? datum : ensemble;
          final CoordinateSystem cs;
          try {
              cs = parseCoordinateSystem(element, WKTKeywords.parametric, 1, false, unit, false, null);
-             final Map<String,?> properties = parseMetadataAndClose(element, name, datum);
+             final Map<String,?> properties = parseMetadataAndClose(element, name, datumOrEnsemble);
 -            if (cs instanceof ParametricCS) {
 +            if (cs instanceof DefaultParametricCS) {
                  final CRSFactory crsFactory = factories.getCRSFactory();
                  if (baseCRS != null) {
                      return crsFactory.createDerivedCRS(properties, baseCRS, fromBase, cs);
                  }
-                 return MissingMethods.createParametricCRS(properties, datum, (DefaultParametricCS) cs, crsFactory);
 -                return crsFactory.createParametricCRS(properties, datum, ensemble, (ParametricCS) cs);
++                return MissingMethods.createParametricCRS(properties, datum, ensemble, (DefaultParametricCS) cs, crsFactory);
              }
          } catch (FactoryException exception) {
              throw element.parseFailed(exception);
@@@ -2260,10 -2407,12 +2424,12 @@@
                  buffer.append(number);
                  axes[i] = csFactory.createCoordinateSystemAxis(
                          singletonMap(CoordinateSystemAxis.NAME_KEY, buffer.toString()),
 -                        number, AxisDirection.UNSPECIFIED, Units.UNITY);
 +                        number, AxisDirections.UNSPECIFIED, Units.UNITY);
              }
              final Map<String,Object> properties = parseMetadataAndClose(element, name, baseCRS);
-             final Map<String,Object> axisName = singletonMap(CoordinateSystem.NAME_KEY, AxisDirections.appendTo(new StringBuilder("CS"), axes));
+             final Map<String,Object> axisName = singletonMap(
+                     CoordinateSystem.NAME_KEY,
+                     AxisDirections.appendTo(new StringBuilder("CS"), axes));
              final var derivedCS = new AbstractCS(axisName, axes);
              /*
               * Creates a derived CRS from the information found in a WKT 1 {@code FITTED_CS} element.
diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/pending/geoapi/referencing/MissingMethods.java
index 091edd126e,0000000000..8b9e51db73
mode 100644,000000..100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/pending/geoapi/referencing/MissingMethods.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/pending/geoapi/referencing/MissingMethods.java
@@@ -1,165 -1,0 +1,292 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one or more
 + * contributor license agreements.  See the NOTICE file distributed with
 + * this work for additional information regarding copyright ownership.
 + * The ASF licenses this file to You under the Apache License, Version 2.0
 + * (the "License"); you may not use this file except in compliance with
 + * the License.  You may obtain a copy of the License at
 + *
 + *     http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing, software
 + * distributed under the License is distributed on an "AS IS" BASIS,
 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 + * See the License for the specific language governing permissions and
 + * limitations under the License.
 + */
 +package org.apache.sis.pending.geoapi.referencing;
 +
 +import java.util.Map;
++import java.util.Collection;
 +import java.util.function.Function;
++import java.time.temporal.Temporal;
 +import org.opengis.util.FactoryException;
++import org.opengis.referencing.cs.*;
 +import org.opengis.referencing.crs.*;
 +import org.opengis.referencing.datum.*;
- import org.opengis.referencing.cs.CSFactory;
- import org.opengis.referencing.cs.CoordinateSystemAxis;
++import org.opengis.metadata.quality.PositionalAccuracy;
 +import org.apache.sis.referencing.crs.DefaultVerticalCRS;
 +import org.apache.sis.referencing.crs.DefaultTemporalCRS;
 +import org.apache.sis.referencing.crs.DefaultEngineeringCRS;
++import org.apache.sis.referencing.crs.DefaultParametricCRS;
 +import org.apache.sis.referencing.cs.DefaultParametricCS;
 +import org.apache.sis.referencing.datum.DefaultDatumEnsemble;
 +import org.apache.sis.referencing.datum.DefaultParametricDatum;
 +import org.apache.sis.referencing.factory.GeodeticObjectFactory;
- import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
 +
 +
 +/**
 + * Placeholder for methods missing in the GeoAPI 3.0 interface.
 + */
 +public final class MissingMethods {
 +    /**
 +     * To be set by static {@code AbstractCRS} initializer.
 +     */
 +    public static volatile Function<CoordinateReferenceSystem, DefaultDatumEnsemble<?>> datumEnsemble;
 +
 +    /**
 +     * To be set by static {@code DefaultGeodeticCRS} initializer.
 +     */
 +    public static volatile Function<GeodeticCRS, DefaultDatumEnsemble<GeodeticDatum>> geodeticDatumEnsemble;
 +
 +    private MissingMethods() {
 +    }
 +
 +    /**
 +     * Returns the datum ensemble of an arbitrary CRS.
 +     *
 +     * @param  datum  the CRS from which to get a datum ensemble, or {@code null} if none.
 +     * @return the datum ensemble, or {@code null} if none.
 +     */
 +    public static DefaultDatumEnsemble<?> getDatumEnsemble(final CoordinateReferenceSystem crs) {
 +        final var m = datumEnsemble;
 +        return (m != null) ? m.apply(crs) : null;
 +    }
 +
 +    /**
 +     * Returns the datum ensemble of an arbitrary geodetic CRS.
 +     *
 +     * @param  datum  the CRS from which to get a datum ensemble, or {@code null} if none.
 +     * @return the datum ensemble, or {@code null} if none.
 +     */
 +    public static DefaultDatumEnsemble<GeodeticDatum> getDatumEnsemble(final GeodeticCRS crs) {
 +        final var m = geodeticDatumEnsemble;
 +        return (m != null) ? m.apply(crs) : null;
 +    }
 +
 +    /**
 +     * Returns the datum ensemble of an arbitrary vertical CRS.
 +     *
 +     * @param  datum  the CRS from which to get a datum ensemble, or {@code null} if none.
 +     * @return the datum ensemble, or {@code null} if none.
 +     */
 +    public static DefaultDatumEnsemble<VerticalDatum> getDatumEnsemble(final VerticalCRS crs) {
 +        return (crs instanceof DefaultVerticalCRS) ? ((DefaultVerticalCRS) crs).getDatumEnsemble() : null;
 +    }
 +
 +    /**
 +     * Returns the datum ensemble of an arbitrary temporal CRS.
 +     *
 +     * @param  datum  the CRS from which to get a datum ensemble, or {@code null} if none.
 +     * @return the datum ensemble, or {@code null} if none.
 +     */
 +    public static DefaultDatumEnsemble<TemporalDatum> getDatumEnsemble(final TemporalCRS crs) {
 +        return (crs instanceof DefaultTemporalCRS) ? ((DefaultTemporalCRS) crs).getDatumEnsemble() : null;
 +    }
 +
 +    /**
 +     * Returns the datum ensemble of an arbitrary engineering CRS.
 +     *
 +     * @param  datum  the CRS from which to get a datum ensemble, or {@code null} if none.
 +     * @return the datum ensemble, or {@code null} if none.
 +     */
 +    public static DefaultDatumEnsemble<EngineeringDatum> getDatumEnsemble(final EngineeringCRS crs) {
 +        return (crs instanceof DefaultEngineeringCRS) ? ((DefaultEngineeringCRS) crs).getDatumEnsemble() : null;
 +    }
 +
++    /**
++     * Creates a datum ensemble. This method requires the <abbr>SIS</abbr> factory
++     * since datum ensembles were not available in GeoAPI 3.0.
++     *
++     * @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.
++     * @param  accuracy    inaccuracy introduced through use of the given collection of datums.
++     * @return the datum ensemble for the given properties.
++     * @throws FactoryException if the object creation failed.
++     */
++    public static <D extends Datum> DefaultDatumEnsemble<D> createDatumEnsemble(
++            final Map<String,?> properties,
++            final Collection<? extends D> members,
++            final PositionalAccuracy accuracy,
++            DatumFactory factory) throws FactoryException
++    {
++        if (!(factory instanceof GeodeticObjectFactory)) {
++            factory = GeodeticObjectFactory.provider();
++        }
++        return ((GeodeticObjectFactory) factory).createDatumEnsemble(properties, members, accuracy);
++    }
++
 +    /**
 +     * Creates a parametric CS. This method requires the <abbr>SIS</abbr> factory
 +     * since parametric CRS were not available in GeoAPI 3.0.
 +     *
 +     * @param  properties  the coordinate system name, and optionally other properties.
 +     * @param  axis        the axis of the parametric coordinate system.
 +     * @param  factory     the factory to use for creating the coordinate system.
 +     * @return a parametric coordinate system using the given axes.
 +     * @throws FactoryException if the parametric object creation failed.
 +     */
 +    public static DefaultParametricCS createParametricCS(final Map<String,?> properties, final CoordinateSystemAxis axis,
 +            CSFactory factory) throws FactoryException
 +    {
 +        if (!(factory instanceof GeodeticObjectFactory)) {
 +            factory = GeodeticObjectFactory.provider();
 +        }
 +        return ((GeodeticObjectFactory) factory).createParametricCS(properties, axis);
 +    }
 +
++    /**
++     * Creates a parametric <abbr>CRS</abbr>. This method requires the <abbr>SIS</abbr> factory
++     * since parametric <abbr>CRS</abbr> were not available in GeoAPI 3.0.
++     *
++     * @param  properties  the coordinate reference system name, and optionally other properties.
++     * @param  datum       the parametric datum.
++     * @param  cs          the parametric coordinate system.
++     * @param  factory     the factory to use for creating the coordinate reference system.
++     * @return a parametric coordinate system using the given axes.
++     * @throws FactoryException if the parametric object creation failed.
++     */
++    public static DefaultParametricCRS createParametricCRS(final Map<String,?> properties, final DefaultParametricDatum datum,
++            final DefaultParametricCS cs, CRSFactory factory) throws FactoryException
++    {
++        if (!(factory instanceof GeodeticObjectFactory)) {
++            factory = GeodeticObjectFactory.provider();
++        }
++        return ((GeodeticObjectFactory) factory).createParametricCRS(properties, datum, cs);
++    }
++
 +    /**
 +     * Creates a parametric datum. This method requires the <abbr>SIS</abbr> factory
 +     * since parametric <abbr>CRS</abbr> were not available in GeoAPI 3.0.
 +     *
 +     * @param  properties  the datum name, and optionally other properties.
 +     * @param  factory     the factory to use for creating the datum.
 +     * @return a parametric datum using the given name.
 +     * @throws FactoryException if the parametric object creation failed.
 +     */
 +    public static DefaultParametricDatum createParametricDatum(final Map<String,?> properties, DatumFactory factory)
 +            throws FactoryException
 +    {
 +        if (!(factory instanceof GeodeticObjectFactory)) {
 +            factory = GeodeticObjectFactory.provider();
 +        }
 +        return ((GeodeticObjectFactory) factory).createParametricDatum(properties);
 +    }
 +
-     /**
-      * Creates a parametric <abbr>CRS</abbr>. This method requires the <abbr>SIS</abbr> factory
-      * since parametric <abbr>CRS</abbr> were not available in GeoAPI 3.0.
-      *
-      * @param  properties  the coordinate reference system name, and optionally other properties.
-      * @param  datum       the parametric datum.
-      * @param  cs          the parametric coordinate system.
-      * @param  factory     the factory to use for creating the coordinate reference system.
-      * @return a parametric coordinate system using the given axes.
-      * @throws FactoryException if the parametric object creation failed.
-      */
-     public static SingleCRS createParametricCRS(final Map<String,?> properties, final DefaultParametricDatum datum,
-             final DefaultParametricCS cs, CRSFactory factory) throws FactoryException
++    public static VerticalDatum createVerticalDatum(final Map<String,?> properties, final VerticalDatumType type,
++            final Temporal epoch, DatumFactory factory) throws FactoryException
++    {
++        if (!(factory instanceof GeodeticObjectFactory)) {
++            factory = GeodeticObjectFactory.provider();
++        }
++        return ((GeodeticObjectFactory) factory).createVerticalDatum(properties, type, epoch);
++    }
++
++    public static GeodeticDatum createGeodeticDatum(final Map<String,?> properties, final Ellipsoid ellipsoid,
++            final PrimeMeridian primeMeridian, final Temporal epoch, DatumFactory factory) throws FactoryException
 +    {
 +        if (!(factory instanceof GeodeticObjectFactory)) {
 +            factory = GeodeticObjectFactory.provider();
 +        }
-         try {
-             return ((GeodeticObjectFactory) factory).createParametricCRS(properties, datum, cs);
-         } catch (ClassCastException e) {
-             throw new InvalidGeodeticParameterException(e.toString(), e);
++        return ((GeodeticObjectFactory) factory).createGeodeticDatum(properties, ellipsoid, primeMeridian, epoch);
++    }
++
++    public static GeographicCRS createGeographicCRS(final Map<String,?> properties, final GeodeticDatum datum,
++            final DefaultDatumEnsemble<GeodeticDatum> ensemble, final EllipsoidalCS cs, CRSFactory factory)
++            throws FactoryException
++    {
++        if (ensemble == null) {
++            return factory.createGeographicCRS(properties, datum, cs);
++        }
++        if (!(factory instanceof GeodeticObjectFactory)) {
++            factory = GeodeticObjectFactory.provider();
++        }
++        return ((GeodeticObjectFactory) factory).createGeographicCRS(properties, datum, ensemble, cs);
++    }
++
++    public static GeodeticCRS createGeodeticCRS(final Map<String,?> properties, final GeodeticDatum datum,
++            final DefaultDatumEnsemble<GeodeticDatum> ensemble, final SphericalCS cs, CRSFactory factory)
++            throws FactoryException
++    {
++        if (ensemble == null) {
++            return factory.createGeocentricCRS(properties, datum, cs);
++        }
++        if (!(factory instanceof GeodeticObjectFactory)) {
++            factory = GeodeticObjectFactory.provider();
++        }
++        return ((GeodeticObjectFactory) factory).createGeodeticCRS(properties, datum, ensemble, cs);
++    }
++
++    public static GeodeticCRS createGeodeticCRS(final Map<String,?> properties, final GeodeticDatum datum,
++            final DefaultDatumEnsemble<GeodeticDatum> ensemble, final CartesianCS cs, CRSFactory factory)
++            throws FactoryException
++    {
++        if (ensemble == null) {
++            return factory.createGeocentricCRS(properties, datum, cs);
++        }
++        if (!(factory instanceof GeodeticObjectFactory)) {
++            factory = GeodeticObjectFactory.provider();
++        }
++        return ((GeodeticObjectFactory) factory).createGeodeticCRS(properties, datum, ensemble, cs);
++    }
++
++    public static VerticalCRS createVerticalCRS(final Map<String,?> properties, final VerticalDatum datum,
++            final DefaultDatumEnsemble<VerticalDatum> ensemble, final VerticalCS cs, CRSFactory factory)
++            throws FactoryException
++    {
++        if (ensemble == null) {
++            return factory.createVerticalCRS(properties, datum, cs);
++        }
++        if (!(factory instanceof GeodeticObjectFactory)) {
++            factory = GeodeticObjectFactory.provider();
++        }
++        return ((GeodeticObjectFactory) factory).createVerticalCRS(properties, datum, ensemble, cs);
++    }
++
++    public static TemporalCRS createTemporalCRS(final Map<String,?> properties, final TemporalDatum datum,
++            final DefaultDatumEnsemble<TemporalDatum> ensemble, final TimeCS cs, CRSFactory factory)
++            throws FactoryException
++    {
++        if (ensemble == null) {
++            return factory.createTemporalCRS(properties, datum, cs);
++        }
++        if (!(factory instanceof GeodeticObjectFactory)) {
++            factory = GeodeticObjectFactory.provider();
++        }
++        return ((GeodeticObjectFactory) factory).createTemporalCRS(properties, datum, ensemble, cs);
++    }
++
++    public static DefaultParametricCRS createParametricCRS(final Map<String,?> properties, final DefaultParametricDatum datum,
++            final DefaultDatumEnsemble<DefaultParametricDatum> ensemble, final DefaultParametricCS cs, CRSFactory factory)
++            throws FactoryException
++    {
++        if (!(factory instanceof GeodeticObjectFactory)) {
++            factory = GeodeticObjectFactory.provider();
++        }
++        return ((GeodeticObjectFactory) factory).createParametricCRS(properties, datum, ensemble, cs);
++    }
++
++    public static EngineeringCRS createEngineeringCRS(final Map<String,?> properties, final EngineeringDatum datum,
++            final DefaultDatumEnsemble<EngineeringDatum> ensemble, final CoordinateSystem cs, CRSFactory factory)
++            throws FactoryException
++    {
++        if (ensemble == null) {
++            return factory.createEngineeringCRS(properties, datum, cs);
++        }
++        if (!(factory instanceof GeodeticObjectFactory)) {
++            factory = GeodeticObjectFactory.provider();
 +        }
++        return ((GeodeticObjectFactory) factory).createEngineeringCRS(properties, datum, ensemble, cs);
 +    }
 +}
diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java
index 7a216d89fa,52b4d3ce8f..48721a0892
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java
@@@ -32,10 -33,12 +33,11 @@@ import org.opengis.referencing.crs.Coor
  import org.apache.sis.referencing.AbstractReferenceSystem;
  import org.apache.sis.referencing.cs.AbstractCS;
  import org.apache.sis.referencing.cs.AxesConvention;
+ import org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis;
  import org.apache.sis.referencing.datum.AbstractDatum;
- import org.apache.sis.referencing.privy.WKTUtilities;
 -import org.apache.sis.referencing.datum.DefaultDatumEnsemble;
  import org.apache.sis.referencing.privy.ReferencingUtilities;
  import org.apache.sis.metadata.privy.ImplementationHelper;
+ import org.apache.sis.io.wkt.FormattableObject;
  import org.apache.sis.io.wkt.Convention;
  import org.apache.sis.io.wkt.Formatter;
  import org.apache.sis.util.Utilities;
@@@ -285,60 -286,6 +287,32 @@@ public class AbstractCRS extends Abstra
          return CoordinateReferenceSystem.class;
      }
  
-     /**
-      * Returns the datum or a view of the ensemble as a datum, or {@code null} if none.
-      * The {@code legacy} argument is usually {@code false}, except when formatting in a legacy <abbr>WKT</abbr> format.
-      *
-      * @param  legacy  whether to allow a view of the ensemble as a datum for interoperability with legacy standards.
-      * @return the datum, or {@code null} if none.
-      */
-     Datum getDatumOrEnsemble(final boolean legacy) {
-         /*
-          * User could provide his own CRS implementation outside this SIS package, so we have
-          * to check for SingleCRS interface. But all SIS classes override this implementation.
-          */
-         if (this instanceof SingleCRS) {
-             final var crs = (SingleCRS) this;
-             final Datum datum = crs.getDatum();
-             if (datum != null) {
-                 return datum;
-             }
-             if (legacy) {
-                 final var ensemble = getDatumEnsemble(crs);
-                 if (ensemble instanceof Datum) {
-                     return (Datum) ensemble;
-                 }
-             }
-         }
-         return null;
-     }
- 
 +    /**
 +     * Returns the datum ensemble of the given <abbr>CRS</abbr>.
 +     *
 +     * @param  crs  the <abbr>CRS</abbr> from which to get the datum ensemble.
 +     * @return the datum ensemble, or {@code null} if none.
 +     */
 +    static DefaultDatumEnsemble<?> getDatumEnsemble(final CoordinateReferenceSystem crs) {
 +        return (crs instanceof AbstractCRS) ? ((AbstractCRS) crs).getDatumEnsemble() : null;
 +    }
 +
 +    /**
 +     * Returns the datum ensemble.
 +     *
 +     * @return the datum ensemble, or {@code null} if none.
 +     */
 +    DefaultDatumEnsemble<?> getDatumEnsemble() {
 +        return null;
 +    }
 +
 +    /**
 +     * Initializes the handler for getting datum ensemble of an arbitrary CRS.
 +     */
 +    static {
 +        MissingMethods.datumEnsemble = AbstractCRS::getDatumEnsemble;
 +    }
 +
      /**
       * Returns the coordinate system.
       *
@@@ -512,6 -456,56 +483,53 @@@
          return keyword;
      }
  
+     /**
+      * Formats the datum or a view of the ensemble as a datum.
+      * Subclasses should override for invoking the static {@code formatDatum(…)} with more specifiy types.
+      */
+     void formatDatum(final Formatter formatter) {
+         /*
+          * User could provide his own CRS implementation outside this SIS package, so we have
+          * to check for SingleCRS interface. But all SIS classes override this implementation.
+          */
+         if (this instanceof SingleCRS) {
+             final var sc = (SingleCRS) this;
 -            formatDatum(formatter, sc, sc.getDatum(), AbstractDatum::castOrCopy, (crs) -> {
 -                final DatumEnsemble<?> ensemble = crs.getDatumEnsemble();
 -                return (ensemble instanceof Datum) ? (Datum) ensemble : null;
 -            });
++            formatDatum(formatter, sc, sc.getDatum(), AbstractDatum::castOrCopy, AbstractCRS::getDatumEnsemble);
+         }
+     }
+ 
+     /**
+      * Formats the datum or a view of the ensemble as a datum.
+      *
+      * @param  <D>            the type of datum or ensemble members.
+      * @param  formatter      the formatter where to write the datum.
+      * @param  crs            the coordinate reference system.
+      * @param  datum          the datum to format, or {@code null} if none.
+      * @param  toFormattable  the function to invoke for converting the datum to a formattable object.
+      * @param  asDatum        the function to invoke for getting an ensemble viewed as a datum.
+      */
+     static <C extends SingleCRS, D extends Datum> void formatDatum(
+             final Formatter formatter,
+             final C crs,
+             final D datum,
+             final Function<D, FormattableObject> toFormattable,
+             final Function<C, D> asDatum)
+     {
+         final boolean supportsDynamic = formatter.getConvention().supports(Convention.WKT2_2019);
+         if (datum != null) {
+             if (supportsDynamic) {
+                 formatter.append(DynamicCRS.createIfDynamic(datum));
+                 formatter.newLine();
+             }
+             formatter.appendFormattable(datum, toFormattable);
+         } else if (supportsDynamic) {
 -            formatter.appendFormattable(crs.getDatumEnsemble(), DefaultDatumEnsemble::castOrCopy);
++            formatter.append(getDatumEnsemble(crs));
+         } else {
+             // Apply `toFormattable` unconditionally for forcing a conversion of ensemble to datum.
+             formatter.append(toFormattable.apply(asDatum.apply(crs)));
+         }
+     }
+ 
      /**
       * Returns {@code true} if the given formatter is in the process of formatting the base CRS of an
       * {@link AbstractDerivedCRS}. In such case, the coordinate system axes shall not be formatted.
diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
index 40f51712e6,ad1d8ec2b7..6964465c93
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
@@@ -429,22 -430,10 +429,10 @@@ public class DefaultDerivedCRS extends 
       * @since 1.5
       */
      @Override
 -    public DatumEnsemble<?> getDatumEnsemble() {
 -        return getBaseCRS().getDatumEnsemble();
 +    public DefaultDatumEnsemble<?> getDatumEnsemble() {
 +        return getDatumEnsemble(getBaseCRS());
      }
  
-     /**
-      * Returns the datum or a view of the ensemble as a datum.
-      */
-     @Override
-     Datum getDatumOrEnsemble(final boolean legacy) {
-         final SingleCRS baseCRS = getBaseCRS();
-         if (baseCRS instanceof AbstractCRS) {
-             return ((AbstractCRS) baseCRS).getDatumOrEnsemble(legacy);
-         }
-         return super.getDatumOrEnsemble(legacy);
-     }
- 
      /**
       * Returns the CRS on which the conversion is applied.
       * This CRS defines the {@linkplain #getDatum() datum} of this CRS and (at least implicitly)
@@@ -691,11 -678,11 +677,6 @@@
              return (GeodeticDatum) super.getDatum();
          }
  
-         /** Returns the datum or a view of the ensemble as a datum. */
-         @Override GeodeticDatum getDatumOrEnsemble(final boolean legacy) {
-             return legacy ? DatumOrEnsemble.asDatum((GeodeticCRS) getBaseCRS()) : getDatum();
 -        /** Returns the datum ensemble of the base geodetic CRS. */
 -        @Override public DatumEnsemble<GeodeticDatum> getDatumEnsemble() {
 -            return ((GeodeticCRS) getBaseCRS()).getDatumEnsemble();
--        }
--
          /** Returns a coordinate reference system of the same type as this CRS but with different axes. */
          @Override AbstractCRS createSameType(final AbstractCS derivedCS) {
              return new Geodetic(this, derivedCS);
@@@ -747,11 -737,11 +731,6 @@@
              return (VerticalDatum) super.getDatum();
          }
  
-         /** Returns the datum or a view of the ensemble as a datum. */
-         @Override VerticalDatum getDatumOrEnsemble(final boolean legacy) {
-             return legacy ? DatumOrEnsemble.asDatum((VerticalCRS) getBaseCRS()) : getDatum();
 -        /** Returns the datum ensemble of the base vertical CRS. */
 -        @Override public DatumEnsemble<VerticalDatum> getDatumEnsemble() {
 -            return ((VerticalCRS) getBaseCRS()).getDatumEnsemble();
--        }
--
          /** Returns the coordinate system given at construction time. */
          @Override public VerticalCS getCoordinateSystem() {
              return (VerticalCS) super.getCoordinateSystem();
@@@ -808,11 -798,11 +787,6 @@@
              return (TemporalDatum) super.getDatum();
          }
  
-         /** Returns the datum or a view of the ensemble as a datum. */
-         @Override TemporalDatum getDatumOrEnsemble(final boolean legacy) {
-             return legacy ? DatumOrEnsemble.asDatum((TemporalCRS) getBaseCRS()) : getDatum();
 -        /** Returns the datum ensemble of the base temporal CRS. */
 -        @Override public DatumEnsemble<TemporalDatum> getDatumEnsemble() {
 -            return ((TemporalCRS) getBaseCRS()).getDatumEnsemble();
--        }
--
          /** Returns the coordinate system given at construction time. */
          @Override public TimeCS getCoordinateSystem() {
              return (TimeCS) super.getCoordinateSystem();
@@@ -865,18 -855,18 +839,13 @@@
          }
  
          /** Returns the datum of the base parametric CRS. */
 -        @Override public ParametricDatum getDatum() {
 -            return (ParametricDatum) super.getDatum();
 -        }
 -
 -        /** Returns the datum ensemble of the base parametric CRS. */
 -        @Override public DatumEnsemble<ParametricDatum> getDatumEnsemble() {
 -            return ((ParametricCRS) getBaseCRS()).getDatumEnsemble();
 +        @Override public DefaultParametricDatum getDatum() {
 +            return (DefaultParametricDatum) super.getDatum();
          }
  
-         /** Returns the datum or a view of the ensemble as a datum. */
-         @Override DefaultParametricDatum getDatumOrEnsemble(final boolean legacy) {
-             return getDatum();
-         }
- 
          /** Returns the coordinate system given at construction time. */
 -        @Override public ParametricCS getCoordinateSystem() {
 -            return (ParametricCS) super.getCoordinateSystem();
 +        @Override public DefaultParametricCS getCoordinateSystem() {
 +            return (DefaultParametricCS) super.getCoordinateSystem();
          }
  
          /** Returns a coordinate reference system of the same type as this CRS but with different axes. */
@@@ -933,11 -923,11 +902,6 @@@
              return (EngineeringDatum) super.getDatum();
          }
  
-         /** Returns the datum or a view of the ensemble as a datum. */
-         @Override EngineeringDatum getDatumOrEnsemble(final boolean legacy) {
-             return legacy ? DatumOrEnsemble.asDatum((EngineeringCRS) getBaseCRS()) : getDatum();
 -        /** Returns the datum ensemble of the base engineering CRS. */
 -        @Override public DatumEnsemble<EngineeringDatum> getDatumEnsemble() {
 -            return ((EngineeringCRS) getBaseCRS()).getDatumEnsemble();
--        }
--
          /** Returns a coordinate reference system of the same type as this CRS but with different axes. */
          @Override AbstractCRS createSameType(final AbstractCS derivedCS) {
              return new Engineering(this, derivedCS);
diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
index 2f6132417f,7071186029..be7105f12e
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
@@@ -43,12 -44,10 +44,11 @@@ import org.apache.sis.referencing.privy
  import org.apache.sis.util.resources.Errors;
  import org.apache.sis.io.wkt.Convention;
  import org.apache.sis.io.wkt.Formatter;
- import org.apache.sis.measure.Units;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.referencing.datum.DatumEnsemble;
 -import org.opengis.metadata.Identifier;
 +// Specific to the main branch:
 +import org.opengis.referencing.ReferenceIdentifier;
 +import org.apache.sis.referencing.datum.DefaultDatumEnsemble;
 +import org.apache.sis.pending.geoapi.referencing.MissingMethods;
  
  
  /**
@@@ -161,23 -160,6 +161,14 @@@ class DefaultGeodeticCRS extends Abstra
          return super.getDatum();
      }
  
 +    /**
 +     * Initializes the handler for getting datum ensemble of an arbitrary CRS.
 +     */
 +    static {
 +        MissingMethods.geodeticDatumEnsemble = (crs) ->
 +                (crs instanceof DefaultGeodeticCRS) ? ((DefaultGeodeticCRS) crs).getDatumEnsemble() : null;
 +    }
 +
-     /**
-      * Returns the datum or a view of the ensemble as a datum.
-      * The {@code legacy} argument tells whether this method is invoked for formatting in a legacy <abbr>WKT</abbr> format.
-      */
-     @Override
-     final GeodeticDatum getDatumOrEnsemble(final boolean legacy) {
-         return legacy ? DatumOrEnsemble.asDatum(this) : getDatum();
-     }
- 
      /**
       * Returns a coordinate reference system of the same type as this CRS but with different axes.
       * This method shall be overridden by all {@code DefaultGeodeticCRS} subclasses in this package.
diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultParametricCRS.java
index efb669c751,285fc6cb3e..44ce14eeec
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultParametricCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultParametricCRS.java
@@@ -23,13 -23,15 +23,12 @@@ import jakarta.xml.bind.annotation.XmlT
  import org.apache.sis.referencing.privy.WKTKeywords;
  import org.apache.sis.referencing.cs.AxesConvention;
  import org.apache.sis.referencing.cs.AbstractCS;
--import org.apache.sis.referencing.datum.DatumOrEnsemble;
 -import org.apache.sis.referencing.datum.DefaultParametricDatum;
  import org.apache.sis.io.wkt.Formatter;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.referencing.cs.ParametricCS;
 -import org.opengis.referencing.crs.ParametricCRS;
 -import org.opengis.referencing.datum.ParametricDatum;
 -import org.opengis.referencing.datum.DatumEnsemble;
 +// Specific to the main branch:
 +import org.apache.sis.referencing.cs.DefaultParametricCS;
 +import org.apache.sis.referencing.datum.DefaultParametricDatum;
 +import org.apache.sis.referencing.datum.DefaultDatumEnsemble;
  
  
  /**
diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
index 5aa77cc06b,481f445d92..97b3a25f21
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
@@@ -247,19 -246,10 +246,10 @@@ public class DefaultProjectedCRS extend
       * @since 1.5
       */
      @Override
 -    public DatumEnsemble<GeodeticDatum> getDatumEnsemble() {
 -        return getBaseCRS().getDatumEnsemble();
 +    public DefaultDatumEnsemble<GeodeticDatum> getDatumEnsemble() {
 +        return (DefaultDatumEnsemble<GeodeticDatum>) getDatumEnsemble(getBaseCRS());
      }
  
-     /**
-      * Returns the datum or a view of the ensemble as a datum.
-      * The {@code legacy} argument tells whether this method is invoked for formatting in a legacy <abbr>WKT</abbr> format.
-      */
-     @Override
-     final GeodeticDatum getDatumOrEnsemble(final boolean legacy) {
-         return legacy ? DatumOrEnsemble.asDatum(getBaseCRS()) : getDatum();
-     }
- 
      /**
       * Returns the geographic CRS on which the map projection is applied.
       * This CRS defines the {@linkplain #getDatum() datum} of this CRS and (at least implicitly)
diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DynamicCRS.java
index 0000000000,23290ce2aa..1d87227402
mode 000000,100644..100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DynamicCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DynamicCRS.java
@@@ -1,0 -1,73 +1,77 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one or more
+  * contributor license agreements.  See the NOTICE file distributed with
+  * this work for additional information regarding copyright ownership.
+  * The ASF licenses this file to You under the Apache License, Version 2.0
+  * (the "License"); you may not use this file except in compliance with
+  * the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS,
+  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  * See the License for the specific language governing permissions and
+  * limitations under the License.
+  */
+ package org.apache.sis.referencing.crs;
+ 
+ import java.time.temporal.Temporal;
+ import org.opengis.referencing.datum.Datum;
+ import org.apache.sis.io.wkt.Formatter;
+ import org.apache.sis.io.wkt.FormattableObject;
+ import org.apache.sis.referencing.internal.Epoch;
+ import org.apache.sis.referencing.privy.WKTKeywords;
+ 
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.referencing.datum.DynamicReferenceFrame;
++// Specific to the main branch:
++import org.apache.sis.referencing.datum.DefaultGeodeticDatum;
++import org.apache.sis.referencing.datum.DefaultVerticalDatum;
+ 
+ 
+ /**
+  * An element inserted in the <abbr>WKT</abbr> formatting of dynamic <abbr>CRS</abbr>.
+  *
+  * @todo {@code MODEL} sub-element is not yet supported.
+  *
+  * @author  Martin Desruisseaux (Geomatys)
+  */
+ final class DynamicCRS extends FormattableObject {
+     /**
+      * The reference frame epoch.
+      */
+     private final Temporal epoch;
+ 
+     /**
+      * Creates a new element.
+      *
+      * @param  epoch  the reference frame epoch.
+      */
+     private DynamicCRS(final Temporal epoch) {
+         this.epoch = epoch;
+     }
+ 
+     /**
+      * Returns a {@code DYNAMIC} element for the given datum, or {@code null} if the datum is not dynamic.
+      */
+     static DynamicCRS createIfDynamic(final Datum datum) {
 -        if (datum instanceof DynamicReferenceFrame) {
 -            return new DynamicCRS(((DynamicReferenceFrame) datum).getFrameReferenceEpoch());
++        if (datum instanceof DefaultGeodeticDatum.Dynamic) {
++            return new DynamicCRS(((DefaultGeodeticDatum.Dynamic) datum).getFrameReferenceEpoch());
++        }
++        if (datum instanceof DefaultVerticalDatum.Dynamic) {
++            return new DynamicCRS(((DefaultVerticalDatum.Dynamic) datum).getFrameReferenceEpoch());
+         }
+         return null;
+     }
+ 
+     /**
+      * Formats this epoch as a <i>Well Known Text</i> {@code CoordinateMetadata[…]} element.
+      *
+      * @param  formatter  the formatter where to format the inner content of this WKT element.
+      * @return {@code "Dynamic"}.
+      */
+     @Override
+     protected String formatTo(final Formatter formatter) {
+         formatter.append(new Epoch(epoch, WKTKeywords.FrameEpoch));
+         return WKTKeywords.Dynamic;
+     }
+ }
diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/AbstractDatum.java
index b8bb4f3b62,703457ad2b..f6585af5e3
--- 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
@@@ -44,12 -48,11 +48,11 @@@ import org.apache.sis.metadata.iso.cita
  import org.apache.sis.io.wkt.ElementKind;
  import org.apache.sis.io.wkt.Formatter;
  import static org.apache.sis.util.Utilities.deepEquals;
- import static org.apache.sis.util.collection.Containers.property;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.metadata.Identifier;
 -import org.opengis.referencing.datum.DatumEnsemble;
 -import org.opengis.referencing.datum.DynamicReferenceFrame;
 +// Specific to the main branch:
 +import org.opengis.referencing.ReferenceIdentifier;
 +import org.opengis.metadata.extent.Extent;
 +import org.apache.sis.referencing.internal.Legacy;
  
  
  /**
@@@ -623,6 -556,9 +634,9 @@@ public class AbstractDatum extends Abst
              }
          }
          formatter.append(name, ElementKind.DATUM);
 -        if (formatter.getEnclosingElement(1) instanceof DatumEnsemble<?>) {
++        if (formatter.getEnclosingElement(1) instanceof DefaultDatumEnsemble<?>) {
+             return WKTKeywords.Member;
+         }
          return null;
      }
  
diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
index 4cc8d40079,fbead3b304..1a6d501a63
--- 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
@@@ -146,10 -154,10 +149,10 @@@ public class DefaultDatumEnsemble<D ext
       * @param  accuracy    inaccuracy introduced through use of this ensemble (mandatory).
       * @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)
+      * @see #create(Map, Class, Collection, PositionalAccuracy)
       */
      protected DefaultDatumEnsemble(final Map<String,?> properties,
                                     final Collection<? extends D> members,
@@@ -219,10 -228,11 +222,11 @@@
       * Creates a datum ensemble from the given properties. The content of the {@code properties} map is described
       * {@linkplain #DefaultDatumEnsemble(Map, Collection, PositionalAccuracy, Class) in the constructor}.
       * 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.
++     * or {@link EngineeringDatum} interface if all members are instances of the same interface.
       *
-      * @param  <D>         the type of datum members contained in this ensemble.
+      * @param  <D>         the type of datum members contained in the ensemble to create.
       * @param  properties  the properties to be given to the identified object.
+      * @param  memberType  type of members, or {@code null} for automatic.
       * @param  members     datum or reference frames which are members of this ensemble.
       * @param  accuracy    inaccuracy introduced through use of this ensemble (mandatory).
       * @return the datum ensemble.
@@@ -234,9 -245,40 +239,11 @@@
              final Collection<? extends D> members,
              final PositionalAccuracy accuracy)
      {
-         return Factory.forMemberType(Datum.class, null, properties, List.copyOf(members), accuracy);
+         return Factory.forMemberType(
+                 memberType != null ? memberType : 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.
@@@ -299,6 -360,31 +306,19 @@@
          return ensembleAccuracy;
      }
  
+     /**
+      * Returns an estimation of positional accuracy in metres, or {@code NaN} if unknown.
+      * If at least one {@linkplain org.apache.sis.metadata.iso.quality.DefaultQuantitativeResult quantitative
+      * result} is found with a linear unit, then returns the largest result value converted to metres.
+      *
+      * @return the accuracy estimation (always in meters), or NaN if unknown.
+      *
+      * @see org.apache.sis.referencing.CRS#getLinearAccuracy(CoordinateOperation)
+      */
+     public double getLinearAccuracy() {
+         return PositionalAccuracyConstant.getLinearAccuracy(CollectionsExt.singletonOrEmpty(getEnsembleAccuracy()));
+     }
+ 
 -    /**
 -     * Returns an anchor definition which is common to all members of the datum ensemble.
 -     * If the value is not the same for all members, or if at least one member returned
 -     * an empty value, then this method returns an empty value.
 -     *
 -     * @return the common anchor definition, or empty if there is no common value.
 -     */
 -    @Override
 -    public Optional<InternationalString> getAnchorDefinition() {
 -        return getCommonOptionalValue(Datum::getAnchorDefinition);
 -    }
 -
      /**
       * Returns an anchor point which is common to all members of the datum ensemble.
       * If the value is not the same for all members, or if at least one member returned
@@@ -460,7 -610,8 +496,7 @@@ check:  if (it.hasNext()) 
      /**
       * An ensemble viewed as a low-accuracy geodetic datum.
       *
-      * @see #create(Map, Collection, PositionalAccuracy)
+      * @see #create(Map, Class, Collection, PositionalAccuracy)
 -     * @see #castOrCopy(DatumEnsemble)
       * @see DatumOrEnsemble#of(GeodeticCRS)
       * @see DatumOrEnsemble#asTargetDatum(GeodeticCRS, GeodeticCRS)
       */
@@@ -514,7 -679,8 +564,7 @@@
      /**
       * An ensemble viewed as a low-accuracy vertical datum.
       *
-      * @see #create(Map, Collection, PositionalAccuracy)
+      * @see #create(Map, Class, Collection, PositionalAccuracy)
 -     * @see #castOrCopy(DatumEnsemble)
       * @see DatumOrEnsemble#of(VerticalCRS)
       * @see DatumOrEnsemble#asTargetDatum(VerticalCRS, VerticalCRS)
       */
@@@ -554,7 -735,8 +604,7 @@@
      /**
       * An ensemble viewed as a low-accuracy temporal datum.
       *
-      * @see #create(Map, Collection, PositionalAccuracy)
+      * @see #create(Map, Class, Collection, PositionalAccuracy)
 -     * @see #castOrCopy(DatumEnsemble)
       * @see DatumOrEnsemble#of(TemporalCRS)
       * @see DatumOrEnsemble#asTargetDatum(TemporalCRS, TemporalCRS)
       */
@@@ -595,7 -777,37 +645,7 @@@
      /**
       * An ensemble viewed as a low-accuracy engineering datum.
       *
-      * @see #create(Map, Collection, PositionalAccuracy)
+      * @see #create(Map, Class, Collection, PositionalAccuracy)
 -     * @see #castOrCopy(DatumEnsemble)
       * @see DatumOrEnsemble#of(EngineeringCRS)
       * @see DatumOrEnsemble#asTargetDatum(EngineeringCRS, EngineeringCRS)
       */
@@@ -628,7 -840,8 +678,7 @@@
       *
       * @param  <D>  base type of all members in the ensembles constructed by this factory instance.
       *
-      * @see #create(Map, Collection, PositionalAccuracy)
+      * @see #create(Map, Class, Collection, PositionalAccuracy)
 -     * @see #castOrCopy(DatumEnsemble)
       */
      private static abstract class Factory<D extends Datum> {
          /**
@@@ -712,10 -925,10 +762,10 @@@ nextType:   for (final Factory<?> facto
           * @param  members     datum or reference frames which are members of this ensemble.
           * @param  accuracy    inaccuracy introduced through use of this ensemble (mandatory).
           * @return the datum ensemble.
 -         * @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)
+          * @see #create(Map, Class, Collection, PositionalAccuracy)
           */
          abstract DefaultDatumEnsemble<D> create(Map<String,?> properties, List<? extends D> members, PositionalAccuracy accuracy);
  
@@@ -730,7 -945,7 +780,7 @@@
  
          /**
           * Factories for all datum types supported by this class. The types are (in order) {@link GeodeticDatum},
--         * {@link VerticalDatum}, {@link TemporalDatum}, {@link ParametricDatum} and {@link EngineeringDatum}.
++         * {@link VerticalDatum}, {@link TemporalDatum} and {@link EngineeringDatum}.
           * The types are tested in iteration order. For example, if a member implements both {@code VerticalDatum}
           * {@code ParametricDatum} interface, then {@code VerticalDatum} has precedence.
           */
diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
index f70bdee1d0,7f5a051171..e2f1a12fde
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
@@@ -51,10 -51,10 +51,9 @@@ import org.apache.sis.io.wkt.Formatter
  import static org.apache.sis.util.Utilities.deepEquals;
  import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
  import static org.apache.sis.util.ArgumentChecks.ensureNonNullElement;
- import static org.apache.sis.referencing.privy.WKTUtilities.toFormattable;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.metadata.Identifier;
 -import org.opengis.referencing.datum.DynamicReferenceFrame;
 +// Specific to the main branch:
 +import org.opengis.referencing.ReferenceIdentifier;
  
  
  /**
diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultParametricDatum.java
index cc63fdbf93,e0efe9b6f9..98aeaf24a8
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultParametricDatum.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultParametricDatum.java
@@@ -128,14 -130,46 +128,15 @@@ public class DefaultParametricDatum ext
      }
  
      /**
 -     * Returns a SIS datum implementation with the same values as the given arbitrary implementation.
 -     * If the given object is {@code null}, then this method returns {@code null}.
 -     * Otherwise if the given object is already a SIS implementation, then the given object is returned unchanged.
 -     * Otherwise a new SIS implementation is created and initialized to the attribute values of the given object.
 -     *
 -     * @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 DefaultParametricDatum castOrCopy(final ParametricDatum object) {
 -        return (object == null) || (object instanceof DefaultParametricDatum) ?
 -                (DefaultParametricDatum) object : new DefaultParametricDatum(object);
 -    }
 -
 -    /**
 -     * Returns the GeoAPI interface implemented by this class.
 -     * The SIS implementation returns {@code ParametricDatum.class}.
 -     *
 -     * <h4>Note for implementers</h4>
 -     * Subclasses usually do not need to override this method since GeoAPI does not define {@code TemporalDatum}
 -     * sub-interface. Overriding possibility is left mostly for implementers who wish to extend GeoAPI with their
 -     * own set of interfaces.
 -     *
 -     * @return {@code ParametricDatum.class} or a user-defined sub-interface.
 -     */
 -    @Override
 -    public Class<? extends ParametricDatum> getInterface() {
 -        return ParametricDatum.class;
 -    }
 -
 -    /**
 -     * Formats this datum as a <i>Well Known Text</i> {@code ParametricDatum[…]} element.
 +     * Formats this datum as a <cite>Well Known Text</cite> {@code ParametricDatum[…]} element.
       *
       * <h4>Compatibility note</h4>
-      * {@code ParametricDatum} is defined in the WKT 2 specification only.
+      * {@code ParametricDatum} is defined in the <abbr>WKT</abbr> 2 specification only.
+      * Apache <abbr>SIS</abbr> accepts this type as members of datum ensembles,
+      * but this is not valid <abbr>WKT</abbr> according <abbr>ISO</abbr> 19162:2019.
       *
-      * @return {@code "ParametricDatum"}.
-      *
-      * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#83">WKT 2 specification</a>
+      * @return {@code "PDatum"} or {@code "ParametricDatum"}.
+      *         May also be {@code "Member"} if this datum is inside a <abbr>WKT</abbr> {@code Ensemble[…]} element.
       */
      @Override
      protected String formatTo(final Formatter formatter) {
diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
index 73bd9c4d48,4cb2b7369e..0094aab2db
--- 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
@@@ -381,11 -441,10 +381,10 @@@ public class DefaultVerticalDatum exten
       * OGC 01-009 defined numerical codes for various vertical datum types, for example 2005 for geoidal height.
       * Such codes were formatted for all {@code Datum} subtypes in WKT 1. Datum types became specified only for
       * vertical datum in the ISO 19111:2003 standard, then removed completely in the ISO 19111:2007 standard.
 -     * They were reintroduced in a different form ({@link RealizationMethod}) in the ISO 19111:2019 standard.
 +     * They were reintroduced in a different form ({@code RealizationMethod}) in the ISO 19111:2019 standard.
       *
-      * @return {@code "VerticalDatum"} (WKT 2) or {@code "Vert_Datum"} (WKT 1).
-      *
-      * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#71">WKT 2 specification §10.2</a>
+      * @return {@code "VDatum"} or {@code "VerticalDatum"} (WKT 2), or {@code "Vert_Datum"} (WKT 1).
+      *         May also be {@code "Member"} if this datum is inside a <abbr>WKT</abbr> {@code Ensemble[…]} element.
       */
      @Override
      protected String formatTo(final Formatter formatter) {
diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultOperationMethod.java
index 60c2a6ed53,27e66e0b76..bcf2c18927
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultOperationMethod.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultOperationMethod.java
@@@ -511,10 -511,9 +511,8 @@@ public class DefaultOperationMethod ext
       * Formats this operation as a <i>Well Known Text</i> {@code Method[…]} element.
       *
       * @return {@code "Method"} (WKT 2) or {@code "Projection"} (WKT 1).
-      *
-      * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#118">WKT 2 specification §17.2.3</a>
       */
      @Override
 -    @SuppressWarnings("deprecation")
      protected String formatTo(final Formatter formatter) {
          final boolean isWKT1 = formatter.getConvention().majorVersion() == 1;
          /*
diff --cc endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/GeodeticObjectParserTest.java
index a2398497f3,8993a6d39e..7d74482934
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/GeodeticObjectParserTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/GeodeticObjectParserTest.java
@@@ -55,6 -60,6 +60,12 @@@ import static org.apache.sis.referencin
  import static org.apache.sis.referencing.Assertions.assertDiagonalEquals;
  import static org.apache.sis.test.TestUtilities.getSingleton;
  
++// Specific to the main branch:
++import org.apache.sis.referencing.crs.DefaultGeographicCRS;
++import org.apache.sis.referencing.crs.DefaultProjectedCRS;
++import org.apache.sis.referencing.crs.DefaultVerticalCRS;
++import org.apache.sis.referencing.datum.DefaultVerticalDatum;
++
  // Specific to the main and geoapi-3.1 branches:
  import org.apache.sis.temporal.TemporalDate;
  
@@@ -481,6 -502,38 +508,38 @@@ public final class GeodeticObjectParser
          assertAxisEquals(AxisNames.GEODETIC_LONGITUDE, "λ", AxisDirection.EAST,  -180, +180, Units.DEGREE, RangeMeaning.WRAPAROUND, cs.getAxis(1));
      }
  
+     /**
+      * Tests the parsing of a geographic CRS with datum ensemble.
+      *
+      * @throws ParseException if the parsing failed.
+      */
+     @Test
+     public void testGeographicCRSWithEnsemble() throws ParseException {
 -        final GeographicCRS crs = parse(GeographicCRS.class,
++        final var crs = parse(DefaultGeographicCRS.class,
+                 "GeodeticCRS[“WGS 84”,\n" +
+                 "  Ensemble[“World Geodetic System 1984”,\n" +
+                 "    Member[“World Geodetic System 1984 (Transit)”],\n" +
+                 "    Member[“World Geodetic System 1984 (G730)”],\n" +
+                 "    Member[“World Geodetic System 1984 (G873)”],\n" +
+                 "    Ellipsoid[“WGS84”, 6378137.0, 298.257223563],\n" +
+                 "    EnsembleAccuracy[2.0]],\n" +
+                 "  PrimeMeridian[“Greenwich”, 0],\n" +
+                 "  CS[ellipsoidal, 2],\n" +
+                 "    Axis[“Geodetic longitude (Lon)”, east],\n" +
+                 "    Axis[“Geodetic latitude (Lat)”, north],\n" +
+                 "    Unit[“degree”, 0.017453292519943295],\n" +
+                 "  Usage[\n" +
+                 "    Scope[“Horizontal component of 3D system.”],\n" +
+                 "    BBox[-90.00, -180.00, 90.00, 180.00]]]");
+         verifyGeographicCRS(0, crs);
+         assertNull(crs.getDatum());
+         final Iterator<GeodeticDatum> members = crs.getDatumEnsemble().getMembers().iterator();
+         assertNameAndIdentifierEqual("World Geodetic System 1984 (Transit)", 0, members.next());
+         assertNameAndIdentifierEqual("World Geodetic System 1984 (G730)",    0, members.next());
+         assertNameAndIdentifierEqual("World Geodetic System 1984 (G873)",    0, members.next());
+         assertFalse(members.hasNext());
+     }
+ 
      /**
       * Implementation of {@link #testGeographicCRS()} and related test methods.
       * This test expects no {@code AUTHORITY} element on any component.
@@@ -731,8 -786,47 +792,47 @@@
                       "  Scope[“Large and medium scale topographic mapping and engineering survey.”],\n" +
                       "  Id[“EPSG”, 27572, URI[“urn:ogc:def:crs:EPSG::27572”]]]";
  
--        final ProjectedCRS crs = parse(ProjectedCRS.class, wkt);
++        final var crs = parse(DefaultProjectedCRS.class, wkt);
+         validateParisFranceII(crs, 27572, false);
+         assertEquals("Large and medium scale topographic mapping and engineering survey.",
+                      getSingleton(crs.getDomains()).getScope().toString());
+         assertNull(getSingleton(crs.getIdentifiers()).getVersion(), "Identifier shall not have a version.");
+     }
+ 
+     /**
+      * Tests the same <abbr>CRS</abbr> as {@link #testProjectedWithGradUnits()},
+      * but from a string mostly conform to <abbr>ISO</abbr> 19162:2019.
+      * A small deviation is that this test includes accented letters.
+      *
+      * @throws ParseException if the parsing failed.
+      */
+     @Test
+     public void testProjectedFromWKT2_2019() throws ParseException {
+         String wkt = "ProjectedCRS[“NTF (Paris) / Lambert zone II”,\n" +
+                      "  BaseGeodCRS[“NTF (Paris)”,\n" +
+                      "    Datum[“Nouvelle Triangulation Française (Paris)”,\n" +
+                      "      Ellipsoid[“Clarke 1880 (IGN)”, 6378249.2, 293.4660212936269]],\n" +
+                      "      PrimeMeridian[“Paris”, 2.5969213, Unit[“grad”, 0.015707963267948967], Id[“EPSG”, 8903]],\n" +
+                      "    AngleUnit[“degree”, 0.017453292519943295]],\n" +
+                      "  Conversion[“Lambert zone II”,\n" +
+                      "    Method[“Lambert Conic Conformal (1SP)”],\n" +
+                      "    Parameter[“Latitude of natural origin”, 52.0, AngleUnit[“grad”, 0.015707963267948967]],\n" +
+                      "    Parameter[“Longitude of natural origin”, 0.0],\n" +
+                      "    Parameter[“Scale factor at natural origin”, 0.99987742],\n" +
+                      "    Parameter[“False easting”, 600.0, LengthUnit[“kilometre”, 1000]],\n" +
+                      "    Parameter[“False northing”, 2200.0, LengthUnit[“kilometre”, 1000]]],\n" +
+                      "  CS[Cartesian, 2],\n" +
+                      "    Axis[“Easting (E)”, east],\n" +
+                      "    Axis[“Northing (N)”, north],\n" +
+                      "    LengthUnit[“kilometre”, 1000],\n" +
+                      "  Usage[\n" +
+                      "    Scope[“Large and medium scale topographic mapping and engineering survey.”]],\n" +
+                      "  Id[“EPSG”, 27572, URI[“urn:ogc:def:crs:EPSG::27572”]]]";
+ 
 -        final ProjectedCRS crs = parse(ProjectedCRS.class, wkt);
++        final var crs = parse(DefaultProjectedCRS.class, wkt);
          validateParisFranceII(crs, 27572, false);
+         assertEquals("Large and medium scale topographic mapping and engineering survey.",
+                      getSingleton(crs.getDomains()).getScope().toString());
          assertNull(getSingleton(crs.getIdentifiers()).getVersion(), "Identifier shall not have a version.");
      }
  
@@@ -890,6 -984,35 +990,35 @@@
                    "AXIS[" + axis + "]]");
      }
  
+     /**
+      * Tests the parsing of a vertical <abbr>CRS</abbr>.
+      *
+      * @throws ParseException if the parsing failed.
+      */
+     @Test
+     public void testVerticalCRS() throws ParseException {
 -        final VerticalCRS crs = parse(VerticalCRS.class,
++        final var crs = parse(DefaultVerticalCRS.class,
+                 "VerticalCRS[“RH2000 height”,\n" +
+                 "  Dynamic[FrameEpoch[2000]],\n" +
+                 "  VerticalDatum[“Rikets hojdsystem 2000”],\n" +
+                 "  CS[vertical, 1],\n" +
+                 "    Axis[“Gravity-related height (H)”, up],\n" +
+                 "    Unit[“metre”, 1],\n" +
+                 "  Usage[\n" +
+                 "    Scope[“Geodesy, engineering survey.”],\n" +
+                 "    Area[“Sweden - onshore.”],\n" +
+                 "    BBox[55.28, 10.93, 69.07, 24.17]],\n" +
+                 "  Id[“EPSG”, 5613, “12.013”, URI[“urn:ogc:def:crs:EPSG:12.013:5613”]],\n" +
+                 "  Remark[“Replaces RH70 (CRS code 5718) from 2005.”]]");
+ 
+         assertNameAndIdentifierEqual("RH2000 height", 5613, crs);
+         assertNameAndIdentifierEqual("Rikets hojdsystem 2000", 0, crs.getDatum());
 -        Temporal epoch = assertInstanceOf(DynamicReferenceFrame.class, crs.getDatum()).getFrameReferenceEpoch();
++        Temporal epoch = assertInstanceOf(DefaultVerticalDatum.Dynamic.class, crs.getDatum()).getFrameReferenceEpoch();
+         assertEquals(Year.of(2000), epoch);
+         assertEquals("Geodesy, engineering survey.", getSingleton(crs.getDomains()).getScope().toString());
+         assertEquals("Replaces RH70 (CRS code 5718) from 2005.", crs.getRemarks().toString());
+     }
+ 
      /**
       * Returns the conversion from {@code north} to {@code south}.
       */
diff --cc endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultVerticalDatumTest.java
index ae7c354dc7,b230467515..4f6752af8c
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultVerticalDatumTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultVerticalDatumTest.java
@@@ -102,9 -74,9 +102,9 @@@ public final class DefaultVerticalDatum
      @Test
      public void testToWKT() {
          DefaultVerticalDatum datum;
 -        datum = new DefaultVerticalDatum(Map.of(DefaultVerticalDatum.NAME_KEY, "Geoidal"), RealizationMethod.GEOID);
 +        datum = new DefaultVerticalDatum(Map.of(DefaultVerticalDatum.NAME_KEY, "Geoidal"), VerticalDatumType.GEOIDAL);
          assertWktEquals(Convention.WKT1, "VERT_DATUM[“Geoidal”, 2005]", datum);
-         assertWktEquals(Convention.WKT2, "VDATUM[“Geoidal”]", datum);
+         assertWktEquals(Convention.WKT2_2015, "VDATUM[“Geoidal”]", datum);
          assertWktEquals(Convention.WKT2_SIMPLIFIED, "VerticalDatum[“Geoidal”]", datum);
  
          datum = new DefaultVerticalDatum(Map.of(DefaultVerticalDatum.NAME_KEY, "Ellipsoidal"), VerticalDatumTypes.ellipsoidal());
diff --cc endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/ConsistencyTest.java
index 03d9b0cfc8,487645a33a..a89761767b
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/ConsistencyTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/ConsistencyTest.java
@@@ -80,7 -81,7 +80,8 @@@ public final class ConsistencyTest exte
       * to rebuild the <abbr>CRS</abbr> at parsing time.
       */
      private final String[] WKT_TO_IGNORE = {
--        "VERTICALEXTENT"
++        "VERTICALEXTENT",
++        "VERT_DATUM"
      };
  
      /**