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

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

commit f0a1dfadf15240c32b5e688a40f2c4f62e417cf6
Merge: 96a055335c d3af997759
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Wed Jul 17 19:06:16 2024 +0200

    Merge branch 'geoapi-3.1': handling of `DatumEnsemble` from ISO 19111:2019.

 .../org/apache/sis/feature/AbstractAttribute.java  |   2 +-
 .../org/apache/sis/feature/AbstractFeature.java    |   2 +-
 .../apache/sis/feature/SingletonAttributeTest.java |   2 +-
 .../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   |  12 +-
 .../sis/referencing/AbstractIdentifiedObject.java  |   7 +-
 .../main/org/apache/sis/referencing/CRS.java       |  34 +-
 .../main/org/apache/sis/referencing/CommonCRS.java |  76 ++-
 .../referencing/EllipsoidalHeightSeparator.java    |  35 +-
 .../apache/sis/referencing/crs/AbstractCRS.java    |  78 +--
 .../sis/referencing/crs/AbstractSingleCRS.java     | 315 ++++++++++++
 .../sis/referencing/crs/DefaultDerivedCRS.java     |  24 +-
 .../sis/referencing/crs/DefaultEngineeringCRS.java |  48 +-
 .../sis/referencing/crs/DefaultGeocentricCRS.java  |   6 +-
 .../sis/referencing/crs/DefaultGeodeticCRS.java    |  54 +-
 .../sis/referencing/crs/DefaultGeographicCRS.java  |  42 +-
 .../sis/referencing/crs/DefaultImageCRS.java       |  30 +-
 .../sis/referencing/crs/DefaultParametricCRS.java  |  45 +-
 .../sis/referencing/crs/DefaultProjectedCRS.java   |  24 +-
 .../sis/referencing/crs/DefaultTemporalCRS.java    |  82 ++-
 .../sis/referencing/crs/DefaultVerticalCRS.java    |  48 +-
 .../sis/referencing/crs/ExplicitParameters.java    |   6 +-
 .../referencing/datum/DefaultDatumEnsemble.java    |  16 +-
 .../apache/sis/referencing/datum/PseudoDatum.java  | 560 +++++++++++++++++++++
 .../referencing/factory/GeodeticObjectFactory.java |  16 +-
 .../apache/sis/referencing/internal/Resources.java |   4 +-
 .../sis/referencing/internal/Resources.properties  |   4 +-
 .../referencing/internal/Resources_fr.properties   |   2 +-
 .../operation/CoordinateOperationFinder.java       |  21 +-
 .../operation/CoordinateOperationRegistry.java     |  12 +-
 .../referencing/operation/DefaultConversion.java   |  18 +-
 .../DefaultCoordinateOperationFactory.java         |  16 +-
 .../transform/DefaultMathTransformFactory.java     |   5 +-
 .../sis/referencing/privy/DefinitionVerifier.java  |  29 +-
 .../privy/EllipsoidalHeightCombiner.java           |  27 +-
 .../referencing/privy/GeodeticObjectBuilder.java   |  39 +-
 .../referencing/privy/ReferencingUtilities.java    | 130 +++--
 .../referencing/AbstractIdentifiedObjectTest.java  |   2 +-
 .../sis/storage/geotiff/reader/CRSBuilder.java     |  13 +-
 .../sis/storage/geotiff/writer/GeoEncoder.java     |   5 +-
 .../apache/sis/storage/netcdf/base/CRSBuilder.java |  27 +-
 .../sis/storage/netcdf/base/GridMapping.java       |   5 +-
 .../main/org/apache/sis/storage/csv/Store.java     |   9 +-
 .../main/org/apache/sis/util/resources/Errors.java |  11 +-
 .../apache/sis/util/resources/Errors.properties    |   7 +-
 .../apache/sis/util/resources/Errors_fr.properties |   1 +
 .../org/apache/sis/gui/map/OperationFinder.java    |   5 +-
 .../main/org/apache/sis/gui/referencing/Utils.java |   6 +-
 51 files changed, 1464 insertions(+), 525 deletions(-)

diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/VerticalInfo.java
index 017d807552,5db9ddb99c..33ee468d60
--- 
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
@@@ -107,8 -108,10 +108,10 @@@ final class VerticalInfo 
       *         became empty as a result of this operation.
       */
      final VerticalInfo resolve(final VerticalCRS crs) {
-         if (crs != null && crs.getDatum().getVerticalDatumType() == 
VerticalDatumType.GEOIDAL) {
-             return resolve(crs, crs.getCoordinateSystem().getAxis(0));
+         if (crs != null) {
 -            if (PseudoDatum.of(crs).getRealizationMethod().orElse(null) == 
RealizationMethod.GEOID) {
++            if (PseudoDatum.of(crs).getVerticalDatumType() == 
VerticalDatumType.GEOIDAL) {
+                 return resolve(crs, crs.getCoordinateSystem().getAxis(0));
+             }
          }
          return this;
      }
@@@ -179,8 -182,10 +182,9 @@@
           *     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(),
++                                                           
PseudoDatum.of(compatibleCRS),
+                                                            cs));
          return next;
      }
  
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java
index 9af53cbf78,7454658ac6..8619fca435
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java
@@@ -461,13 -452,16 +461,16 @@@ public class AbstractIdentifiedObject e
       *
       * <p>This constructor performs a shallow copy, i.e. the properties are 
not cloned.</p>
       *
-      * @param object  the object to shallow copy.
+      * @param  object  the object to shallow copy.
       */
      protected AbstractIdentifiedObject(final IdentifiedObject object) {
-         name        =          object.getName();
+         name = object.getName();
+         if (name == null) {
+             throw new 
IllegalArgumentException(Errors.format(Errors.Keys.MissingValueForProperty_1, 
NAME_KEY));
+         }
          alias       = nonEmpty(object.getAlias()); // Favor null for empty 
set in case it is not Collections.EMPTY_SET
          identifiers = nonEmpty(object.getIdentifiers());
 -        domains     = nonEmpty(object.getDomains());
 +        domains     = nonEmpty(Legacy.getDomains(object));
          remarks     =          object.getRemarks();
          deprecated  = (object instanceof Deprecable) ? ((Deprecable) 
object).isDeprecated() : false;
      }
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
index f3d219197f,6d4457e1bd..c4ce6961ad
--- 
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
@@@ -86,10 -87,9 +87,11 @@@ import org.apache.sis.measure.Latitude
  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;
 +// Specific to the main branch:
 +import org.opengis.referencing.crs.GeocentricCRS;
 +import org.opengis.referencing.datum.VerticalDatumType;
++import org.apache.sis.referencing.datum.DefaultDatumEnsemble;
 +import static 
org.apache.sis.pending.geoapi.referencing.MissingMethods.getDatumEnsemble;
  
  
  /**
@@@ -501,7 -501,7 +503,7 @@@ public enum CommonCRS 
          }
          final Datum datum = single.getDatum();
          if (datum instanceof GeodeticDatum) {
-             final CommonCRS c = forDatum((GeodeticDatum) datum);
 -            final CommonCRS c = forDatum((GeodeticDatum) datum, 
single.getDatumEnsemble());
++            final CommonCRS c = forDatum((GeodeticDatum) datum, 
getDatumEnsemble(single));
              if (c != null) return c;
          }
          throw new IllegalArgumentException(Errors.format(
@@@ -510,8 -510,12 +512,12 @@@
  
      /**
       * 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}.
+      * @param  ensemble  the datum ensemble 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) {
 -    static CommonCRS forDatum(final GeodeticDatum datum, final 
DatumEnsemble<?> ensemble) {
++    static CommonCRS forDatum(final GeodeticDatum datum, final 
DefaultDatumEnsemble<?> ensemble) {
          /*
           * First, try to search using only the EPSG code. This approach avoid 
initializing unneeded
           * geodetic objects (such initializations are costly if they require 
connection to the EPSG
@@@ -838,7 -842,10 +852,10 @@@
                      if (cs == null) {
                          cs = (SphericalCS) 
StandardDefinitions.createCoordinateSystem(StandardDefinitions.SPHERICAL, true);
                      }
-                     object = new 
DefaultGeocentricCRS(IdentifiedObjects.getProperties(base, exclude()), 
base.getDatum(), getDatumEnsemble(base), cs);
+                     object = new 
DefaultGeocentricCRS(IdentifiedObjects.getProperties(base, exclude()),
+                                                       base.getDatum(),
 -                                                      base.getDatumEnsemble(),
++                                                      getDatumEnsemble(base),
+                                                       cs);
                      cachedSpherical = object;
                  }
              }
@@@ -893,6 -900,17 +910,17 @@@
          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();
++    public DefaultDatumEnsemble<GeodeticDatum> datumEnsemble() {
++        return getDatumEnsemble(geographic());
+     }
+ 
      /**
       * Returns the ellipsoid associated to this geodetic object.
       * The following table summarizes the ellipsoids known to this class,
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EllipsoidalHeightSeparator.java
index 4b70ced344,366257c40e..0dc7d6e7f0
--- 
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
@@@ -42,6 -42,9 +42,11 @@@ import static org.apache.sis.referencin
  // Specific to the main and geoapi-3.1 branches:
  import org.opengis.referencing.crs.GeographicCRS;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.referencing.datum.DatumEnsemble;
++// Specific to the main branch:
++import org.apache.sis.referencing.datum.PseudoDatum;
++import org.apache.sis.referencing.datum.DefaultDatumEnsemble;
++import static 
org.apache.sis.pending.geoapi.referencing.MissingMethods.getDatumEnsemble;
+ 
  
  /**
   * Helper class for separating the ellipsoidal height from the horizontal 
part of a CRS.
@@@ -57,19 -60,25 +62,31 @@@ final class EllipsoidalHeightSeparator 
       */
      private final GeodeticDatum datum;
  
+     /**
+      * The datum ensemble of the <abbr>CRS</abbr> to separate, or {@code 
null} if none.
+      */
 -    private final DatumEnsemble<GeodeticDatum> ensemble;
++    private final DefaultDatumEnsemble<GeodeticDatum> ensemble;
++
++    /**
++     * Workaround for GeoAPI 3.0 (to be removed with GeoAPI 3.1).
++     */
++    private final GeodeticDatum pseudo;
+ 
      /**
       * 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.ensemble = getDatumEnsemble(baseCRS);
++        this.pseudo   = PseudoDatum.of(baseCRS);
          this.vertical = vertical;
      }
  
@@@ -103,7 -112,10 +120,9 @@@
          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(),
++                                                        
PseudoDatum.of(component),
+                                                         (VerticalCS) cs);
              }
              return component;
          }
@@@ -118,13 -130,13 +137,13 @@@
              }
              final CommonCRS ref = CommonCRS.WGS84;
              if 
(Utilities.equalsIgnoreMetadata(ref.geographic().getCoordinateSystem(), cs)) {
-                 final CommonCRS c = CommonCRS.forDatum(datum);
+                 final CommonCRS c = CommonCRS.forDatum(datum, ensemble);
                  if (c != null) return c.geographic();
              } else if 
(Utilities.equalsIgnoreMetadata(ref.normalizedGeographic().getCoordinateSystem(),
 cs)) {
-                 final CommonCRS c = CommonCRS.forDatum(datum);
+                 final CommonCRS c = CommonCRS.forDatum(datum, ensemble);
                  if (c != null) return c.normalizedGeographic();
              }
-             return 
factory().createGeographicCRS(getPropertiesForModifiedCRS(crs), datum, 
(EllipsoidalCS) cs);
 -            return 
factory().createGeographicCRS(getPropertiesForModifiedCRS(crs), datum, 
ensemble, (EllipsoidalCS) cs);
++            return 
factory().createGeographicCRS(getPropertiesForModifiedCRS(crs), pseudo, 
(EllipsoidalCS) cs);
          }
          /*
           * In the projected CRS case, in addition of reducing the number of 
dimensions in the CartesianCS,
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java
index c47378a318,794042176d..bd6e31b3fc
--- 
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
@@@ -40,8 -38,6 +38,7 @@@ import org.apache.sis.referencing.privy
  import org.apache.sis.metadata.privy.ImplementationHelper;
  import org.apache.sis.io.wkt.Convention;
  import org.apache.sis.io.wkt.Formatter;
 +import org.apache.sis.pending.geoapi.referencing.MissingMethods;
- import org.apache.sis.util.ArgumentChecks;
  import org.apache.sis.util.Utilities;
  import org.apache.sis.util.ComparisonMode;
  import org.apache.sis.util.resources.Errors;
@@@ -245,10 -210,10 +212,10 @@@ public class AbstractCRS extends Abstra
       *
       * @see #createSameType(AbstractCS)
       */
 -    AbstractCRS(final AbstractCRS original, final Identifier id, final 
AbstractCS cs) {
 +    AbstractCRS(final AbstractCRS original, final ReferenceIdentifier id, 
final AbstractCS cs) {
          super(ReferencingUtilities.getPropertiesWithoutIdentifiers(original, 
(id == null) ? null : Map.of(IDENTIFIERS_KEY, id)));
          coordinateSystem = cs;
-         forConvention = cs.hasSameAxes(original.coordinateSystem) ? 
original.forConvention : forConvention(original);
+         forConvention = cs.hasSameAxes(original.coordinateSystem) ? 
original.forConvention : original.forConvention();
      }
  
      /**
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractSingleCRS.java
index 0000000000,6cff7e3c74..4aa8a7a943
mode 000000,100644..100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractSingleCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractSingleCRS.java
@@@ -1,0 -1,314 +1,315 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one or more
+  * contributor license agreements.  See the NOTICE file distributed with
+  * this work for additional information regarding copyright ownership.
+  * The ASF licenses this file to You under the Apache License, Version 2.0
+  * (the "License"); you may not use this file except in compliance with
+  * the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS,
+  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  * See the License for the specific language governing permissions and
+  * limitations under the License.
+  */
+ package org.apache.sis.referencing.crs;
+ 
+ import java.util.Map;
+ import java.util.Objects;
+ import jakarta.xml.bind.annotation.XmlType;
+ import jakarta.xml.bind.annotation.XmlSeeAlso;
+ import jakarta.xml.bind.annotation.XmlRootElement;
 -import org.opengis.metadata.Identifier;
+ import org.opengis.referencing.crs.SingleCRS;
+ import org.opengis.referencing.cs.CoordinateSystem;
+ import org.opengis.referencing.datum.Datum;
+ import org.apache.sis.util.Utilities;
+ import org.apache.sis.util.ArgumentChecks;
+ import org.apache.sis.util.ComparisonMode;
+ import org.apache.sis.util.resources.Errors;
+ import org.apache.sis.referencing.IdentifiedObjects;
+ import org.apache.sis.referencing.cs.AbstractCS;
+ import org.apache.sis.referencing.datum.PseudoDatum;
+ import org.apache.sis.referencing.internal.Resources;
+ import org.apache.sis.metadata.privy.ImplementationHelper;
+ 
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.referencing.datum.DatumEnsemble;
++// Specific to the main branch:
++import org.opengis.referencing.ReferenceIdentifier;
++import org.apache.sis.pending.geoapi.referencing.MissingMethods;
++import org.apache.sis.referencing.datum.DefaultDatumEnsemble;
+ 
+ 
+ /**
+  * Base class of <abbr>CRS</abbr> associated to a datum.
+  *
+  * @param  <D>  the type of datum associated to this <abbr>CRS</abbr>.
+  *
+  * @author  Martin Desruisseaux (IRD, Geomatys)
+  */
+ @XmlType(name = "AbstractSingleCRSType")
+ @XmlRootElement(name = "AbstractSingleCRS")
+ @XmlSeeAlso({
+     AbstractDerivedCRS.class,
+     DefaultGeodeticCRS.class,
+     DefaultVerticalCRS.class,
+     DefaultTemporalCRS.class,
+     DefaultParametricCRS.class,
+     DefaultEngineeringCRS.class,
+     DefaultImageCRS.class
+ })
+ class AbstractSingleCRS<D extends Datum> extends AbstractCRS implements 
SingleCRS {
+     /**
+      * Serial number for inter-operability with different versions.
+      */
+     private static final long serialVersionUID = 2876221982955686798L;
+ 
+     /**
+      * The datum, or {@code null} if the <abbr>CRS</abbr> is associated only 
to a datum ensemble.
+      *
+      * <p><b>Consider this field as final!</b>
+      * This field is non-final only for construction convenience and for 
unmarshalling.</p>
+      *
+      * @see #getDatum()
+      */
+     @SuppressWarnings("serial")     // Most SIS implementations are 
serializable.
+     private D datum;
+ 
+     /**
+      * Collection of reference frames which for low accuracy requirements may 
be considered to be
+      * insignificantly different from each other. May be {@code null} if 
there is no such ensemble.
+      *
+      * @see #getDatumEnsemble()
+      */
+     @SuppressWarnings("serial")     // Most SIS implementations are 
serializable.
 -    private final DatumEnsemble<D> ensemble;
++    private final DefaultDatumEnsemble<D> ensemble;
+ 
+     /**
+      * Creates a coordinate reference system from the given properties, datum 
and coordinate system.
+      * At least one of the {@code datum} and {@code ensemble} arguments shall 
be non-null.
+      * The properties given in argument follow the same rules as for the
+      * {@linkplain AbstractReferenceSystem#AbstractReferenceSystem(Map) 
super-class constructor}.
+      *
+      * @param  properties  the properties to be given to the coordinate 
reference system.
+      * @param  datumType   GeoAPI interface of the datum or members of the 
datum ensemble.
+      * @param  datum       the datum, or {@code null} if the CRS is 
associated only to a datum ensemble.
+      * @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.
+      * @param  cs          the coordinate system.
+      */
+     AbstractSingleCRS(final Map<String,?> properties,
+                       final Class<D> datumType,
+                       final D datum,
 -                      final DatumEnsemble<D> ensemble,
++                      final DefaultDatumEnsemble<D> ensemble,
+                       final CoordinateSystem cs)
+     {
+         super(properties, cs);
+         /*
+          * If the given datum is actually a wrapper for a datum ensemble, 
unwrap the datum ensemble
+          * and verify the consistency. This class should never store 
`PseudoDatum` instances.
+          */
+         if (datum instanceof PseudoDatum<?>) {
+             @SuppressWarnings("unchecked")      // Type is verified below.
+             final var pseudo = (PseudoDatum<D>) datum;
+             final var member = pseudo.getInterface();
+             if (member != datumType) {
+                 throw new 
IllegalArgumentException(Errors.forProperties(properties)
+                             .getString(Errors.Keys.IllegalArgumentClass_2, 
"datum",
+                                        PseudoDatum.class.getSimpleName() + 
'<' + member.getSimpleName() + '>'));
+             }
+             if (ensemble == null) {
+                 this.ensemble = pseudo.ensemble;
+             } else if (Utilities.equalsIgnoreMetadata(ensemble, 
pseudo.ensemble)) {
+                 this.ensemble = ensemble;
+             } else {
+                 throw new 
IllegalArgumentException(Errors.forProperties(properties)
+                             
.getString(Errors.Keys.IncompatiblePropertyValue_1, "pseudo-datum"));
+             }
+             ArgumentChecks.ensureNonEmpty((ensemble != null) ? "ensemble" : 
"pseudo-datum", this.ensemble.getMembers());
+         } else {
+             this.datum    = datum;
+             this.ensemble = ensemble;
+             checkDatum(properties);
+         }
+     }
+ 
+     /**
+      * Verifies the consistency between the datum and the ensemble.
+      * At least one of the {@link #datum} and {@link #ensemble} arguments 
shall be non-null.
+      *
+      * @param  properties  user-specified properties given at construction 
time, or {@code null} if none.
+      * @throws NullPointerException if both {@link #datum} and {@link 
#ensemble} are null.
+      * @throws IllegalArgumentException if the datum is not a member of the 
ensemble.
+      */
+     private void checkDatum(final Map<String,?> properties) {
+         if (ensemble == null) {
+             ArgumentChecks.ensureNonNull("datum", datum);
+         } else if (datum != null) {
+             for (final D member : ensemble.getMembers()) {
+                 if (Utilities.equalsIgnoreMetadata(datum, member)) {
+                     return;
+                 }
+             }
+             throw new 
IllegalArgumentException(Resources.forProperties(properties)
+                         .getString(Resources.Keys.NotAMemberOfDatumEnsemble_2,
+                                    IdentifiedObjects.getDisplayName(ensemble),
+                                    IdentifiedObjects.getDisplayName(datum)));
+         } else {
+             ArgumentChecks.ensureNonEmpty("ensemble", ensemble.getMembers());
+         }
+     }
+ 
+     /**
+      * Creates a new CRS derived from the specified one, but with different 
axis order or unit.
+      *
+      * @param original  the original CRS from which to derive a new one.
+      * @param id        new identifier for this CRS, or {@code null} if none.
+      * @param cs        coordinate system with new axis order or units of 
measurement.
+      */
 -    AbstractSingleCRS(final AbstractSingleCRS<D> original, final Identifier 
id, final AbstractCS cs) {
++    AbstractSingleCRS(final AbstractSingleCRS<D> original, final 
ReferenceIdentifier id, final AbstractCS cs) {
+         super(original, id, cs);
+         datum    = original.datum;
+         ensemble = original.ensemble;
+     }
+ 
+     /**
+      * Constructs a new coordinate reference system with the same values as 
the specified one.
+      * This copy constructor provides a way to convert an arbitrary 
implementation into a SIS one
+      * or a user-defined one (as a subclass), usually in order to leverage 
some implementation-specific API.
+      *
+      * <p>This constructor performs a shallow copy, i.e. the properties are 
not cloned.</p>
+      *
+      * <h4>Type safety</h4>
+      * This constructor shall be invoked only by subclass constructors with a 
method signature where
+      * the <abbr>CRS</abbr> type is an interface with {@code getDatum()} and 
{@code getDatumEnsemble()}
+      * methods overridden with return type {@code <D>}.
+      *
+      * @param  crs  the coordinate reference system to copy.
+      */
+     @SuppressWarnings("unchecked")              // See "Type safety" in above 
Javadoc.
+     AbstractSingleCRS(final SingleCRS crs) {
+         super(crs);
+         datum = (D) crs.getDatum();
+         if (datum instanceof PseudoDatum<?>) {
+             throw new IllegalArgumentException(
+                     Errors.format(Errors.Keys.IllegalPropertyValueClass_2, 
"datum", PseudoDatum.class));
+         }
 -        ensemble = (DatumEnsemble<D>) crs.getDatumEnsemble();
++        ensemble = (DefaultDatumEnsemble<D>) 
MissingMethods.getDatumEnsemble(crs);
+         checkDatum(null);
+     }
+ 
+     /**
+      * Returns the GeoAPI interface implemented by this class.
+      * The default implementation returns {@code SingleCRS.class}.
+      * Subclasses implementing a more specific GeoAPI interface shall 
override this method.
+      *
+      * @return the coordinate reference system interface implemented by this 
class.
+      */
+     @Override
+     public Class<? extends SingleCRS> getInterface() {
+         return SingleCRS.class;
+     }
+ 
+     /**
+      * Returns the datum, or {@code null} if this <abbr>CRS</abbr> is 
associated only to a datum ensemble.
+      *
+      * @return the datum, or {@code null} if none.
+      */
+     @Override
+     public D getDatum() {
+         return datum;
+     }
+ 
+     /**
+      * Returns the datum ensemble, or {@code null} if none.
+      *
+      * @return the datum ensemble, or {@code null} if none.
+      */
+     @Override
 -    public DatumEnsemble<D> getDatumEnsemble() {
++    public DefaultDatumEnsemble<D> getDatumEnsemble() {
+         return ensemble;
+     }
+ 
+     /**
+      * Compares this coordinate reference system with the specified object 
for equality.
+      *
+      * @param  object  the object to compare to {@code this}.
+      * @param  mode    whether to perform a strict or lenient comparison.
+      * @return {@code true} if both objects are equal.
+      * @hidden
+      */
+     @Override
+     public boolean equals(final Object object, final ComparisonMode mode) {
+         if (super.equals(object, mode)) {
+             switch (mode) {
+                 case STRICT: {
+                     final var that = (AbstractSingleCRS<?>) object;
+                     return Objects.equals(datum, that.datum) && 
Objects.equals(ensemble, that.ensemble);
+                 }
+                 default: {
+                     final var that = (SingleCRS) object;
+                     return Utilities.deepEquals(getDatum(), that.getDatum(), 
mode) &&
 -                           Utilities.deepEquals(getDatumEnsemble(), 
that.getDatumEnsemble(), mode);
++                           Utilities.deepEquals(getDatumEnsemble(), 
MissingMethods.getDatumEnsemble(that), mode);
+                 }
+             }
+         }
+         return false;
+     }
+ 
+     /**
+      * Invoked by {@code hashCode()} for computing the hash code when first 
needed.
+      *
+      * @return the hash code value. This value may change in any future 
Apache SIS version.
+      * @hidden
+      */
+     @Override
+     protected long computeHashCode() {
+         return super.computeHashCode() + Objects.hash(datum, ensemble);
+     }
+ 
+ 
+ 
+ 
+     /*
+      
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+      ┃                                                                        
          ┃
+      ┃                               XML support with JAXB                    
          ┃
+      ┃                                                                        
          ┃
+      ┃        The following methods are invoked by JAXB using reflection 
(even if       ┃
+      ┃        they are private) or are helpers for other methods invoked by 
JAXB.       ┃
+      ┃        Those methods can be safely removed if Geographic Markup 
Language         ┃
+      ┃        (GML) support is not needed.                                    
          ┃
+      ┃                                                                        
          ┃
+      
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+      */
+ 
+     /**
+      * Constructs a new object in which every attributes are set to a null 
value.
+      * <strong>This is not a valid object.</strong> This constructor is 
strictly
+      * reserved to JAXB, which will assign values to the fields using 
reflection.
+      */
+     AbstractSingleCRS() {
+         ensemble = null;
+         /*
+          * The coordinate system is mandatory for SIS working. We do not 
verify its presence here
+          * because the verification would have to be done in an 
`afterMarshal(…)` method and throwing
+          * an exception in that method causes the whole unmarshalling to 
fail. But the SC_CRS adapter
+          * does some verifications.
+          */
+     }
+ 
+     /**
+      * Sets the datum to the given value.
+      * This method is indirectly invoked by JAXB at unmarshalling time.
+      *
+      * @param  name  the property name, used only in case of error message to 
format. Can be null for auto-detect.
+      * @throws IllegalStateException if the datum has already been set.
+      */
+     final void setDatum(final String name, final D value) {
+         if (datum == null) {
+             datum = value;
+         } else {
+             ImplementationHelper.propertyAlreadySet(AbstractSingleCRS.class, 
"setDatum", name);
+         }
+     }
+ }
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
index 3c475877ed,fb088144bb..39934220ed
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
@@@ -59,10 -59,12 +59,12 @@@ import org.apache.sis.io.wkt.Formatter
  import org.apache.sis.util.ComparisonMode;
  import org.apache.sis.util.collection.Containers;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.referencing.datum.DatumEnsemble;
 -import org.opengis.referencing.datum.ParametricDatum;
 -import org.opengis.referencing.crs.ParametricCRS;
 -import org.opengis.referencing.cs.ParametricCS;
 -import org.opengis.coordinate.MismatchedDimensionException;
 +// Specific to the main branch:
 +import org.opengis.geometry.MismatchedDimensionException;
 +import org.apache.sis.referencing.cs.DefaultParametricCS;
++import org.apache.sis.referencing.datum.DefaultDatumEnsemble;
 +import org.apache.sis.referencing.datum.DefaultParametricDatum;
++import org.apache.sis.pending.geoapi.referencing.MissingMethods;
  
  
  /**
@@@ -413,6 -418,21 +418,21 @@@ public class DefaultDerivedCRS extends 
          return getBaseCRS().getDatum();
      }
  
+     /**
+      * Returns the datum ensemble of the base <abbr>CRS</abbr>.
+      * This property may be null if this <abbr>CRS</abbr> is related to an 
object
+      * identified only by a {@linkplain #getDatum() reference frame}.
+      *
+      * @return the datum ensemble of the {@linkplain #getBaseCRS() base CRS}, 
or {@code null} if this
+      *         <abbr>CRS</abbr> is related to an object identified only by a 
{@linkplain #getDatum() datum}.
+      *
+      * @since 1.5
+      */
+     @Override
 -    public DatumEnsemble<?> getDatumEnsemble() {
 -        return getBaseCRS().getDatumEnsemble();
++    public DefaultDatumEnsemble<?> getDatumEnsemble() {
++        return MissingMethods.getDatumEnsemble(getBaseCRS());
+     }
+ 
      /**
       * Returns the CRS on which the conversion is applied.
       * This CRS defines the {@linkplain #getDatum() datum} of this CRS and 
(at least implicitly)
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java
index ee0410f016,b4a6f315d1..710e9202a7
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java
@@@ -149,19 -128,16 +128,16 @@@ public class DefaultEngineeringCRS exte
       *                     insignificantly different from each other, or 
{@code null} if there is no such ensemble.
       * @param  cs          the coordinate system.
       *
-      * @see 
org.apache.sis.referencing.factory.GeodeticObjectFactory#createEngineeringCRS(Map,
 EngineeringDatum, CoordinateSystem)
 -     * @see 
org.apache.sis.referencing.factory.GeodeticObjectFactory#createEngineeringCRS(Map,
 EngineeringDatum, DatumEnsemble, CoordinateSystem)
++     * @see 
org.apache.sis.referencing.factory.GeodeticObjectFactory#createEngineeringCRS(Map,
 EngineeringDatum, DefaultDatumEnsemble, CoordinateSystem)
       *
       * @since 1.5
       */
      public DefaultEngineeringCRS(final Map<String,?> properties,
                                   final EngineeringDatum datum,
 -                                 final DatumEnsemble<EngineeringDatum> 
ensemble,
 +                                 final DefaultDatumEnsemble<EngineeringDatum> 
ensemble,
                                   final CoordinateSystem cs)
      {
-         super(properties, cs);
-         this.datum    = datum;
-         this.ensemble = ensemble;
-         checkDatum(datum, ensemble);
+         super(properties, EngineeringDatum.class, datum, ensemble, cs);
      }
  
      /**
@@@ -264,8 -231,8 +235,8 @@@
       * @since 1.5
       */
      @Override
 -    public DatumEnsemble<EngineeringDatum> getDatumEnsemble() {
 +    public DefaultDatumEnsemble<EngineeringDatum> getDatumEnsemble() {
-         return ensemble;
+         return super.getDatumEnsemble();
      }
  
      /**
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java
index bed6a0eb05,b35fdc3d41..18c99916e6
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java
@@@ -132,7 -133,7 +132,7 @@@ public class DefaultGeocentricCRS exten
       *                     insignificantly different from each other, or 
{@code null} if there is no such ensemble.
       * @param  cs          the coordinate system, which must be 
three-dimensional.
       *
-      * @see 
org.apache.sis.referencing.factory.GeodeticObjectFactory#createGeocentricCRS(Map,
 GeodeticDatum, CartesianCS)
 -     * @see 
org.apache.sis.referencing.factory.GeodeticObjectFactory#createGeodeticCRS(Map, 
GeodeticDatum, DatumEnsemble, CartesianCS)
++     * @see 
org.apache.sis.referencing.factory.GeodeticObjectFactory#createGeodeticCRS(Map, 
GeodeticDatum, DefaultDatumEnsemble, CartesianCS)
       *
       * @since 1.5
       */
@@@ -157,7 -158,7 +157,7 @@@
       *                     insignificantly different from each other, or 
{@code null} if there is no such ensemble.
       * @param  cs          the coordinate system.
       *
-      * @see 
org.apache.sis.referencing.factory.GeodeticObjectFactory#createGeocentricCRS(Map,
 GeodeticDatum, SphericalCS)
 -     * @see 
org.apache.sis.referencing.factory.GeodeticObjectFactory#createGeodeticCRS(Map, 
GeodeticDatum, DatumEnsemble, SphericalCS)
++     * @see 
org.apache.sis.referencing.factory.GeodeticObjectFactory#createGeodeticCRS(Map, 
GeodeticDatum, DefaultDatumEnsemble, SphericalCS)
       *
       * @since 1.5
       */
@@@ -286,8 -278,8 +286,8 @@@
       * @since 1.5
       */
      @Override
 -    public DatumEnsemble<GeodeticDatum> getDatumEnsemble() {
 +    public DefaultDatumEnsemble<GeodeticDatum> getDatumEnsemble() {
-         return ensemble;
+         return super.getDatumEnsemble();
      }
  
      /**
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
index b1f79eb8e8,c46575f586..414c01286c
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
@@@ -113,23 -91,18 +92,18 @@@ class DefaultGeodeticCRS extends Abstra
       */
      DefaultGeodeticCRS(final Map<String,?> properties,
                         final GeodeticDatum datum,
 -                       final DatumEnsemble<GeodeticDatum> ensemble,
 +                       final DefaultDatumEnsemble<GeodeticDatum> ensemble,
                         final CoordinateSystem cs)
      {
-         super(properties, cs);
-         this.datum    = datum;
-         this.ensemble = ensemble;
-         checkDatum(datum, ensemble);
+         super(properties, GeodeticDatum.class, datum, ensemble, cs);
      }
  
      /**
       * Creates a new CRS derived from the specified one, but with different 
axis order or unit.
       * This is for implementing the {@link #createSameType(AbstractCS)} 
method only.
       */
 -    DefaultGeodeticCRS(final DefaultGeodeticCRS original, final Identifier 
id, final AbstractCS cs) {
 +    DefaultGeodeticCRS(final DefaultGeodeticCRS original, final 
ReferenceIdentifier id, final AbstractCS cs) {
          super(original, id, cs);
-         datum    = original.datum;
-         ensemble = original.ensemble;
      }
  
      /**
@@@ -185,27 -155,9 +156,17 @@@
      @Override
      @XmlElement(name = "geodeticDatum", required = true)
      public GeodeticDatum getDatum() {
-         return datum;
-     }
- 
-     /**
-      * Returns the datum ensemble.
-      *
-      * @return the datum ensemble, or {@code null} if none.
-      */
-     @Override
-     DefaultDatumEnsemble<GeodeticDatum> getDatumEnsemble() {
-         return ensemble;
+         return super.getDatum();
      }
  
 +    /**
 +     * Initializes the handler for getting datum ensemble of an arbitrary CRS.
 +     */
 +    static {
 +        MissingMethods.geodeticDatumEnsemble = (crs) ->
 +                (crs instanceof DefaultGeodeticCRS) ? ((DefaultGeodeticCRS) 
crs).getDatumEnsemble() : null;
 +    }
 +
      /**
       * Returns a coordinate reference system of the same type as this CRS but 
with different axes.
       * This method shall be overridden by all {@code DefaultGeodeticCRS} 
subclasses in this package.
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeographicCRS.java
index 9dfa6615ef,fd685ca0b9..221722f0b3
--- 
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
@@@ -151,7 -156,7 +156,7 @@@ public class DefaultGeographicCRS exten
       *                     insignificantly different from each other, or 
{@code null} if there is no such ensemble.
       * @param  cs          the two- or three-dimensional coordinate system.
       *
-      * @see 
org.apache.sis.referencing.factory.GeodeticObjectFactory#createGeographicCRS(Map,
 GeodeticDatum, EllipsoidalCS)
 -     * @see 
org.apache.sis.referencing.factory.GeodeticObjectFactory#createGeographicCRS(Map,
 GeodeticDatum, DatumEnsemble, EllipsoidalCS)
++     * @see 
org.apache.sis.referencing.factory.GeodeticObjectFactory#createGeographicCRS(Map,
 GeodeticDatum, DefaultDatumEnsemble, EllipsoidalCS)
       *
       * @since 1.5
       */
@@@ -273,8 -307,8 +311,8 @@@
       * @since 1.5
       */
      @Override
 -    public DatumEnsemble<GeodeticDatum> getDatumEnsemble() {
 +    public DefaultDatumEnsemble<GeodeticDatum> getDatumEnsemble() {
-         return ensemble;
+         return super.getDatumEnsemble();
      }
  
      /**
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultParametricCRS.java
index 7d63756418,adcee10299..f637341b31
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultParametricCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultParametricCRS.java
@@@ -62,7 -62,7 +61,7 @@@ import org.apache.sis.referencing.datum
      "datum"
  })
  @XmlRootElement(name = "ParametricCRS")
- public class DefaultParametricCRS extends AbstractCRS implements 
ParametricCRS {
 -public class DefaultParametricCRS extends AbstractSingleCRS<ParametricDatum> 
implements ParametricCRS {
++public class DefaultParametricCRS extends 
AbstractSingleCRS<DefaultParametricDatum> implements ParametricCRS {
      /**
       * Serial number for inter-operability with different versions.
       */
@@@ -136,14 -113,13 +116,11 @@@
       * @since 1.5
       */
      public DefaultParametricCRS(final Map<String,?> properties,
 -                                final ParametricDatum datum,
 -                                final DatumEnsemble<ParametricDatum> ensemble,
 -                                final ParametricCS cs)
 +                                final DefaultParametricDatum datum,
 +                                final 
DefaultDatumEnsemble<DefaultParametricDatum> ensemble,
 +                                final DefaultParametricCS cs)
      {
-         super(properties, cs);
-         this.datum    = datum;
-         this.ensemble = ensemble;
-         checkDatum(datum, ensemble);
 -        super(properties, ParametricDatum.class, datum, ensemble, cs);
++        super(properties, DefaultParametricDatum.class, datum, ensemble, cs);
          checkDimension(1, 1, cs);
      }
  
@@@ -175,18 -149,45 +150,15 @@@
       *
       * <p>This constructor performs a shallow copy, i.e. the properties are 
not cloned.</p>
       *
 -     * @param  crs  the coordinate reference system to copy.
 +     * <div class="warning"><b>Warning:</b> in a future SIS version, the 
parameter type may be changed
 +     * to {@code org.opengis.referencing.crs.ParametricCRS}. This change is 
pending GeoAPI revision.</div>
       *
 -     * @see #castOrCopy(ParametricCRS)
 +     * @param  crs  the coordinate reference system to copy.
       */
 -    protected DefaultParametricCRS(final ParametricCRS crs) {
 +    protected DefaultParametricCRS(final DefaultParametricCRS crs) {
          super(crs);
-         datum    = crs.getDatum();
-         ensemble = crs.getDatumEnsemble();
-         checkDatum(datum, ensemble);
      }
  
 -    /**
 -     * Returns a SIS coordinate reference system implementation with the same 
values as the given
 -     * arbitrary implementation. If the given object is {@code null}, then 
this method returns {@code null}.
 -     * Otherwise if the given object is already a SIS implementation, then 
the given object is returned unchanged.
 -     * Otherwise a new SIS implementation is created and initialized to the 
attribute values of the given object.
 -     *
 -     * @param  object  the object to get as a SIS implementation, or {@code 
null} if none.
 -     * @return a SIS implementation containing the values of the given object 
(may be the
 -     *         given object itself), or {@code null} if the argument was null.
 -     */
 -    public static DefaultParametricCRS castOrCopy(final ParametricCRS object) 
{
 -        return (object == null || object instanceof DefaultParametricCRS)
 -                ? (DefaultParametricCRS) object : new 
DefaultParametricCRS(object);
 -    }
 -
 -    /**
 -     * Returns the GeoAPI interface implemented by this class.
 -     * The SIS implementation returns {@code ParametricCRS.class}.
 -     *
 -     * <h4>Note for implementers</h4>
 -     * Subclasses usually do not need to override this method since GeoAPI 
does not define {@code ParametricCRS}
 -     * sub-interface. Overriding possibility is left mostly for implementers 
who wish to extend GeoAPI with their
 -     * own set of interfaces.
 -     *
 -     * @return {@code ParametricCRS.class} or a user-defined sub-interface.
 -     */
 -    @Override
 -    public Class<? extends ParametricCRS> getInterface() {
 -        return ParametricCRS.class;
 -    }
 -
      /**
       * Returns the reference surface used as the origin of this 
<abbr>CRS</abbr>.
       * This property may be null if this <abbr>CRS</abbr> is related to an 
object
@@@ -197,8 -198,8 +169,8 @@@
       */
      @Override
      @XmlElement(name = "parametricDatum", required = true)
 -    public ParametricDatum getDatum() {
 +    public DefaultParametricDatum getDatum() {
-         return datum;
+         return super.getDatum();
      }
  
      /**
@@@ -217,8 -214,8 +189,8 @@@
       * @since 1.5
       */
      @Override
 -    public DatumEnsemble<ParametricDatum> getDatumEnsemble() {
 +    public DefaultDatumEnsemble<DefaultParametricDatum> getDatumEnsemble() {
-         return ensemble;
+         return super.getDatumEnsemble();
      }
  
      /**
@@@ -309,12 -305,8 +280,8 @@@
       *
       * @see #getDatum()
       */
 -    private void setDatum(final ParametricDatum value) {
 +    private void setDatum(final DefaultParametricDatum value) {
-         if (datum == null) {
-             datum = value;
-         } else {
-             
ImplementationHelper.propertyAlreadySet(DefaultParametricCRS.class, "setDatum", 
"parametricDatum");
-         }
+         setDatum("parametricDatum", value);
      }
  
      /**
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
index 52f8fee4af,c190cbe749..541069b3bb
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
@@@ -44,8 -44,9 +44,10 @@@ import org.apache.sis.util.Workaround
  import org.opengis.referencing.crs.GeographicCRS;
  import org.opengis.referencing.operation.Projection;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.coordinate.MismatchedDimensionException;
 -import org.opengis.referencing.datum.DatumEnsemble;
 +// Specific to the main branch:
 +import org.opengis.geometry.MismatchedDimensionException;
++import org.apache.sis.referencing.datum.DefaultDatumEnsemble;
++import org.apache.sis.pending.geoapi.referencing.MissingMethods;
  
  
  /**
@@@ -231,6 -235,21 +236,21 @@@ public class DefaultProjectedCRS extend
          return getBaseCRS().getDatum();
      }
  
+     /**
+      * Returns the datum ensemble of the base <abbr>CRS</abbr>.
+      * This property may be null if this <abbr>CRS</abbr> is related to an 
object
+      * identified only by a {@linkplain #getDatum() reference frame}.
+      *
+      * @return the datum ensemble of the {@linkplain #getBaseCRS() base CRS}, 
or {@code null} if this
+      *         <abbr>CRS</abbr> is related to an object identified only by a 
{@linkplain #getDatum() datum}.
+      *
+      * @since 1.5
+      */
+     @Override
 -    public DatumEnsemble<GeodeticDatum> getDatumEnsemble() {
 -        return getBaseCRS().getDatumEnsemble();
++    public DefaultDatumEnsemble<GeodeticDatum> getDatumEnsemble() {
++        return MissingMethods.getDatumEnsemble(getBaseCRS());
+     }
+ 
      /**
       * Returns the geographic CRS on which the map projection is applied.
       * This CRS defines the {@linkplain #getDatum() datum} of this CRS and 
(at least implicitly)
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
index 957e0076a9,4eceaac3c7..05df774103
--- 
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
@@@ -169,20 -150,17 +150,17 @@@ public class DefaultTemporalCRS extend
       *                     insignificantly different from each other, or 
{@code null} if there is no such ensemble.
       * @param  cs          the coordinate system.
       *
-      * @see 
org.apache.sis.referencing.factory.GeodeticObjectFactory#createTemporalCRS(Map, 
TemporalDatum, TimeCS)
 -     * @see 
org.apache.sis.referencing.factory.GeodeticObjectFactory#createTemporalCRS(Map, 
TemporalDatum, DatumEnsemble, TimeCS)
++     * @see 
org.apache.sis.referencing.factory.GeodeticObjectFactory#createTemporalCRS(Map, 
TemporalDatum, DefaultDatumEnsemble, TimeCS)
       *
       * @since 1.5
       */
      @SuppressWarnings("this-escape")
      public DefaultTemporalCRS(final Map<String,?> properties,
                                final TemporalDatum datum,
 -                              final DatumEnsemble<TemporalDatum> ensemble,
 +                              final DefaultDatumEnsemble<TemporalDatum> 
ensemble,
                                final TimeCS cs)
      {
-         super(properties, cs);
-         this.datum    = datum;
-         this.ensemble = ensemble;
-         checkDatum(datum, ensemble);
+         super(properties, TemporalDatum.class, datum, ensemble, cs);
          checkDimension(1, 1, cs);
          initializeConverter();
      }
@@@ -321,8 -290,8 +294,8 @@@
       * @since 1.5
       */
      @Override
 -    public DatumEnsemble<TemporalDatum> getDatumEnsemble() {
 +    public DefaultDatumEnsemble<TemporalDatum> getDatumEnsemble() {
-         return ensemble;
+         return super.getDatumEnsemble();
      }
  
      /**
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultVerticalCRS.java
index 2fc9133ede,41ef613856..3eff4ea989
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultVerticalCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultVerticalCRS.java
@@@ -27,11 -27,10 +27,10 @@@ import org.apache.sis.referencing.Abstr
  import org.apache.sis.referencing.cs.AxesConvention;
  import org.apache.sis.referencing.cs.AbstractCS;
  import org.apache.sis.referencing.privy.WKTKeywords;
- import org.apache.sis.metadata.privy.ImplementationHelper;
  import org.apache.sis.io.wkt.Formatter;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.referencing.datum.DatumEnsemble;
 +// Specific to the main branch:
 +import org.apache.sis.referencing.datum.DefaultDatumEnsemble;
  
  
  /**
@@@ -132,19 -111,16 +111,16 @@@ public class DefaultVerticalCRS extend
       *                     insignificantly different from each other, or 
{@code null} if there is no such ensemble.
       * @param  cs          the coordinate system.
       *
-      * @see 
org.apache.sis.referencing.factory.GeodeticObjectFactory#createVerticalCRS(Map, 
VerticalDatum, VerticalCS)
 -     * @see 
org.apache.sis.referencing.factory.GeodeticObjectFactory#createVerticalCRS(Map, 
VerticalDatum, DatumEnsemble, VerticalCS)
++     * @see 
org.apache.sis.referencing.factory.GeodeticObjectFactory#createVerticalCRS(Map, 
VerticalDatum, DefaultDatumEnsemble, VerticalCS)
       *
       * @since 1.5
       */
      public DefaultVerticalCRS(final Map<String,?> properties,
                                final VerticalDatum datum,
 -                              final DatumEnsemble<VerticalDatum> ensemble,
 +                              final DefaultDatumEnsemble<VerticalDatum> 
ensemble,
                                final VerticalCS cs)
      {
-         super(properties, cs);
-         this.datum    = datum;
-         this.ensemble = ensemble;
-         checkDatum(datum, ensemble);
+         super(properties, VerticalDatum.class, datum, ensemble, cs);
          checkDimension(1, 1, cs);
      }
  
@@@ -248,8 -215,8 +219,8 @@@
       * @since 1.5
       */
      @Override
 -    public DatumEnsemble<VerticalDatum> getDatumEnsemble() {
 +    public DefaultDatumEnsemble<VerticalDatum> getDatumEnsemble() {
-         return ensemble;
+         return super.getDatumEnsemble();
      }
  
      /**
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
index fbade17b6d,0b03e1ba43..b3fe37f2e1
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
@@@ -111,14 -113,61 +111,19 @@@ public class DefaultDatumEnsemble<D ext
          validate();
      }
  
 -    /**
 -     * Creates a new ensemble with the same values as the specified one.
 -     * This copy constructor provides a way to convert an arbitrary 
implementation into a SIS one
 -     * or a user-defined one (as a subclass), usually in order to leverage 
some implementation-specific API.
 -     *
 -     * <p>This constructor performs a shallow copy, i.e. the properties are 
not cloned.</p>
 -     *
 -     * @param  ensemble  the ensemble to copy.
 -     */
 -    protected DefaultDatumEnsemble(final DatumEnsemble<? extends D> ensemble) 
{
 -        super(ensemble);
 -        members = List.copyOf(ensemble.getMembers());
 -        ensembleAccuracy = 
Objects.requireNonNull(ensemble.getEnsembleAccuracy());
 -        validate();
 -    }
 -
 -    /**
 -     * Returns a SIS ensemble implementation with the values of the given 
arbitrary implementation.
 -     * This method performs the first applicable action in the following 
choices:
 -     *
 -     * <ul>
 -     *   <li>If the given object is {@code null}, then this method returns 
{@code null}.</li>
 -     *   <li>Otherwise if the given object is already an instance of
 -     *       {@code DefaultDatumEnsemble}, then it is returned unchanged.</li>
 -     *   <li>Otherwise a new {@code DefaultDatumEnsemble} instance is created 
using the
 -     *       {@linkplain #DefaultDatumEnsemble(DatumEnsemble) copy 
constructor} and returned.
 -     *       Note that this is a <em>shallow</em> copy operation,
 -     *       because the other properties contained in the given object are 
not recursively copied.</li>
 -     * </ul>
 -     *
 -     * @param  <D>     the type of datum contained in the ensemble.
 -     * @param  object  the object to get as a SIS implementation, or {@code 
null} if none.
 -     * @return a SIS implementation containing the values of the given object 
(may be the
 -     *         given object itself), or {@code null} if the argument was null.
 -     */
 -    public static <D extends Datum> DefaultDatumEnsemble<D> castOrCopy(final 
DatumEnsemble<D> object) {
 -        if (object == null || object instanceof DefaultDatumEnsemble<?>) {
 -            return (DefaultDatumEnsemble<D>) object;
 -        } else {
 -            return new DefaultDatumEnsemble<>(object);
 -        }
 -    }
 -
      /**
       * Verifies this ensemble. All members shall have the same conventional 
reference system.
+      * No member can be an instance of {@link PseudoDatum}.
       */
      private void validate() {
          IdentifiedObject rs = null;
          for (final D datum : members) {
+             if (datum instanceof PseudoDatum<?>) {
+                 throw new IllegalArgumentException(
+                         
Errors.format(Errors.Keys.IllegalPropertyValueClass_2, "members", 
PseudoDatum.class));
+             }
 -            final IdentifiedObject dr = 
datum.getConventionalRS().orElse(null);
 +            if (!(datum instanceof AbstractDatum)) continue;
 +            final IdentifiedObject dr = ((AbstractDatum) 
datum).getConventionalRS().orElse(null);
              if (dr != null) {
                  if (rs == null) {
                      rs = dr;
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/PseudoDatum.java
index 0000000000,4ee7b6837c..d5ef3f68e1
mode 000000,100644..100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/PseudoDatum.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/PseudoDatum.java
@@@ -1,0 -1,601 +1,560 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one or more
+  * contributor license agreements.  See the NOTICE file distributed with
+  * this work for additional information regarding copyright ownership.
+  * The ASF licenses this file to You under the Apache License, Version 2.0
+  * (the "License"); you may not use this file except in compliance with
+  * the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS,
+  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  * See the License for the specific language governing permissions and
+  * limitations under the License.
+  */
+ package org.apache.sis.referencing.datum;
+ 
+ import java.util.Set;
+ import java.util.Collection;
+ import java.util.Iterator;
+ import java.util.Objects;
+ import java.util.Optional;
+ import java.util.NoSuchElementException;
+ import java.util.function.Function;
 -import java.time.temporal.Temporal;
+ import java.io.Serializable;
+ import org.opengis.util.GenericName;
+ import org.opengis.util.InternationalString;
+ import org.opengis.referencing.IdentifiedObject;
 -import org.opengis.referencing.ObjectDomain;
+ import org.opengis.referencing.datum.*;
+ import org.opengis.referencing.crs.*;
+ import org.apache.sis.util.Utilities;
+ import org.apache.sis.util.ComparisonMode;
+ import org.apache.sis.util.LenientComparable;
+ import org.apache.sis.util.resources.Errors;
+ import org.apache.sis.referencing.IdentifiedObjects;
+ import org.apache.sis.referencing.GeodeticException;
+ 
++// Specific to the main branch:
++import static 
org.apache.sis.pending.geoapi.referencing.MissingMethods.getDatumEnsemble;
++
+ // Specific to the main and geoapi-3.1 branches:
+ import java.util.Date;
++import org.opengis.metadata.extent.Extent;
+ import org.opengis.referencing.ReferenceIdentifier;
+ 
+ 
+ /**
+  * A datum ensemble viewed as if it was a single datum for the sake of 
simplicity.
+  * This pseudo-datum is a non-standard mechanism used by the Apache 
<abbr>SIS</abbr> implementation
+  * for handling datum and datum ensemble in a uniform way. For example, 
{@code PseudoDatum.of(crs)}
+  * allows to {@linkplain IdentifiedObjects#isHeuristicMatchForName compare 
the datum name} without
+  * the need to check which one of the {@code getDatum()} or {@code 
getDatumEnsemble()} methods
+  * returns a non-null value.
+  *
+  * <p>{@code PseudoDatum} instances should live only for a short time.
+  * They should not be stored as {@link SingleCRS} properties.
+  * If a {@code PseudoDatum} instances is given to the constructor of an 
Apache <abbr>SIS</abbr> class,
+  * the constructor will automatically unwraps the {@linkplain #ensemble}.</p>
+  *
+  * <h2>Default method implementations</h2>
+  * Unless otherwise specified in the Javadoc, all methods in this class 
delegate
+  * to the same method in the wrapper datum {@linkplain #ensemble}.
+  *
+  * <h2>Object comparisons</h2>
+  * The {@link #equals(Object)} method returns {@code true} only if the two 
compared objects are instances
+  * of the same class, which implies that the {@code Object} argument must be 
a {@code PseudoDatum}.
+  * The {@link #equals(Object, ComparisonMode)} method with a non-strict 
comparison mode compares
+  * the wrapped datum ensemble, which implies that:
+  *
+  * <ul>
+  *   <li>A pseudo-datum is never equal to a real datum, regardless the names 
and identifiers of the compared objects.</li>
+  *   <li>A pseudo-datum can be equal to another pseudo-datum or to a datum 
ensemble.</li>
+  * </ul>
+  *
+  * @author  Martin Desruisseaux (Geomatys)
+  * @version 1.5
+  *
+  * @param <D> the type of datum contained in this ensemble.
+  *
+  * @since 1.5
+  */
+ public abstract class PseudoDatum<D extends Datum> implements Datum, 
LenientComparable, Serializable {
+     /**
+      * For cross-versions compatibility.
+      */
+     private static final long serialVersionUID = 3889895625961827486L;
+ 
+     /**
+      * The datum ensemble wrapped by this pseudo-datum.
+      */
+     @SuppressWarnings("serial")     // Most SIS implementations are 
serializable.
 -    public final DatumEnsemble<D> ensemble;
++    public final DefaultDatumEnsemble<D> ensemble;
+ 
+     /**
+      * Creates a new pseudo-datum.
+      *
+      * @param ensemble the datum ensemble wrapped by this pseudo-datum.
+      */
 -    protected PseudoDatum(final DatumEnsemble<D> ensemble) {
++    protected PseudoDatum(final DefaultDatumEnsemble<D> ensemble) {
+         this.ensemble = Objects.requireNonNull(ensemble);
+     }
+ 
+     /**
+      * Returns the datum of the given <abbr>CRS</abbr> if presents, or the 
datum ensemble otherwise.
+      * This is an alternative to the {@code of(…)} methods when the caller 
does not need to view the
+      * object as a datum.
+      *
+      * @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, or 
{@code null}.
+      */
+     public static IdentifiedObject getDatumOrEnsemble(final SingleCRS crs) {
+         if (crs == null) return null;
+         final Datum datum = crs.getDatum();
+         if (datum != null) {
+             if (datum instanceof PseudoDatum<?>) {
+                 return ((PseudoDatum) datum).ensemble;
+             }
+             return datum;
+         }
 -        return crs.getDatumEnsemble();
++        return getDatumEnsemble(crs);
+     }
+ 
+     /**
+      * Returns the datum or pseudo-datum of the given geodetic 
<abbr>CRS</abbr>.
+      * If the given <abbr>CRS</abbr> is associated to a non-null datum, then 
this method returns that datum.
+      * Otherwise, this method returns the <abbr>CRS</abbr> datum ensemble 
wrapped in a pseudo-datum.
+      * In the latter case, the pseudo-datum implementations of the {@link 
GeodeticDatum#getEllipsoid()}
+      * and {@link GeodeticDatum#getPrimeMeridian()} methods expect an 
ellipsoid or prime meridian which
+      * is the same for all {@linkplain #ensemble} members.
+      * If this condition does not hold, a {@link GeodeticException} will be 
thrown.
+      *
+      * @param  crs  the coordinate reference system for which to get the 
datum or datum ensemble.
+      * @return the datum or pseudo-datum of the given <abbr>CRS</abbr>.
+      * @throws NullPointerException if the given argument is {@code null},
+      *         or if both the datum and datum ensemble are null.
+      */
+     public static GeodeticDatum of(final GeodeticCRS crs) {
+         GeodeticDatum datum = crs.getDatum();
+         if (datum == null) {
 -            datum = new PseudoDatum.Geodetic(crs.getDatumEnsemble());
++            datum = new PseudoDatum.Geodetic(getDatumEnsemble(crs));
+         }
+         return datum;
+     }
+ 
+     /**
+      * Returns the datum or pseudo-datum of the given vertical 
<abbr>CRS</abbr>.
+      * If the given <abbr>CRS</abbr> is associated to a non-null datum, then 
this method returns that datum.
+      * Otherwise, this method returns the <abbr>CRS</abbr> datum ensemble 
wrapped in a pseudo-datum.
+      *
+      * @param  crs  the coordinate reference system for which to get the 
datum or datum ensemble.
+      * @return the datum or pseudo-datum of the given <abbr>CRS</abbr>.
+      * @throws NullPointerException if the given argument is {@code null},
+      *         or if both the datum and datum ensemble are null.
+      */
+     public static VerticalDatum of(final VerticalCRS crs) {
+         VerticalDatum datum = crs.getDatum();
+         if (datum == null) {
 -            datum = new PseudoDatum.Vertical(crs.getDatumEnsemble());
++            datum = new PseudoDatum.Vertical(getDatumEnsemble(crs));
+         }
+         return datum;
+     }
+ 
+     /**
+      * Returns the datum or pseudo-datum of the given temporal 
<abbr>CRS</abbr>.
+      * If the given <abbr>CRS</abbr> is associated to a non-null datum, then 
this method returns that datum.
+      * Otherwise, this method returns the <abbr>CRS</abbr> datum ensemble 
wrapped in a pseudo-datum.
+      * In the latter case, the pseudo-datum implementations of the {@link 
TemporalDatum#getOrigin()}
+      * expects a temporal origin which is the same for all {@linkplain 
#ensemble} members.
+      * If this condition does not hold, a {@link GeodeticException} will be 
thrown.
+      *
+      * @param  crs  the coordinate reference system for which to get the 
datum or datum ensemble.
+      * @return the datum or pseudo-datum of the given <abbr>CRS</abbr>.
+      * @throws NullPointerException if the given argument is {@code null},
+      *         or if both the datum and datum ensemble are null.
+      */
+     public static TemporalDatum of(final TemporalCRS crs) {
+         TemporalDatum datum = crs.getDatum();
+         if (datum == null) {
 -            datum = new PseudoDatum.Time(crs.getDatumEnsemble());
 -        }
 -        return datum;
 -    }
 -
 -    /**
 -     * Returns the datum or pseudo-datum of the given parametric 
<abbr>CRS</abbr>.
 -     * If the given <abbr>CRS</abbr> is associated to a non-null datum, then 
this method returns that datum.
 -     * Otherwise, this method returns the <abbr>CRS</abbr> datum ensemble 
wrapped in a pseudo-datum.
 -     *
 -     * @param  crs  the coordinate reference system for which to get the 
datum or datum ensemble.
 -     * @return the datum or pseudo-datum of the given <abbr>CRS</abbr>.
 -     * @throws NullPointerException if the given argument is {@code null},
 -     *         or if both the datum and datum ensemble are null.
 -     */
 -    public static ParametricDatum of(final ParametricCRS crs) {
 -        ParametricDatum datum = crs.getDatum();
 -        if (datum == null) {
 -            datum = new PseudoDatum.Parametric(crs.getDatumEnsemble());
++            datum = new PseudoDatum.Time(getDatumEnsemble(crs));
+         }
+         return datum;
+     }
+ 
+     /**
+      * Returns the datum or pseudo-datum of the given engineering 
<abbr>CRS</abbr>.
+      * If the given <abbr>CRS</abbr> is associated to a non-null datum, then 
this method returns that datum.
+      * Otherwise, this method returns the <abbr>CRS</abbr> datum ensemble 
wrapped in a pseudo-datum.
+      *
+      * @param  crs  the coordinate reference system for which to get the 
datum or datum ensemble.
+      * @return the datum or pseudo-datum of the given <abbr>CRS</abbr>.
+      * @throws NullPointerException if the given argument is {@code null},
+      *         or if both the datum and datum ensemble are null.
+      */
+     public static EngineeringDatum of(final EngineeringCRS crs) {
+         EngineeringDatum datum = crs.getDatum();
+         if (datum == null) {
 -            datum = new PseudoDatum.Engineering(crs.getDatumEnsemble());
++            datum = new PseudoDatum.Engineering(getDatumEnsemble(crs));
+         }
+         return datum;
+     }
+ 
+     /**
+      * Returns the GeoAPI interface of the ensemble members.
+      * It should also be the interface implemented by this class.
+      *
+      * @return the GeoAPI interface of the ensemble members.
+      */
+     public abstract Class<D> getInterface();
+ 
+     /**
+      * Returns the primary name by which the datum ensemble is identified.
+      *
+      * @return {@code ensemble.getName()}.
+      * @hidden
+      */
+     @Override
+     public ReferenceIdentifier getName() {
+         return ensemble.getName();
+     }
+ 
+     /**
+      * Returns alternative names by which the datum ensemble is identified.
+      *
+      * @return {@code ensemble.getAlias()}.
+      * @hidden
+      */
+     @Override
+     public Collection<GenericName> getAlias() {
+         return ensemble.getAlias();
+     }
+ 
+     /**
+      * Returns an identifier which references elsewhere the datum ensemble 
information.
+      *
+      * @return {@code ensemble.getIdentifiers()}.
+      * @hidden
+      */
+     @Override
+     public Set<ReferenceIdentifier> getIdentifiers() {
+         return ensemble.getIdentifiers();
+     }
+ 
+     /**
 -     * Returns the usage of the datum ensemble.
++     * Returns the anchor point common to all datum members, if any.
+      *
 -     * @return {@code ensemble.getDomains()}.
 -     * @hidden
++     * @return value common to all ensemble members, or {@code null} if none.
+      */
+     @Override
 -    public Collection<ObjectDomain> getDomains() {
 -        return ensemble.getDomains();
++    public InternationalString getAnchorPoint() {
++        return getCommonNullableValue(Datum::getAnchorPoint);
+     }
+ 
+     /**
 -     * Returns an anchor definition which is common to all members of the 
datum ensemble.
 -     * If the value is not the same for all members (including the case where 
a member
 -     * has an empty value), then this method returns an empty value.
++     * Returns the realization epoch common to all datum members, if any.
+      *
 -     * @return the common anchor definition, or empty if there is no common 
value.
++     * @return value common to all ensemble members, or {@code null} if none.
+      */
+     @Override
 -    public Optional<InternationalString> getAnchorDefinition() {
 -        return getCommonOptionalValue(Datum::getAnchorDefinition);
++    public Date getRealizationEpoch() {
++        return getCommonNullableValue(Datum::getRealizationEpoch);
+     }
+ 
+     /**
 -     * Returns an anchor epoch which is common to all members of the datum 
ensemble.
 -     * If the value is not the same for all members (including the case where 
a member
 -     * has an empty value), then this method returns an empty value.
++     * Returns the domain of validity common to all datum members, if any.
+      *
 -     * @return the common anchor epoch, or empty if there is no common value.
++     * @return value common to all ensemble members, or {@code null} if none.
+      */
+     @Override
 -    public Optional<Temporal> getAnchorEpoch() {
 -        return getCommonOptionalValue(Datum::getAnchorEpoch);
++    public Extent getDomainOfValidity() {
++        return getCommonNullableValue(Datum::getDomainOfValidity);
+     }
+ 
+     /**
 -     * Returns a publication date which is common to all members of the datum 
ensemble.
 -     * If the value is not the same for all members (including the case where 
a member
 -     * has an empty value), then this method returns an empty value.
++     * Returns the scope common to all datum members, if any.
+      *
 -     * @return the common publication date, or empty if there is no common 
value.
++     * @return value common to all ensemble members, or {@code null} if none.
+      */
+     @Override
 -    public Optional<Temporal> getPublicationDate() {
 -        return getCommonOptionalValue(Datum::getPublicationDate);
++    public InternationalString getScope() {
++        return getCommonNullableValue(Datum::getScope);
+     }
+ 
+     /**
 -     * Returns a conventional reference system which is common to all members 
of the datum ensemble.
 -     * The returned value should never be empty, because it is illegal for a 
datum ensemble to have
 -     * members with different conventional reference system. If this case 
nevertheless happens,
 -     * this method returns an empty value.
++     * Returns an optional value which is common to all ensemble members.
++     * If all members do not have the same value, returns {@code null}.
+      *
 -     * @return the common conventional reference system, or empty if there is 
no common value.
++     * @param  <V>     type of value.
++     * @param  getter  method to invoke on each member for getting the value.
++     * @return a value common to all members, or {@code null} if none.
+      */
 -    @Override
 -    public Optional<IdentifiedObject> getConventionalRS() {
 -        return getCommonOptionalValue(Datum::getConventionalRS);
++    final <V> V getCommonNullableValue(final Function<D, V> getter) {
++        final Iterator<D> it = ensemble.getMembers().iterator();
++check:  if (it.hasNext()) {
++            final V value = getter.apply(it.next());
++            if (value != null) {
++                while (it.hasNext()) {
++                    if (!value.equals(getter.apply(it.next()))) {
++                        break check;
++                    }
++                }
++                return value;
++            }
++        }
++        return null;
+     }
+ 
+     /**
+      * Returns an optional value which is common to all ensemble members.
+      * If all members do not have the same value, returns an empty value.
+      *
+      * @param  <V>     type of value.
+      * @param  getter  method to invoke on each member for getting the value.
+      * @return a value common to all members, or {@code null} if none.
+      */
+     final <V> Optional<V> getCommonOptionalValue(final Function<D, 
Optional<V>> getter) {
+         final Iterator<D> it = ensemble.getMembers().iterator();
+ check:  if (it.hasNext()) {
+             final Optional<V> value = getter.apply(it.next());
+             if (value.isPresent()) {
+                 while (it.hasNext()) {
+                     if (!value.equals(getter.apply(it.next()))) {
+                         break check;
+                     }
+                 }
+                 return value;
+             }
+         }
+         return Optional.empty();
+     }
+ 
+     /**
+      * Returns a mandatory value which is common to all ensemble members.
+      *
+      * @param  <V>     type of value.
+      * @param  getter  method to invoke on each member for getting the value.
+      * @return a value common to all members.
+      * @throws NoSuchElementException if the ensemble does not contain at 
least one member.
+      * @throws GeodeticException if the value is not the same for all members 
of the datum ensemble.
+      */
+     final <V> V getCommonMandatoryValue(final Function<D, V> getter) {
+         final Iterator<D> it = ensemble.getMembers().iterator();
+         final V value = getter.apply(it.next());   // Mandatory.
+         if (it.hasNext()) {
+             final V other = getter.apply(it.next());
+             if (!Objects.equals(value, other)) {
+                 throw new 
GeodeticException(Errors.format(Errors.Keys.NonUniformValue_2,
+                         (value instanceof IdentifiedObject) ? 
IdentifiedObjects.getDisplayName((IdentifiedObject) value) : value,
+                         (other instanceof IdentifiedObject) ? 
IdentifiedObjects.getDisplayName((IdentifiedObject) other) : other));
+             }
+         }
+         return value;
+     }
+ 
+     /**
+      * Returns comments on or information about the datum ensemble.
+      *
+      * @return {@code ensemble.getRemarks()}.
+      * @hidden
+      */
+     @Override
+     public InternationalString getRemarks() {
+         return ensemble.getRemarks();
+     }
+ 
+     /**
+      * Formats a <i>Well-Known Text</i> (WKT) for the datum ensemble.
+      *
+      * @return {@code ensemble.toWKT()}.
+      * @hidden
+      */
+     @Override
+     public String toWKT() {
+         return ensemble.toWKT();
+     }
+ 
+     /**
+      * Returns a string representation of the datum ensemble.
+      *
+      * @return {@code ensemble.toString()}.
+      * @hidden
+      */
+     @Override
+     public String toString() {
+         return ensemble.toString();
+     }
+ 
+     /**
+      * Returns a hash-code value of this pseudo-datum.
+      *
+      * @return a hash-code value of this pseudo-datum.
+      */
+     @Override
+     public int hashCode() {
+         return ensemble.hashCode() ^ getClass().hashCode();
+     }
+ 
+     /**
+      * Compares this pseudo-datum to the given object for equality.
+      * The two objects are equal if they are of the same classes and
+      * the wrapped {@link #ensemble} are equal.
+      *
+      * @param  other  the object to compare with this pseudo-datum.
+      * @return whether the two objects are equal.
+      */
+     @Override
+     public boolean equals(final Object other) {
+         return (other != null) && other.getClass() == getClass() && 
ensemble.equals(((PseudoDatum<?>) other).ensemble);
+     }
+ 
+     /**
+      * Compares this object with the given object for equality.
+      * If the comparison mode is strict, then this method delegates to {@link 
#equals(Object)}.
+      * Otherwise, this method unwrap the ensembles, then compare the 
ensembles.
+      *
+      * @param  other  the object to compare to {@code this}.
+      * @param  mode   the strictness level of the comparison.
+      * @return {@code true} if both objects are equal according the given 
comparison mode.
+      */
+     @Override
+     public boolean equals(Object other, final ComparisonMode mode) {
+         if (mode == ComparisonMode.STRICT) {
+             return equals(other);
+         }
+         if (other instanceof PseudoDatum<?>) {
+             other = ((PseudoDatum<?>) other).ensemble;
+         }
+         return Utilities.deepEquals(ensemble, other, mode);
+     }
+ 
+     /**
+      * A pseudo-datum for an ensemble of geodetic datum.
+      */
+     private static final class Geodetic extends PseudoDatum<GeodeticDatum> 
implements GeodeticDatum {
+         /** For cross-versions compatibility. */
+         private static final long serialVersionUID = 7669230365507661290L;
+ 
+         /** Creates a new pseudo-datum wrapping the given ensemble. */
 -        Geodetic(final DatumEnsemble<GeodeticDatum> ensemble) {
++        Geodetic(final DefaultDatumEnsemble<GeodeticDatum> ensemble) {
+             super(ensemble);
+         }
+ 
+         /**
+          * Returns the GeoAPI interface implemented by this pseudo-datum.
+          */
+         @Override
+         public Class<GeodeticDatum> getInterface() {
+             return GeodeticDatum.class;
+         }
+ 
+         /**
+          * Returns the ellipsoid which is indirectly (through a datum) 
associated to this datum ensemble.
+          * If all members of the ensemble use the same ellipsoid, then this 
method returns that ellipsoid.
+          *
+          * @return the ellipsoid indirectly associated to this datum ensemble.
+          * @throws NoSuchElementException if the ensemble does not contain at 
least one member.
+          * @throws GeodeticException if the ellipsoid is not the same for all 
members of the datum ensemble.
+          */
+         @Override
+         public Ellipsoid getEllipsoid() {
+             return getCommonMandatoryValue(GeodeticDatum::getEllipsoid);
+         }
+ 
+         /**
+          * Returns the prime meridian which is indirectly (through a datum) 
associated to this datum ensemble.
+          * If all members of the ensemble use the same prime meridian, then 
this method returns that meridian.
+          *
+          * @return the prime meridian indirectly associated to this datum 
ensemble.
+          * @throws NoSuchElementException if the ensemble does not contain at 
least one member.
+          * @throws GeodeticException if the prime meridian is not the same 
for all members of the datum ensemble.
+          */
+         @Override
+         public PrimeMeridian getPrimeMeridian() {
+             return getCommonMandatoryValue(GeodeticDatum::getPrimeMeridian);
+         }
+     }
+ 
+     /**
+      * A pseudo-datum for an ensemble of vertical datum.
+      */
+     private static final class Vertical extends PseudoDatum<VerticalDatum> 
implements VerticalDatum {
+         /** For cross-versions compatibility. */
+         private static final long serialVersionUID = 7242417944400289818L;
+ 
+         /** Creates a new pseudo-datum wrapping the given ensemble. */
 -        Vertical(final DatumEnsemble<VerticalDatum> ensemble) {
++        Vertical(final DefaultDatumEnsemble<VerticalDatum> ensemble) {
+             super(ensemble);
+         }
+ 
+         /**
+          * Returns the GeoAPI interface implemented by this pseudo-datum.
+          */
+         @Override
+         public Class<VerticalDatum> getInterface() {
+             return VerticalDatum.class;
+         }
+ 
+         /**
 -         * Returns a realization method which is common to all members of the 
datum ensemble.
 -         * If the value is not the same for all members (including the case 
where a member
 -         * has an empty value), then this method returns an empty value.
++         * Returns a datum type which is common to all members of the datum 
ensemble.
+          *
 -         * @return the common realization method, or empty if there is no 
common value.
 -         */
 -        @Override
 -        public Optional<RealizationMethod> getRealizationMethod() {
 -            return 
getCommonOptionalValue(VerticalDatum::getRealizationMethod);
 -        }
 -
 -        /**
 -         * @deprecated Replaced by {@link #getRealizationMethod()}.
++         * @return the common datum type.
+          */
+         @Override
 -        @Deprecated
+         public VerticalDatumType getVerticalDatumType() {
+             return 
getCommonMandatoryValue(VerticalDatum::getVerticalDatumType);
+         }
+     }
+ 
+     /**
+      * A pseudo-datum for an ensemble of temporal datum.
+      */
+     private static final class Time extends PseudoDatum<TemporalDatum> 
implements TemporalDatum {
+         /** For cross-versions compatibility. */
+         private static final long serialVersionUID = -4208563828181087035L;
+ 
+         /** Creates a new pseudo-datum wrapping the given ensemble. */
 -        Time(final DatumEnsemble<TemporalDatum> ensemble) {
++        Time(final DefaultDatumEnsemble<TemporalDatum> ensemble) {
+             super(ensemble);
+         }
+ 
+         /**
+          * Returns the GeoAPI interface implemented by this pseudo-datum.
+          */
+         @Override
+         public Class<TemporalDatum> getInterface() {
+             return TemporalDatum.class;
+         }
+ 
+         /**
+          * Returns the temporal origin which is indirectly (through a datum) 
associated to this datum ensemble.
+          * If all members of the ensemble use the same temporal origin, then 
this method returns that origin.
+          *
+          * @return the temporal origin indirectly associated to this datum 
ensemble.
+          * @throws NoSuchElementException if the ensemble does not contain at 
least one member.
+          * @throws GeodeticException if the temporal origin is not the same 
for all members of the datum ensemble.
+          */
+         @Override
+         public Date getOrigin() {
+             return getCommonMandatoryValue(TemporalDatum::getOrigin);
+         }
+     }
+ 
 -    /**
 -     * A pseudo-datum for an ensemble of parametric datum.
 -     */
 -    private static final class Parametric extends 
PseudoDatum<ParametricDatum> implements ParametricDatum {
 -        /** For cross-versions compatibility. */
 -        private static final long serialVersionUID = -8277774591738789437L;
 -
 -        /** Creates a new pseudo-datum wrapping the given ensemble. */
 -        Parametric(final DatumEnsemble<ParametricDatum> ensemble) {
 -            super(ensemble);
 -        }
 -
 -        /** Returns the GeoAPI interface implemented by this pseudo-datum. */
 -        @Override public Class<ParametricDatum> getInterface() {
 -            return ParametricDatum.class;
 -        }
 -    }
 -
+     /**
+      * A pseudo-datum for an ensemble of engineering datum.
+      */
+     private static final class Engineering extends 
PseudoDatum<EngineeringDatum> implements EngineeringDatum {
+         /** For cross-versions compatibility. */
+         private static final long serialVersionUID = -8978468990963666861L;
+ 
+         /** Creates a new pseudo-datum wrapping the given ensemble. */
 -        Engineering(final DatumEnsemble<EngineeringDatum> ensemble) {
++        Engineering(final DefaultDatumEnsemble<EngineeringDatum> ensemble) {
+             super(ensemble);
+         }
+ 
+         /** Returns the GeoAPI interface implemented by this pseudo-datum. */
+         @Override public Class<EngineeringDatum> getInterface() {
+             return EngineeringDatum.class;
+         }
+     }
+ }
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
index 9e88d79f05,c1d241e855..ae296a5de6
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
@@@ -353,8 -349,8 +353,8 @@@ public class GeodeticObjectFactory exte
       * @param  cs          the three-dimensional Cartesian coordinate system 
for the created CRS.
       * @throws FactoryException if the object creation failed.
       *
++     * @see DefaultGeocentricCRS#DefaultGeocentricCRS(Map, GeodeticDatum, 
DefaultDatumEnsemble, CartesianCS)
       * @see GeodeticAuthorityFactory#createGeodeticCRS(String)
-      * @see DefaultGeocentricCRS#DefaultGeocentricCRS(Map, GeodeticDatum, 
CartesianCS)
 -     * @see DefaultGeocentricCRS#DefaultGeocentricCRS(Map, GeodeticDatum, 
DatumEnsemble, CartesianCS)
       *
       * @since 1.5
       */
@@@ -468,8 -462,8 +468,8 @@@
       * @param  cs          the spherical coordinate system for the created 
CRS.
       * @throws FactoryException if the object creation failed.
       *
-      * @see DefaultGeocentricCRS#DefaultGeocentricCRS(Map, GeodeticDatum, 
SphericalCS)
-      * @see GeodeticAuthorityFactory#createGeocentricCRS(String)
 -     * @see DefaultGeocentricCRS#DefaultGeocentricCRS(Map, GeodeticDatum, 
DatumEnsemble, SphericalCS)
++     * @see DefaultGeocentricCRS#DefaultGeocentricCRS(Map, GeodeticDatum, 
DefaultDatumEnsemble, SphericalCS)
+      * @see GeodeticAuthorityFactory#createGeodeticCRS(String)
       *
       * @since 1.5
       */
@@@ -616,7 -612,7 +616,7 @@@
       * @param  cs          the two- or three-dimensional ellipsoidal 
coordinate system for the created CRS.
       * @throws FactoryException if the object creation failed.
       *
-      * @see DefaultGeographicCRS#DefaultGeographicCRS(Map, GeodeticDatum, 
EllipsoidalCS)
 -     * @see DefaultGeographicCRS#DefaultGeographicCRS(Map, GeodeticDatum, 
DatumEnsemble, EllipsoidalCS)
++     * @see DefaultGeographicCRS#DefaultGeographicCRS(Map, GeodeticDatum, 
DefaultDatumEnsemble, EllipsoidalCS)
       * @see GeodeticAuthorityFactory#createGeographicCRS(String)
       *
       * @since 1.5
@@@ -1047,7 -1026,7 +1047,7 @@@
       * @param  cs          the vertical coordinate system for the created CRS.
       * @throws FactoryException if the object creation failed.
       *
-      * @see DefaultVerticalCRS#DefaultVerticalCRS(Map, VerticalDatum, 
VerticalCS)
 -     * @see DefaultVerticalCRS#DefaultVerticalCRS(Map, VerticalDatum, 
DatumEnsemble, VerticalCS)
++     * @see DefaultVerticalCRS#DefaultVerticalCRS(Map, VerticalDatum, 
DefaultDatumEnsemble, VerticalCS)
       * @see GeodeticAuthorityFactory#createVerticalCRS(String)
       *
       * @since 1.5
@@@ -1165,7 -1156,7 +1165,7 @@@
       * @param  cs          the temporal coordinate system for the created CRS.
       * @throws FactoryException if the object creation failed.
       *
-      * @see DefaultTemporalCRS#DefaultTemporalCRS(Map, TemporalDatum, TimeCS)
 -     * @see DefaultTemporalCRS#DefaultTemporalCRS(Map, TemporalDatum, 
DatumEnsemble, TimeCS)
++     * @see DefaultTemporalCRS#DefaultTemporalCRS(Map, TemporalDatum, 
DefaultDatumEnsemble, TimeCS)
       * @see GeodeticAuthorityFactory#createTemporalCRS(String)
       *
       * @since 1.5
@@@ -1290,7 -1258,7 +1290,7 @@@
       * @param  cs          the parametric coordinate system for the created 
CRS.
       * @throws FactoryException if the object creation failed.
       *
-      * @see DefaultParametricCRS#DefaultParametricCRS(Map, 
DefaultParametricDatum, DefaultParametricCS)
 -     * @see DefaultParametricCRS#DefaultParametricCRS(Map, ParametricDatum, 
DatumEnsemble, ParametricCS)
++     * @see DefaultParametricCRS#DefaultParametricCRS(Map, 
DefaultParametricDatum, DefaultDatumEnsemble, ParametricCS)
       * @see GeodeticAuthorityFactory#createParametricCRS(String)
       *
       * @since 1.5
@@@ -1310,26 -1279,6 +1310,26 @@@
          return unique("createParametricCRS", crs);
      }
  
 +    /**
 +     * Creates a parametric <abbr>CRS</abbr> from a datum.
-      * This is a shortcut for the {@linkplain #createParametricCRS(Map, 
ParametricDatum, DatumEnsemble, ParametricCS)
++     * This is a shortcut for the {@linkplain #createParametricCRS(Map, 
ParametricDatum, DefaultDatumEnsemble, ParametricCS)
 +     * more generic method} without datum ensemble.
 +     *
 +     * @param  properties  name and other properties to give to the new 
object.
 +     *         Available properties are {@linkplain ObjectFactory listed 
there}.
 +     * @param  datum  temporal datum to use in created CRS.
 +     * @param  cs  the temporal coordinate system for the created CRS.
 +     * @return the coordinate reference system for the given properties.
 +     * @throws FactoryException if the object creation failed.
 +     */
 +    public DefaultParametricCRS createParametricCRS(final Map<String,?> 
properties,
 +                                             final DefaultParametricDatum 
datum,
 +                                             final DefaultParametricCS cs)
 +            throws FactoryException
 +    {
 +        return createParametricCRS(properties, datum, null, cs);
 +    }
 +
      /**
       * Creates a parametric datum.
       * The default implementation creates a {@link DefaultParametricDatum} 
instance.
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
index 67fc5baa6a,c8f9b22cbb..ad2cc220c3
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
@@@ -744,7 -749,7 +745,7 @@@ public class CoordinateOperationFinder 
              final EllipsoidalCS cs = 
CommonCRS.WGS84.geographic3D().getCoordinateSystem();
              if (!equalsIgnoreMetadata(interpolationCS, cs)) {
                  final GeographicCRS stepCRS = factorySIS.crsFactory
-                         .createGeographicCRS(derivedFrom(sourceCRS), 
sourceCRS.getDatum(), cs);
 -                        .createGeographicCRS(derivedFrom(sourceCRS), 
sourceCRS.getDatum(), sourceCRS.getDatumEnsemble(), cs);
++                        .createGeographicCRS(derivedFrom(sourceCRS), 
PseudoDatum.of(sourceCRS), cs);
                  step1 = createOperation(sourceCRS, 
toAuthorityDefinition(GeographicCRS.class, stepCRS));
                  interpolationCRS = step1.getTargetCRS();
                  interpolationCS  = interpolationCRS.getCoordinateSystem();
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java
index 07339c08af,68d5ff6355..90416ab0fc
--- 
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
@@@ -49,6 -49,6 +49,9 @@@ import org.opengis.referencing.crs.Gene
  import org.opengis.referencing.crs.GeographicCRS;
  import org.opengis.referencing.crs.ProjectedCRS;
  
++// Specific to the main branch:
++import static 
org.apache.sis.pending.geoapi.referencing.MissingMethods.getDatumEnsemble;
++
  
  /**
   * A parameterized mathematical operation that converts coordinates to 
another CRS without any change of
@@@ -420,8 -422,17 +423,17 @@@ public class DefaultConversion extends 
              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();
++                datum = getDatumEnsemble(crs1);
++                other = getDatumEnsemble(crs2);
+             }
+             if (datum != null && other != null && 
!Utilities.equalsIgnoreMetadata(datum, other)) {
                  throw new MismatchedDatumException(Resources.format(
                          Resources.Keys.IncompatibleDatum_2, datum.getName(), 
param));
              }
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/DefinitionVerifier.java
index 52974665c4,d2d82bca6d..e6dc025bd8
--- 
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
@@@ -307,8 -306,9 +306,8 @@@ public final class DefinitionVerifier 
       * 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.
       */
 -    @SuppressWarnings("deprecation")
      private static int diffCode(final Iterator<SingleCRS> authoritative, 
final Iterator<SingleCRS> given) {
          while (authoritative.hasNext() && given.hasNext()) {
              final SingleCRS crsA = authoritative.next();
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/EllipsoidalHeightCombiner.java
index db88f2192a,fabc5f747a..dca098812d
--- 
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
@@@ -140,8 -144,10 +141,10 @@@ public final class EllipsoidalHeightCom
                      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);
++                        crs  = 
factories.getCRSFactory().createGeographicCRS(crsProps, PseudoDatum.of(geod), 
cs3D);
                      } else {
                          final ProjectedCRS proj = (ProjectedCRS) crs;
                          GeographicCRS base = proj.getBaseCRS();
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/GeodeticObjectBuilder.java
index ddfb55c7c2,df58dc19ca..e772cb085a
--- 
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
@@@ -61,9 -61,10 +61,10 @@@ import org.apache.sis.referencing.cs.Ax
  import org.apache.sis.referencing.internal.Resources;
  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 main branch:
 +import org.apache.sis.temporal.TemporalDate;
++import org.apache.sis.referencing.datum.PseudoDatum;
 +import org.apache.sis.referencing.operation.transform.MathTransformBuilder;
  
  
  /**
@@@ -357,7 -367,8 +360,7 @@@ public class GeodeticObjectBuilder exte
          conversionName = c.getName().getCode();
          method         = c.getMethod();
          parameters     = c.getParameterValues();
--        datum          = crs.getDatum();
 -        ensemble       = crs.getDatumEnsemble();
++        datum          = PseudoDatum.of(crs.getBaseCRS());
          properties.putAll(IdentifiedObjects.getProperties(crs, 
ProjectedCRS.IDENTIFIERS_KEY));
          return this;
      }
@@@ -506,12 -517,21 +509,21 @@@
       */
      public ProjectedCRS createProjectedCRS() throws FactoryException {
          GeographicCRS crs = getBaseCRS();
-         if (datum != null) {
-             crs = factories.getCRSFactory().createGeographicCRS(name(datum), 
datum, crs.getCoordinateSystem());
+         final IdentifiedObject id = getDatumOrEnsemble();
+         if (id != null) {
 -            crs = factories.getCRSFactory().createGeographicCRS(name(id), 
datum, ensemble, crs.getCoordinateSystem());
++            crs = factories.getCRSFactory().createGeographicCRS(name(id), 
datum, crs.getCoordinateSystem());
          }
          return createProjectedCRS(crs, factories.getStandardProjectedCS());
      }
  
+     /**
+      * Returns the datum if defined, or the datum ensemble otherwise.
+      * Both of them may be {@code null}.
+      */
+     private IdentifiedObject getDatumOrEnsemble() {
 -        return (datum != null) ? datum : ensemble;
++        return datum;   // There is more code in the GeoAPI 3.1/4.0 branches.
+     }
+ 
      /**
       * Returns the CRS to use as the base of a projected CRS.
       *
@@@ -529,8 -549,11 +541,11 @@@
       */
      public GeographicCRS createGeographicCRS() throws FactoryException {
          final GeographicCRS crs = getBaseCRS();
-         if (datum != null) properties.putIfAbsent(GeographicCRS.NAME_KEY, 
datum.getName());
+         final IdentifiedObject id = getDatumOrEnsemble();
+         if (id != null) {
+             properties.putIfAbsent(GeographicCRS.NAME_KEY, id.getName());
+         }
 -        return factories.getCRSFactory().createGeographicCRS(properties, 
datum, ensemble, crs.getCoordinateSystem());
 +        return factories.getCRSFactory().createGeographicCRS(properties, 
datum, crs.getCoordinateSystem());
      }
  
      /**
@@@ -583,15 -606,15 +598,15 @@@
              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, 
TemporalDate.toDate(origin));
 -                td = 
factories.getDatumFactory().createTemporalDatum(properties, origin);
++                td = 
factories.getDatumFactory().createTemporalDatum(properties, 
TemporalDate.toDate(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 --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingUtilities.java
index 54e8ba460e,8bc3a0948c..036fb14c51
--- 
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
@@@ -52,13 -53,8 +53,14 @@@ import org.apache.sis.referencing.cs.De
  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;
 +// Specific to the main branch:
 +import java.util.Collection;
 +import java.util.NoSuchElementException;
 +import org.opengis.referencing.ReferenceIdentifier;
++import org.apache.sis.referencing.datum.DefaultDatumEnsemble;
 +import org.apache.sis.pending.geoapi.referencing.MissingMethods;
 +import org.apache.sis.metadata.privy.Identifiers;
 +import org.apache.sis.xml.NilObject;
  
  
  /**
@@@ -210,51 -206,30 +212,75 @@@ public final class ReferencingUtilitie
          return (types.length != 0) ? types[0] : type;
      }
  
 +    /**
 +     * Copies all {@link SingleCRS} components from the given source to the 
given collection.
 +     * For each {@link CompoundCRS} element found in the iteration, this 
method replaces the
 +     * {@code CompoundCRS} by its {@linkplain CompoundCRS#getComponents() 
components}, which
 +     * may themselves have other {@code CompoundCRS}. Those replacements are 
performed recursively
 +     * until we obtain a flat view of CRS components.
 +     *
 +     * @param  source  the collection of single or compound CRS.
 +     * @param  addTo   where to add the single CRS in order to obtain a flat 
view of {@code source}.
 +     * @return {@code true} if this method found only single CRS in {@code 
source}, in which case {@code addTo}
 +     *         got the same content (assuming that {@code addTo} was empty 
prior this method call).
 +     * @throws NoSuchElementException if a CRS component is missing.
 +     * @throws ClassCastException if a CRS is neither a {@link SingleCRS} or 
a {@link CompoundCRS}.
 +     *
 +     * @see 
org.apache.sis.referencing.CRS#getSingleComponents(CoordinateReferenceSystem)
 +     */
 +    public static boolean getSingleComponents(final Iterable<? extends 
CoordinateReferenceSystem> source,
 +            final Collection<? super SingleCRS> addTo) throws 
ClassCastException
 +    {
 +        boolean sameContent = true;
 +        for (final CoordinateReferenceSystem candidate : source) {
 +            if (candidate instanceof CompoundCRS) {
 +                getSingleComponents(((CompoundCRS) 
candidate).getComponents(), addTo);
 +                sameContent = false;
 +            } else if (candidate instanceof SingleCRS) {
 +                addTo.add((SingleCRS) candidate);
 +            } else {
 +                /*
 +                 * Illegal class. Try to provide a better error message, in 
particular when the CRS component
 +                 * is nil because it is an unresolved xlink in a GML 
document. Nil objects are proxies, which
 +                 * have hard to understand class names.
 +                 */
 +                final String message;
 +                if (candidate instanceof NilObject) {
 +                    message = Errors.format(Errors.Keys.NilObject_1, 
Identifiers.getNilReason((NilObject) candidate));
 +                    throw new NoSuchElementException(message);
 +                } else {
 +                    message = 
Errors.format(Errors.Keys.NestedElementNotAllowed_1, getInterface(candidate));
 +                    throw new ClassCastException(message);
 +                }
 +            }
 +        }
 +        return sameContent;
 +    }
 +
+     /**
+      * Returns whether the given <abbr>CRS</abbr> uses the given datum.
+      *
+      * @param  crs    the <abbr>CRS</abbr>, or {@code null}.
+      * @param  datum  the datum to compare with the <abbr>CRS</abbr> datum or 
datum ensemble.
+      * @return whether the given CRS <abbr>CRS</abbr> uses the specified 
datum.
+      */
+     public static boolean uses(final SingleCRS crs, final Datum datum) {
+         if (crs != null && datum != null) {
+             if (Utilities.equalsIgnoreMetadata(crs.getDatum(), datum)) {
+                 return true;
+             }
 -            final var ensemble = crs.getDatumEnsemble();
++            final var ensemble = MissingMethods.getDatumEnsemble(crs);
+             if (ensemble != null) {
+                 for (final Datum member : ensemble.getMembers()) {
+                     if (Utilities.equalsIgnoreMetadata(member, datum)) {
+                         return true;
+                     }
+                 }
+             }
+         }
+         return false;
+     }
+ 
      /**
       * 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,
@@@ -281,12 -256,16 +307,16 @@@
       * <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
++     *   <li>Otherwise, if the given CRS is associated to a {@code 
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.
@@@ -311,36 -275,63 +326,63 @@@
      }
  
      /**
-      * 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)}.
+      * The difference between this method and {@link 
org.apache.sis.referencing.datum.PseudoDatum}
+      * is that this method ignore null values and does not throw an exception 
in case of mismatch.
       *
-      * @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();
++            final DefaultDatumEnsemble<?> ensemble = 
MissingMethods.getDatumEnsemble(scrs);
+             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 --cc 
endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/CRSBuilder.java
index a7abf5c1b0,b3d4d844fd..3ef7d8d1db
--- 
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
@@@ -60,10 -61,6 +61,11 @@@ import org.apache.sis.util.resources.Er
  import org.apache.sis.measure.NumberRange;
  import org.apache.sis.measure.Units;
  
 +// Specific to the main branch:
 +import org.apache.sis.referencing.factory.GeodeticObjectFactory;
++import org.apache.sis.referencing.datum.PseudoDatum;
 +import org.apache.sis.temporal.TemporalDate;
 +
  
  /**
   * Temporary object for building a coordinate reference system from the 
variables in a netCDF file.
@@@ -137,7 -134,7 +139,6 @@@ abstract class CRSBuilder<D extends Dat
  
      /**
       * The datum created by {@link #createDatum(DatumFactory, Map)}.
--     * At least one of {@code datum} and {@link #datumEnsemble} shall be 
initialized.
       */
      protected D datum;
  
@@@ -619,8 -622,18 +620,15 @@@ previous:   for (int i=components.size(
           * 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();
 -            }
++            datum = PseudoDatum.of(crs.geographic());
          }
  
          /**
@@@ -673,9 -686,10 +681,9 @@@
                  }
                  referenceSystem  = crs;
                  coordinateSystem = (SphericalCS) crs.getCoordinateSystem();
--                datum            = crs.getDatum();
 -                datumEnsemble    = crs.getDatumEnsemble();
++                datum            = PseudoDatum.of(crs);
              } else {
-                 datum = defaultCRS.datum();
+                 setDatum(defaultCRS);
              }
          }
  
@@@ -738,9 -750,10 +746,9 @@@
                  }
                  referenceSystem  = crs;
                  coordinateSystem = crs.getCoordinateSystem();
--                datum            = crs.getDatum();
 -                datumEnsemble    = crs.getDatumEnsemble();
++                datum            = PseudoDatum.of(crs);
              } else {
-                 datum = defaultCRS.datum();
+                 setDatum(defaultCRS);
                  final Integer epsg = epsgCandidateCS(Units.DEGREE);
                  if (epsg != null) try {
                      coordinateSystem = 
decoder.getCSAuthorityFactory().createEllipsoidalCS(epsg.toString());
@@@ -843,8 -856,10 +851,9 @@@
          @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)) {
 -            if (!Utilities.equalsIgnoreMetadata(baseCRS.getDatum(), datum) &&
 -                !Utilities.equalsIgnoreMetadata(baseCRS.getDatumEnsemble(), 
datumEnsemble))
++            if (!Utilities.equalsIgnoreMetadata(baseCRS.getDatum(), datum))
+             {
 -                baseCRS = factory.createGeographicCRS(properties, datum, 
datumEnsemble, baseCRS.getCoordinateSystem());
 +                baseCRS = factory.createGeographicCRS(properties, datum, 
baseCRS.getCoordinateSystem());
              }
              referenceSystem = factory.createProjectedCRS(properties, baseCRS, 
UNKNOWN_PROJECTION, coordinateSystem);
          }
diff --cc 
endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java
index 78856ab0cd,609cc18f5f..9bf97ac2ec
--- 
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 +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;
++// Specific to the main branch:
++import org.apache.sis.referencing.datum.PseudoDatum;
+ 
  
  /**
   * Temporary objects for creating a {@link GridGeometry} instance defined by 
attributes on a variable.
@@@ -376,7 -380,10 +379,7 @@@ final class GridMapping 
              }
              datum = datumFactory.createGeodeticDatum(properties, ellipsoid, 
meridian);
          } else {
--            datum = defaultDefinitions.datum();
 -            if (datum == null) {
 -                ensemble = defaultDefinitions.datumEnsemble();
 -            }
++            datum = PseudoDatum.of(defaultDefinitions.geographic());
          }
          /*
           * Geographic CRS from all above properties.
diff --cc 
endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java
index 1410b81fb0,8ac1db8491..260b46eb5f
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java
@@@ -71,12 -73,13 +73,11 @@@ import org.apache.sis.geometry.wrapper.
  import org.apache.sis.metadata.iso.DefaultMetadata;
  import org.apache.sis.metadata.sql.MetadataStoreException;
  import org.apache.sis.setup.OptionKey;
- import org.apache.sis.util.resources.Errors;
  import org.apache.sis.measure.Units;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.feature.Feature;
 -import org.opengis.feature.FeatureType;
 -import org.opengis.feature.PropertyType;
 -import org.opengis.feature.AttributeType;
 +// Specific to the main branch:
 +import org.apache.sis.feature.AbstractFeature;
 +import org.apache.sis.feature.AbstractIdentifiedType;
  
  
  /**

Reply via email to