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

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

commit e769f5515843a08c15cd99e279f93a4a0564f8b2
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri Aug 22 19:30:04 2025 +0200

    Improve interoperability between CRS defined with a datum ensemble and
    CRS defined in the old way, before datum ensembles were introduced.
---
 .../main/org/apache/sis/referencing/CRS.java       |   1 +
 .../apache/sis/referencing/crs/AbstractCRS.java    |  28 ++-
 .../sis/referencing/crs/AbstractDerivedCRS.java    |   9 -
 .../sis/referencing/crs/AbstractSingleCRS.java     |  53 ++---
 .../sis/referencing/crs/DefaultCompoundCRS.java    |   2 +-
 .../sis/referencing/crs/DefaultDerivedCRS.java     |  38 +++
 .../sis/referencing/crs/DefaultEngineeringCRS.java |  10 +
 .../sis/referencing/crs/DefaultGeodeticCRS.java    |  14 +-
 .../sis/referencing/crs/DefaultParametricCRS.java  |  10 +
 .../sis/referencing/crs/DefaultProjectedCRS.java   |  10 +
 .../sis/referencing/crs/DefaultTemporalCRS.java    |   9 +
 .../sis/referencing/crs/DefaultVerticalCRS.java    |  10 +
 .../sis/referencing/datum/DatumOrEnsemble.java     | 263 +++++++++++++++++----
 .../referencing/internal/VerticalDatumTypes.java   |   1 +
 .../apache/sis/referencing/privy/WKTUtilities.java |  32 ---
 .../sis/test/integration/ConsistencyTest.java      |  15 +-
 16 files changed, 366 insertions(+), 139 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
index d098c91904..ee0fe5f571 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
@@ -798,6 +798,7 @@ public final class CRS extends Static {
      * @return the accuracy estimation (always in meters), or NaN if unknown.
      *
      * @see #findOperation(CoordinateReferenceSystem, 
CoordinateReferenceSystem, GeographicBoundingBox)
+     * @see DatumOrEnsemble#getAccuracy(IdentifiedObject)
      *
      * @since 0.7
      */
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java
index 9fc0ed3c86..0c2e4c37fa 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java
@@ -33,6 +33,7 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.referencing.AbstractReferenceSystem;
 import org.apache.sis.referencing.cs.AbstractCS;
 import org.apache.sis.referencing.cs.AxesConvention;
+import org.apache.sis.referencing.datum.AbstractDatum;
 import org.apache.sis.referencing.privy.WKTUtilities;
 import org.apache.sis.referencing.privy.ReferencingUtilities;
 import org.apache.sis.metadata.privy.ImplementationHelper;
@@ -282,20 +283,31 @@ public class AbstractCRS extends AbstractReferenceSystem 
implements CoordinateRe
     }
 
     /**
-     * Returns the datum, or {@code null} if none.
-     *
-     * This property does not exist in {@code CoordinateReferenceSystem} 
interface — it is defined in the
-     * {@link SingleCRS} sub-interface instead. This method is defined here 
for the convenience of the
-     * {@link #formatTo(Formatter)} method implementation.
+     * Returns the datum or a view of the ensemble as a datum, or {@code null} 
if none.
+     * The {@code legacy} argument is usually {@code false}, except when 
formatting in a legacy <abbr>WKT</abbr> format.
      *
+     * @param  legacy  whether to allow a view of the ensemble as a datum for 
interoperability with legacy standards.
      * @return the datum, or {@code null} if none.
      */
-    Datum getDatum() {
+    Datum getDatumOrEnsemble(final boolean legacy) {
         /*
          * User could provide his own CRS implementation outside this SIS 
package, so we have
          * to check for SingleCRS interface. But all SIS classes override this 
implementation.
          */
-        return (this instanceof SingleCRS) ? ((SingleCRS) this).getDatum() : 
null;
+        if (this instanceof SingleCRS) {
+            final var crs = (SingleCRS) this;
+            final Datum datum = crs.getDatum();
+            if (datum != null) {
+                return datum;
+            }
+            if (legacy) {
+                final var ensemble = crs.getDatumEnsemble();
+                if (ensemble instanceof Datum) {
+                    return (Datum) ensemble;
+                }
+            }
+        }
+        return null;
     }
 
     /**
@@ -454,7 +466,7 @@ public class AbstractCRS extends AbstractReferenceSystem 
implements CoordinateRe
     protected String formatTo(final Formatter formatter) {
         final String keyword = super.formatTo(formatter);
         formatter.newLine();
-        formatter.append(WKTUtilities.toFormattable(getDatum()));
+        formatter.append(AbstractDatum.castOrCopy(getDatumOrEnsemble(true)));  
   // For the conversion of ensemble to datum.
         formatter.newLine();
         final Convention convention = formatter.getConvention();
         final boolean isWKT1 = convention.majorVersion() == 1;
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractDerivedCRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractDerivedCRS.java
index ee27c22b00..a957194948 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractDerivedCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractDerivedCRS.java
@@ -23,7 +23,6 @@ import jakarta.xml.bind.annotation.XmlSeeAlso;
 import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlRootElement;
 import org.opengis.util.FactoryException;
-import org.opengis.referencing.datum.Datum;
 import org.opengis.referencing.crs.SingleCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.cs.CoordinateSystem;
@@ -176,14 +175,6 @@ abstract class AbstractDerivedCRS extends AbstractCRS 
implements DerivedCRS {
         }
     }
 
-    /**
-     * Returns the datum of the base CRS.
-     *
-     * @return the datum of the base CRS.
-     */
-    @Override
-    public abstract Datum getDatum();
-
     /**
      * Returns the conversion from the base CRS to this CRS.
      *
diff --git 
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
index ac88426d4a..115d7d3d79 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
@@ -31,7 +31,7 @@ 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.internal.Resources;
-import org.apache.sis.metadata.privy.Identifiers;
+import org.apache.sis.referencing.datum.DatumOrEnsemble;
 import org.apache.sis.metadata.privy.ImplementationHelper;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
@@ -209,22 +209,6 @@ class AbstractSingleCRS<D extends Datum> extends 
AbstractCRS implements SingleCR
         return ensemble;
     }
 
-    /**
-     * Returns whether the given datum may be considered as equivalent to the 
given datum ensemble.
-     * Used for comparisons with {@link ComparisonMode#APPROXIMATE} for 
interoperability between
-     * the legacy and the new definition of EPSG:4326.
-     */
-    private static boolean isHeuristicMatchForName(final DatumEnsemble<?> 
ensemble, final Datum datum, final ComparisonMode mode) {
-        if (ensemble == null || datum == null) {
-            return false;
-        }
-        final Boolean match = 
Identifiers.hasCommonIdentifier(ensemble.getIdentifiers(), 
datum.getIdentifiers());
-        if (match != null) {
-            return match;
-        }
-        return IdentifiedObjects.isHeuristicMatchForName(datum, 
ensemble.getName().getCode());
-    }
-
     /**
      * Compares this coordinate reference system with the specified object for 
equality.
      *
@@ -237,25 +221,24 @@ class AbstractSingleCRS<D extends Datum> extends 
AbstractCRS implements SingleCR
     @Override
     public boolean equals(final Object object, 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;
-                    final var d1   = this.getDatum();
-                    final var d2   = that.getDatum();
-                    if (mode == ComparisonMode.DEBUG) {
-                        mode = ComparisonMode.ALLOW_VARIANT;    // For 
avoiding too early `AssertionError`.
-                    }
-                    if (Utilities.deepEquals(d1, d2, mode)) {
-                        return mode.allowsVariant() || 
Utilities.deepEquals(getDatumEnsemble(), that.getDatumEnsemble(), mode);
-                    } else if (mode.allowsVariant()) {
-                        return 
isHeuristicMatchForName(this.getDatumEnsemble(), d2, mode) ||
-                               
isHeuristicMatchForName(that.getDatumEnsemble(), d1, mode);
-                    }
+            if (mode == ComparisonMode.STRICT) {
+                final var that = (AbstractSingleCRS<?>) object;
+                return Objects.equals(datum, that.datum) && 
Objects.equals(ensemble, that.ensemble);
+            }
+            final var that = (SingleCRS) object;
+            final var d1   = this.getDatum();
+            final var d2   = that.getDatum();
+            if (mode == ComparisonMode.DEBUG) {
+                mode = ComparisonMode.ALLOW_VARIANT;    // For avoiding too 
early `AssertionError`.
+            }
+            if (Utilities.deepEquals(d1, d2, mode)) {
+                if (d1 != null && d2 != null && mode.allowsVariant()) {
+                    return true;
                 }
+                return Utilities.deepEquals(getDatumEnsemble(), 
that.getDatumEnsemble(), mode);
+            } else if (mode.allowsVariant()) {
+                return DatumOrEnsemble.isLegacyDatum(this.getDatumEnsemble(), 
d2, mode) ||
+                       DatumOrEnsemble.isLegacyDatum(that.getDatumEnsemble(), 
d1, mode);
             }
         }
         return false;
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
index f9a0db01eb..0ac473adaa 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
@@ -313,7 +313,7 @@ public class DefaultCompoundCRS extends AbstractCRS 
implements CompoundCRS {
      * Compound CRS do not have datum.
      */
     @Override
-    final Datum getDatum() {
+    final Datum getDatumOrEnsemble(final boolean legacy) {
         return null;
     }
 
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
index ced04249ef..c7258fde90 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
@@ -53,6 +53,7 @@ import 
org.apache.sis.xml.bind.referencing.CS_CoordinateSystem;
 import org.apache.sis.referencing.privy.ReferencingUtilities;
 import org.apache.sis.referencing.privy.WKTUtilities;
 import org.apache.sis.referencing.privy.WKTKeywords;
+import org.apache.sis.referencing.datum.DatumOrEnsemble;
 import static org.apache.sis.referencing.internal.Legacy.DERIVED_TYPE_KEY;
 import org.apache.sis.io.wkt.Convention;
 import org.apache.sis.io.wkt.Formatter;
@@ -424,6 +425,18 @@ public class DefaultDerivedCRS extends AbstractDerivedCRS 
implements DerivedCRS
         return getBaseCRS().getDatumEnsemble();
     }
 
+    /**
+     * Returns the datum or a view of the ensemble as a datum.
+     */
+    @Override
+    Datum getDatumOrEnsemble(final boolean legacy) {
+        final SingleCRS baseCRS = getBaseCRS();
+        if (baseCRS instanceof AbstractCRS) {
+            return ((AbstractCRS) baseCRS).getDatumOrEnsemble(legacy);
+        }
+        return super.getDatumOrEnsemble(legacy);
+    }
+
     /**
      * Returns the CRS on which the conversion is applied.
      * This CRS defines the {@linkplain #getDatum() datum} of this CRS and (at 
least implicitly)
@@ -677,6 +690,11 @@ public class DefaultDerivedCRS extends AbstractDerivedCRS 
implements DerivedCRS
             return ((GeodeticCRS) getBaseCRS()).getDatumEnsemble();
         }
 
+        /** Returns the datum or a view of the ensemble as a datum. */
+        @Override GeodeticDatum getDatumOrEnsemble(final boolean legacy) {
+            return legacy ? DatumOrEnsemble.asDatum((GeodeticCRS) 
getBaseCRS()) : getDatum();
+        }
+
         /** Returns a coordinate reference system of the same type as this CRS 
but with different axes. */
         @Override AbstractCRS createSameType(final AbstractCS derivedCS) {
             return new Geodetic(this, derivedCS);
@@ -733,6 +751,11 @@ public class DefaultDerivedCRS extends AbstractDerivedCRS 
implements DerivedCRS
             return ((VerticalCRS) getBaseCRS()).getDatumEnsemble();
         }
 
+        /** Returns the datum or a view of the ensemble as a datum. */
+        @Override VerticalDatum getDatumOrEnsemble(final boolean legacy) {
+            return legacy ? DatumOrEnsemble.asDatum((VerticalCRS) 
getBaseCRS()) : getDatum();
+        }
+
         /** Returns the coordinate system given at construction time. */
         @Override public VerticalCS getCoordinateSystem() {
             return (VerticalCS) super.getCoordinateSystem();
@@ -794,6 +817,11 @@ public class DefaultDerivedCRS extends AbstractDerivedCRS 
implements DerivedCRS
             return ((TemporalCRS) getBaseCRS()).getDatumEnsemble();
         }
 
+        /** Returns the datum or a view of the ensemble as a datum. */
+        @Override TemporalDatum getDatumOrEnsemble(final boolean legacy) {
+            return legacy ? DatumOrEnsemble.asDatum((TemporalCRS) 
getBaseCRS()) : getDatum();
+        }
+
         /** Returns the coordinate system given at construction time. */
         @Override public TimeCS getCoordinateSystem() {
             return (TimeCS) super.getCoordinateSystem();
@@ -855,6 +883,11 @@ public class DefaultDerivedCRS extends AbstractDerivedCRS 
implements DerivedCRS
             return ((ParametricCRS) getBaseCRS()).getDatumEnsemble();
         }
 
+        /** Returns the datum or a view of the ensemble as a datum. */
+        @Override ParametricDatum getDatumOrEnsemble(final boolean legacy) {
+            return legacy ? DatumOrEnsemble.asDatum((ParametricCRS) 
getBaseCRS()) : getDatum();
+        }
+
         /** Returns the coordinate system given at construction time. */
         @Override public ParametricCS getCoordinateSystem() {
             return (ParametricCS) super.getCoordinateSystem();
@@ -919,6 +952,11 @@ public class DefaultDerivedCRS extends AbstractDerivedCRS 
implements DerivedCRS
             return ((EngineeringCRS) getBaseCRS()).getDatumEnsemble();
         }
 
+        /** Returns the datum or a view of the ensemble as a datum. */
+        @Override EngineeringDatum getDatumOrEnsemble(final boolean legacy) {
+            return legacy ? DatumOrEnsemble.asDatum((EngineeringCRS) 
getBaseCRS()) : getDatum();
+        }
+
         /** Returns a coordinate reference system of the same type as this CRS 
but with different axes. */
         @Override AbstractCRS createSameType(final AbstractCS derivedCS) {
             return new Engineering(this, derivedCS);
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java
index e08f83a07a..3872c76e63 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java
@@ -26,6 +26,7 @@ import org.opengis.referencing.crs.EngineeringCRS;
 import org.opengis.referencing.datum.EngineeringDatum;
 import org.apache.sis.referencing.AbstractReferenceSystem;
 import org.apache.sis.referencing.cs.*;
+import org.apache.sis.referencing.datum.DatumOrEnsemble;
 import org.apache.sis.referencing.privy.WKTKeywords;
 import org.apache.sis.xml.bind.referencing.CS_CoordinateSystem;
 import org.apache.sis.io.wkt.Formatter;
@@ -235,6 +236,15 @@ public class DefaultEngineeringCRS extends 
AbstractSingleCRS<EngineeringDatum> i
         return super.getDatumEnsemble();
     }
 
+    /**
+     * Returns the datum or a view of the ensemble as a datum.
+     * The {@code legacy} argument tells whether this method is invoked for 
formatting in a legacy <abbr>WKT</abbr> format.
+     */
+    @Override
+    final EngineeringDatum getDatumOrEnsemble(final boolean legacy) {
+        return legacy ? DatumOrEnsemble.asDatum(this) : getDatum();
+    }
+
     /**
      * {@inheritDoc}
      *
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
index 7e695e2db6..04ef599736 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
@@ -34,6 +34,7 @@ import org.apache.sis.referencing.AbstractReferenceSystem;
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.cs.AbstractCS;
 import org.apache.sis.referencing.datum.DatumOrEnsemble;
+import org.apache.sis.referencing.datum.DefaultGeodeticDatum;
 import org.apache.sis.referencing.internal.Legacy;
 import org.apache.sis.referencing.privy.AxisDirections;
 import org.apache.sis.referencing.privy.WKTKeywords;
@@ -159,6 +160,15 @@ class DefaultGeodeticCRS extends 
AbstractSingleCRS<GeodeticDatum> implements Geo
         return super.getDatum();
     }
 
+    /**
+     * Returns the datum or a view of the ensemble as a datum.
+     * The {@code legacy} argument tells whether this method is invoked for 
formatting in a legacy <abbr>WKT</abbr> format.
+     */
+    @Override
+    final GeodeticDatum getDatumOrEnsemble(final boolean legacy) {
+        return legacy ? DatumOrEnsemble.asDatum(this) : getDatum();
+    }
+
     /**
      * Returns a coordinate reference system of the same type as this CRS but 
with different axes.
      * This method shall be overridden by all {@code DefaultGeodeticCRS} 
subclasses in this package.
@@ -211,9 +221,9 @@ class DefaultGeodeticCRS extends 
AbstractSingleCRS<GeodeticDatum> implements Geo
          * as a sibling (rather than a child) element in WKT for historical 
reasons.
          */
         @SuppressWarnings("LocalVariableHidesMemberVariable")
-        final GeodeticDatum datum = getDatum();             // Gives 
subclasses a chance to override.
+        final GeodeticDatum datum = getDatumOrEnsemble(true);
         formatter.newLine();
-        formatter.append(WKTUtilities.toFormattable(datum));
+        formatter.append(DefaultGeodeticDatum.castOrCopy(datum));   // For the 
conversion of ensemble to datum.
         formatter.newLine();
         final Unit<Angle> angularUnit = AxisDirections.getAngularUnit(cs, 
null);
         DatumOrEnsemble.getPrimeMeridian(this).ifPresent((PrimeMeridian pm) -> 
{
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultParametricCRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultParametricCRS.java
index adcee10299..5fedc4597a 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultParametricCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultParametricCRS.java
@@ -23,6 +23,7 @@ import jakarta.xml.bind.annotation.XmlType;
 import org.apache.sis.referencing.privy.WKTKeywords;
 import org.apache.sis.referencing.cs.AxesConvention;
 import org.apache.sis.referencing.cs.AbstractCS;
+import org.apache.sis.referencing.datum.DatumOrEnsemble;
 import org.apache.sis.io.wkt.Formatter;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
@@ -218,6 +219,15 @@ public class DefaultParametricCRS extends 
AbstractSingleCRS<ParametricDatum> imp
         return super.getDatumEnsemble();
     }
 
+    /**
+     * Returns the datum or a view of the ensemble as a datum.
+     * The {@code legacy} argument tells whether this method is invoked for 
formatting in a legacy <abbr>WKT</abbr> format.
+     */
+    @Override
+    final ParametricDatum getDatumOrEnsemble(final boolean legacy) {
+        return legacy ? DatumOrEnsemble.asDatum(this) : getDatum();
+    }
+
     /**
      * Returns the coordinate system.
      *
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
index 9f328f3ed7..0356022db8 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
@@ -30,6 +30,7 @@ import org.opengis.referencing.datum.GeodeticDatum;
 import org.opengis.referencing.operation.Conversion;
 import org.apache.sis.referencing.cs.AxesConvention;
 import org.apache.sis.referencing.cs.AbstractCS;
+import org.apache.sis.referencing.datum.DatumOrEnsemble;
 import org.apache.sis.referencing.privy.ReferencingUtilities;
 import org.apache.sis.referencing.privy.AxisDirections;
 import org.apache.sis.referencing.privy.WKTKeywords;
@@ -237,6 +238,15 @@ public class DefaultProjectedCRS extends 
AbstractDerivedCRS implements Projected
         return getBaseCRS().getDatumEnsemble();
     }
 
+    /**
+     * Returns the datum or a view of the ensemble as a datum.
+     * The {@code legacy} argument tells whether this method is invoked for 
formatting in a legacy <abbr>WKT</abbr> format.
+     */
+    @Override
+    final GeodeticDatum getDatumOrEnsemble(final boolean legacy) {
+        return legacy ? DatumOrEnsemble.asDatum(getBaseCRS()) : getDatum();
+    }
+
     /**
      * Returns the geographic CRS on which the map projection is applied.
      * This CRS defines the {@linkplain #getDatum() datum} of this CRS and (at 
least implicitly)
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
index 9941694085..5445374ec4 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
@@ -291,6 +291,15 @@ public class DefaultTemporalCRS extends 
AbstractSingleCRS<TemporalDatum> impleme
         return super.getDatumEnsemble();
     }
 
+    /**
+     * Returns the datum or a view of the ensemble as a datum.
+     * The {@code legacy} argument tells whether this method is invoked for 
formatting in a legacy <abbr>WKT</abbr> format.
+     */
+    @Override
+    final TemporalDatum getDatumOrEnsemble(final boolean legacy) {
+        return legacy ? DatumOrEnsemble.asDatum(this) : getDatum();
+    }
+
     /**
      * Returns the coordinate system.
      *
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultVerticalCRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultVerticalCRS.java
index 41ef613856..7e1653920d 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultVerticalCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultVerticalCRS.java
@@ -26,6 +26,7 @@ import org.opengis.referencing.datum.VerticalDatum;
 import org.apache.sis.referencing.AbstractReferenceSystem;
 import org.apache.sis.referencing.cs.AxesConvention;
 import org.apache.sis.referencing.cs.AbstractCS;
+import org.apache.sis.referencing.datum.DatumOrEnsemble;
 import org.apache.sis.referencing.privy.WKTKeywords;
 import org.apache.sis.io.wkt.Formatter;
 
@@ -219,6 +220,15 @@ public class DefaultVerticalCRS extends 
AbstractSingleCRS<VerticalDatum> impleme
         return super.getDatumEnsemble();
     }
 
+    /**
+     * Returns the datum or a view of the ensemble as a datum.
+     * The {@code legacy} argument tells whether this method is invoked for 
formatting in a legacy <abbr>WKT</abbr> format.
+     */
+    @Override
+    final VerticalDatum getDatumOrEnsemble(final boolean legacy) {
+        return legacy ? DatumOrEnsemble.asDatum(this) : getDatum();
+    }
+
     /**
      * Returns the coordinate system.
      *
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DatumOrEnsemble.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DatumOrEnsemble.java
index 7cf521849a..b6a813a112 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DatumOrEnsemble.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DatumOrEnsemble.java
@@ -19,8 +19,10 @@ package org.apache.sis.referencing.datum;
 import java.util.ArrayDeque;
 import java.util.Collection;
 import java.util.Iterator;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.function.Function;
+import java.util.function.Predicate;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.crs.EngineeringCRS;
@@ -36,9 +38,13 @@ import org.opengis.referencing.datum.TemporalDatum;
 import org.opengis.referencing.datum.VerticalDatum;
 import org.opengis.referencing.datum.PrimeMeridian;
 import org.opengis.referencing.datum.Ellipsoid;
+import org.opengis.referencing.operation.CoordinateOperation;
 import org.opengis.metadata.quality.PositionalAccuracy;
 import org.apache.sis.util.Static;
 import org.apache.sis.util.Utilities;
+import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.metadata.privy.Identifiers;
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.GeodeticException;
 
@@ -46,6 +52,7 @@ import org.apache.sis.referencing.GeodeticException;
 import org.opengis.referencing.crs.ParametricCRS;
 import org.opengis.referencing.datum.DatumEnsemble;
 import org.opengis.referencing.datum.ParametricDatum;
+import org.opengis.referencing.datum.RealizationMethod;
 
 
 /**
@@ -62,9 +69,32 @@ import org.opengis.referencing.datum.ParametricDatum;
  */
 public final class DatumOrEnsemble extends Static {
     /**
-     * Do not allow instantiation of this class.
+     * The {@value} keyword which sometime appear at the end of a datum 
ensemble name.
      */
-    private DatumOrEnsemble() {
+    private static final String ENSEMBLE = "ensemble";
+
+    /**
+     * The datum of a <abbr>CRS</abbr> or specified by the user, or {@code 
null} if none.
+     */
+    private final Datum datum;
+
+    /**
+     * The ensemble of a <abbr>CRS</abbr> or specified by the user, or {@code 
null} if none.
+     */
+    private final DatumEnsemble<?> ensemble;
+
+    /**
+     * Criterion for deciding if two properties should be considered equal.
+     */
+    private final ComparisonMode mode;
+
+    /**
+     * For internal usage only. The fact that we may create instances of this 
class is a hidden implementation details.
+     */
+    private DatumOrEnsemble(final Datum datum, final DatumEnsemble<?> 
ensemble, final ComparisonMode mode) {
+        this.datum    = datum;
+        this.ensemble = ensemble;
+        this.mode     = mode;
     }
 
     /**
@@ -279,17 +309,17 @@ public final class DatumOrEnsemble extends Static {
      * or <var>target</var> datum ensembles met that criterion, then this 
method returns an empty value.
      * A non-empty value means that it is okay, for low accuracy requirements, 
to ignore the datum shift.
      *
-     * @param  source       the source <abbr>CRS</abbr> of a coordinate 
operation.
+     * @param  sourceCRS    the source <abbr>CRS</abbr> of a coordinate 
operation.
      * @param  sourceDatum  the datum of the source <abbr>CRS</abbr>.
-     * @param  target       the target <abbr>CRS</abbr> of a coordinate 
operation.
+     * @param  targetCRS    the target <abbr>CRS</abbr> of a coordinate 
operation.
      * @param  targetDatum  the datum of the target <abbr>CRS</abbr>.
      * @param  constructor  function to invoke for wrapping a datum ensemble 
in a pseudo-datum.
      * @return datum or pseudo-datum of the coordinate operation result if it 
is okay to ignore datum shift.
      */
     @SuppressWarnings("unchecked")          // Casts are safe because callers 
know the method signature of <D>.
     private static <C extends SingleCRS, D extends Datum, R extends 
IdentifiedObject> Optional<R> asTargetDatum(
-            final C source, final R sourceDatum,
-            final C target, final R targetDatum,
+            final C sourceCRS, final R sourceDatum,
+            final C targetCRS, final R targetDatum,
             final Function<DatumEnsemble<D>, R> constructor)
     {
         if (sourceDatum != null && Utilities.equalsIgnoreMetadata(sourceDatum, 
targetDatum)) {
@@ -298,8 +328,8 @@ public final class DatumOrEnsemble extends Static {
         DatumEnsemble<D> sourceEnsemble;
         DatumEnsemble<D> targetEnsemble;
         DatumEnsemble<D> selected;
-        if ((isMember(selected = targetEnsemble = (DatumEnsemble<D>) 
target.getDatumEnsemble(), sourceDatum)) ||
-            (isMember(selected = sourceEnsemble = (DatumEnsemble<D>) 
source.getDatumEnsemble(), targetDatum)))
+        if ((isMember(selected = targetEnsemble = (DatumEnsemble<D>) 
targetCRS.getDatumEnsemble(), sourceDatum)) ||
+            (isMember(selected = sourceEnsemble = (DatumEnsemble<D>) 
sourceCRS.getDatumEnsemble(), targetDatum)))
         {
             return Optional.of(constructor.apply(selected));
         }
@@ -352,29 +382,87 @@ public final class DatumOrEnsemble extends Static {
         return false;
     }
 
+    /**
+     * Returns whether a legacy definition of a datum may be considered as 
equivalent to the given datum ensemble.
+     * This is {@code true} if all reference frames (both the specified datum 
and the ensemble members) have the
+     * same properties (ellipsoid and prime meridians in the geodetic case), 
and the datum and datum ensemble either
+     * have a common identifier or an {@linkplain 
IdentifiedObjects#isHeuristicMatchForName(IdentifiedObject, String)
+     * heuristic match of name}.
+     *
+     * <p>This method does not verify if the given datum is a member of the 
given ensemble.
+     * If the datum was a member, then the two objects would <em>not</em> be 
conceptually equal.
+     * We would rather have one object clearly identified as more accurate 
than the other.</p>
+     *
+     * <h4>Use case</h4>
+     * This method is for interoperability between the old and new definitions 
of <abbr>WGS</abbr> 1984 (<abbr>EPSG</abbr>:4326).
+     * Before <abbr>ISO</abbr> 19111:2019, the <i>datum ensemble</i> concept 
did not existed in the <abbr>OGC</abbr>/<abbr>ISO</abbr> standards
+     * and <abbr>WGS</abbr> 1984 was defined as a {@link Datum}.
+     * In recent standards, <abbr>WGS</abbr> 1984 is defined as a {@link 
DatumEnsemble}, but the old definition is still encountered.
+     * For example, a <abbr>CRS</abbr> may have been parsed from a <abbr 
title="Geographic Markup Language">GML</abbr> document,
+     * or from a <abbr title="Well-Known Text">WKT</abbr> 1 string, or from a 
<abbr>ISO</abbr> 19162:2015 string, <i>etc.</i>
+     * This method can be used for detecting such situations.
+     * While <abbr>WGS</abbr> 1984 is the main use case, this method can be 
used for any datum in the same situation.
+     *
+     * @param  ensemble  the datum ensemble, or {@code null}.
+     * @param  datum     the datum, or {@code null}.
+     * @param  mode      the criterion for comparing ellipsoids and prime 
meridians.
+     * @return whether the two objects could be considered as equal if the 
concept of datum ensemble did not existed.
+     */
+    public static boolean isLegacyDatum(final DatumEnsemble<?> ensemble, final 
Datum datum, final ComparisonMode mode) {
+        if (ensemble == null || datum == null) {
+            return false;
+        }
+        // Two null values are not considered equal because they are not of 
the same type.
+        if (ensemble == datum) {
+            return true;
+        }
+        final var c = new DatumOrEnsemble(datum, ensemble, mode);
+        if (!(c.isPropertyEqual(GeodeticDatum.class, 
GeodeticDatum::getEllipsoid,         Objects::nonNull) &&
+              c.isPropertyEqual(GeodeticDatum.class, 
GeodeticDatum::getPrimeMeridian,     Objects::nonNull) &&
+              c.isPropertyEqual(VerticalDatum.class, 
VerticalDatum::getRealizationMethod, Optional::isPresent)))
+        {
+            return false;
+        }
+        final Boolean match = 
Identifiers.hasCommonIdentifier(ensemble.getIdentifiers(), 
datum.getIdentifiers());
+        if (match != null) {
+            return match;
+        }
+        String name = ensemble.getName().getCode();
+        if (IdentifiedObjects.isHeuristicMatchForName(datum, name)) {
+            return true;
+        }
+        if (name.endsWith(ENSEMBLE)) {
+            int i = name.length() - ENSEMBLE.length();
+            if (i > (i = CharSequences.skipTrailingWhitespaces(name, 0, i))) {
+                name = name.substring(0, i);    // Remove the "ensemble" 
suffix.
+                return IdentifiedObjects.isHeuristicMatchForName(datum, name);
+            }
+        }
+        return false;
+    }
+
     /**
      * Returns the ellipsoid used by the given coordinate reference system.
-     * More specifically:
+     * This method searches in the following locations:
      *
      * <ul>
      *   <li>If the given <abbr>CRS</abbr> 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 <abbr>CRS</abbr> is associated to a 
{@link DatumEnsemble} and all members
-     *       of the ensemble have equal (ignoring metadata) ellipsoid, then 
returns that ellipsoid.</li>
+     *   <li>Otherwise, if the given <abbr>CRS</abbr> is an instance of {@link 
SingleCRS}, is associated to a
+     *       {@link DatumEnsemble}, and all members of the ensemble have equal 
(ignoring metadata) ellipsoid,
+     *       then returns that ellipsoid.</li>
      *   <li>Otherwise, if the given <abbr>CRS</abbr> is an instance of {@link 
CompoundCRS}, then this method
      *       searches recursively in each component until a geodetic reference 
frame is found.</li>
      *   <li>Otherwise, this method returns an empty value.</li>
      * </ul>
      *
-     * 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 an 
empty value even
-     * if the <abbr>CRS</abbr> is geodetic.
+     * This method may return an empty value if the ellipsoid is not equal 
(ignoring metadata) for all members of the ensemble.
      *
      * @param  crs  the coordinate reference system for which to get the 
ellipsoid.
-     * @return the ellipsoid, or an empty value if none or inconsistent.
+     * @return the ellipsoid, or an empty value if none or not equivalent for 
all members of the ensemble.
      */
     public static Optional<Ellipsoid> getEllipsoid(final 
CoordinateReferenceSystem crs) {
-        return getGeodeticProperty(crs, GeodeticDatum::getEllipsoid);
+        return Optional.ofNullable(getProperty(crs, GeodeticDatum.class, 
GeodeticDatum::getEllipsoid, Objects::nonNull));
     }
 
     /**
@@ -382,72 +470,151 @@ public final class DatumOrEnsemble extends Static {
      * This method applies the same rules as {@link 
#getEllipsoid(CoordinateReferenceSystem)}.
      *
      * @param  crs  the coordinate reference system for which to get the prime 
meridian.
-     * @return the prime meridian, or an empty value if none or inconsistent.
+     * @return the prime meridian, or an empty value if none or not equivalent 
for all members of the ensemble.
      *
      * @see org.apache.sis.referencing.CRS#getGreenwichLongitude(GeodeticCRS)
      */
     public static Optional<PrimeMeridian> getPrimeMeridian(final 
CoordinateReferenceSystem crs) {
-        return getGeodeticProperty(crs, GeodeticDatum::getPrimeMeridian);
+        return Optional.ofNullable(getProperty(crs, GeodeticDatum.class, 
GeodeticDatum::getPrimeMeridian, Objects::nonNull));
+    }
+
+    /**
+     * Returns the realization method used by the given coordinate reference 
system.
+     * This method searches in the following locations:
+     *
+     * <ul>
+     *   <li>If the given <abbr>CRS</abbr> is an instance of {@link SingleCRS} 
and its datum
+     *       is a {@link VerticalDatum}, then this method returns the 
realization method.</li>
+     *   <li>Otherwise, if the given <abbr>CRS</abbr> is an instance of {@link 
SingleCRS}, is associated to a
+     *       {@link DatumEnsemble}, and all members of the ensemble have equal 
(ignoring metadata) realization
+     *       methods, then returns that method.</li>
+     *   <li>Otherwise, if the given <abbr>CRS</abbr> is an instance of {@link 
CompoundCRS}, then this method
+     *       searches recursively in each component until a vertical reference 
frame is found.</li>
+     *   <li>Otherwise, this method returns an empty value.</li>
+     * </ul>
+     *
+     * This method may return an empty value if a datum ensemble contains 
different realization methods.
+     *
+     * @param  crs  the coordinate reference system for which to get the 
realization method.
+     * @return the realization method, or an empty value if none or not equal 
for all members.
+     *
+     * @since 2.0 (temporary version number until this branch is released)
+     */
+    public static Optional<RealizationMethod> getRealizationMethod(final 
CoordinateReferenceSystem crs) {
+        Optional<RealizationMethod> common = getProperty(crs, 
VerticalDatum.class, VerticalDatum::getRealizationMethod, Optional::isPresent);
+        return (common != null) ? common : Optional.empty();
     }
 
     /**
      * Implementation of {@code getEllipsoid(CRS)} and {@code 
getPrimeMeridian(CRS)}.
      *
-     * @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 an empty value if none of 
inconsistent.
+     * @param  <P>      the type of property to get.
+     * @param  <D>      the type of datum expected by the given {@code getter}.
+     * @param  crs      the coordinate reference system for which to get the 
ellipsoid or prime meridian.
+     * @param  getter   the method to invoke on {@link Datum} instances for 
getting the property.
+     * @param  nonNull  test about whether a property value is non-null or 
present.
+     * @return the property value, or {@code null} if none or not equal for 
all members.
      */
-    private static <P> Optional<P> getGeodeticProperty(final 
CoordinateReferenceSystem crs, final Function<GeodeticDatum, P> getter) {
-single: if (crs instanceof SingleCRS) {
+    private static <P, D extends Datum> P getProperty(final 
CoordinateReferenceSystem crs, final Class<D> datumType,
+                                                      final Function<D, P> 
getter, final Predicate<P> nonNull)
+    {
+        if (crs instanceof SingleCRS) {
             final var scrs = (SingleCRS) crs;
             final Datum datum = scrs.getDatum();
-            if (datum instanceof GeodeticDatum) {
-                P property = getter.apply((GeodeticDatum) datum);
+            if (datumType.isInstance(datum)) {
+                @SuppressWarnings("unchecked")
+                P property = getter.apply((D) datum);
+                if (nonNull.test(property)) {
+                    return property;
+                }
+            }
+            final var c = new DatumOrEnsemble(datum, scrs.getDatumEnsemble(), 
ComparisonMode.IGNORE_METADATA);
+            return c.getEnsembleProperty(null, datumType, getter, nonNull);
+        } else if (crs instanceof CompoundCRS) {
+            for (final CoordinateReferenceSystem c : ((CompoundCRS) 
crs).getComponents()) {
+                final P property = getProperty(c, datumType, getter, nonNull);
                 if (property != null) {
-                    return Optional.of(property);
+                    return property;
                 }
             }
-            final DatumEnsemble<?> ensemble = scrs.getDatumEnsemble();
-            if (ensemble != null) {
-                P common = null;
-                for (Datum member : ensemble.getMembers()) {
-                    if (member instanceof GeodeticDatum) {
-                        final P property = getter.apply((GeodeticDatum) 
member);
-                        if (property != null) {
-                            if (common == null) {
-                                common = property;
-                            } else if 
(!Utilities.equalsIgnoreMetadata(property, common)) {
-                                break single;
-                            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns a property of ensemble member if it is the same for all members.
+     * Returns {@code null} if the value is absent or not equal for all 
members.
+     * If {@code common} is non-null, then this method take in account only the
+     * property values equal to {@code common}
+     * (i.e., it searches if the value is present).
+     *
+     * @param  <P>      the type of property to get.
+     * @param  <D>      the type of datum expected by the given {@code getter}.
+     * @param  common   if non-null, ignore all properties not equal to {@code 
common}.
+     * @param  getter   the method to invoke on {@link Datum} instances for 
getting the property.
+     * @param  nonNull  test about whether a property value is non-null or 
present.
+     * @return the property value, or {@code null} if none or not equal for 
all members.
+     */
+    private <P, D extends Datum> P getEnsembleProperty(P common, final 
Class<D> datumType, final Function<D,P> getter, final Predicate<P> nonNull) {
+        final boolean searching = (common != null);
+        if (ensemble != null) {
+            for (Datum member : ensemble.getMembers()) {
+                if (datumType.isInstance(member)) {
+                    @SuppressWarnings("unchecked")
+                    final P property = getter.apply((D) member);
+                    if (nonNull.test(property)) {
+                        if (common == null) {
+                            common = property;
+                        } else if (Utilities.deepEquals(property, common, 
mode) == searching) {
+                            return searching ? common : null;
                         }
                     }
                 }
-                return Optional.ofNullable(common);
             }
         }
-        if (crs instanceof CompoundCRS) {
-            for (final CoordinateReferenceSystem c : ((CompoundCRS) 
crs).getComponents()) {
-                final Optional<P> property = getGeodeticProperty(c, getter);
-                if (property.isPresent()) {
-                    return property;
-                }
+        return searching ? null : common;
+    }
+
+    /**
+     * Checks whether the datum and datum ensemble have equal values for a 
given property
+     *
+     * @param  <P>      the type of property to get.
+     * @param  <D>      the type of datum expected by the given {@code getter}.
+     * @param  getter   the method to invoke on {@link Datum} instances for 
getting the property.
+     * @param  nonNull  test about whether a property value is non-null or 
present.
+     * @return whether the property values are equal.
+     */
+    @SuppressWarnings("unchecked")
+    private <P, D extends Datum> boolean isPropertyEqual(final Class<D> 
datumType, final Function<D,P> getter, final Predicate<P> nonNull) {
+        P property = null;
+        if (datumType.isInstance(datum)) {
+            property = getter.apply((D) datum);
+            if (!nonNull.test(property)) {
+                // Property unspecified in the datum. Accept any value in the 
ensemble.
+                return true;
             }
         }
-        return Optional.empty();
+        return getEnsembleProperty(property, datumType, getter, nonNull) == 
property;
     }
 
     /**
-     * If the given object is a datum ensemble, returns its accuracy.
+     * If the given object is a datum ensemble or a <abbr>CRS</abbr> 
associated to a datum ensemble, returns its accuracy.
      *
      * @param  object  the object from which to get the ensemble accuracy, or 
{@code null}.
      * @return the datum ensemble accuracy if the given object is a datum 
ensemble.
      * @throws NullPointerException if the given object should provide an 
accuracy but didn't.
+     *
+     * @see 
org.apache.sis.referencing.CRS#getLinearAccuracy(CoordinateOperation)
      */
     public static Optional<PositionalAccuracy> getAccuracy(final 
IdentifiedObject object) {
         final DatumEnsemble<?> ensemble;
         if (object instanceof DatumEnsemble<?>) {
             ensemble = (DatumEnsemble<?>) object;
+        } else if (object instanceof SingleCRS) {
+            ensemble = ((SingleCRS) object).getDatumEnsemble();
+            if (ensemble == null) {
+                return Optional.empty();
+            }
         } else {
             return Optional.empty();
         }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java
index 91a2b9111b..88a6330dfa 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java
@@ -149,6 +149,7 @@ public final class VerticalDatumTypes {
                 case ELLIPSOIDAL: return 2002;      // CS_VD_Ellipsoidal
                 case BAROMETRIC:  return 2003;      // CS_VD_AltitudeBarometric
                 case "GEOID":     return 2005;      // CS_VD_GeoidModelDerived
+                case "LEVELLING": // From ISO: "adjustment of a levelling 
network fixed to one or more tide gauges".
                 case "TIDAL":     return 2006;      // CS_VD_Depth
             }
         }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTUtilities.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTUtilities.java
index ae07909d91..fe7e6f172e 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTUtilities.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTUtilities.java
@@ -30,8 +30,6 @@ import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
-import org.opengis.referencing.datum.Datum;
-import org.opengis.referencing.datum.GeodeticDatum;
 import org.opengis.referencing.datum.PrimeMeridian;
 import org.opengis.referencing.datum.Ellipsoid;
 import org.opengis.referencing.operation.Matrix;
@@ -40,9 +38,7 @@ import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.crs.AbstractCRS;
 import org.apache.sis.referencing.cs.AbstractCS;
 import org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis;
-import org.apache.sis.referencing.datum.AbstractDatum;
 import org.apache.sis.referencing.datum.DatumOrEnsemble;
-import org.apache.sis.referencing.datum.DefaultGeodeticDatum;
 import org.apache.sis.referencing.datum.DefaultPrimeMeridian;
 import org.apache.sis.referencing.datum.DefaultEllipsoid;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
@@ -164,34 +160,6 @@ public final class WKTUtilities extends Static {
         }
     }
 
-    /**
-     * Returns the given datum as a formattable object.
-     *
-     * @param  object  the datum, or {@code null}.
-     * @return the given datum as a formattable object, or {@code null}.
-     */
-    public static FormattableObject toFormattable(final Datum object) {
-        if (object instanceof FormattableObject) {
-            return (FormattableObject) object;
-        } else {
-            return AbstractDatum.castOrCopy(object);
-        }
-    }
-
-    /**
-     * Returns the given geodetic reference frame as a formattable object.
-     *
-     * @param  object  the datum, or {@code null}.
-     * @return the given datum as a formattable object, or {@code null}.
-     */
-    public static FormattableObject toFormattable(final GeodeticDatum object) {
-        if (object instanceof FormattableObject) {
-            return (FormattableObject) object;
-        } else {
-            return DefaultGeodeticDatum.castOrCopy(object);
-        }
-    }
-
     /**
      * Returns the ellipsoid as a formattable object.
      *
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/ConsistencyTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/ConsistencyTest.java
index 64d3f4ff07..2b95a3777d 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/ConsistencyTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/ConsistencyTest.java
@@ -99,7 +99,7 @@ public final class ConsistencyTest extends TestCase {
         final CoordinateReferenceSystem crs = CRS.forCode(code);
         final var format = new WKTFormat();
         format.setConvention(Convention.WKT2);
-        lookup(parseAndFormat(format, code, crs), crs);
+        lookup(parseAndFormat(format, code, crs), crs, true);
     }
 
     /**
@@ -131,8 +131,8 @@ public final class ConsistencyTest extends TestCase {
                     fail("Cannot create CRS for \"" + code + "\".", e);
                     continue;
                 }
-                lookup(parseAndFormat(v2,  code, crs), crs);
-                lookup(parseAndFormat(v2s, code, crs), crs);
+                lookup(parseAndFormat(v2,  code, crs), crs, false);
+                lookup(parseAndFormat(v2s, code, crs), crs, false);
                 /*
                  * There is more information lost in WKT 1 than in WKT 2, so 
we cannot test everything.
                  * For example, we cannot format fully three-dimensional 
geographic CRS because the unit
@@ -254,7 +254,7 @@ public final class ConsistencyTest extends TestCase {
     /**
      * Verifies that {@code IdentifiedObjects.lookupURN(…)} on the parsed CRS 
can find back the original CRS.
      */
-    private void lookup(final CoordinateReferenceSystem parsed, final 
CoordinateReferenceSystem crs) throws FactoryException {
+    private void lookup(final CoordinateReferenceSystem parsed, final 
CoordinateReferenceSystem crs, final boolean lossless) throws FactoryException {
         final Identifier id = IdentifiedObjects.getIdentifier(crs, null);
         final String urn = IdentifiedObjects.toURN(crs.getClass(), id);
         assertNotNull(urn, crs.getName().getCode());
@@ -270,6 +270,13 @@ public final class ConsistencyTest extends TestCase {
             toStandardUnit(parsed.getCoordinateSystem().getAxis(0).getUnit())))
         {
             assertTrue(Utilities.deepEquals(crs, parsed, 
ComparisonMode.DEBUG), urn);
+            /*
+             * The lookup operaiton may fail if the CRS has a vertical 
component parsed from
+             * a format older than ISO 19162:2019, because of missing 
"realization method".
+             */
+            if (!lossless) {
+                if (CRS.getVerticalComponent(crs, false) != null) return;
+            }
             /*
              * Now test the lookup operation. Since the parsed CRS has an 
identifier,
              * that lookup operation should not do a lot of work actually.

Reply via email to