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

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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new c08fea432c Supports the `ENSEMBLE` element at WKT parsing and 
formatting time.
c08fea432c is described below

commit c08fea432cbadbce85dd8748e3de942a74a851ef
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Sat Sep 6 18:52:54 2025 +0200

    Supports the `ENSEMBLE` element at WKT parsing and formatting time.
---
 .../org/apache/sis/console/CRSCommandTest.java     |   6 +-
 .../sis/metadata/iso/citation/Citations.java       |   2 +-
 .../sis/metadata/iso/citation/CitationsTest.java   |   2 +-
 .../main/org/apache/sis/io/wkt/AbstractParser.java |   9 +-
 .../apache/sis/io/wkt/GeodeticObjectParser.java    | 201 +++++++++++++++------
 .../apache/sis/referencing/crs/AbstractCRS.java    |  79 +++++---
 .../sis/referencing/crs/AbstractDerivedCRS.java    |  14 ++
 .../sis/referencing/crs/DefaultCompoundCRS.java    |   9 -
 .../sis/referencing/crs/DefaultDerivedCRS.java     |  38 ----
 .../sis/referencing/crs/DefaultEngineeringCRS.java |  18 +-
 .../sis/referencing/crs/DefaultGeodeticCRS.java    |  27 ++-
 .../sis/referencing/crs/DefaultParametricCRS.java  |  18 +-
 .../sis/referencing/crs/DefaultProjectedCRS.java   |  10 -
 .../sis/referencing/crs/DefaultTemporalCRS.java    |  18 +-
 .../sis/referencing/crs/DefaultVerticalCRS.java    |  18 +-
 .../sis/referencing/datum/AbstractDatum.java       |  33 +++-
 .../referencing/datum/DefaultDatumEnsemble.java    |  74 ++++++--
 .../referencing/datum/DefaultEngineeringDatum.java |  16 +-
 .../referencing/datum/DefaultGeodeticDatum.java    |   7 +-
 .../sis/referencing/datum/DefaultImageDatum.java   |  21 ++-
 .../referencing/datum/DefaultParametricDatum.java  |  16 +-
 .../referencing/datum/DefaultTemporalDatum.java    |  14 +-
 .../referencing/datum/DefaultVerticalDatum.java    |   9 +-
 .../referencing/factory/GeodeticObjectFactory.java |   2 +-
 .../internal/PositionalAccuracyConstant.java       |  58 +++---
 .../operation/AbstractCoordinateOperation.java     |  10 +-
 .../provider/MercatorAuxiliarySphere.java          |   3 +-
 .../referencing/operation/provider/Mollweide.java  |   3 +-
 .../provider/ObliqueMercatorTwoPoints.java         |   5 +-
 .../apache/sis/referencing/privy/WKTKeywords.java  |  28 +--
 .../apache/sis/referencing/privy/WKTUtilities.java |  18 ++
 .../sis/io/wkt/GeodeticObjectParserTest.java       |  35 +++-
 .../operation/HardCodedConversions.java            |   5 +-
 .../main/org/apache/sis/setup/GeometryLibrary.java |   3 +-
 .../main/org/apache/sis/util/privy/Constants.java  |   5 +
 35 files changed, 534 insertions(+), 300 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.console/test/org/apache/sis/console/CRSCommandTest.java
 
b/endorsed/src/org.apache.sis.console/test/org/apache/sis/console/CRSCommandTest.java
index 380b0b0c71..ac42465ab6 100644
--- 
a/endorsed/src/org.apache.sis.console/test/org/apache/sis/console/CRSCommandTest.java
+++ 
b/endorsed/src/org.apache.sis.console/test/org/apache/sis/console/CRSCommandTest.java
@@ -44,8 +44,10 @@ public final class CRSCommandTest extends TestCase {
         final String name = "\"WGS\\E\\s?(?:19)?\\Q84\"";                      
                 // Accept "WGS 84" or "WGS 1984".
         WGS84 = "(?m)\\Q" +                                                    
                 // Multilines.
             "GeodeticCRS[" + name + ",\n" +
-            "  Datum[\"World Geodetic System 1984\\E\\s?\\w*\\Q\",\n" +        
                 // End with "ensemble" in EPSG 10+.
-            "    Ellipsoid[" + name + ", 6378137.0, 298.257223563]],\n" +
+            "  Ensemble[\"World Geodetic System 1984\\E\\s?\\w*\\Q\",\n" +     
                 // Ignore any suffix in the name.
+            "\\E(?:    Member\\[\".+\"\\],\n)+\\Q" +                           
                 // At least one MEMBER[…].
+            "    Ellipsoid[" + name + ", 6378137.0, 298.257223563],\n" +
+            "    EnsembleAccuracy[2.0]],\n" +
             "  CS[ellipsoidal, 2],\n" +
             "    Axis[\"Latitude (B)\", north],\n" +
             "    Axis[\"Longitude (L)\", east],\n" +
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/Citations.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/Citations.java
index 972fe9ba19..bd805c8be8 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/Citations.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/Citations.java
@@ -294,7 +294,7 @@ public final class Citations extends Static {
      *
      * @since 0.4
      */
-    public static final IdentifierSpace<String> ESRI = new 
CitationConstant.Authority<>("ArcGIS", "ESRI");
+    public static final IdentifierSpace<String> ESRI = new 
CitationConstant.Authority<>("ArcGIS", Constants.ESRI);
 
     /**
      * The authority for identifiers of objects defined by the World 
Meteorological Organization (<abbr>WMO</abbr>).
diff --git 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/CitationsTest.java
 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/CitationsTest.java
index 1e15401bc4..c7f066ea78 100644
--- 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/CitationsTest.java
+++ 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/CitationsTest.java
@@ -90,7 +90,7 @@ public final class CitationsTest extends TestCase {
         assertSame(EPSG,             fromName(Constants.EPSG));  // Success of 
this test is important for remaining of SIS.
         assertSame(IOGP,             fromName(Constants.IOGP));
         assertSame(IOGP,             fromName("OGP"));
-        assertSame(ESRI,             fromName("ESRI"));          // Handled in 
a way very similar to "OGC".
+        assertSame(ESRI,             fromName(Constants.ESRI));  // Handled in 
a way very similar to "OGC".
         assertSame(NETCDF,           fromName(Constants.NETCDF));
         assertSame(GEOTIFF,          fromName(Constants.GEOTIFF));
         assertSame(PROJ4,            fromName("Proj.4"));
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/AbstractParser.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/AbstractParser.java
index d6abe3c310..4e70bf62f5 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/AbstractParser.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/AbstractParser.java
@@ -425,17 +425,18 @@ abstract class AbstractParser implements Parser {
      *
      * @param  parent  the element from which to pull a child element.
      * @param  key     name of the element to pull.
-     * @return the value, or {@code null} if none.
+     * @param  mode    {@link #MANDATORY} or {@link #OPTIONAL}.
+     * @return the value, or {@code Double.NaN} if none.
      * @throws ParseException if an element cannot be parsed.
      */
-    final Double pullElementAsDouble(final Element parent, final String key) 
throws ParseException {
-        Element element = parent.pullElement(OPTIONAL, key);
+    final double pullElementAsDouble(final Element parent, final String key, 
final int mode) throws ParseException {
+        Element element = parent.pullElement(mode, key);
         if (element != null) {
             double value = element.pullDouble(key);
             element.close(ignoredElements);
             return value;
         }
-        return null;
+        return Double.NaN;
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
index d64fc1d548..2fda55c79c 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
@@ -304,27 +304,22 @@ class GeodeticObjectParser extends MathTransformParser 
implements Comparator<Coo
      */
     @Override
     final Object buildFromTree(final Element element) throws ParseException {
-        Object value = parseCoordinateReferenceSystem(element, false);
-        if (value != null) {
-            return value;
-        }
-        value = parseMathTransform(element, false);
-        if (value != null) {
-            return value;
-        }
         Object object;
-        if ((object = parseAxis             (FIRST, element, null,  
Units.METRE )) == null &&
-            (object = parsePrimeMeridian    (FIRST, element, false, 
Units.DEGREE)) == null &&
-            (object = parseDatum            (FIRST, element, null )) == null &&
-            (object = parseEllipsoid        (FIRST, element       )) == null &&
-            (object = parseToWGS84          (FIRST, element       )) == null &&
-            (object = parseVerticalDatum    (FIRST, element, false)) == null &&
-            (object = parseTimeDatum        (FIRST, element       )) == null &&
-            (object = parseParametricDatum  (FIRST, element       )) == null &&
-            (object = parseEngineeringDatum (FIRST, element, false)) == null &&
-            (object = parseImageDatum       (FIRST, element       )) == null &&
-            (object = parseOperation        (FIRST, element))        == null &&
-            (object = parseGeogTranslation  (FIRST, element))        == null)
+        if    (null == (object = parseCoordinateReferenceSystem(element, 
false))
+            && null == (object = parseMathTransform            (element, 
false))
+            && null == (object = parseAxis              (FIRST, element, null, 
 Units.METRE ))
+            && null == (object = parsePrimeMeridian     (FIRST, element, 
false, Units.DEGREE))
+            && null == (object = parseEnsemble          (FIRST, element, 
Datum.class, greenwich()))
+            && null == (object = parseDatum             (FIRST, element, 
greenwich()))
+            && null == (object = parseEllipsoid         (FIRST, element))
+            && null == (object = parseToWGS84           (FIRST, element))
+            && null == (object = parseVerticalDatum     (FIRST, element, 
false))
+            && null == (object = parseTimeDatum         (FIRST, element))
+            && null == (object = parseParametricDatum   (FIRST, element))
+            && null == (object = parseEngineeringDatum  (FIRST, element, 
false))
+            && null == (object = parseImageDatum        (FIRST, element))
+            && null == (object = parseOperation         (FIRST, element))
+            && null == (object = parseGeogTranslation   (FIRST, element)))
         {
             throw element.missingOrUnknownComponent(WKTKeywords.GeodeticCRS);
         }
@@ -1111,12 +1106,12 @@ class GeodeticObjectParser extends MathTransformParser 
implements Comparator<Coo
          * If present, we will store that value for processing by the 
`parseCoordinateSystem(…)` method.
          */
         final Integer order   = pullElementAsInteger(element, 
WKTKeywords.Order);
-        final Double  minimum = pullElementAsDouble (element, 
WKTKeywords.AxisMinValue);
-        final Double  maximum = pullElementAsDouble (element, 
WKTKeywords.AxisMaxValue);
+        final double  minimum = pullElementAsDouble (element, 
WKTKeywords.AxisMinValue, OPTIONAL);
+        final double  maximum = pullElementAsDouble (element, 
WKTKeywords.AxisMaxValue, OPTIONAL);
         final String  meaning = pullElementAsEnum   (element, 
WKTKeywords.RangeMeaning);
         final Map<String, Object> properties = parseMetadataAndClose(element, 
name, null);
-        properties.put(DefaultCoordinateSystemAxis.MINIMUM_VALUE_KEY, minimum);
-        properties.put(DefaultCoordinateSystemAxis.MAXIMUM_VALUE_KEY, maximum);
+        if (Double.isFinite(minimum)) 
properties.put(DefaultCoordinateSystemAxis.MINIMUM_VALUE_KEY, minimum);
+        if (Double.isFinite(maximum)) 
properties.put(DefaultCoordinateSystemAxis.MAXIMUM_VALUE_KEY, maximum);
         properties.put(DefaultCoordinateSystemAxis.RANGE_MEANING_KEY,
                 Types.forCodeName(RangeMeaning.class, meaning, 
RangeMeaning::valueOf));
         /*
@@ -1414,6 +1409,75 @@ class GeodeticObjectParser extends MathTransformParser 
implements Comparator<Coo
         }
     }
 
+    /**
+     * 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.
+     * @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,
+            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, 
null);
+                    } else if (datumType == TemporalDatum.class) {
+                        member = datumFactory.createTemporalDatum(properties, 
null);
+                    } else if (datumType == ParametricDatum.class) {
+                        member = 
datumFactory.createParametricDatum(properties);
+                    } 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);
+        } catch (FactoryException exception) {
+            throw ensemble.parseFailed(exception);
+        }
+    }
+
     /**
      * Parses a {@code "Datum"} (WKT 2) element.
      *
@@ -1425,13 +1489,13 @@ class GeodeticObjectParser extends MathTransformParser 
implements Comparator<Coo
      *
      * @param  mode      {@link #FIRST}, {@link #OPTIONAL} or {@link 
#MANDATORY}.
      * @param  parent    the parent element.
-     * @param  meridian  the prime meridian, or {@code null} for Greenwich.
+     * @param  meridian  the prime meridian.
      * @return the {@code "Datum"} element as a {@link GeodeticDatum} object.
      * @throws ParseException if the {@code "Datum"} element cannot be parsed.
      *
      * @see 
org.apache.sis.referencing.datum.DefaultGeodeticDatum#formatTo(Formatter)
      */
-    private GeodeticDatum parseDatum(final int mode, final Element parent, 
PrimeMeridian meridian) throws ParseException {
+    private GeodeticDatum parseDatum(final int mode, final Element parent, 
final PrimeMeridian meridian) throws ParseException {
         final Element element = parent.pullElement(mode, WKTKeywords.Datum, 
WKTKeywords.GeodeticDatum);
         if (element == null) {
             return null;
@@ -1440,9 +1504,6 @@ class GeodeticObjectParser extends MathTransformParser 
implements Comparator<Coo
         final Ellipsoid          ellipsoid  = parseEllipsoid(MANDATORY, 
element);
         final Object             toWGS84    = parseToWGS84(OPTIONAL, element);
         final Map<String,Object> properties = parseAnchorAndClose(element, 
name);
-        if (meridian == null) {
-            meridian = CommonCRS.WGS84.primeMeridian();
-        }
         if (toWGS84 != null) {
             properties.put(DefaultGeodeticDatum.BURSA_WOLF_KEY, toWGS84);
         }
@@ -1639,6 +1700,7 @@ class GeodeticObjectParser extends MathTransformParser 
implements Comparator<Coo
          * 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;
         EngineeringDatum datum    = null;
         SingleCRS        baseCRS  = null;
         Conversion       fromBase = null;
@@ -1669,18 +1731,20 @@ class GeodeticObjectParser extends MathTransformParser 
implements Comparator<Coo
                 }
             }
         }
-        if (baseCRS == null) {                                                 
 // The most usual case.
-            datum = parseEngineeringDatum(MANDATORY, element, isWKT1);
+        if (baseCRS == null) {      // The most usual case.
+            ensemble = parseEnsemble(OPTIONAL, element, 
EngineeringDatum.class, null);
+            datum = parseEngineeringDatum(ensemble == null ? MANDATORY : 
OPTIONAL, element, isWKT1);
         }
+        final IdentifiedObject datumOrEnsemble = (datum != null) ? datum : 
ensemble;
         final CRSFactory crsFactory = factories.getCRSFactory();
         try {
             final CoordinateSystem cs = parseCoordinateSystem(element, null, 
1, isWKT1, unit, false, null);
-            final Map<String,Object> properties = 
parseMetadataAndClose(element, name, datum);
+            final Map<String,Object> properties = 
parseMetadataAndClose(element, name, datumOrEnsemble);
             if (baseCRS != null) {
                 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);
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
         }
@@ -1874,19 +1938,23 @@ class GeodeticObjectParser extends MathTransformParser 
implements Comparator<Coo
                 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 DatumEnsemble<GeodeticDatum> datumEnsemble = null;    // TODO
-            final Map<String,?> properties = parseMetadataAndClose(element, 
name, datum);
+            PrimeMeridian meridian = parsePrimeMeridian(OPTIONAL, element, 
isWKT1, longitudeUnit);
+            if (meridian == null) {
+                meridian = greenwich();
+            }
+            final DatumEnsemble<GeodeticDatum> ensemble = 
parseEnsemble(OPTIONAL, element, GeodeticDatum.class, meridian);
+            final GeodeticDatum datum = parseDatum(ensemble == null ? 
MANDATORY : OPTIONAL, element, meridian);
+            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, 
datumEnsemble, (EllipsoidalCS) cs);
+                return crsFactory.createGeographicCRS(properties, datum, 
ensemble, (EllipsoidalCS) cs);
             }
             if (cs instanceof CartesianCS) {                                   
 // The second most frequent case.
-                return crsFactory.createGeodeticCRS(properties, datum, 
datumEnsemble,
+                return crsFactory.createGeodeticCRS(properties, datum, 
ensemble,
                         Legacy.forGeocentricCRS((CartesianCS) cs, false));
             }
             if (cs instanceof SphericalCS) {                                   
 // Not very common case.
-                return crsFactory.createGeodeticCRS(properties, datum, 
datumEnsemble, (SphericalCS) cs);
+                return crsFactory.createGeodeticCRS(properties, datum, 
ensemble, (SphericalCS) cs);
             }
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
@@ -1927,6 +1995,7 @@ class GeodeticObjectParser extends MathTransformParser 
implements Comparator<Coo
          * 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;
         VerticalDatum datum    = null;
         SingleCRS     baseCRS  = null;
         Conversion    fromBase = null;
@@ -1946,13 +2015,17 @@ class GeodeticObjectParser extends MathTransformParser 
implements Comparator<Coo
                 baseCRS = parseVerticalCRS(MANDATORY, element, true);
             }
         }
-        if (baseCRS == null) {                                                 
 // The most usual case.
-            datum = parseVerticalDatum(MANDATORY, element, isWKT1);
+        if (baseCRS == null) {      // The most usual case.
+            ensemble = parseEnsemble(OPTIONAL, element, VerticalDatum.class, 
null);
+            datum = parseVerticalDatum(ensemble == null ? MANDATORY : 
OPTIONAL, element, isWKT1);
         }
+        final IdentifiedObject datumOrEnsemble = (datum != null) ? datum : 
ensemble;
         final CoordinateSystem cs;
         try {
-            cs = parseCoordinateSystem(element, WKTKeywords.vertical, 1, 
isWKT1, unit, false, datum.getRealizationMethod().orElse(null));
-            final Map<String,?> properties = parseMetadataAndClose(element, 
name, datum);
+            final RealizationMethod method = (datumOrEnsemble instanceof 
VerticalDatum)
+                    ? ((VerticalDatum) 
datumOrEnsemble).getRealizationMethod().orElse(null) : null;
+            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) {
@@ -1963,14 +2036,14 @@ class GeodeticObjectParser extends MathTransformParser 
implements Comparator<Coo
                  * But sometimes the axis (which was not available when we 
created the datum) provides
                  * more information. Verify if we can have a better type now, 
and if so rebuild the datum.
                  */
-                if (datum.getRealizationMethod().isEmpty()) {
+                if (method == null && datum != null) {
                     var type = 
VerticalDatumTypes.fromDatum(datum.getName().getCode(), datum.getAlias(), 
cs.getAxis(0));
                     if (type != null) {
                         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);
                 /*
                  * Some DefaultVerticalExtent objects may be waiting for the 
VerticalCRS before to complete
                  * their construction. If this is the case, try to complete 
them now.
@@ -2008,6 +2081,7 @@ class GeodeticObjectParser extends MathTransformParser 
implements Comparator<Coo
          * 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;
         TemporalDatum datum    = null;
         SingleCRS     baseCRS  = null;
         Conversion    fromBase = null;
@@ -2027,19 +2101,21 @@ class GeodeticObjectParser extends MathTransformParser 
implements Comparator<Coo
                 baseCRS = parseTimeCRS(MANDATORY, element, true);
             }
         }
-        if (baseCRS == null) {                                                 
 // The most usual case.
-            datum = parseTimeDatum(MANDATORY, element);
+        if (baseCRS == null) {      // The most usual case.
+            ensemble = parseEnsemble(OPTIONAL, element, TemporalDatum.class, 
null);
+            datum = parseTimeDatum(ensemble == null ? MANDATORY : OPTIONAL, 
element);
         }
+        final IdentifiedObject datumOrEnsemble = (datum != null) ? datum : 
ensemble;
         final CoordinateSystem cs;
         try {
             cs = parseCoordinateSystem(element, WKTKeywords.temporal, 1, 
false, unit, false, null);
-            final Map<String,?> properties = parseMetadataAndClose(element, 
name, datum);
+            final Map<String,?> properties = parseMetadataAndClose(element, 
name, datumOrEnsemble);
             if (cs instanceof TimeCS) {
                 final CRSFactory crsFactory = factories.getCRSFactory();
                 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);
             }
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
@@ -2069,6 +2145,7 @@ class GeodeticObjectParser extends MathTransformParser 
implements Comparator<Coo
          * 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;
         SingleCRS baseCRS = null;
         Conversion fromBase = null;
@@ -2088,19 +2165,21 @@ class GeodeticObjectParser extends MathTransformParser 
implements Comparator<Coo
                 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);
+            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) {
                 final CRSFactory crsFactory = factories.getCRSFactory();
                 if (baseCRS != null) {
                     return crsFactory.createDerivedCRS(properties, baseCRS, 
fromBase, cs);
                 }
-                return crsFactory.createParametricCRS(properties, datum, 
(ParametricCS) cs);
+                return crsFactory.createParametricCRS(properties, datum, 
ensemble, (ParametricCS) cs);
             }
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
@@ -2329,12 +2408,11 @@ class GeodeticObjectParser extends MathTransformParser 
implements Comparator<Coo
         final CoordinateReferenceSystem targetCRS        = 
parseCoordinateReferenceSystem(element, MANDATORY, WKTKeywords.TargetCRS);
         final CoordinateReferenceSystem interpolationCRS = 
parseCoordinateReferenceSystem(element, OPTIONAL,  
WKTKeywords.InterpolationCRS);
         final OperationMethod           method           = 
parseMethod(element, WKTKeywords.Method);
-        final Element                   accuracy         = 
element.pullElement(OPTIONAL, WKTKeywords.OperationAccuracy);
+        final double                    accuracy         = 
pullElementAsDouble(element, WKTKeywords.OperationAccuracy, OPTIONAL);
         final Map<String,Object>        properties       = 
parseParametersAndClose(element, name, method);
-        if (accuracy != null) {
+        if (Double.isFinite(accuracy)) {
             
properties.put(CoordinateOperation.COORDINATE_OPERATION_ACCURACY_KEY,
-                    
PositionalAccuracyConstant.transformation(accuracy.pullDouble("accuracy")));
-            accuracy.close(ignoredElements);
+                           
PositionalAccuracyConstant.transformation(accuracy));
         }
         try {
             final DefaultCoordinateOperationFactory df = getOperationFactory();
@@ -2376,4 +2454,11 @@ class GeodeticObjectParser extends MathTransformParser 
implements Comparator<Coo
             return DefaultCoordinateOperationFactory.provider();
         }
     }
+
+    /**
+     * Returns the prime meridian to use by default when none is specified.
+     */
+    private static PrimeMeridian greenwich() {
+        return CommonCRS.WGS84.primeMeridian();
+    }
 }
diff --git 
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
index 184d2b5ae7..a30da11b1c 100644
--- 
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
@@ -19,6 +19,7 @@ package org.apache.sis.referencing.crs;
 import java.util.Map;
 import java.util.EnumMap;
 import java.util.Objects;
+import java.util.function.Function;
 import jakarta.xml.bind.annotation.XmlType;
 import jakarta.xml.bind.annotation.XmlRootElement;
 import jakarta.xml.bind.annotation.XmlSeeAlso;
@@ -34,8 +35,10 @@ 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.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;
@@ -44,6 +47,7 @@ import org.apache.sis.util.resources.Errors;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.metadata.Identifier;
+import org.opengis.referencing.datum.DatumEnsemble;
 
 // Specific to the geoapi-4.0 branch:
 import org.opengis.referencing.crs.DerivedCRS;
@@ -281,34 +285,6 @@ public class AbstractCRS extends AbstractReferenceSystem 
implements CoordinateRe
         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 = crs.getDatumEnsemble();
-                if (ensemble instanceof Datum) {
-                    return (Datum) ensemble;
-                }
-            }
-        }
-        return null;
-    }
-
     /**
      * Returns the coordinate system.
      *
@@ -468,7 +444,7 @@ public class AbstractCRS extends AbstractReferenceSystem 
implements CoordinateRe
     protected String formatTo(final Formatter formatter) {
         final String keyword = super.formatTo(formatter);
         formatter.newLine();
-        formatter.append(AbstractDatum.castOrCopy(getDatumOrEnsemble(true)));  
   // For the conversion of ensemble to datum.
+        formatDatum(formatter);
         formatter.newLine();
         final Convention convention = formatter.getConvention();
         final boolean isWKT1 = convention.majorVersion() == 1;
@@ -479,6 +455,51 @@ public class AbstractCRS extends AbstractReferenceSystem 
implements CoordinateRe
         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;
+            });
+        }
+    }
+
+    /**
+     * 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)
+    {
+        if (datum != null) {
+            formatter.appendFormattable(datum, toFormattable);
+        } else if (formatter.getConvention().supports(Convention.WKT2_2019)) {
+            formatter.appendFormattable(crs.getDatumEnsemble(), 
DefaultDatumEnsemble::castOrCopy);
+        } 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 --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractDerivedCRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractDerivedCRS.java
index cabb6fd81c..dba0e2c4c1 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractDerivedCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractDerivedCRS.java
@@ -37,6 +37,7 @@ import 
org.apache.sis.referencing.privy.ReferencingFactoryContainer;
 import org.apache.sis.xml.bind.referencing.CC_Conversion;
 import org.apache.sis.metadata.privy.ImplementationHelper;
 import org.apache.sis.metadata.privy.Identifiers;
+import org.apache.sis.io.wkt.Formatter;
 import org.apache.sis.system.Semaphores;
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.ArgumentChecks;
@@ -186,6 +187,19 @@ abstract class AbstractDerivedCRS extends AbstractCRS 
implements DerivedCRS {
         return conversionFromBase;
     }
 
+    /**
+     * Formats the datum or a view of the ensemble as a datum.
+     */
+    @Override
+    final void formatDatum(final Formatter formatter) {
+        final SingleCRS baseCRS = getBaseCRS();
+        if (baseCRS instanceof AbstractCRS) {
+            ((AbstractCRS) baseCRS).formatDatum(formatter);
+        } else {
+            super.formatDatum(formatter);
+        }
+    }
+
     /**
      * Compares this coordinate reference system with the specified object for 
equality.
      *
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
index d6abe1b8be..fb2ba4b411 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
@@ -27,7 +27,6 @@ import jakarta.xml.bind.annotation.XmlType;
 import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlRootElement;
 import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
-import org.opengis.referencing.datum.Datum;
 import org.opengis.referencing.crs.SingleCRS;
 import org.opengis.referencing.crs.CompoundCRS;
 import org.opengis.referencing.crs.GeodeticCRS;
@@ -309,14 +308,6 @@ public class DefaultCompoundCRS extends AbstractCRS 
implements CompoundCRS {
         return CompoundCRS.class;
     }
 
-    /**
-     * Compound CRS do not have datum.
-     */
-    @Override
-    final Datum getDatumOrEnsemble(final boolean legacy) {
-        return null;
-    }
-
     /**
      * Returns the ordered list of coordinate reference systems.
      * This is the list of CRS given at construction time.
diff --git 
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
index 7973e5ed0d..72119d0ec0 100644
--- 
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
@@ -53,7 +53,6 @@ import 
org.apache.sis.xml.bind.referencing.CS_CoordinateSystem;
 import org.apache.sis.referencing.privy.ReferencingUtilities;
 import org.apache.sis.referencing.privy.WKTUtilities;
 import org.apache.sis.referencing.privy.WKTKeywords;
-import org.apache.sis.referencing.datum.DatumOrEnsemble;
 import static org.apache.sis.referencing.internal.Legacy.DERIVED_TYPE_KEY;
 import org.apache.sis.io.wkt.Convention;
 import org.apache.sis.io.wkt.Formatter;
@@ -425,18 +424,6 @@ public class DefaultDerivedCRS extends AbstractDerivedCRS 
implements DerivedCRS
         return getBaseCRS().getDatumEnsemble();
     }
 
-    /**
-     * 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)
@@ -686,11 +673,6 @@ public class DefaultDerivedCRS extends AbstractDerivedCRS 
implements DerivedCRS
             return ((GeodeticCRS) getBaseCRS()).getDatumEnsemble();
         }
 
-        /** Returns the datum or a view of the ensemble as a datum. */
-        @Override GeodeticDatum getDatumOrEnsemble(final boolean legacy) {
-            return legacy ? DatumOrEnsemble.asDatum((GeodeticCRS) 
getBaseCRS()) : getDatum();
-        }
-
         /** Returns a coordinate reference system of the same type as this CRS 
but with different axes. */
         @Override AbstractCRS createSameType(final AbstractCS derivedCS) {
             return new Geodetic(this, derivedCS);
@@ -747,11 +729,6 @@ public class DefaultDerivedCRS extends AbstractDerivedCRS 
implements DerivedCRS
             return ((VerticalCRS) getBaseCRS()).getDatumEnsemble();
         }
 
-        /** Returns the datum or a view of the ensemble as a datum. */
-        @Override VerticalDatum getDatumOrEnsemble(final boolean legacy) {
-            return legacy ? DatumOrEnsemble.asDatum((VerticalCRS) 
getBaseCRS()) : getDatum();
-        }
-
         /** Returns the coordinate system given at construction time. */
         @Override public VerticalCS getCoordinateSystem() {
             return (VerticalCS) super.getCoordinateSystem();
@@ -813,11 +790,6 @@ public class DefaultDerivedCRS extends AbstractDerivedCRS 
implements DerivedCRS
             return ((TemporalCRS) getBaseCRS()).getDatumEnsemble();
         }
 
-        /** Returns the datum or a view of the ensemble as a datum. */
-        @Override TemporalDatum getDatumOrEnsemble(final boolean legacy) {
-            return legacy ? DatumOrEnsemble.asDatum((TemporalCRS) 
getBaseCRS()) : getDatum();
-        }
-
         /** Returns the coordinate system given at construction time. */
         @Override public TimeCS getCoordinateSystem() {
             return (TimeCS) super.getCoordinateSystem();
@@ -879,11 +851,6 @@ public class DefaultDerivedCRS extends AbstractDerivedCRS 
implements DerivedCRS
             return ((ParametricCRS) getBaseCRS()).getDatumEnsemble();
         }
 
-        /** Returns the datum or a view of the ensemble as a datum. */
-        @Override ParametricDatum getDatumOrEnsemble(final boolean legacy) {
-            return legacy ? DatumOrEnsemble.asDatum((ParametricCRS) 
getBaseCRS()) : getDatum();
-        }
-
         /** Returns the coordinate system given at construction time. */
         @Override public ParametricCS getCoordinateSystem() {
             return (ParametricCS) super.getCoordinateSystem();
@@ -948,11 +915,6 @@ public class DefaultDerivedCRS extends AbstractDerivedCRS 
implements DerivedCRS
             return ((EngineeringCRS) getBaseCRS()).getDatumEnsemble();
         }
 
-        /** Returns the datum or a view of the ensemble as a datum. */
-        @Override EngineeringDatum getDatumOrEnsemble(final boolean legacy) {
-            return legacy ? DatumOrEnsemble.asDatum((EngineeringCRS) 
getBaseCRS()) : getDatum();
-        }
-
         /** Returns a coordinate reference system of the same type as this CRS 
but with different axes. */
         @Override AbstractCRS createSameType(final AbstractCS derivedCS) {
             return new Engineering(this, derivedCS);
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java
index 95c51219cc..a20f930a20 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java
@@ -27,6 +27,7 @@ import org.opengis.referencing.datum.EngineeringDatum;
 import org.apache.sis.referencing.AbstractReferenceSystem;
 import org.apache.sis.referencing.cs.*;
 import org.apache.sis.referencing.datum.DatumOrEnsemble;
+import org.apache.sis.referencing.datum.DefaultEngineeringDatum;
 import org.apache.sis.referencing.privy.WKTKeywords;
 import org.apache.sis.xml.bind.referencing.CS_CoordinateSystem;
 import org.apache.sis.io.wkt.Formatter;
@@ -236,15 +237,6 @@ public class DefaultEngineeringCRS extends 
AbstractSingleCRS<EngineeringDatum> i
         return super.getDatumEnsemble();
     }
 
-    /**
-     * Returns the datum or a view of the ensemble as a datum.
-     * The {@code legacy} argument tells whether this method is invoked for 
formatting in a legacy <abbr>WKT</abbr> format.
-     */
-    @Override
-    final EngineeringDatum getDatumOrEnsemble(final boolean legacy) {
-        return legacy ? DatumOrEnsemble.asDatum(this) : getDatum();
-    }
-
     /**
      * {@inheritDoc}
      *
@@ -279,6 +271,14 @@ public class DefaultEngineeringCRS extends 
AbstractSingleCRS<EngineeringDatum> i
                  : formatter.shortOrLong(WKTKeywords.EngCRS, 
WKTKeywords.EngineeringCRS);
     }
 
+    /**
+     * Formats the datum or a view of the ensemble as a datum.
+     */
+    @Override
+    final void formatDatum(final Formatter formatter) {
+        formatDatum(formatter, this, getDatum(), 
DefaultEngineeringDatum::castOrCopy, DatumOrEnsemble::asDatum);
+    }
+
 
 
 
diff --git 
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
index 93844e7a95..bbc1e2e742 100644
--- 
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
@@ -44,7 +44,6 @@ import org.apache.sis.referencing.privy.ReferencingUtilities;
 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;
@@ -161,15 +160,6 @@ class DefaultGeodeticCRS extends 
AbstractSingleCRS<GeodeticDatum> implements Geo
         return super.getDatum();
     }
 
-    /**
-     * 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.
@@ -221,16 +211,13 @@ class DefaultGeodeticCRS extends 
AbstractSingleCRS<GeodeticDatum> implements Geo
          * The prime meridian is part of datum according ISO 19111, but is 
formatted
          * as a sibling (rather than a child) element in WKT for historical 
reasons.
          */
-        @SuppressWarnings("LocalVariableHidesMemberVariable")
-        final GeodeticDatum datum = getDatumOrEnsemble(true);
         formatter.newLine();
-        formatter.append(DefaultGeodeticDatum.castOrCopy(datum));   // For the 
conversion of ensemble to datum.
+        formatDatum(formatter);
         formatter.newLine();
         final Unit<Angle> angularUnit = AxisDirections.getAngularUnit(cs, 
null);
         DatumOrEnsemble.getPrimeMeridian(this).ifPresent((PrimeMeridian pm) -> 
{
-            if (convention != Convention.WKT2_SIMPLIFIED ||     // Really this 
specific enum, not Convention.isSimplified().
-                    ReferencingUtilities.getGreenwichLongitude(pm, 
Units.DEGREE) != 0)
-            {
+            // Really this specific enum, not Convention.isSimplified().
+            if (convention != Convention.WKT2_SIMPLIFIED || 
pm.getGreenwichLongitude() != 0) {
                 final Unit<Angle> oldUnit = 
formatter.addContextualUnit(angularUnit);
                 formatter.indent(1);
                 formatter.appendFormattable(pm, 
DefaultPrimeMeridian::castOrCopy);
@@ -291,6 +278,14 @@ class DefaultGeodeticCRS extends 
AbstractSingleCRS<GeodeticDatum> implements Geo
         }
     }
 
+    /**
+     * Formats the datum or a view of the ensemble as a datum.
+     */
+    @Override
+    final void formatDatum(final Formatter formatter) {
+        formatDatum(formatter, this, getDatum(), 
DefaultGeodeticDatum::castOrCopy, DatumOrEnsemble::asDatum);
+    }
+
 
 
 
diff --git 
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
index e0334e0404..285fc6cb3e 100644
--- 
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
@@ -24,6 +24,7 @@ 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:
@@ -219,15 +220,6 @@ public class DefaultParametricCRS extends 
AbstractSingleCRS<ParametricDatum> imp
         return super.getDatumEnsemble();
     }
 
-    /**
-     * Returns the datum or a view of the ensemble as a datum.
-     * The {@code legacy} argument tells whether this method is invoked for 
formatting in a legacy <abbr>WKT</abbr> format.
-     */
-    @Override
-    final ParametricDatum getDatumOrEnsemble(final boolean legacy) {
-        return legacy ? DatumOrEnsemble.asDatum(this) : getDatum();
-    }
-
     /**
      * Returns the coordinate system.
      *
@@ -278,6 +270,14 @@ public class DefaultParametricCRS extends 
AbstractSingleCRS<ParametricDatum> imp
         return isBaseCRS(formatter) ? WKTKeywords.BaseParamCRS : 
WKTKeywords.ParametricCRS;
     }
 
+    /**
+     * Formats the datum or a view of the ensemble as a datum.
+     */
+    @Override
+    final void formatDatum(final Formatter formatter) {
+        formatDatum(formatter, this, getDatum(), 
DefaultParametricDatum::castOrCopy, DatumOrEnsemble::asDatum);
+    }
+
 
 
 
diff --git 
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
index 25ad210d3d..bc289ee30f 100644
--- 
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
@@ -30,7 +30,6 @@ import org.opengis.referencing.datum.GeodeticDatum;
 import org.opengis.referencing.operation.Conversion;
 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.privy.ReferencingUtilities;
 import org.apache.sis.referencing.privy.AxisDirections;
 import org.apache.sis.referencing.privy.WKTKeywords;
@@ -238,15 +237,6 @@ public class DefaultProjectedCRS extends 
AbstractDerivedCRS implements Projected
         return getBaseCRS().getDatumEnsemble();
     }
 
-    /**
-     * Returns the datum or a view of the ensemble as a datum.
-     * The {@code legacy} argument tells whether this method is invoked for 
formatting in a legacy <abbr>WKT</abbr> format.
-     */
-    @Override
-    final 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 --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
index 11f109ec18..b9c063073e 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
@@ -38,6 +38,7 @@ import org.apache.sis.referencing.AbstractReferenceSystem;
 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.DefaultTemporalDatum;
 import org.apache.sis.referencing.privy.WKTKeywords;
 import org.apache.sis.io.wkt.Formatter;
 import org.apache.sis.measure.Units;
@@ -291,15 +292,6 @@ public class DefaultTemporalCRS extends 
AbstractSingleCRS<TemporalDatum> impleme
         return super.getDatumEnsemble();
     }
 
-    /**
-     * Returns the datum or a view of the ensemble as a datum.
-     * The {@code legacy} argument tells whether this method is invoked for 
formatting in a legacy <abbr>WKT</abbr> format.
-     */
-    @Override
-    final TemporalDatum getDatumOrEnsemble(final boolean legacy) {
-        return legacy ? DatumOrEnsemble.asDatum(this) : getDatum();
-    }
-
     /**
      * Returns the coordinate system.
      *
@@ -518,6 +510,14 @@ public class DefaultTemporalCRS extends 
AbstractSingleCRS<TemporalDatum> impleme
         return isBaseCRS(formatter) ? WKTKeywords.BaseTimeCRS : 
WKTKeywords.TimeCRS;
     }
 
+    /**
+     * Formats the datum or a view of the ensemble as a datum.
+     */
+    @Override
+    final void formatDatum(final Formatter formatter) {
+        formatDatum(formatter, this, getDatum(), 
DefaultTemporalDatum::castOrCopy, DatumOrEnsemble::asDatum);
+    }
+
 
 
 
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultVerticalCRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultVerticalCRS.java
index 7fe47b27fa..75f7420703 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultVerticalCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultVerticalCRS.java
@@ -27,6 +27,7 @@ import org.apache.sis.referencing.AbstractReferenceSystem;
 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.DefaultVerticalDatum;
 import org.apache.sis.referencing.privy.WKTKeywords;
 import org.apache.sis.io.wkt.Formatter;
 
@@ -220,15 +221,6 @@ public class DefaultVerticalCRS extends 
AbstractSingleCRS<VerticalDatum> impleme
         return super.getDatumEnsemble();
     }
 
-    /**
-     * Returns the datum or a view of the ensemble as a datum.
-     * The {@code legacy} argument tells whether this method is invoked for 
formatting in a legacy <abbr>WKT</abbr> format.
-     */
-    @Override
-    final VerticalDatum getDatumOrEnsemble(final boolean legacy) {
-        return legacy ? DatumOrEnsemble.asDatum(this) : getDatum();
-    }
-
     /**
      * Returns the coordinate system.
      *
@@ -271,6 +263,14 @@ public class DefaultVerticalCRS extends 
AbstractSingleCRS<VerticalDatum> impleme
                  : formatter.shortOrLong(WKTKeywords.VertCRS, 
WKTKeywords.VerticalCRS);
     }
 
+    /**
+     * Formats the datum or a view of the ensemble as a datum.
+     */
+    @Override
+    final void formatDatum(final Formatter formatter) {
+        formatDatum(formatter, this, getDatum(), 
DefaultVerticalDatum::castOrCopy, DatumOrEnsemble::asDatum);
+    }
+
 
 
 
diff --git 
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
index f9d86b2e79..93fa957ec0 100644
--- 
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
@@ -33,9 +33,13 @@ import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.datum.Datum;
 import org.apache.sis.referencing.AbstractIdentifiedObject;
 import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.referencing.privy.WKTKeywords;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.iso.Types;
+import org.apache.sis.util.privy.Constants;
+import org.apache.sis.util.collection.Containers;
+import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.temporal.TemporalDate;
 import org.apache.sis.metadata.privy.Identifiers;
 import org.apache.sis.metadata.privy.NameToIdentifier;
@@ -44,10 +48,10 @@ import org.apache.sis.metadata.iso.citation.Citations;
 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 geoapi-4.0 branch:
@@ -201,15 +205,15 @@ public class AbstractDatum extends 
AbstractIdentifiedObject implements Datum {
         if (anchorDefinition == null) {
             anchorDefinition = Types.toInternationalString(properties, 
"anchorPoint");      // Legacy name.
         }
-        anchorEpoch = property(properties, ANCHOR_EPOCH_KEY, Temporal.class);
+        anchorEpoch = Containers.property(properties, ANCHOR_EPOCH_KEY, 
Temporal.class);
         if (anchorEpoch == null) {
-            Date date = property(properties, "realizationEpoch", Date.class);  
             // Legacy name.
+            Date date = Containers.property(properties, "realizationEpoch", 
Date.class);    // Legacy name.
             if (date != null) {
                 anchorEpoch = date.toInstant();
             }
         }
-        publicationDate = property(properties, PUBLICATION_DATE_KEY, 
Temporal.class);
-        conventionalRS  = property(properties, CONVENTIONAL_RS_KEY, 
IdentifiedObject.class);
+        publicationDate = Containers.property(properties, 
PUBLICATION_DATE_KEY, Temporal.class);
+        conventionalRS  = Containers.property(properties, CONVENTIONAL_RS_KEY, 
IdentifiedObject.class);
     }
 
     /**
@@ -500,6 +504,10 @@ public class AbstractDatum extends 
AbstractIdentifiedObject implements Datum {
      * Formats the inner part of the <i>Well Known Text</i> (WKT) 
representation for this datum.
      * See {@link AbstractIdentifiedObject#formatTo(Formatter)} for more 
information.
      *
+     * <h4>Default implementation</h4>
+     * The default implementation appends the datum name and returns {@code 
"Member"} if this datum
+     * is formatted inside a {@code ENSEMBLE} <abbr>WKT</abbr> element, or 
{@code null} otherwise.
+     *
      * @param  formatter  the formatter where to format the inner content of 
this WKT element.
      * @return the {@linkplain org.apache.sis.io.wkt.KeywordCase#CAMEL_CASE 
CamelCase} keyword
      *         for the WKT element, or {@code null} if unknown.
@@ -511,13 +519,20 @@ public class AbstractDatum extends 
AbstractIdentifiedObject implements Datum {
         if (name == null) {
             name = IdentifiedObjects.getName(this, null);
             if (name == null) {                                 // Should 
never happen, but be safe.
-                return super.formatTo(formatter);
-            }
-            if ("ESRI".equalsIgnoreCase(Citations.toCodeSpace(authority)) && 
!name.startsWith(Simplifier.ESRI_DATUM_PREFIX)) {
-                name = Simplifier.ESRI_DATUM_PREFIX + name;
+                name = 
Vocabulary.forLocale(formatter.getLocale()).getString(Vocabulary.Keys.Unnamed);
+            } else if 
(Constants.ESRI.equalsIgnoreCase(Citations.toCodeSpace(authority))) {
+                /*
+                 * ESRI specific convention: datum names start with the "D_" 
suffix.
+                 */
+                if (!name.startsWith(Simplifier.ESRI_DATUM_PREFIX)) {
+                    name = Simplifier.ESRI_DATUM_PREFIX + name;
+                }
             }
         }
         formatter.append(name, ElementKind.DATUM);
+        if (formatter.getEnclosingElement(1) instanceof DatumEnsemble<?>) {
+            return WKTKeywords.Member;
+        }
         return null;
     }
 
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
index 4c3ebe3bc5..6a216fc196 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
@@ -43,12 +43,15 @@ import org.apache.sis.referencing.GeodeticException;
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.internal.Resources;
 import org.apache.sis.referencing.privy.WKTKeywords;
+import org.apache.sis.referencing.privy.WKTUtilities;
+import org.apache.sis.referencing.internal.PositionalAccuracyConstant;
 import org.apache.sis.metadata.privy.SecondaryTrait;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.privy.CollectionsExt;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import java.util.Optional;
@@ -150,7 +153,7 @@ public class DefaultDatumEnsemble<D extends Datum> extends 
AbstractIdentifiedObj
      * @throws IllegalArgumentException if a member is an instance of {@link 
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,
@@ -223,8 +226,9 @@ public class DefaultDatumEnsemble<D extends Datum> extends 
AbstractIdentifiedObj
      * 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 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.
@@ -233,10 +237,13 @@ public class DefaultDatumEnsemble<D extends Datum> 
extends AbstractIdentifiedObj
      */
     public static <D extends Datum> DefaultDatumEnsemble<D> create(
             final Map<String,?> properties,
+            final Class<D> memberType,
             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);
     }
 
     /**
@@ -349,6 +356,19 @@ public class DefaultDatumEnsemble<D extends Datum> extends 
AbstractIdentifiedObj
         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
@@ -499,16 +519,32 @@ check:  if (it.hasNext()) {
     @Override
     protected String formatTo(final Formatter formatter) {
         super.formatTo(formatter);
-        if (Convention.WKT2_2015.compareTo(formatter.getConvention()) >= 0) {
+        for (final Datum member : getMembers()) {
+            formatter.newLine();
+            formatter.appendFormattable(member, AbstractDatum::castOrCopy);
+        }
+        complete(formatter);
+        formatter.newLine();
+        WKTUtilities.appendElementIfPositive(WKTKeywords.EnsembleAccuracy, 
getLinearAccuracy(), formatter);
+        formatter.newLine();
+        if (!formatter.getConvention().supports(Convention.WKT2_2019)) {
             formatter.setInvalidWKT(this, null);
         }
         return WKTKeywords.Ensemble;
     }
 
+    /**
+     * Completes the <abbr>WKT</abbr> formatting with elements to insert after 
members and before accuracy.
+     *
+     * @param  formatter  the formatter where to format the inner content of 
this WKT element.
+     */
+    void complete(final Formatter formatter) {
+    }
+
     /**
      * 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)
@@ -558,12 +594,26 @@ check:  if (it.hasNext()) {
         public PrimeMeridian getPrimeMeridian() {
             return getCommonMandatoryValue(GeodeticDatum::getPrimeMeridian);
         }
+
+        /**
+         * Completes the <abbr>WKT</abbr> formatting with elements to insert 
after members and before accuracy.
+         * It includes the ellipsoid, but not the prime meridian which is 
formatted outside the ensemble.
+         */
+        @Override
+        void complete(final Formatter formatter) {
+            formatter.newLine();
+            try {
+                formatter.appendFormattable(getEllipsoid(), 
DefaultEllipsoid::castOrCopy);
+            } catch (GeodeticException e) {
+                formatter.setInvalidWKT(this, e);
+            }
+        }
     }
 
     /**
      * 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)
@@ -604,7 +654,7 @@ check:  if (it.hasNext()) {
     /**
      * 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)
@@ -646,7 +696,7 @@ check:  if (it.hasNext()) {
     /**
      * An ensemble viewed as a low-accuracy parametric datum.
      *
-     * @see #create(Map, Collection, PositionalAccuracy)
+     * @see #create(Map, Class, Collection, PositionalAccuracy)
      * @see #castOrCopy(DatumEnsemble)
      * @see DatumOrEnsemble#of(ParametricCRS)
      * @see DatumOrEnsemble#asTargetDatum(ParametricCRS, ParametricCRS)
@@ -675,7 +725,7 @@ check:  if (it.hasNext()) {
     /**
      * 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)
@@ -709,7 +759,7 @@ check:  if (it.hasNext()) {
      *
      * @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> {
@@ -784,7 +834,7 @@ nextType:   for (final Factory<?> factory : FACTORIES) {
                     }
                 }
             }
-            throw new 
ClassCastException(Errors.format(Errors.Keys.IllegalClass_2, Datum.class, 
Classes.getClass(illegal)));
+            throw new 
ClassCastException(Errors.format(Errors.Keys.IllegalClass_2, memberType, 
Classes.getClass(illegal)));
         }
 
         /**
@@ -797,7 +847,7 @@ nextType:   for (final Factory<?> factory : FACTORIES) {
          * @throws IllegalArgumentException if a member is an instance of 
{@link 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);
 
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultEngineeringDatum.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultEngineeringDatum.java
index d16159f328..d0a5d1f080 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultEngineeringDatum.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultEngineeringDatum.java
@@ -41,7 +41,7 @@ import org.opengis.metadata.Identifier;
  * components were created using only SIS factories and static constants.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  *
  * @see org.apache.sis.referencing.crs.DefaultEngineeringCRS
  * @see 
org.apache.sis.referencing.factory.GeodeticAuthorityFactory#createEngineeringDatum(String)
@@ -155,11 +155,21 @@ public class DefaultEngineeringDatum extends 
AbstractDatum implements Engineerin
     /**
      * Formats this datum as a <i>Well Known Text</i> {@code 
EngineeringDatum[…]} element.
      *
-     * @return {@code "EngineeringDatum"} (WKT 2) or {@code "Local_Datum"} 
(WKT 1).
+     * <h4>Compatibility note</h4>
+     * 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 "EDatum"} or {@code "EngineeringDatum"} (WKT 2), or 
{@code "Local_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) {
-        super.formatTo(formatter);
+        final String name = super.formatTo(formatter);
+        if (name != null) {
+            // Member of a datum ensemble, but ISO 19162:2019 allows that for 
geodetic and vertical datum only.
+            formatter.setInvalidWKT(this, null);
+            return name;
+        }
         if (formatter.getConvention().majorVersion() == 1) {
             /*
              * Datum type was provided for all kind of datum in the legacy OGC 
01-009 specification.
diff --git 
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
index 3e00f7e85c..7f5a051171 100644
--- 
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
@@ -740,10 +740,15 @@ public class DefaultGeodeticDatum extends AbstractDatum 
implements GeodeticDatum
      * as a separated element after the geodetic reference frame (for 
compatibility with WKT 1).
      *
      * @return {@code "Datum"} or {@code "GeodeticDatum"}.
+     *         May also be {@code "Member"} if this datum is inside a 
<abbr>WKT</abbr> {@code Ensemble[…]} element.
      */
     @Override
     protected String formatTo(final Formatter formatter) {
-        super.formatTo(formatter);
+        final String name = super.formatTo(formatter);
+        if (name != null) {
+            // Member of a datum ensemble.
+            return name;
+        }
         formatter.newLine();
         formatter.appendFormattable(getEllipsoid(), 
DefaultEllipsoid::castOrCopy);
         final boolean isWKT1 = formatter.getConvention().majorVersion() == 1;
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultImageDatum.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultImageDatum.java
index 97c61996f2..5118816d1b 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultImageDatum.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultImageDatum.java
@@ -26,7 +26,6 @@ import org.opengis.util.InternationalString;
 import org.apache.sis.referencing.privy.WKTKeywords;
 import org.apache.sis.metadata.privy.ImplementationHelper;
 import org.apache.sis.io.wkt.Formatter;
-import org.apache.sis.io.wkt.Convention;
 import org.apache.sis.util.ComparisonMode;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
@@ -178,18 +177,24 @@ public final class DefaultImageDatum extends 
AbstractDatum {
      * Formats this datum as a <i>Well Known Text</i> {@code ImageDatum[…]} 
element.
      *
      * <h4>Compatibility note</h4>
-     * {@code ImageDatum} is defined in the WKT 2 specification only.
+     * {@code ImageDatum} is defined only in the first edition of the
+     * <abbr>WKT</abbr> 2 specification (<abbr>ISO</abbr> 19162:2015).
      *
-     * @return {@code "ImageDatum"}.
+     * @return {@code "IDatum"} or {@code "ImageDatum"}.
      */
     @Override
     protected String formatTo(final Formatter formatter) {
-        super.formatTo(formatter);
-        final Convention convention = formatter.getConvention();
-        if (convention == Convention.INTERNAL) {
-            formatter.append(getPixelInCell(), ElementKind.CODE_LIST);    // 
This is an extension compared to ISO 19162.
-        } else if (convention.majorVersion() == 1) {
+        final String name = super.formatTo(formatter);
+        if (name != null) {
+            // Member of a datum ensemble, but ISO 19162:2019 allows that for 
geodetic and vertical datum only.
             formatter.setInvalidWKT(this, null);
+            return name;
+        }
+        switch (formatter.getConvention()) {
+            // `PixelInCell` is an extension compared to ISO 19162.
+            case INTERNAL: formatter.append(getPixelInCell(), 
ElementKind.CODE_LIST); break;
+            case WKT2_2015: break;      // The only standard where this 
element is defined.
+            default: formatter.setInvalidWKT(this, null); break;
         }
         return formatter.shortOrLong(WKTKeywords.IDatum, 
WKTKeywords.ImageDatum);
     }
diff --git 
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
index 0f4272d196..e0efe9b6f9 100644
--- 
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
@@ -48,7 +48,7 @@ import org.opengis.referencing.datum.ParametricDatum;
  * all components were created using only SIS factories and static constants.
  *
  * @author  Johann Sorel (Geomatys)
- * @version 1.4
+ * @version 1.5
  *
  * @see org.apache.sis.referencing.cs.DefaultParametricCS
  * @see org.apache.sis.referencing.crs.DefaultParametricCRS
@@ -164,13 +164,21 @@ public class DefaultParametricDatum extends AbstractDatum 
implements ParametricD
      * Formats this datum as a <i>Well Known Text</i> {@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"}.
+     * @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) {
-        super.formatTo(formatter);
+        final String name = super.formatTo(formatter);
+        if (name != null) {
+            // Member of a datum ensemble, but ISO 19162:2019 allows that for 
geodetic and vertical datum only.
+            formatter.setInvalidWKT(this, null);
+            return name;
+        }
         if (formatter.getConvention().majorVersion() == 1) {
             formatter.setInvalidWKT(this, null);
         }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultTemporalDatum.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultTemporalDatum.java
index 52fffaee63..f851aa3604 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultTemporalDatum.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultTemporalDatum.java
@@ -257,13 +257,21 @@ public class DefaultTemporalDatum extends AbstractDatum 
implements TemporalDatum
      * Formats this datum as a <i>Well Known Text</i> {@code TimeDatum[…]} 
element.
      *
      * <h4>Compatibility note</h4>
-     * {@code TimeDatum} is defined in the WKT 2 specification only.
+     * {@code TimeDatum} 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 "TimeDatum"}.
+     * @return {@code "TDatum"} or {@code "TimeDatum"}.
+     *         May also be {@code "Member"} if this datum is inside a 
<abbr>WKT</abbr> {@code Ensemble[…]} element.
      */
     @Override
     protected String formatTo(final Formatter formatter) {
-        super.formatTo(formatter);
+        final String name = super.formatTo(formatter);
+        if (name != null) {
+            // Member of a datum ensemble, but ISO 19162:2019 allows that for 
geodetic and vertical datum only.
+            formatter.setInvalidWKT(this, null);
+            return name;
+        }
         formatter.append(new Origin(getOrigin()));
         if (formatter.getConvention().majorVersion() == 1) {
             formatter.setInvalidWKT(this, null);
diff --git 
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
index 3063f0d003..e34d5b2ad9 100644
--- 
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
@@ -376,11 +376,16 @@ public class DefaultVerticalDatum extends AbstractDatum 
implements VerticalDatum
      * 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.
      *
-     * @return {@code "VerticalDatum"} (WKT 2) or {@code "Vert_Datum"} (WKT 1).
+     * @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) {
-        super.formatTo(formatter);
+        final String name = super.formatTo(formatter);
+        if (name != null) {
+            // Member of a datum ensemble.
+            return name;
+        }
         if (formatter.getConvention().majorVersion() == 1) {
             
formatter.append(VerticalDatumTypes.toLegacyCode(getOrGuessMethod(formatter.getEnclosingElement(1))));
             return WKTKeywords.Vert_Datum;
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
index 373dd67588..ff3f7e2713 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
@@ -620,7 +620,7 @@ public class GeodeticObjectFactory extends AbstractFactory 
implements CRSFactory
     {
         final DefaultDatumEnsemble<D> ensemble;
         try {
-            ensemble = DefaultDatumEnsemble.create(complete(properties), 
members, accuracy);
+            ensemble = DefaultDatumEnsemble.create(complete(properties), null, 
members, accuracy);
         } catch (IllegalArgumentException exception) {
             throw new InvalidGeodeticParameterException(exception);
         }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/PositionalAccuracyConstant.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/PositionalAccuracyConstant.java
index 22a60b2a89..bacab75f14 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/PositionalAccuracyConstant.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/PositionalAccuracyConstant.java
@@ -240,33 +240,15 @@ public final class PositionalAccuracyConstant extends 
DefaultAbsoluteExternalPos
     }
 
     /**
-     * Convenience method returning the accuracy in meters for the specified 
operation.
-     * This method tries each of the following procedures and returns the 
first successful one:
+     * Extracts the accuracy in meters from the given metadata.
+     * If at least one {@link QuantitativeResult} is found with a linear unit, 
then the largest
+     * accuracy estimate is converted to {@linkplain Units#METRE metres} and 
returned.
      *
-     * <ul>
-     *   <li>If at least one {@link QuantitativeResult} is found with a linear 
unit, then the largest
-     *       accuracy estimate is converted to {@linkplain Units#METRE metres} 
and returned.</li>
-     *   <li>Otherwise, if the operation is a {@link Conversion}, then returns 
0 since a conversion
-     *       is by definition accurate up to rounding errors.</li>
-     *   <li>Otherwise, if the operation is a {@link Transformation}, then 
checks if the datum shift
-     *       were applied with the help of Bursa-Wolf parameters. This 
procedure looks for SIS-specific
-     *       {@link #DATUM_SHIFT_APPLIED} and {@link #DATUM_SHIFT_OMITTED 
DATUM_SHIFT_OMITTED} constants.</li>
-     *   <li>Otherwise, if the operation is a {@link ConcatenatedOperation}, 
returns the sum of the accuracy
-     *       of all components. This is a conservative scenario where we 
assume that errors cumulate linearly.
-     *       Note that this is not necessarily the "worst case" scenario since 
the accuracy could be worst
-     *       if the math transforms are highly non-linear.</li>
-     * </ul>
-     *
-     * If the above is modified, please update {@code 
AbstractCoordinateOperation.getLinearAccuracy()} javadoc.
-     *
-     * @param  operation  the operation to inspect for accuracy.
+     * @param  accuracies  the metadata to inspect for accuracy.
      * @return the accuracy estimate (always in meters), or NaN if unknown.
-     *
-     * @see 
org.apache.sis.referencing.operation.AbstractCoordinateOperation#getLinearAccuracy()
      */
-    public static double getLinearAccuracy(final CoordinateOperation 
operation) {
+    public static double getLinearAccuracy(final Iterable<PositionalAccuracy> 
accuracies) {
         double accuracy = Double.NaN;
-        final Collection<PositionalAccuracy> accuracies = 
operation.getCoordinateOperationAccuracy();
         for (final PositionalAccuracy metadata : accuracies) {
             for (final Result result : metadata.getResults()) {
                 if (result instanceof QuantitativeResult) {
@@ -292,6 +274,36 @@ public final class PositionalAccuracyConstant extends 
DefaultAbsoluteExternalPos
                 }
             }
         }
+        return accuracy;
+    }
+
+    /**
+     * Extracts the accuracy in meters from the specified operation.
+     * This method tries each of the following procedures and returns the 
first successful one:
+     *
+     * <ul>
+     *   <li>If at least one {@link QuantitativeResult} is found with a linear 
unit, then the largest
+     *       accuracy estimate is converted to {@linkplain Units#METRE metres} 
and returned.</li>
+     *   <li>Otherwise, if the operation is a {@link Conversion}, then returns 
0 since a conversion
+     *       is by definition accurate up to rounding errors.</li>
+     *   <li>Otherwise, if the operation is a {@link Transformation}, then 
checks if the datum shift
+     *       were applied with the help of Bursa-Wolf parameters. This 
procedure looks for SIS-specific
+     *       {@link #DATUM_SHIFT_APPLIED} and {@link #DATUM_SHIFT_OMITTED 
DATUM_SHIFT_OMITTED} constants.</li>
+     *   <li>Otherwise, if the operation is a {@link ConcatenatedOperation}, 
returns the sum of the accuracy
+     *       of all components. This is a conservative scenario where we 
assume that errors cumulate linearly.
+     *       Note that this is not necessarily the "worst case" scenario since 
the accuracy could be worst
+     *       if the math transforms are highly non-linear.</li>
+     * </ul>
+     *
+     * If the above is modified, please update {@code 
AbstractCoordinateOperation.getLinearAccuracy()} javadoc.
+     *
+     * @param  operation  the operation to inspect for accuracy.
+     * @return the accuracy estimate (always in meters), or NaN if unknown.
+     *
+     * @see 
org.apache.sis.referencing.operation.AbstractCoordinateOperation#getLinearAccuracy()
+     */
+    public static double getLinearAccuracy(final CoordinateOperation 
operation) {
+        double accuracy = 
getLinearAccuracy(operation.getCoordinateOperationAccuracy());
         if (Double.isNaN(accuracy)) {
             /*
              * No quantitative (linear) accuracy were found. If the coordinate 
operation is actually
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
index c96d0cb1ed..7ba89eed2b 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
@@ -1026,15 +1026,7 @@ check:      for (int isTarget=0; ; isTarget++) {        
// 0 == source check; 1
          */
         if (!isSubOperation && !isGeogTran && !(this instanceof 
ConcatenatedOperation)) {
             append(formatter, getInterpolationCRS().orElse(null), 
WKTKeywords.InterpolationCRS);
-            final double accuracy = getLinearAccuracy();
-            if (accuracy > 0) {
-                formatter.append(new FormattableObject() {
-                    @Override protected String formatTo(final Formatter 
formatter) {
-                        formatter.append(accuracy);
-                        return WKTKeywords.OperationAccuracy;
-                    }
-                });
-            }
+            
WKTUtilities.appendElementIfPositive(WKTKeywords.OperationAccuracy, 
getLinearAccuracy(), formatter);
         }
         /*
          * Verifies if what we wrote is allowed by the standard.
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/MercatorAuxiliarySphere.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/MercatorAuxiliarySphere.java
index 87129c0d00..b8345c2318 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/MercatorAuxiliarySphere.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/MercatorAuxiliarySphere.java
@@ -21,6 +21,7 @@ import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterDescriptorGroup;
 import org.apache.sis.parameter.ParameterBuilder;
 import org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.util.privy.Constants;
 
 
 /**
@@ -65,7 +66,7 @@ public final class MercatorAuxiliarySphere extends 
AbstractMercator {
      */
     private static final ParameterDescriptorGroup PARAMETERS;
     static {
-        final ParameterBuilder builder = 
builder().setCodeSpace(Citations.ESRI, "ESRI");
+        final ParameterBuilder builder = 
builder().setCodeSpace(Citations.ESRI, Constants.ESRI);
         AUXILIARY_SPHERE_TYPE = 
builder.addName("Auxiliary_Sphere_Type").createBounded(0, 3, 0);
 
         final ParameterDescriptor<?>[] descriptors = 
toArray(Mercator2SP.PARAMETERS.descriptors(), 1);
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Mollweide.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Mollweide.java
index 9214a12e32..5a7ba1f070 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Mollweide.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Mollweide.java
@@ -22,6 +22,7 @@ import org.opengis.parameter.ParameterDescriptorGroup;
 import org.apache.sis.parameter.Parameters;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.referencing.operation.projection.NormalizedProjection;
+import org.apache.sis.util.privy.Constants;
 
 
 /**
@@ -92,7 +93,7 @@ public final class Mollweide extends MapProjection {
      */
     private static final ParameterDescriptorGroup PARAMETERS;
     static {
-        PARAMETERS = builder().setCodeSpace(Citations.ESRI, "ESRI")
+        PARAMETERS = builder().setCodeSpace(Citations.ESRI, Constants.ESRI)
                 .addName("Mollweide")
                 .addName(null, "Homalographic")
                 .addName(null, "Homolographic")
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/ObliqueMercatorTwoPoints.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/ObliqueMercatorTwoPoints.java
index 4f4af29d81..d668e5700d 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/ObliqueMercatorTwoPoints.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/ObliqueMercatorTwoPoints.java
@@ -24,6 +24,7 @@ import org.apache.sis.measure.MeasurementRange;
 import org.apache.sis.measure.Longitude;
 import org.apache.sis.measure.Latitude;
 import org.apache.sis.measure.Units;
+import org.apache.sis.util.privy.Constants;
 
 
 /**
@@ -48,7 +49,7 @@ public class ObliqueMercatorTwoPoints extends ObliqueMercator 
{
                                                     LAT_OF_2ND_POINT, 
LONG_OF_2ND_POINT;
 
     static {
-        final ParameterBuilder builder = 
builder().setCodeSpace(Citations.ESRI, "ESRI");
+        final ParameterBuilder builder = 
builder().setCodeSpace(Citations.ESRI, Constants.ESRI);
         LAT_OF_1ST_POINT  = create(builder.addName("Latitude_Of_1st_Point"),   
Latitude.MIN_VALUE,  Latitude.MAX_VALUE);
         LAT_OF_2ND_POINT  = create(builder.addName("Latitude_Of_2nd_Point"),   
Latitude.MIN_VALUE,  Latitude.MAX_VALUE);
         LONG_OF_1ST_POINT = create(builder.addName("Longitude_Of_1st_Point"), 
Longitude.MIN_VALUE, Longitude.MAX_VALUE);
@@ -77,7 +78,7 @@ public class ObliqueMercatorTwoPoints extends ObliqueMercator 
{
                              final ParameterDescriptor<Double> easting,
                              final ParameterDescriptor<Double> northing)
     {
-        super(builder().setCodeSpace(Citations.ESRI, "ESRI").addName(name)
+        super(builder().setCodeSpace(Citations.ESRI, 
Constants.ESRI).addName(name)
                 .createGroupForMapProjection(
                         LAT_OF_1ST_POINT,    LONG_OF_1ST_POINT,
                         LAT_OF_2ND_POINT,    LONG_OF_2ND_POINT,
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTKeywords.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTKeywords.java
index 80ad11133c..c2fe067df1 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTKeywords.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTKeywords.java
@@ -78,19 +78,21 @@ public final class WKTKeywords extends Static {
      * and {@link org.apache.sis.referencing.datum.AbstractDatum}.
      */
     public static final String
-            CS            = "CS",
-            Axis          = "Axis",
-            AxisMinValue  = "AxisMinValue",
-            AxisMaxValue  = "AxisMaxValue",
-            RangeMeaning  = "RangeMeaning",
-            Order         = "Order",
-            Meridian      = "Meridian",
-            PrimeMeridian = "PrimeMeridian",
-            PrimeM        = "PrimeM",
-            Ellipsoid     = "Ellipsoid",
-            Spheroid      = "Spheroid",
-            Ensemble      = "Ensemble",
-            ToWGS84       = "ToWGS84";
+            CS               = "CS",
+            Axis             = "Axis",
+            AxisMinValue     = "AxisMinValue",
+            AxisMaxValue     = "AxisMaxValue",
+            RangeMeaning     = "RangeMeaning",
+            Order            = "Order",
+            Meridian         = "Meridian",
+            PrimeMeridian    = "PrimeMeridian",
+            PrimeM           = "PrimeM",
+            Ellipsoid        = "Ellipsoid",
+            Spheroid         = "Spheroid",
+            Ensemble         = "Ensemble",
+            Member           = "Member",
+            EnsembleAccuracy = "EnsembleAccuracy",
+            ToWGS84          = "ToWGS84";
 
     /**
      * Related to {@link org.apache.sis.referencing.crs.DefaultGeocentricCRS}
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTUtilities.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTUtilities.java
index 7f118cc5f7..71e7ed7df4 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTUtilities.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTUtilities.java
@@ -192,6 +192,24 @@ public final class WKTUtilities extends Static {
         formatter.append(name, (type != null) ? type : ElementKind.NAME);
     }
 
+    /**
+     * Appends an element containing only a {@code double} value if that value 
is strictly greater than zero.
+     *
+     * @param name       name of the element to add.
+     * @param value      value to add.
+     * @param formatter  formatter where to add the value.
+     */
+    public static void appendElementIfPositive(final String name, final double 
value, final Formatter formatter) {
+        if (value > 0) {
+            formatter.append(new FormattableObject() {
+                @Override protected String formatTo(final Formatter formatter) 
{
+                    formatter.append(value);
+                    return name;
+                }
+            });
+        }
+    }
+
     /**
      * Appends a {@linkplain ParameterValueGroup group of parameters} in a 
{@code Param_MT[…]} element.
      *
diff --git 
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
index 074b3c4774..54790bb884 100644
--- 
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
@@ -39,6 +39,7 @@ import org.apache.sis.measure.Longitude;
 import org.apache.sis.metadata.privy.AxisNames;
 import org.apache.sis.referencing.privy.ReferencingFactoryContainer;
 import org.apache.sis.referencing.cs.CoordinateSystems;
+import org.apache.sis.referencing.datum.DatumOrEnsemble;
 import org.apache.sis.referencing.datum.BursaWolfParameters;
 import org.apache.sis.referencing.datum.DefaultGeodeticDatum;
 import org.apache.sis.referencing.factory.GeodeticObjectFactory;
@@ -496,6 +497,38 @@ public final class GeodeticObjectParserTest extends 
EPSGDependentTestCase {
         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,
+                "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.
@@ -506,7 +539,7 @@ public final class GeodeticObjectParserTest extends 
EPSGDependentTestCase {
     private void verifyGeographicCRS(final int swap, final GeographicCRS crs) 
throws ParseException {
         assertNameAndIdentifierEqual("WGS 84", 0, crs);
 
-        final GeodeticDatum datum = crs.getDatum();
+        final GeodeticDatum datum = DatumOrEnsemble.asDatum(crs);
         assertNameAndIdentifierEqual("World Geodetic System 1984", 0, datum);
         assertNameAndIdentifierEqual("Greenwich", 0, datum.getPrimeMeridian());
 
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/HardCodedConversions.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/HardCodedConversions.java
index f7720d31a2..2aa9f19ce0 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/HardCodedConversions.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/HardCodedConversions.java
@@ -22,13 +22,14 @@ import org.opengis.referencing.crs.GeographicCRS;
 import org.opengis.referencing.crs.ProjectedCRS;
 import org.opengis.referencing.operation.OperationMethod;
 import org.apache.sis.referencing.ImmutableIdentifier;
+import org.apache.sis.referencing.crs.DefaultProjectedCRS;
 import org.apache.sis.referencing.operation.provider.Mercator1SP;
 import org.apache.sis.referencing.operation.provider.TransverseMercator;
 import org.apache.sis.referencing.operation.provider.LambertConformal1SP;
 import org.apache.sis.referencing.operation.provider.LambertConformal2SP;
 import org.apache.sis.referencing.operation.provider.PolarStereographicB;
 import org.apache.sis.metadata.iso.citation.Citations;
-import org.apache.sis.referencing.crs.DefaultProjectedCRS;
+import org.apache.sis.util.privy.Constants;
 
 // Test dependencies
 import org.apache.sis.referencing.crs.HardCodedCRS;
@@ -177,7 +178,7 @@ public final class HardCodedConversions {
         pg.parameter("Easting at false origin") .setValue( 700000);
         pg.parameter("Northing at false origin").setValue(6600000);
         final DefaultConversion c = create("Lambert Conic Conformal", method, 
pg);
-        final ImmutableIdentifier id = new ImmutableIdentifier(Citations.ESRI, 
"ESRI", "102110");
+        final ImmutableIdentifier id = new ImmutableIdentifier(Citations.ESRI, 
Constants.ESRI, "102110");
         return new DefaultProjectedCRS(
                 Map.of(ProjectedCRS.NAME_KEY, "RGF 1993 Lambert",
                        ProjectedCRS.IDENTIFIERS_KEY, id),
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/setup/GeometryLibrary.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/setup/GeometryLibrary.java
index 2af321afa8..1f06b907e5 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/setup/GeometryLibrary.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/setup/GeometryLibrary.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.setup;
 
+import org.apache.sis.util.privy.Constants;
 import org.opengis.metadata.acquisition.GeometryType;
 
 
@@ -70,7 +71,7 @@ public enum GeometryLibrary {
      *
      * @see <a href="https://github.com/Esri/geometry-api-java/wiki";>API wiki 
page</a>
      */
-    ESRI("ESRI"),
+    ESRI(Constants.ESRI),
 
     /**
      * The Java Topology Suite (JTS) library. This open source library 
provides an object model
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/Constants.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/Constants.java
index d04ca9a625..bc47d7a9be 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/Constants.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/Constants.java
@@ -112,6 +112,11 @@ public final class Constants extends Static {
      */
     public static final String UCUM = "UCUM";
 
+    /**
+     * The {@value} code space.
+     */
+    public static final String ESRI = "ESRI";
+
     /**
      * The {@value} code space.
      */

Reply via email to