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 94d30d99d2 The referencing module needs to check if the datum is null, 
and fallback on the datum ensemble if needed. This commit does not resolve all 
cases. In particular, the cases that depend on the EPSG database update are not 
yet done: EPSGDataAccess, CoordinateOperationRegistry and 
CoordinateOperationFinder.
94d30d99d2 is described below

commit 94d30d99d2bd07a08ee6cd4af5150135ca48537a
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Sat Jul 13 16:38:36 2024 +0200

    The referencing module needs to check if the datum is null, and fallback on 
the datum ensemble if needed.
    This commit does not resolve all cases. In particular, the cases that 
depend on the EPSG database update
    are not yet done: EPSGDataAccess, CoordinateOperationRegistry and 
CoordinateOperationFinder.
---
 .../org/apache/sis/portrayal/CanvasContext.java    |  12 +-
 .../gazetteer/GeohashReferenceSystem.java          |   6 +-
 .../gazetteer/MilitaryGridReferenceSystem.java     |   2 +-
 .../org/apache/sis/geometry/CoordinateFormat.java  |   7 +-
 .../main/org/apache/sis/io/wkt/VerticalInfo.java   |  14 ++-
 .../main/org/apache/sis/referencing/CRS.java       |  34 +++---
 .../main/org/apache/sis/referencing/CommonCRS.java |  41 ++++++-
 .../referencing/EllipsoidalHeightSeparator.java    |  24 +++-
 .../sis/referencing/MultiRegisterOperations.java   |  23 +++-
 .../apache/sis/referencing/crs/AbstractCRS.java    |   4 +-
 .../sis/referencing/crs/DefaultGeographicCRS.java  |  70 +++++++++++
 .../sis/referencing/crs/DefaultTemporalCRS.java    |  39 +++++-
 .../sis/referencing/crs/ExplicitParameters.java    |   6 +-
 .../apache/sis/referencing/internal/Resources.java |   2 +-
 .../sis/referencing/internal/Resources.properties  |   2 +-
 .../referencing/internal/Resources_fr.properties   |   2 +-
 .../referencing/operation/DefaultConversion.java   |  15 ++-
 .../DefaultCoordinateOperationFactory.java         |  16 +--
 .../transform/DefaultMathTransformFactory.java     |   5 +-
 .../sis/referencing/privy/DefinitionVerifier.java  |  26 ++--
 .../privy/EllipsoidalHeightCombiner.java           |  24 ++--
 .../referencing/privy/GeodeticObjectBuilder.java   |  36 ++++--
 .../referencing/privy/ReferencingUtilities.java    | 136 ++++++++++++++-------
 .../sis/storage/geotiff/reader/CRSBuilder.java     |   8 +-
 .../sis/storage/geotiff/writer/GeoEncoder.java     |  62 +++++-----
 .../apache/sis/storage/netcdf/base/CRSBuilder.java |  37 ++++--
 .../sis/storage/netcdf/base/GridMapping.java       |   9 +-
 .../main/org/apache/sis/util/resources/Errors.java |   5 +
 .../apache/sis/util/resources/Errors.properties    |   1 +
 .../apache/sis/util/resources/Errors_fr.properties |   1 +
 30 files changed, 473 insertions(+), 196 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/CanvasContext.java
 
b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/CanvasContext.java
index aae76518d5..b6c9144308 100644
--- 
a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/CanvasContext.java
+++ 
b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/CanvasContext.java
@@ -19,7 +19,6 @@ package org.apache.sis.portrayal;
 import java.util.Optional;
 import java.util.OptionalDouble;
 import org.opengis.metadata.extent.GeographicBoundingBox;
-import org.opengis.referencing.crs.GeodeticCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.datum.Ellipsoid;
 import org.opengis.referencing.operation.Matrix;
@@ -35,6 +34,7 @@ import org.apache.sis.measure.Units;
 import org.apache.sis.geometry.Envelopes;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.referencing.privy.Formulas;
+import org.apache.sis.referencing.privy.ReferencingUtilities;
 import org.apache.sis.system.Configuration;
 
 
@@ -201,10 +201,12 @@ final class CanvasContext extends 
CoordinateOperationContext {
                     }
                     combined[j] = m;
                 }
-                final Ellipsoid ellipsoid = ((GeodeticCRS) 
objectiveToGeographic.getTargetCRS()).getDatum().getEllipsoid();
-                double radius = Formulas.radiusOfConformalSphere(ellipsoid, 
combined[1]);
-                radius = 
ellipsoid.getAxisUnit().getConverterTo(Units.METRE).convert(radius);
-                resolution = MathFunctions.magnitude(combined) * radius;
+                final Ellipsoid ellipsoid = 
ReferencingUtilities.getEllipsoid(objectiveToGeographic.getTargetCRS());
+                if (ellipsoid != null) {
+                    double radius = 
Formulas.radiusOfConformalSphere(ellipsoid, combined[1]);
+                    radius = 
ellipsoid.getAxisUnit().getConverterTo(Units.METRE).convert(radius);
+                    resolution = MathFunctions.magnitude(combined) * radius;
+                }
             }
         }
     }
diff --git 
a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/GeohashReferenceSystem.java
 
b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/GeohashReferenceSystem.java
index 1666a27797..512e608b95 100644
--- 
a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/GeohashReferenceSystem.java
+++ 
b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/GeohashReferenceSystem.java
@@ -60,7 +60,7 @@ import org.opengis.referencing.gazetteer.LocationType;
  *
  * @author  Chris Mattmann (JPL)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.4
+ * @version 1.5
  *
  * @see <a href="https://en.wikipedia.org/wiki/Geohash";>Geohash on 
Wikipedia</a>
  *
@@ -315,7 +315,7 @@ public class GeohashReferenceSystem extends 
ReferencingByIdentifiers {
          */
         @Override
         public Quantity<Length> getPrecision(DirectPosition position) {
-            final Ellipsoid ellipsoid = 
normalizedCRS.getDatum().getEllipsoid();
+            final Ellipsoid ellipsoid = normalizedCRS.getEllipsoid();
             final Unit<Length> unit = ellipsoid.getAxisUnit();
             final int latNumBits = (5*length) >>> 1;            // Number of 
bits for latitude value.
             final int lonNumBits = latNumBits + (length & 1);   // Longitude 
has 1 more bit when length is odd.
@@ -353,7 +353,7 @@ public class GeohashReferenceSystem extends 
ReferencingByIdentifiers {
                 numLat = Latitude .MAX_VALUE / p;
                 numLon = Longitude.MAX_VALUE / p;
             } else {
-                final Ellipsoid ellipsoid = 
normalizedCRS.getDatum().getEllipsoid();
+                final Ellipsoid ellipsoid = normalizedCRS.getEllipsoid();
                 p = unit.getConverterToAny(ellipsoid.getAxisUnit()).convert(p);
                 if (position != null) try {
                     position = toGeographic(position);
diff --git 
a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
 
b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
index 85906610bc..24bacf8623 100644
--- 
a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
+++ 
b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
@@ -576,7 +576,7 @@ public class MilitaryGridReferenceSystem extends 
ReferencingByIdentifiers {
          * Returns the ellipsoid of the geodetic reference frame of MGRS 
identifiers.
          */
         final Ellipsoid getEllipsoid() {
-            return datum.geographic().getDatum().getEllipsoid();
+            return datum.ellipsoid();
         }
 
         /**
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/CoordinateFormat.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/CoordinateFormat.java
index b5752a0760..b2a15f9d9d 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/CoordinateFormat.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/CoordinateFormat.java
@@ -51,6 +51,7 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.crs.TemporalCRS;
 import org.opengis.referencing.datum.Ellipsoid;
 import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.crs.DefaultTemporalCRS;
 import org.apache.sis.referencing.privy.Formulas;
 import org.apache.sis.referencing.privy.AxisDirections;
 import org.apache.sis.referencing.privy.ReferencingUtilities;
@@ -58,13 +59,13 @@ import org.apache.sis.system.Loggers;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.Characters;
+import org.apache.sis.util.logging.Logging;
+import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.privy.LocalizedParseException;
 import org.apache.sis.util.privy.Constants;
 import org.apache.sis.util.privy.Numerics;
 import org.apache.sis.math.DecimalFunctions;
 import org.apache.sis.math.MathFunctions;
-import org.apache.sis.util.logging.Logging;
-import org.apache.sis.util.resources.Errors;
 import org.apache.sis.temporal.TemporalDate;
 import org.apache.sis.measure.Angle;
 import org.apache.sis.measure.AngleFormat;
@@ -580,7 +581,7 @@ public class CoordinateFormat extends 
CompoundFormat<DirectPosition> {
                     }
                     types  [i] = DATE;
                     formats[i] = getFormat(Date.class);
-                    epochs [i] = TemporalDate.toInstant(((TemporalCRS) 
t).getDatum().getOrigin(), null);
+                    epochs [i] = 
TemporalDate.toInstant(DefaultTemporalCRS.castOrCopy((TemporalCRS) 
t).getOrigin(), null);
                     setConverter(dimension, i, 
unit.asType(Time.class).getConverterTo(Units.SECOND));
                     if (direction == AxisDirection.PAST) {
                         negate(i);
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/VerticalInfo.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/VerticalInfo.java
index 37275e7cc3..a11f49dbec 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/VerticalInfo.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/VerticalInfo.java
@@ -27,6 +27,7 @@ import org.opengis.referencing.cs.CSFactory;
 import org.opengis.referencing.cs.VerticalCS;
 import org.opengis.referencing.crs.CRSFactory;
 import org.opengis.referencing.crs.VerticalCRS;
+import org.opengis.referencing.datum.VerticalDatum;
 import org.apache.sis.metadata.privy.AxisNames;
 import org.apache.sis.metadata.iso.extent.DefaultExtent;
 import org.apache.sis.metadata.iso.extent.DefaultVerticalExtent;
@@ -107,8 +108,11 @@ final class VerticalInfo {
      *         became empty as a result of this operation.
      */
     final VerticalInfo resolve(final VerticalCRS crs) {
-        if (crs != null && crs.getDatum().getRealizationMethod().orElse(null) 
== RealizationMethod.GEOID) {
-            return resolve(crs, crs.getCoordinateSystem().getAxis(0));
+        if (crs != null) {
+            final VerticalDatum datum = crs.getDatum();
+            if (datum != null && datum.getRealizationMethod().orElse(null) == 
RealizationMethod.GEOID) {
+                return resolve(crs, crs.getCoordinateSystem().getAxis(0));
+            }
         }
         return this;
     }
@@ -179,8 +183,10 @@ final class VerticalInfo {
          *     cases the previous name may contain terms like "depth", which 
are not appropriate for our new CRS.
          */
         final VerticalCS cs = csFactory.createVerticalCS 
(properties(axis.getName()), axis);
-        extent.setVerticalCRS(crsFactory.createVerticalCRS(
-                properties((isUP ? compatibleCRS : axis).getName()), 
compatibleCRS.getDatum(), cs));
+        extent.setVerticalCRS(crsFactory.createVerticalCRS(properties((isUP ? 
compatibleCRS : axis).getName()),
+                                                           
compatibleCRS.getDatum(),
+                                                           
compatibleCRS.getDatumEnsemble(),
+                                                           cs));
         return next;
     }
 
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
index 3f71eab3bf..6174b51d20 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
@@ -45,7 +45,6 @@ import org.opengis.referencing.crs.TemporalCRS;
 import org.opengis.referencing.crs.VerticalCRS;
 import org.opengis.referencing.crs.EngineeringCRS;
 import org.opengis.referencing.datum.Datum;
-import org.opengis.referencing.datum.GeodeticDatum;
 import org.opengis.referencing.operation.Conversion;
 import org.opengis.referencing.operation.OperationNotFoundException;
 import org.opengis.referencing.operation.CoordinateOperation;
@@ -1124,7 +1123,7 @@ public final class CRS extends Static {
     {
         final long current = (Numerics.bitmask(dimension) - 1) << previous;
         final long intersect = selected & current;
-        if (intersect != 0) {
+choice: if (intersect != 0) {
             if (intersect == current) {
                 addTo.add(crs);
                 selected &= ~current;
@@ -1135,18 +1134,23 @@ public final class CRS extends Static {
                     if ((selected & current) == 0) break;           // Stop if 
it would be useless to continue.
                     previous += dimension;
                 }
-            } else if (dimension == 3 && crs instanceof SingleCRS) {
-                final Datum datum = ((SingleCRS) crs).getDatum();
-                if (datum instanceof GeodeticDatum) {
-                    final boolean isVertical = Long.bitCount(intersect) == 1;  
             // Presumed for now, verified later.
-                    final int verticalDimension = 
Long.numberOfTrailingZeros((isVertical ? intersect : ~intersect) >>> previous);
-                    final CoordinateSystemAxis verticalAxis = 
crs.getCoordinateSystem().getAxis(verticalDimension);
-                    if 
(AxisDirections.isVertical(verticalAxis.getDirection())) try {
-                        addTo.add(new 
EllipsoidalHeightSeparator((GeodeticDatum) datum, 
isVertical).separate((SingleCRS) crs));
-                        selected &= ~current;
-                    } catch (IllegalArgumentException | ClassCastException e) {
-                        throw new 
FactoryException(Resources.format(Resources.Keys.CanNotSeparateCRS_1, 
crs.getName()));
-                    }
+            } else if (dimension == 3) {
+                final GeodeticCRS baseCRS;
+                if (crs instanceof GeodeticCRS) {
+                    baseCRS = (GeodeticCRS) crs;
+                } else if (crs instanceof ProjectedCRS) {
+                    baseCRS = ((ProjectedCRS) crs).getBaseCRS();
+                } else {
+                    break choice;
+                }
+                final boolean isVertical = Long.bitCount(intersect) == 1;      
         // Presumed for now, verified later.
+                final int verticalDimension = 
Long.numberOfTrailingZeros((isVertical ? intersect : ~intersect) >>> previous);
+                final CoordinateSystemAxis verticalAxis = 
crs.getCoordinateSystem().getAxis(verticalDimension);
+                if (AxisDirections.isVertical(verticalAxis.getDirection())) 
try {
+                    addTo.add(new EllipsoidalHeightSeparator(baseCRS, 
isVertical).separate((SingleCRS) crs));
+                    selected &= ~current;
+                } catch (IllegalArgumentException | ClassCastException e) {
+                    throw new 
FactoryException(Resources.format(Resources.Keys.CanNotSeparateCRS_1, 
crs.getName()));
                 }
             }
         }
@@ -1552,7 +1556,7 @@ check:  while (lower != 0 || upper != dimension) {
      * @since 0.5
      */
     public static double getGreenwichLongitude(final GeodeticCRS crs) {
-        return 
ReferencingUtilities.getGreenwichLongitude(crs.getDatum().getPrimeMeridian(), 
Units.DEGREE);
+        return 
ReferencingUtilities.getGreenwichLongitude(ReferencingUtilities.getPrimeMeridian(crs),
 Units.DEGREE);
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
index 5f54b5e9d5..b8331ff590 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
@@ -69,6 +69,7 @@ import 
org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
 import org.apache.sis.referencing.factory.UnavailableFactoryException;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.referencing.operation.provider.TransverseMercator;
+import org.apache.sis.referencing.privy.ReferencingUtilities;
 import org.apache.sis.referencing.privy.Formulas;
 import org.apache.sis.referencing.internal.Resources;
 import org.apache.sis.system.SystemListener;
@@ -87,6 +88,7 @@ import org.apache.sis.measure.Units;
 import static org.apache.sis.util.privy.Constants.SECONDS_PER_DAY;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.referencing.datum.DatumEnsemble;
 import org.opengis.referencing.datum.RealizationMethod;
 
 
@@ -508,6 +510,9 @@ public enum CommonCRS {
 
     /**
      * Returns the {@code CommonCRS} enumeration value for the given datum, or 
{@code null} if none.
+     *
+     * @param  datum  the datum to represent as an enumeration value, or 
{@code null}.
+     * @return enumeration value for the given datum, or {@code null} if none.
      */
     static CommonCRS forDatum(final GeodeticDatum datum) {
         /*
@@ -828,7 +833,10 @@ public enum CommonCRS {
                     if (cs == null) {
                         cs = (SphericalCS) 
StandardDefinitions.createCoordinateSystem(StandardDefinitions.SPHERICAL, true);
                     }
-                    object = new 
DefaultGeocentricCRS(IdentifiedObjects.getProperties(base, exclude()), 
base.getDatum(), base.getDatumEnsemble(), cs);
+                    object = new 
DefaultGeocentricCRS(IdentifiedObjects.getProperties(base, exclude()),
+                                                      base.getDatum(),
+                                                      base.getDatumEnsemble(),
+                                                      cs);
                     cachedSpherical = object;
                 }
             }
@@ -854,7 +862,7 @@ public enum CommonCRS {
      *   <tr><td>World Geodetic System 1984</td>                        
<td>{@link #WGS84}</td>  <td>6326</td></tr>
      * </table></blockquote>
      *
-     * @return the geodetic reference frame associated to this enum.
+     * @return the geodetic reference frame associated to this enum, or {@code 
null} for a datum ensemble.
      *
      * @see #forDatum(CoordinateReferenceSystem)
      * @see org.apache.sis.referencing.datum.DefaultGeodeticDatum
@@ -883,6 +891,17 @@ public enum CommonCRS {
         return object;
     }
 
+    /**
+     * Returns the datum ensemble associated to this geodetic object.
+     *
+     * @return the datum ensemble associated to this enum, or {@code null} if 
none.
+     *
+     * @since 1.5
+     */
+    public DatumEnsemble<GeodeticDatum> datumEnsemble() {
+        return geographic().getDatumEnsemble();
+    }
+
     /**
      * Returns the ellipsoid associated to this geodetic object.
      * The following table summarizes the ellipsoids known to this class,
@@ -997,8 +1016,13 @@ public enum CommonCRS {
         if (object instanceof Ellipsoid) {
             return (Ellipsoid) object;
         }
-        final GeodeticDatum datum = datum(object);
-        return (datum != null) ? datum.getEllipsoid() : null;
+        if (object instanceof GeodeticDatum) {
+            return ((GeodeticDatum) object).getEllipsoid();
+        }
+        if (object instanceof CoordinateReferenceSystem) {
+            return 
ReferencingUtilities.getEllipsoid((CoordinateReferenceSystem) object);
+        }
+        return null;
     }
 
     /**
@@ -1008,8 +1032,13 @@ public enum CommonCRS {
         if (object instanceof PrimeMeridian) {
             return (PrimeMeridian) object;
         }
-        final GeodeticDatum datum = datum(object);
-        return (datum != null) ? datum.getPrimeMeridian() : null;
+        if (object instanceof GeodeticDatum) {
+            return ((GeodeticDatum) object).getPrimeMeridian();
+        }
+        if (object instanceof CoordinateReferenceSystem) {
+            return 
ReferencingUtilities.getPrimeMeridian((CoordinateReferenceSystem) object);
+        }
+        return null;
     }
 
     /*
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EllipsoidalHeightSeparator.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EllipsoidalHeightSeparator.java
index d03a744c4f..e4ca8f5200 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EllipsoidalHeightSeparator.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EllipsoidalHeightSeparator.java
@@ -39,6 +39,9 @@ import org.apache.sis.util.resources.Errors;
 import org.apache.sis.referencing.factory.GeodeticObjectFactory;
 import static 
org.apache.sis.referencing.privy.ReferencingUtilities.getPropertiesForModifiedCRS;
 
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.referencing.datum.DatumEnsemble;
+
 
 /**
  * Helper class for separating the ellipsoidal height from the horizontal part 
of a CRS.
@@ -54,19 +57,25 @@ final class EllipsoidalHeightSeparator implements 
AxisFilter {
      */
     private final GeodeticDatum datum;
 
+    /**
+     * The datum ensemble of the <abbr>CRS</abbr> to separate, or {@code null} 
if none.
+     */
+    private final DatumEnsemble<GeodeticDatum> ensemble;
+
     /**
      * Whether to extract the vertical component ({@code true}) or the 
horizontal component ({@code false}).
      */
     private final boolean vertical;
 
     /**
-     * Creates a new separator for a CRS having the given datum.
+     * Creates a new separator for a CRS having the given base.
      *
-     * @param  datum     the datum of the CRS to separate.
+     * @param  baseCRS   the CRS to separate, or the base CRS of the projected 
CRS to separate.
      * @param  vertical  whether to extract the vertical component ({@code 
true}) or the horizontal component ({@code false}).
      */
-    EllipsoidalHeightSeparator(final GeodeticDatum datum, final boolean 
vertical) {
-        this.datum    = datum;
+    EllipsoidalHeightSeparator(final GeodeticCRS baseCRS, final boolean 
vertical) {
+        this.datum    = baseCRS.getDatum();
+        this.ensemble = baseCRS.getDatumEnsemble();
         this.vertical = vertical;
     }
 
@@ -100,7 +109,10 @@ final class EllipsoidalHeightSeparator implements 
AxisFilter {
         if (vertical) {
             VerticalCRS component = CommonCRS.Vertical.ELLIPSOIDAL.crs();
             if 
(!Utilities.equalsIgnoreMetadata(component.getCoordinateSystem(), cs)) {
-                component = 
factory().createVerticalCRS(getPropertiesForModifiedCRS(component), 
component.getDatum(), (VerticalCS) cs);
+                component = 
factory().createVerticalCRS(getPropertiesForModifiedCRS(component),
+                                                        component.getDatum(),
+                                                        
component.getDatumEnsemble(),
+                                                        (VerticalCS) cs);
             }
             return component;
         }
@@ -121,7 +133,7 @@ final class EllipsoidalHeightSeparator implements 
AxisFilter {
                 final CommonCRS c = CommonCRS.forDatum(datum);
                 if (c != null) return c.normalizedGeographic();
             }
-            return 
factory().createGeographicCRS(getPropertiesForModifiedCRS(crs), datum, 
(EllipsoidalCS) cs);
+            return 
factory().createGeographicCRS(getPropertiesForModifiedCRS(crs), datum, 
ensemble, (EllipsoidalCS) cs);
         }
         /*
          * In the projected CRS case, in addition of reducing the number of 
dimensions in the CartesianCS,
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/MultiRegisterOperations.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/MultiRegisterOperations.java
index 2ddcd417b8..25b4aaede9 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/MultiRegisterOperations.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/MultiRegisterOperations.java
@@ -50,7 +50,8 @@ import org.apache.sis.util.iso.AbstractFactory;
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.referencing.RegisterOperations;
 import org.opengis.referencing.crs.SingleCRS;
-import org.apache.sis.util.Utilities;
+import org.opengis.referencing.crs.CompoundCRS;
+import org.apache.sis.referencing.privy.ReferencingUtilities;
 
 
 /**
@@ -341,10 +342,22 @@ public class MultiRegisterOperations extends 
AbstractFactory implements Register
     public boolean areMembersOfSameEnsemble(CoordinateReferenceSystem source, 
CoordinateReferenceSystem target)
             throws FactoryException
     {
-        return (source instanceof SingleCRS) && (target instanceof SingleCRS)
-                && Utilities.equalsIgnoreMetadata(
-                        ((SingleCRS) source).getDatumEnsemble(),
-                        ((SingleCRS) target).getDatumEnsemble());
+        if (source instanceof SingleCRS && target instanceof SingleCRS) {
+            return ReferencingUtilities.areMembersOfSameEnsemble((SingleCRS) 
source, (SingleCRS) target);
+        }
+        if (source instanceof CompoundCRS && target instanceof CompoundCRS) {
+            final List<SingleCRS> sources = ((CompoundCRS) 
source).getSingleComponents();
+            final List<SingleCRS> targets = ((CompoundCRS) 
target).getSingleComponents();
+            final int n = targets.size();
+            if (sources.size() == n) {
+                for (int i=0; i<n; i++) {
+                    if 
(!ReferencingUtilities.areMembersOfSameEnsemble(sources.get(i), 
targets.get(i))) {
+                        return false;
+                    }
+                }
+            }
+        }
+        return false;
     }
 
     /**
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 8aec007e7b..30f4513010 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
@@ -219,7 +219,7 @@ public class AbstractCRS extends AbstractReferenceSystem 
implements CoordinateRe
      * @param  ensemble    collection of reference frames which for low 
accuracy requirements may be considered to be
      *                     insignificantly different from each other, or 
{@code null} if there is no such ensemble.
      * @throws NullPointerException if both arguments are null.
-     * @throws IllegalArgumentException
+     * @throws IllegalArgumentException if the datum is not a member of the 
ensemble.
      */
     static <D extends Datum> void checkDatum(final D datum, final 
DatumEnsemble<D> ensemble) {
         if (ensemble == null) {
@@ -232,6 +232,8 @@ public class AbstractCRS extends AbstractReferenceSystem 
implements CoordinateRe
             }
             throw new 
IllegalArgumentException(Resources.format(Resources.Keys.NotAMemberOfDatumEnsemble_2,
                     IdentifiedObjects.getDisplayName(ensemble), 
IdentifiedObjects.getDisplayName(datum)));
+        } else {
+            ArgumentChecks.ensureNonEmpty("ensemble", ensemble.getMembers());
         }
     }
 
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeographicCRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeographicCRS.java
index a68014ca05..40ff61cca7 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeographicCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeographicCRS.java
@@ -18,7 +18,11 @@ package org.apache.sis.referencing.crs;
 
 import java.util.Map;
 import java.util.Arrays;
+import java.util.Iterator;
 import jakarta.xml.bind.annotation.XmlTransient;
+import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.datum.Ellipsoid;
+import org.opengis.referencing.datum.PrimeMeridian;
 import org.opengis.referencing.datum.GeodeticDatum;
 import org.opengis.referencing.crs.GeodeticCRS;
 import org.opengis.referencing.crs.GeographicCRS;
@@ -26,10 +30,13 @@ import org.opengis.referencing.cs.EllipsoidalCS;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.referencing.GeodeticException;
+import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.ImmutableIdentifier;
 import org.apache.sis.referencing.AbstractReferenceSystem;
 import org.apache.sis.referencing.cs.AxesConvention;
 import org.apache.sis.referencing.cs.AbstractCS;
+import org.apache.sis.util.resources.Errors;
 import org.apache.sis.io.wkt.Formatter;
 import org.apache.sis.measure.Longitude;
 import static org.apache.sis.util.privy.Constants.CRS;
@@ -196,6 +203,7 @@ public class DefaultGeographicCRS extends 
DefaultGeodeticCRS implements Geograph
         if (!(cs instanceof EllipsoidalCS)) {
             throw illegalCoordinateSystemType(cs);
         }
+        checkDimension(2, 3, cs);
     }
 
     /**
@@ -283,6 +291,68 @@ public class DefaultGeographicCRS extends 
DefaultGeodeticCRS implements Geograph
         return (EllipsoidalCS) super.getCoordinateSystem();
     }
 
+    /**
+     * Returns the ellipsoid which is indirectly (through a datum) associated 
to this <abbr>CRS</abbr>.
+     * If the {@linkplain #getDatum() datum} is non-null, then this method 
returns the datum ellipsoid.
+     * Otherwise, if all members of the {@linkplain #getDatumEnsemble() datum 
ensemble} use the same ellipsoid,
+     * then this method returns that ellipsoid.
+     *
+     * @return the ellipsoid indirectly associated to this <abbr>CRS</abbr>.
+     * @throws NullPointerException if an ellipsoid, which are mandatory in 
the context of geographic <abbr>CRS</abbr>, is null.
+     * @throws GeodeticException if the ellipsoid is not the same for all 
members of the datum ensemble.
+     *
+     * @since 1.5
+     */
+    public Ellipsoid getEllipsoid() {
+        final GeodeticDatum datum = super.getDatum();
+        if (datum != null) {
+            return datum.getEllipsoid();        // Has precedence regardless 
the value.
+        }
+        // If the datum is null, then the datum ensemble must be non-null.
+        final Iterator<GeodeticDatum> it = ensemble.getMembers().iterator();
+        final Ellipsoid ellipsoid = it.next().getEllipsoid();  // Mandatory
+        while (it.hasNext()) {
+            checkDatumConsistency(ellipsoid, it.next().getEllipsoid());
+        }
+        return ellipsoid;
+    }
+
+    /**
+     * Returns the prime meridian which is indirectly (through a datum) 
associated to this <abbr>CRS</abbr>.
+     * If the {@linkplain #getDatum() datum} is non-null, then this method 
returns the datum prime meridian.
+     * Otherwise, if all members of the {@linkplain #getDatumEnsemble() datum 
ensemble} use the same prime meridian,
+     * then this method returns that meridian.
+     *
+     * @return the prime meridian indirectly associated to this 
<abbr>CRS</abbr>.
+     * @throws NullPointerException if a prime meridian, which are mandatory, 
is null.
+     * @throws GeodeticException if the prime meridian is not the same for all 
members of the datum ensemble.
+     *
+     * @since 1.5
+     */
+    public PrimeMeridian getPrimeMeridian() {
+        final GeodeticDatum datum = super.getDatum();
+        if (datum != null) {
+            return datum.getPrimeMeridian();    // Has precedence regardless 
the value.
+        }
+        // If the datum is null, then the datum ensemble must be non-null.
+        final Iterator<GeodeticDatum> it = ensemble.getMembers().iterator();
+        final PrimeMeridian pm = it.next().getPrimeMeridian();  // Mandatory
+        while (it.hasNext()) {
+            checkDatumConsistency(pm, it.next().getPrimeMeridian());
+        }
+        return pm;
+    }
+
+    /**
+     * Ensures that the ellipsoid or prime meridian has the same value in all 
members of a datum ensemble.
+     */
+    private static void checkDatumConsistency(final IdentifiedObject expected, 
final IdentifiedObject actual) {
+        if (!expected.equals(actual)) {
+            throw new 
GeodeticException(Errors.format(Errors.Keys.NonUniformValue_2,
+                    IdentifiedObjects.getDisplayName(expected), 
IdentifiedObjects.getDisplayName(actual)));
+        }
+    }
+
     /**
      * {@inheritDoc}
      *
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 8080e6010c..b3fbfe6256 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
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.referencing.crs;
 
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Date;
 import java.time.Instant;
@@ -33,6 +34,7 @@ import javax.measure.quantity.Time;
 import org.opengis.referencing.cs.TimeCS;
 import org.opengis.referencing.crs.TemporalCRS;
 import org.opengis.referencing.datum.TemporalDatum;
+import org.apache.sis.referencing.GeodeticException;
 import org.apache.sis.referencing.AbstractReferenceSystem;
 import org.apache.sis.referencing.cs.AxesConvention;
 import org.apache.sis.referencing.cs.AbstractCS;
@@ -41,6 +43,7 @@ import org.apache.sis.metadata.privy.ImplementationHelper;
 import org.apache.sis.io.wkt.Formatter;
 import org.apache.sis.measure.Units;
 import org.apache.sis.math.Fraction;
+import org.apache.sis.util.resources.Errors;
 import static org.apache.sis.util.privy.Constants.NANOS_PER_SECOND;
 import static org.apache.sis.util.privy.Constants.MILLIS_PER_SECOND;
 
@@ -118,9 +121,9 @@ public class DefaultTemporalCRS extends AbstractCRS 
implements TemporalCRS {
     private transient UnitConverter toSeconds;
 
     /**
-     * The {@linkplain TemporalDatum#getOrigin origin} in seconds since 
January 1st, 1970.
-     * This field could be implicit in the {@link #toSeconds} converter, but 
we still handle
-     * it explicitly in order to use integer arithmetic.
+     * The {@linkplain #getOrigin origin} in seconds since January 1st, 1970.
+     * This field could be implicit in the {@link #toSeconds} converter,
+     * but we still handle it explicitly in order to use integer arithmetic.
      */
     private transient long origin;
 
@@ -258,7 +261,7 @@ public class DefaultTemporalCRS extends AbstractCRS 
implements TemporalCRS {
      */
     private void initializeConverter() {
         toSeconds = getUnit().getConverterTo(Units.SECOND);
-        final Temporal t = datum.getOrigin();
+        final Temporal t = getOrigin();
         origin = t.getLong(ChronoField.INSTANT_SECONDS);
         int r = t.get(ChronoField.NANO_OF_SECOND);
         if (r != 0) {
@@ -350,6 +353,34 @@ public class DefaultTemporalCRS extends AbstractCRS 
implements TemporalCRS {
         return 
super.getCoordinateSystem().getAxis(0).getUnit().asType(Time.class);
     }
 
+    /**
+     * Returns the temporal origin which is indirectly (through a datum) 
associated to this <abbr>CRS</abbr>.
+     * If the {@linkplain #getDatum() datum} is non-null, then this method 
returns the datum origin.
+     * Otherwise, if all members of the {@linkplain #getDatumEnsemble() datum 
ensemble} use the same origin,
+     * then this method returns that origin.
+     *
+     * @return the origin indirectly associated to this <abbr>CRS</abbr>.
+     * @throws NullPointerException if an origin, which are mandatory, is null.
+     * @throws GeodeticException if the origin is not the same for all members 
of the datum ensemble.
+     *
+     * @since 1.5
+     */
+    public final Temporal getOrigin() {     // Must be final because invoked 
at construction time.
+        if (datum != null) {
+            return datum.getOrigin();       // Has precedence regardless the 
value.
+        }
+        // If the datum is null, then the datum ensemble must be non-null.
+        final Iterator<TemporalDatum> it = ensemble.getMembers().iterator();
+        final Temporal origin = it.next().getOrigin();
+        while (it.hasNext()) {
+            final Temporal actual = it.next().getOrigin();
+            if (!origin.equals(actual)) {
+                throw new 
GeodeticException(Errors.format(Errors.Keys.NonUniformValue_2, origin, actual));
+            }
+        }
+        return origin;
+    }
+
     /**
      * {@inheritDoc}
      *
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/ExplicitParameters.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/ExplicitParameters.java
index 65a7624e86..c476ed9e01 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/ExplicitParameters.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/ExplicitParameters.java
@@ -19,12 +19,11 @@ package org.apache.sis.referencing.crs;
 import org.opengis.parameter.ParameterValue;
 import org.opengis.parameter.GeneralParameterValue;
 import org.opengis.parameter.GeneralParameterDescriptor;
-import org.opengis.referencing.datum.Datum;
-import org.opengis.referencing.datum.GeodeticDatum;
 import org.opengis.referencing.datum.Ellipsoid;
 import org.opengis.referencing.operation.Conversion;
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.operation.DefaultOperationMethod;
+import org.apache.sis.referencing.privy.ReferencingUtilities;
 import org.apache.sis.referencing.privy.WKTKeywords;
 import org.apache.sis.referencing.privy.WKTUtilities;
 import org.apache.sis.util.privy.Constants;
@@ -61,8 +60,7 @@ final class ExplicitParameters extends FormattableObject {
      */
     ExplicitParameters(final AbstractDerivedCRS crs, final String keyword) {
         conversion = crs.getConversionFromBase();
-        final Datum datum = crs.getDatum();
-        ellipsoid = (datum instanceof GeodeticDatum) ? ((GeodeticDatum) 
datum).getEllipsoid() : null;
+        ellipsoid = ReferencingUtilities.getEllipsoid(crs);
         this.keyword = keyword;
     }
 
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.java
index 6ed76bff3f..1709cc3110 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.java
@@ -438,7 +438,7 @@ public class Resources extends IndexedResourceBundle {
         /**
          * The given “{0}” description does not conform to the “{1}” 
authoritative description.
          * Differences are found in {2,choice,0#conversion method|1#conversion 
description|2#coordinate
-         * system|3#datum|4#prime meridian|5#CRS}.
+         * system|3#datum|4#prime meridian|5#ellipsoid|6#CRS}.
          */
         public static final short NonConformCRS_3 = 73;
 
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.properties
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.properties
index 41f456f7cf..0f3b751097 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.properties
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.properties
@@ -42,7 +42,7 @@ MisnamedParameter_1               = Despite its name, this 
parameter is effectiv
 NotFormalProjectionParameter_1    = This parameter borrowed from the 
\u201c{0}\u201d projection is not formally a parameter of this projection.
 NonConformAxes_2                  = The coordinate system axes in the given 
\u201c{0}\u201d description do not conform to the expected axes according 
\u201c{1}\u201d authoritative description.
 NonConformCRS_3                   = The given \u201c{0}\u201d description does 
not conform to the \u201c{1}\u201d authoritative description. \
-                                    Differences are found in 
{2,choice,0#conversion method|1#conversion description|2#coordinate 
system|3#datum|4#prime meridian|5#CRS}.
+                                    Differences are found in 
{2,choice,0#conversion method|1#conversion description|2#coordinate 
system|3#datum|4#prime meridian|5#ellipsoid|6#CRS}.
 RestrictedToPoleLatitudes         = The only valid entries are \u00b190\u00b0 
or equivalent in alternative angle units.
 
 #
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources_fr.properties
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources_fr.properties
index 163eac2614..245a08e502 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources_fr.properties
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources_fr.properties
@@ -47,7 +47,7 @@ MisnamedParameter_1               = Malgr\u00e9 son nom, ce 
param\u00e8tre produ
 NotFormalProjectionParameter_1    = Ce param\u00e8tre emprunt\u00e9 \u00e0 la 
projection \u00ab\u202f{0}\u202f\u00bb n\u2019est pas formellement un 
param\u00e8tre de cette projection.
 NonConformAxes_2                  = Les axes du syst\u00e8me de 
coordonn\u00e9es d\u00e9finis dans \u00ab\u202f{0}\u202f\u00bb ne sont pas 
conformes aux axes attendus d\u2019apr\u00e8s la description officielle de 
\u00ab\u202f{1}\u202f\u00bb.
 NonConformCRS_3                   = La description donn\u00e9e pour 
\u00ab\u202f{0}\u202f\u00bb n\u2019est pas conforme \u00e0 la description 
officielle de \u00ab\u202f{1}\u202f\u00bb. \
-                                    Des diff\u00e9rences ont \u00e9t\u00e9 
trouv\u00e9es dans {2,choice,0#la m\u00e9thode de conversion|1#la description 
de la conversion|2#le syst\u00e8me de coordonn\u00e9es|3#le 
r\u00e9f\u00e9rentiel|4#le m\u00e9ridien d\u2019origine|5#le CRS}.
+                                    Des diff\u00e9rences ont \u00e9t\u00e9 
trouv\u00e9es dans {2,choice,0#la m\u00e9thode de conversion|1#la description 
de la conversion|2#le syst\u00e8me de coordonn\u00e9es|3#le 
r\u00e9f\u00e9rentiel|4#le m\u00e9ridien 
d\u2019origine|5#l\u2019ellipso\u00efde|6#le CRS}.
 RestrictedToPoleLatitudes         = Les seules valeurs valides sont 
\u00b190\u00b0 ou \u00e9quivalent dans d\u2019autres unit\u00e9s.
 
 #
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java
index af3c6d7ae3..5b6030e34e 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java
@@ -23,6 +23,7 @@ import jakarta.xml.bind.annotation.XmlRootElement;
 import javax.measure.IncommensurableException;
 import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterValueGroup;
+import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.operation.Conversion;
 import org.opengis.referencing.operation.OperationMethod;
 import org.opengis.referencing.operation.MathTransform;
@@ -30,7 +31,6 @@ import org.opengis.referencing.operation.MathTransformFactory;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.crs.SingleCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.datum.Datum;
 import org.apache.sis.referencing.cs.CoordinateSystems;
 import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
 import org.apache.sis.referencing.internal.ParameterizedTransformBuilder;
@@ -409,8 +409,17 @@ public class DefaultConversion extends 
AbstractSingleOperation implements Conver
             final CoordinateReferenceSystem actual)
     {
         if ((expected instanceof SingleCRS) && (actual instanceof SingleCRS)) {
-            final Datum datum = ((SingleCRS) expected).getDatum();
-            if (datum != null && !Utilities.equalsIgnoreMetadata(datum, 
((SingleCRS) actual).getDatum())) {
+            final var crs1 = (SingleCRS) expected;
+            final var crs2 = (SingleCRS) actual;
+            IdentifiedObject datum = crs1.getDatum();
+            IdentifiedObject other;
+            if (datum != null) {
+                other = crs2.getDatum();
+            } else {
+                datum = crs1.getDatumEnsemble();
+                other = crs2.getDatumEnsemble();
+            }
+            if (datum != null && other != null && 
!Utilities.equalsIgnoreMetadata(datum, other)) {
                 throw new MismatchedDatumException(Resources.format(
                         Resources.Keys.IncompatibleDatum_2, datum.getName(), 
param));
             }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
index 3fcaec2cd3..1573b2ef58 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
@@ -30,24 +30,24 @@ import 
org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.crs.SingleCRS;
 import org.opengis.referencing.crs.CRSFactory;
 import org.opengis.referencing.cs.CSFactory;
-import org.opengis.referencing.datum.Datum;
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.AbstractIdentifiedObject;
+import org.apache.sis.referencing.factory.GeodeticObjectFactory;
+import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
+import org.apache.sis.referencing.operation.transform.AbstractMathTransform;
+import 
org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory;
 import org.apache.sis.referencing.internal.Resources;
 import org.apache.sis.referencing.internal.MergedProperties;
 import org.apache.sis.referencing.internal.ParameterizedTransformBuilder;
 import org.apache.sis.referencing.privy.CoordinateOperations;
 import org.apache.sis.referencing.privy.ReferencingFactoryContainer;
+import org.apache.sis.referencing.privy.ReferencingUtilities;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.Debug;
 import org.apache.sis.util.privy.Constants;
-import org.apache.sis.referencing.factory.GeodeticObjectFactory;
-import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
-import org.apache.sis.referencing.operation.transform.AbstractMathTransform;
-import 
org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory;
 import org.apache.sis.util.collection.WeakHashSet;
 import org.apache.sis.util.collection.Containers;
 import org.apache.sis.util.collection.Cache;
@@ -409,13 +409,13 @@ public class DefaultCoordinateOperationFactory extends 
AbstractFactory implement
     {
         List<SingleCRS> components = CRS.getSingleComponents(sourceCRS);
         int n = components.size();                      // Number of remaining 
datum from sourceCRS to verify.
-        final Datum[] datum = new Datum[n];
+        final IdentifiedObject[] datum = new IdentifiedObject[n];
         for (int i=0; i<n; i++) {
-            datum[i] = components.get(i).getDatum();
+            datum[i] = 
ReferencingUtilities.getDatumOrEnsemble(components.get(i));
         }
         components = CRS.getSingleComponents(targetCRS);
 next:   for (int i=components.size(); --i >= 0;) {
-            final Datum d = components.get(i).getDatum();
+            final IdentifiedObject d = 
ReferencingUtilities.getDatumOrEnsemble(components.get(i));
             for (int j=n; --j >= 0;) {
                 if (Utilities.equalsIgnoreMetadata(d, datum[j])) {
                     System.arraycopy(datum, j+1, datum, j, --n - j);    // 
Remove the datum from the list.
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
index e64ab32b0d..84062a6b64 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
@@ -45,6 +45,7 @@ import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.privy.Constants;
 import org.apache.sis.util.iso.AbstractFactory;
 import org.apache.sis.util.collection.WeakHashSet;
+import org.apache.sis.referencing.privy.ReferencingUtilities;
 import org.apache.sis.referencing.privy.CoordinateOperations;
 import org.apache.sis.referencing.operation.DefaultOperationMethod;
 import org.apache.sis.referencing.internal.ParameterizedTransformBuilder;
@@ -552,7 +553,7 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
         public void setSource(final GeodeticCRS crs) {
             if (crs != null) {
                 sourceCS = crs.getCoordinateSystem();
-                sourceEllipsoid = crs.getDatum().getEllipsoid();
+                sourceEllipsoid = ReferencingUtilities.getEllipsoid(crs);
             } else {
                 sourceCS = null;
                 sourceEllipsoid = null;
@@ -592,7 +593,7 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
         public void setTarget(final GeodeticCRS crs) {
             if (crs != null) {
                 targetCS = crs.getCoordinateSystem();
-                targetEllipsoid = crs.getDatum().getEllipsoid();
+                targetEllipsoid = ReferencingUtilities.getEllipsoid(crs);
             } else {
                 targetCS = null;
                 targetEllipsoid = null;
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/DefinitionVerifier.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/DefinitionVerifier.java
index 866957855b..2bcd77dd12 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/DefinitionVerifier.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/DefinitionVerifier.java
@@ -24,8 +24,6 @@ import org.opengis.util.FactoryException;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.NoSuchAuthorityCodeException;
-import org.opengis.referencing.datum.Datum;
-import org.opengis.referencing.datum.GeodeticDatum;
 import org.opengis.referencing.crs.CRSAuthorityFactory;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.crs.SingleCRS;
@@ -301,13 +299,13 @@ public final class DefinitionVerifier {
      * Indicates in which part of CRS description a difference has been found. 
Numerical values must match the number
      * in the {@code {choice}} instruction in the message associated to {@link 
Resources.Keys#NonConformCRS_3}.
      */
-    private static final int METHOD=0, CONVERSION=1, CS=2, DATUM=3, 
PRIME_MERIDIAN=4, OTHER=5;
+    private static final int METHOD=0, CONVERSION=1, CS=2, DATUM=3, 
PRIME_MERIDIAN=4, ELLIPSOID=5, OTHER=6;
 
     /**
      * Returns a code indicating in which part the two given CRS differ. The 
given iterators usually iterate over
      * exactly one element, but may iterate over more elements if the CRS were 
instance of {@code CompoundCRS}.
      * The returned value is one of {@link #METHOD}, {@link #CONVERSION}, 
{@link #CS}, {@link #DATUM},
-     * {@link #PRIME_MERIDIAN} or {@link #OTHER} constants.
+     * {@link #PRIME_MERIDIAN}, {@link #ELLIPSOID} or {@link #OTHER} constants.
      */
     private static int diffCode(final Iterator<SingleCRS> authoritative, final 
Iterator<SingleCRS> given) {
         while (authoritative.hasNext() && given.hasNext()) {
@@ -324,15 +322,17 @@ public final class DefinitionVerifier {
                 if (!Utilities.equalsApproximately(crsA.getCoordinateSystem(), 
crsG.getCoordinateSystem())) {
                     return CS;
                 }
-                final Datum datumA = crsA.getDatum();
-                final Datum datumG = crsG.getDatum();
-                if (!Utilities.equalsApproximately(datumA, datumG)) {
-                    if ((datumA instanceof GeodeticDatum) && (datumG 
instanceof GeodeticDatum) &&
-                        !Utilities.equalsApproximately(((GeodeticDatum) 
datumA).getPrimeMeridian(),
-                                                       ((GeodeticDatum) 
datumG).getPrimeMeridian()))
-                    {
-                        return PRIME_MERIDIAN;
-                    }
+                if 
(!Utilities.equalsApproximately(ReferencingUtilities.getEllipsoid(crsA),
+                                                   
ReferencingUtilities.getEllipsoid(crsG)))
+                {
+                    return ELLIPSOID;
+                }
+                if 
(!Utilities.equalsApproximately(ReferencingUtilities.getPrimeMeridian(crsA),
+                                                   
ReferencingUtilities.getPrimeMeridian(crsG)))
+                {
+                    return PRIME_MERIDIAN;
+                }
+                if (!Utilities.equalsApproximately(crsA.getDatum(), 
crsG.getDatum())) {
                     return DATUM;
                 }
                 break;
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/EllipsoidalHeightCombiner.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/EllipsoidalHeightCombiner.java
index c8791dbd93..27deaab23a 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/EllipsoidalHeightCombiner.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/EllipsoidalHeightCombiner.java
@@ -116,14 +116,14 @@ public final class EllipsoidalHeightCombiner {
                 final VerticalDatum datum = ((VerticalCRS) 
vertical).getDatum();
                 if (ReferencingUtilities.isEllipsoidalHeight(datum)) {
                     int axisPosition = 0;
-                    CoordinateSystem cs = null;
+                    CoordinateSystem cs2D;
                     CoordinateReferenceSystem crs = null;
-                    if (i == 0 || (cs = getCsIfHorizontal2D(crs = components[i 
- 1])) == null) {
+                    if (i == 0 || (cs2D = getCsIfHorizontal2D(crs = 
components[i - 1])) == null) {
                         /*
                          * GeographicCRS are normally before VerticalCRS. But 
Apache SIS is tolerant to the
                          * opposite order (note however that such ordering is 
illegal according ISO 19162).
                          */
-                        if (i+1 >= components.length || (cs = 
getCsIfHorizontal2D(crs = components[i + 1])) == null) {
+                        if (i+1 >= components.length || (cs2D = 
getCsIfHorizontal2D(crs = components[i + 1])) == null) {
                             continue;
                         }
                         axisPosition = 1;
@@ -136,15 +136,17 @@ public final class EllipsoidalHeightCombiner {
                      * implementation recycles the properties of the existing 
two-dimensional CRS.
                      */
                     final CoordinateSystemAxis[] axes = new 
CoordinateSystemAxis[3];
-                    axes[axisPosition++   ] = cs.getAxis(0);
-                    axes[axisPosition++   ] = cs.getAxis(1);
+                    axes[axisPosition++   ] = cs2D.getAxis(0);
+                    axes[axisPosition++   ] = cs2D.getAxis(1);
                     axes[axisPosition %= 3] = 
vertical.getCoordinateSystem().getAxis(0);
-                    final Map<String,?> csProps  = 
IdentifiedObjects.getProperties(cs, CoordinateSystem.IDENTIFIERS_KEY);
+                    final Map<String,?> csProps  = 
IdentifiedObjects.getProperties(cs2D, CoordinateSystem.IDENTIFIERS_KEY);
                     final Map<String,?> crsProps = (components.length == 2) ? 
properties
                                                  : 
IdentifiedObjects.getProperties(crs, CoordinateReferenceSystem.IDENTIFIERS_KEY);
                     if (crs instanceof GeodeticCRS) {
-                        cs  = factories.getCSFactory() 
.createEllipsoidalCS(csProps, axes[0], axes[1], axes[2]);
-                        crs = 
factories.getCRSFactory().createGeographicCRS(crsProps, ((GeodeticCRS) 
crs).getDatum(), (EllipsoidalCS) cs);
+                        final var geod = (GeodeticCRS) crs;
+                        final EllipsoidalCS cs3D;
+                        cs3D = factories.getCSFactory() 
.createEllipsoidalCS(csProps, axes[0], axes[1], axes[2]);
+                        crs  = 
factories.getCRSFactory().createGeographicCRS(crsProps, geod.getDatum(), 
geod.getDatumEnsemble(), cs3D);
                     } else {
                         final ProjectedCRS proj = (ProjectedCRS) crs;
                         GeodeticCRS base = proj.getBaseCRS();
@@ -161,8 +163,10 @@ public final class EllipsoidalHeightCombiner {
                         fromBase = 
factories.getCoordinateOperationFactory().createDefiningConversion(
                                     IdentifiedObjects.getProperties(fromBase),
                                     fromBase.getMethod(), 
fromBase.getParameterValues());
-                        cs  = factories.getCSFactory() 
.createCartesianCS(csProps, axes[0], axes[1], axes[2]);
-                        crs = 
factories.getCRSFactory().createProjectedCRS(crsProps, base, fromBase, 
(CartesianCS) cs);
+
+                        final CartesianCS cs3D;
+                        cs3D = factories.getCSFactory() 
.createCartesianCS(csProps, axes[0], axes[1], axes[2]);
+                        crs  = 
factories.getCRSFactory().createProjectedCRS(crsProps, base, fromBase, cs3D);
                     }
                     /*
                      * Remove the VerticalCRS and store the three-dimensional 
GeographicCRS in place of the previous
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/GeodeticObjectBuilder.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/GeodeticObjectBuilder.java
index 0c84f2a1eb..1c30eea584 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/GeodeticObjectBuilder.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/GeodeticObjectBuilder.java
@@ -63,6 +63,7 @@ import org.apache.sis.parameter.Parameters;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.referencing.ObjectDomain;
+import org.opengis.referencing.datum.DatumEnsemble;
 import org.opengis.referencing.operation.MathTransform;
 
 // Specific to the geoapi-4.0 branch:
@@ -86,6 +87,11 @@ public class GeodeticObjectBuilder extends 
Builder<GeodeticObjectBuilder> {
      */
     private GeodeticDatum datum;
 
+    /**
+     * The datum ensemble, or {@code null} if none.
+     */
+    private DatumEnsemble<GeodeticDatum> ensemble;
+
     /**
      * The name of the conversion to use for creating a {@code ProjectedCRS} 
or {@code DerivedCRS}.
      * This name is for information purpose; its value does not impact the 
numerical results of coordinate operations.
@@ -361,6 +367,7 @@ public class GeodeticObjectBuilder extends 
Builder<GeodeticObjectBuilder> {
         method         = c.getMethod();
         parameters     = c.getParameterValues();
         datum          = crs.getDatum();
+        ensemble       = crs.getDatumEnsemble();
         properties.putAll(IdentifiedObjects.getProperties(crs, 
ProjectedCRS.IDENTIFIERS_KEY));
         return this;
     }
@@ -509,8 +516,9 @@ public class GeodeticObjectBuilder extends 
Builder<GeodeticObjectBuilder> {
      */
     public ProjectedCRS createProjectedCRS() throws FactoryException {
         GeographicCRS crs = getBaseCRS();
-        if (datum != null) {
-            crs = factories.getCRSFactory().createGeographicCRS(name(datum), 
datum, crs.getCoordinateSystem());
+        if (datum != null || ensemble != null) {
+            crs = factories.getCRSFactory().createGeographicCRS(
+                    name(datum != null ? datum : ensemble), datum, ensemble, 
crs.getCoordinateSystem());
         }
         return createProjectedCRS(crs, factories.getStandardProjectedCS());
     }
@@ -532,8 +540,12 @@ public class GeodeticObjectBuilder extends 
Builder<GeodeticObjectBuilder> {
      */
     public GeographicCRS createGeographicCRS() throws FactoryException {
         final GeographicCRS crs = getBaseCRS();
-        if (datum != null) properties.putIfAbsent(GeographicCRS.NAME_KEY, 
datum.getName());
-        return factories.getCRSFactory().createGeographicCRS(properties, 
datum, crs.getCoordinateSystem());
+        IdentifiedObject id = datum;
+        if (id == null) id = ensemble;
+        if (id != null) {
+            properties.putIfAbsent(GeographicCRS.NAME_KEY, id.getName());
+        }
+        return factories.getCRSFactory().createGeographicCRS(properties, 
datum, ensemble, crs.getCoordinateSystem());
     }
 
     /**
@@ -551,19 +563,19 @@ public class GeodeticObjectBuilder extends 
Builder<GeodeticObjectBuilder> {
          * This not only saves a little bit of memory, but also provides 
better names.
          */
         TimeCS cs = null;
-        TemporalDatum datum = null;
+        TemporalDatum td = null;
         for (final CommonCRS.Temporal c : CommonCRS.Temporal.values()) {
-            if (datum == null) {
+            if (td == null) {
                 final TemporalDatum candidate = c.datum();
                 if (origin.equals(candidate.getOrigin())) {
-                    datum = candidate;
+                    td = candidate;
                 }
             }
             if (cs == null) {
                 final TemporalCRS crs = c.crs();
                 final TimeCS candidate = crs.getCoordinateSystem();
                 if (unit.equals(candidate.getAxis(0).getUnit())) {
-                    if (datum == candidate && properties.isEmpty()) {
+                    if (td == candidate && properties.isEmpty()) {
                         return crs;
                     }
                     cs = candidate;
@@ -586,15 +598,15 @@ public class GeodeticObjectBuilder extends 
Builder<GeodeticObjectBuilder> {
             if (properties.get(TemporalCRS.NAME_KEY) == null) {
                 properties.putAll(name(cs));
             }
-            if (datum == null) {
+            if (td == null) {
                 final Object remarks    = 
properties.remove(TemporalCRS.REMARKS_KEY);
                 final Object identifier = 
properties.remove(TemporalCRS.IDENTIFIERS_KEY);
-                datum = 
factories.getDatumFactory().createTemporalDatum(properties, origin);
+                td = 
factories.getDatumFactory().createTemporalDatum(properties, origin);
                 properties.put(TemporalCRS.IDENTIFIERS_KEY, identifier);
                 properties.put(TemporalCRS.REMARKS_KEY,     remarks);
-                properties.put(TemporalCRS.NAME_KEY, datum.getName());      // 
Share the Identifier instance.
+                properties.put(TemporalCRS.NAME_KEY, td.getName());     // 
Share the Identifier instance.
             }
-            return factories.getCRSFactory().createTemporalCRS(properties, 
datum, cs);
+            return factories.getCRSFactory().createTemporalCRS(properties, td, 
cs);
         } finally {
             onCreate(true);
         }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingUtilities.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingUtilities.java
index 48fae67a4a..07aa3e2bbd 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingUtilities.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingUtilities.java
@@ -18,6 +18,7 @@ package org.apache.sis.referencing.privy;
 
 import java.util.Map;
 import java.util.HashMap;
+import java.util.function.Function;
 import javax.measure.Unit;
 import javax.measure.quantity.Angle;
 import org.opengis.annotation.UML;
@@ -52,6 +53,9 @@ import org.apache.sis.referencing.cs.DefaultEllipsoidalCS;
 import org.apache.sis.referencing.internal.VerticalDatumTypes;
 import 
org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory;
 
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.referencing.datum.DatumEnsemble;
+
 
 /**
  * A set of static methods working on GeoAPI referencing objects.
@@ -202,6 +206,38 @@ public final class ReferencingUtilities extends Static {
         return (types.length != 0) ? types[0] : type;
     }
 
+    /**
+     * Returns whether the given <abbr>CRS</abbr> use the same datum or the 
same datum ensemble.
+     *
+     * @param  crs1  the first <abbr>CRS</abbr>.
+     * @param  crs2  the second <abbr>CRS</abbr>.
+     * @return whether the two reference systems use the same datum or the 
same datum ensemble.
+     */
+    public static boolean areMembersOfSameEnsemble(final SingleCRS crs1, final 
SingleCRS crs2) {
+        IdentifiedObject d1 = crs1.getDatum();
+        IdentifiedObject d2 = crs2.getDatum();
+        if (d1 == null && d2 == null) {
+            d1 = crs1.getDatumEnsemble();
+            d2 = crs2.getDatumEnsemble();
+            if (d1 == null && d2 == null) {
+                return false;
+            }
+        }
+        return Utilities.equalsIgnoreMetadata(d1, d2);
+    }
+
+    /**
+     * Returns the datum of the given <abbr>CRS</abbr> if presents, or the 
datum ensemble otherwise.
+     *
+     * @param  crs  the <abbr>CRS</abbr> from which to get the datum or 
ensemble, or {@code null}.
+     * @return the datum if present, or the datum ensemble otherwise.
+     */
+    public static IdentifiedObject getDatumOrEnsemble(final SingleCRS crs) {
+        if (crs == null) return null;
+        final Datum datum = crs.getDatum();
+        return (datum != null) ? datum : crs.getDatumEnsemble();
+    }
+
     /**
      * Returns {@code true} if the type of the given datum is ellipsoidal. A 
vertical datum is not allowed
      * to be ellipsoidal according ISO 19111, but Apache SIS relaxes this 
restriction in some limited cases,
@@ -228,66 +264,80 @@ public final class ReferencingUtilities extends Static {
      * <ul>
      *   <li>If the given CRS is an instance of {@link SingleCRS} and its 
datum is a {@link GeodeticDatum},
      *       then this method returns the datum ellipsoid.</li>
-     *   <li>Otherwise if the given CRS is an instance of {@link CompoundCRS}, 
then this method
+     *   <li>Otherwise, if the given CRS is associated to a {@link 
DatumEnsemble} and all members of the
+     *       ensemble have equal (ignoring metadata) ellipsoid, then returns 
that ellipsoid.</li>
+     *   <li>Otherwise, if the given CRS is an instance of {@link 
CompoundCRS}, then this method
      *       invokes itself recursively for each component until a geodetic 
reference frame is found.</li>
-     *   <li>Otherwise this method returns {@code null}.</li>
+     *   <li>Otherwise, this method returns {@code null}.</li>
      * </ul>
      *
-     * Note that this method does not check if there is more than one 
ellipsoid (it should never be the case).
+     * Note that this method does not check if a compound <abbr>CRS</abbr> 
contains more than one ellipsoid
+     * (it should never be the case). Note also that this method may return 
{@code null} even if the CRS is
+     * geodetic.
      *
      * @param  crs  the coordinate reference system for which to get the 
ellipsoid.
      * @return the ellipsoid, or {@code null} if none.
      */
     public static Ellipsoid getEllipsoid(final CoordinateReferenceSystem crs) {
-        if (crs != null) {
-            if (crs instanceof SingleCRS) {
-                final Datum datum = ((SingleCRS) crs).getDatum();
-                if (datum instanceof GeodeticDatum) {
-                    final Ellipsoid e = ((GeodeticDatum) datum).getEllipsoid();
-                    if (e != null) return e;
-                }
-            }
-            if (crs instanceof CompoundCRS) {
-                for (final CoordinateReferenceSystem c : ((CompoundCRS) 
crs).getComponents()) {
-                    final Ellipsoid e = getEllipsoid(c);
-                    if (e != null) return e;
-                }
-            }
-        }
-        return null;
+        return getGeodeticProperty(crs, GeodeticDatum::getEllipsoid);
     }
 
     /**
-     * Returns the ellipsoid used by the specified coordinate reference 
system, provided that the two first dimensions
-     * use an instance of {@link GeographicCRS}. Otherwise (i.e. if the two 
first dimensions are not geographic),
-     * returns {@code null}.
+     * Returns the prime meridian used by the given coordinate reference 
system, or {@code null} if none.
+     * This method applies the same rules as {@link 
#getEllipsoid(CoordinateReferenceSystem)}.
      *
-     * <p>This method excludes geocentric CRS on intent. Some callers needs 
this exclusion as a way to identify
-     * which CRS in a Geographic/Geocentric conversion is the geographic one. 
Another point of view is to said
-     * that if this method returns a non-null value, then the coordinates are 
expected to be either two-dimensional
-     * or three-dimensional with an ellipsoidal height.</p>
+     * @param  crs  the coordinate reference system for which to get the prime 
meridian.
+     * @return the prime meridian, or {@code null} if none.
+     */
+    public static PrimeMeridian getPrimeMeridian(final 
CoordinateReferenceSystem crs) {
+        return getGeodeticProperty(crs, GeodeticDatum::getPrimeMeridian);
+    }
+
+    /**
+     * Implementation of {@code getEllipsoid(CRS)} and {@code 
getPrimeMeridian(CRS)}.
      *
-     * @param  crs  the coordinate reference system for which to get the 
ellipsoid.
-     * @return the ellipsoid in the given CRS, or {@code null} if none.
+     * @param  <P>     the type of object to get.
+     * @param  crs     the coordinate reference system for which to get the 
ellipsoid or prime meridian.
+     * @param  getter  the method to invoke on {@link GeodeticDatum} instances.
+     * @return the ellipsoid or prime meridian, or {@code null} if none.
      */
-    public static Ellipsoid 
getEllipsoidOfGeographicCRS(CoordinateReferenceSystem crs) {
-        while (!(crs instanceof GeodeticCRS)) {
-            if (crs instanceof CompoundCRS) {
-                crs = ((CompoundCRS) crs).getComponents().get(0);
-            } else {
-                return null;
+    private static <P> P getGeodeticProperty(final CoordinateReferenceSystem 
crs, final Function<GeodeticDatum, P> getter) {
+single: if (crs instanceof SingleCRS) {
+            final SingleCRS scrs = (SingleCRS) crs;
+            final Datum datum = scrs.getDatum();
+            if (datum instanceof GeodeticDatum) {
+                P property = getter.apply((GeodeticDatum) datum);
+                if (property != null) {
+                    return property;
+                }
+            }
+            final DatumEnsemble<?> ensemble = scrs.getDatumEnsemble();
+            if (ensemble != null) {
+                P common = null;
+                for (Datum member : ensemble.getMembers()) {
+                    if (member instanceof GeodeticDatum) {
+                        final P property = getter.apply((GeodeticDatum) 
member);
+                        if (property != null) {
+                            if (common == null) {
+                                common = property;
+                            } else if 
(!Utilities.equalsIgnoreMetadata(property, common)) {
+                                break single;
+                            }
+                        }
+                    }
+                }
+                return common;
             }
         }
-        /*
-         * In order to determine if the CRS is geographic, checking the 
CoordinateSystem type is more reliable
-         * then checking if the CRS implements the GeographicCRS interface.  
This is because the GeographicCRS
-         * interface is GeoAPI-specific, so a CRS may be OGC-compliant without 
implementing that interface.
-         */
-        if (crs.getCoordinateSystem() instanceof EllipsoidalCS) {
-            return ((GeodeticCRS) crs).getDatum().getEllipsoid();
-        } else {
-            return null;    // Geocentric CRS.
+        if (crs instanceof CompoundCRS) {
+            for (final CoordinateReferenceSystem c : ((CompoundCRS) 
crs).getComponents()) {
+                final P property = getGeodeticProperty(c, getter);
+                if (property != null) {
+                    return property;
+                }
+            }
         }
+        return null;
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/CRSBuilder.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/CRSBuilder.java
index d0fa83779a..381d6e5a9a 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/CRSBuilder.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/CRSBuilder.java
@@ -903,11 +903,11 @@ public final class CRSBuilder extends 
ReferencingFactoryContainer {
                 name = Strings.toUpperCase(name, 
Characters.Filter.LETTERS_AND_DIGITS, true);
                 lastName = datum.getName();
                 try {
-                    final GeodeticDatum predefined = 
CommonCRS.valueOf(name).datum();
-                    if (equalsIgnoreMetadata(predefined.getEllipsoid(), 
ellipsoid) &&
-                        equalsIgnoreMetadata(predefined.getPrimeMeridian(), 
meridian))
+                    final CommonCRS predefined = CommonCRS.valueOf(name);
+                    if (equalsIgnoreMetadata(predefined.ellipsoid(), 
ellipsoid) &&
+                        equalsIgnoreMetadata(predefined.primeMeridian(), 
meridian))
                     {
-                        return predefined;
+                        return predefined.datum();
                     }
                 } catch (IllegalArgumentException e) {
                     // Not a name that can be mapped to CommonCRS. Ignore.
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java
index 9b5b9df2cc..0519241239 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java
@@ -325,38 +325,42 @@ public final class GeoEncoder {
         writeModelType(isBaseCRS ? GeoCodes.ModelTypeProjected : type);
         if (writeEPSG(GeoKeys.GeodeticCRS, crs)) {
             writeName(GeoKeys.GeodeticCitation, "GCS Name", crs);
-            final GeodeticDatum datum = crs.getDatum();
-            if (writeEPSG(GeoKeys.GeodeticDatum, datum)) {
-                appendName(WKTKeywords.Datum, datum);
-                final PrimeMeridian primem = datum.getPrimeMeridian();
-                final double longitude;
-                if (writeEPSG(GeoKeys.PrimeMeridian, primem)) {
-                    appendName(WKTKeywords.PrimeM, datum);
-                    longitude = primem.getGreenwichLongitude();
+            final GeodeticDatum  datum = crs.getDatum();
+            final Ellipsoid  ellipsoid = 
ReferencingUtilities.getEllipsoid(crs);
+            if (datum == null && ellipsoid != null) {
+                // Case of a datum ensemble instead of a single datum.
+                writeShort(GeoKeys.GeodeticDatum, GeoCodes.userDefined);
+            } else if (!writeEPSG(GeoKeys.GeodeticDatum, datum)) {
+                return true;
+            }
+            appendName(WKTKeywords.Datum, datum);
+            final PrimeMeridian primem = 
ReferencingUtilities.getPrimeMeridian(crs);
+            final double longitude;
+            if (writeEPSG(GeoKeys.PrimeMeridian, primem)) {
+                appendName(WKTKeywords.PrimeM, primem);
+                longitude = primem.getGreenwichLongitude();
+            } else {
+                longitude = 0;                                      // Means 
"do not write prime meridian".
+            }
+            final Unit<Length>  axisUnit   = ellipsoid.getAxisUnit();
+            final Unit<?>       linearUnit = units.putIfAbsent(UnitKey.LINEAR, 
axisUnit);
+            final UnitConverter toLinear   = 
axisUnit.getConverterToAny(linearUnit != null ? linearUnit : axisUnit);
+            writeUnit(UnitKey.LINEAR);     // Must be after the `units` map 
have been updated.
+            writeUnit(UnitKey.ANGULAR);
+            if (writeEPSG(GeoKeys.Ellipsoid, ellipsoid)) {
+                appendName(WKTKeywords.Ellipsoid, ellipsoid);
+                writeDouble(GeoKeys.SemiMajorAxis, 
toLinear.convert(ellipsoid.getSemiMajorAxis()));
+                if (ellipsoid.isSphere() || !ellipsoid.isIvfDefinitive()) {
+                    writeDouble(GeoKeys.SemiMinorAxis, 
toLinear.convert(ellipsoid.getSemiMinorAxis()));
                 } else {
-                    longitude = 0;                                      // 
Means "do not write prime meridian".
-                }
-                final Ellipsoid     ellipsoid  = datum.getEllipsoid();
-                final Unit<Length>  axisUnit   = ellipsoid.getAxisUnit();
-                final Unit<?>       linearUnit = 
units.putIfAbsent(UnitKey.LINEAR, axisUnit);
-                final UnitConverter toLinear   = 
axisUnit.getConverterToAny(linearUnit != null ? linearUnit : axisUnit);
-                writeUnit(UnitKey.LINEAR);     // Must be after the `units` 
map have been updated.
-                writeUnit(UnitKey.ANGULAR);
-                if (writeEPSG(GeoKeys.Ellipsoid, ellipsoid)) {
-                    appendName(WKTKeywords.Ellipsoid, ellipsoid);
-                    writeDouble(GeoKeys.SemiMajorAxis, 
toLinear.convert(ellipsoid.getSemiMajorAxis()));
-                    if (ellipsoid.isSphere() || !ellipsoid.isIvfDefinitive()) {
-                        writeDouble(GeoKeys.SemiMinorAxis, 
toLinear.convert(ellipsoid.getSemiMinorAxis()));
-                    } else {
-                        writeDouble(GeoKeys.InvFlattening, 
ellipsoid.getInverseFlattening());
-                    }
-                }
-                if (longitude != 0) {
-                    Unit<Angle> unit = primem.getAngularUnit();
-                    UnitConverter c = 
unit.getConverterToAny(units.getOrDefault(UnitKey.ANGULAR, Units.DEGREE));
-                    writeDouble(GeoKeys.PrimeMeridianLongitude, 
c.convert(longitude));
+                    writeDouble(GeoKeys.InvFlattening, 
ellipsoid.getInverseFlattening());
                 }
             }
+            if (longitude != 0) {
+                Unit<Angle> unit = primem.getAngularUnit();
+                UnitConverter c = 
unit.getConverterToAny(units.getOrDefault(UnitKey.ANGULAR, Units.DEGREE));
+                writeDouble(GeoKeys.PrimeMeridianLongitude, 
c.convert(longitude));
+            }
         } else if (isBaseCRS) {
             writeUnit(UnitKey.ANGULAR);         // Map projection parameters 
may need this unit.
         }
diff --git 
a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/CRSBuilder.java
 
b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/CRSBuilder.java
index 87352f7e20..b3d4d844fd 100644
--- 
a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/CRSBuilder.java
+++ 
b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/CRSBuilder.java
@@ -56,6 +56,7 @@ import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.netcdf.internal.Resources;
 import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.Utilities;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.measure.Units;
@@ -621,8 +622,18 @@ previous:   for (int i=components.size(); --i >= 0;) {
          * This method is invoked only if {@link 
#setPredefinedComponents(Decoder)} failed to create a datum.
          */
         @Override final void createDatum(DatumFactory factory, Map<String,?> 
properties) throws FactoryException {
-            final GeodeticDatum template = defaultCRS.datum();
-            datum = factory.createGeodeticDatum(properties, 
template.getEllipsoid(), template.getPrimeMeridian());
+            datum = factory.createGeodeticDatum(properties, 
defaultCRS.ellipsoid(), defaultCRS.primeMeridian());
+        }
+
+        /**
+         * Sets the datum from the enumeration value of a predefined CRS.
+         * The predefined CRS is {@link #defaultCRS} or a spherical CRS.
+         */
+        protected final void setDatum(final CommonCRS crs) {
+            datum = crs.datum();
+            if (datum == null) {
+                datumEnsemble = crs.datumEnsemble();
+            }
         }
 
         /**
@@ -676,8 +687,9 @@ previous:   for (int i=components.size(); --i >= 0;) {
                 referenceSystem  = crs;
                 coordinateSystem = (SphericalCS) crs.getCoordinateSystem();
                 datum            = crs.getDatum();
+                datumEnsemble    = crs.getDatumEnsemble();
             } else {
-                datum = defaultCRS.datum();
+                setDatum(defaultCRS);
             }
         }
 
@@ -739,8 +751,9 @@ previous:   for (int i=components.size(); --i >= 0;) {
                 referenceSystem  = crs;
                 coordinateSystem = crs.getCoordinateSystem();
                 datum            = crs.getDatum();
+                datumEnsemble    = crs.getDatumEnsemble();
             } else {
-                datum = defaultCRS.datum();
+                setDatum(defaultCRS);
                 final Integer epsg = epsgCandidateCS(Units.DEGREE);
                 if (epsg != null) try {
                     coordinateSystem = 
decoder.getCSAuthorityFactory().createEllipsoidalCS(epsg.toString());
@@ -768,7 +781,7 @@ previous:   for (int i=components.size(); --i >= 0;) {
          * This method is invoked under conditions similar to the ones of 
above {@code createCS(…)} method.
          */
         @Override void createCRS(CRSFactory factory, Map<String,?> properties) 
throws FactoryException {
-            referenceSystem = factory.createGeographicCRS(properties, datum, 
coordinateSystem);
+            referenceSystem = factory.createGeographicCRS(properties, datum, 
datumEnsemble, coordinateSystem);
         }
     }
 
@@ -817,7 +830,7 @@ previous:   for (int i=components.size(); --i >= 0;) {
         @Override void setPredefinedComponents(final Decoder decoder) throws 
FactoryException {
             super.setPredefinedComponents(decoder);
             sphericalDatum = decoder.convention().defaultHorizontalCRS(true);
-            datum = sphericalDatum.datum();
+            setDatum(sphericalDatum);
             if (isPredefinedCS(Units.METRE)) {
                 coordinateSystem = decoder.getStandardProjectedCS();
             }
@@ -843,8 +856,10 @@ previous:   for (int i=components.size(); --i >= 0;) {
         @Override void createCRS(CRSFactory factory, Map<String,?> properties) 
throws FactoryException {
             final boolean is3D = (coordinateSystem.getDimension() >= 3);
             GeographicCRS baseCRS = is3D ? sphericalDatum.geographic3D() : 
sphericalDatum.geographic();
-            if (!baseCRS.getDatum().equals(datum)) {
-                baseCRS = factory.createGeographicCRS(properties, datum, 
baseCRS.getCoordinateSystem());
+            if (!Utilities.equalsIgnoreMetadata(baseCRS.getDatum(), datum) &&
+                !Utilities.equalsIgnoreMetadata(baseCRS.getDatumEnsemble(), 
datumEnsemble))
+            {
+                baseCRS = factory.createGeographicCRS(properties, datum, 
datumEnsemble, baseCRS.getCoordinateSystem());
             }
             referenceSystem = factory.createProjectedCRS(properties, baseCRS, 
UNKNOWN_PROJECTION, coordinateSystem);
         }
@@ -912,7 +927,7 @@ previous:   for (int i=components.size(); --i >= 0;) {
          * Creates the coordinate reference system from datum and coordinate 
system computed in previous steps.
          */
         @Override void createCRS(CRSFactory factory, Map<String,?> properties) 
throws FactoryException {
-            referenceSystem =  factory.createVerticalCRS(properties, datum, 
coordinateSystem);
+            referenceSystem =  factory.createVerticalCRS(properties, datum, 
datumEnsemble, coordinateSystem);
         }
     }
 
@@ -992,7 +1007,7 @@ previous:   for (int i=components.size(); --i >= 0;) {
         @Override void createCRS(CRSFactory factory, Map<String,?> properties) 
throws FactoryException {
             properties = 
properties(getFirstAxis().coordinates.getUnitsString());
             if (datum != null) {
-                referenceSystem =  factory.createTemporalCRS(properties, 
datum, coordinateSystem);
+                referenceSystem =  factory.createTemporalCRS(properties, 
datum, datumEnsemble, coordinateSystem);
             } else {
                 referenceSystem =  factory.createEngineeringCRS(properties,
                         CommonCRS.Engineering.TIME.datum(), coordinateSystem);
@@ -1058,7 +1073,7 @@ previous:   for (int i=components.size(); --i >= 0;) {
          * Creates the coordinate reference system from datum and coordinate 
system computed in previous steps.
          */
         @Override void createCRS(CRSFactory factory, Map<String,?> properties) 
throws FactoryException {
-            referenceSystem =  factory.createEngineeringCRS(properties, datum, 
coordinateSystem);
+            referenceSystem =  factory.createEngineeringCRS(properties, datum, 
datumEnsemble, coordinateSystem);
         }
     }
 
diff --git 
a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java
 
b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java
index e470fbd1d5..609cc18f5f 100644
--- 
a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java
+++ 
b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java
@@ -78,6 +78,9 @@ import org.apache.sis.io.wkt.WKTFormat;
 import org.apache.sis.io.wkt.Warnings;
 import org.apache.sis.measure.Units;
 
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.referencing.datum.DatumEnsemble;
+
 
 /**
  * Temporary objects for creating a {@link GridGeometry} instance defined by 
attributes on a variable.
@@ -367,6 +370,7 @@ final class GridMapping {
          */
         final Object bursaWolf = definition.remove(Convention.TOWGS84);
         final GeodeticDatum datum;
+        DatumEnsemble<GeodeticDatum> ensemble = null;
         if (isSpecified | bursaWolf != null) {
             Map<String,Object> properties = properties(definition, 
Convention.GEODETIC_DATUM_NAME, ellipsoid);
             if (bursaWolf instanceof BursaWolfParameters) {
@@ -377,13 +381,16 @@ final class GridMapping {
             datum = datumFactory.createGeodeticDatum(properties, ellipsoid, 
meridian);
         } else {
             datum = defaultDefinitions.datum();
+            if (datum == null) {
+                ensemble = defaultDefinitions.datumEnsemble();
+            }
         }
         /*
          * Geographic CRS from all above properties.
          */
         if (isSpecified) {
             final Map<String,?> properties = properties(definition, 
Convention.GEOGRAPHIC_CRS_NAME, datum);
-            return decoder.getCRSFactory().createGeographicCRS(properties, 
datum,
+            return decoder.getCRSFactory().createGeographicCRS(properties, 
datum, ensemble,
                     defaultDefinitions.geographic().getCoordinateSystem());
         } else {
             return defaultDefinitions.geographic();
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.java
index 78d747d87c..027b7f1b48 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.java
@@ -739,6 +739,11 @@ public class Errors extends IndexedResourceBundle {
          */
         public static final short NonTemporalUnit_1 = 133;
 
+        /**
+         * Expected the “{0}” value for all members, but found a member with 
the “{1}” value.
+         */
+        public static final short NonUniformValue_2 = 207;
+
         /**
          * No element for the “{0}” identifier, or the identifier is a forward 
reference.
          */
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.properties
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.properties
index 103ad55d34..9fdefff89d 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.properties
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.properties
@@ -158,6 +158,7 @@ NonLinearUnit_1                   = \u201c{0}\u201d is not 
a linear unit.
 NonScaleUnit_1                    = \u201c{0}\u201d is not a scale unit.
 NonTemporalUnit_1                 = \u201c{0}\u201d is not a time unit.
 NonRatioUnit_1                    = The scale of measurement for 
\u201c{0}\u201d unit is not a ratio scale.
+NonUniformValue_2                 = Expected the \u201c{0}\u201d value for all 
members, but found a member with the \u201c{1}\u201d value.
 NotABackwardReference_1           = No element for the \u201c{0}\u201d 
identifier, or the identifier is a forward reference.
 NotADivisorOrMultiple_4           = Value of \u2018{0}\u2019 shall be a 
{1,choice,0#divisor|1#multiple} of {2} but the given value is {3}.
 NotAnInteger_1                    = {0} is not an integer value.
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties
index ef040968eb..ef89339a87 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties
@@ -155,6 +155,7 @@ NonLinearUnit_1                   = 
\u00ab\u202f{0}\u202f\u00bb n\u2019est pas u
 NonScaleUnit_1                    = \u00ab\u202f{0}\u202f\u00bb n\u2019est pas 
une unit\u00e9 d\u2019\u00e9chelles.
 NonTemporalUnit_1                 = \u00ab\u202f{0}\u202f\u00bb n\u2019est pas 
une unit\u00e9 de temps.
 NonRatioUnit_1                    = L\u2019\u00e9chelle de mesure de 
l\u2019unit\u00e9 \u00ab\u202f{0}\u202f\u00bb n\u2019est pas une \u00e9chelle 
de rapports.
+NonUniformValue_2                 = La valeur \u00ab\u202f{0}\u202f\u00bb 
\u00e9tait attendue pour tous les membres, mais un membre a la valeur 
\u00ab\u202f{1}\u202f\u00bb.
 NotABackwardReference_1           = Il n\u2019y a pas d\u2019\u00e9l\u00e9ment 
pour l\u2019identifiant \u201c{0}\u201d, ou l\u2019identifiant est une 
r\u00e9f\u00e9rence vers l\u2019avant.
 NotADivisorOrMultiple_4           = La valeur de \u2018{0}\u2019 doit 
\u00eatre un {1,choice,0#diviseur|1#multiple} de {2}, mais la valeur 
donn\u00e9e est {3}.
 NotAnInteger_1                    = {0} n\u2019est pas un nombre entier.


Reply via email to