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 <[email protected]>
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"
      };
  
      /**

Reply via email to