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

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

commit d3af9977592bc92a3bf414f8c62c492d11d347c5
Merge: c4eceb3ef1 f8421abcf5
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Wed Jul 17 17:37:11 2024 +0200

    Merge branch 'geoapi-4.0' into geoapi-3.1

 .../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   |  13 +-
 .../sis/referencing/AbstractIdentifiedObject.java  |   7 +-
 .../main/org/apache/sis/referencing/CRS.java       |  34 +-
 .../main/org/apache/sis/referencing/CommonCRS.java |  76 ++-
 .../referencing/EllipsoidalHeightSeparator.java    |  28 +-
 .../sis/referencing/MultiRegisterOperations.java   |  26 +-
 .../apache/sis/referencing/crs/AbstractCRS.java    |  79 +--
 .../sis/referencing/crs/AbstractSingleCRS.java     | 314 +++++++++++
 .../sis/referencing/crs/DefaultDerivedCRS.java     |  22 +-
 .../sis/referencing/crs/DefaultEngineeringCRS.java |  48 +-
 .../sis/referencing/crs/DefaultGeocentricCRS.java  |   6 +-
 .../sis/referencing/crs/DefaultGeodeticCRS.java    |  44 +-
 .../sis/referencing/crs/DefaultGeographicCRS.java  |  42 +-
 .../sis/referencing/crs/DefaultImageCRS.java       |  30 +-
 .../sis/referencing/crs/DefaultParametricCRS.java  |  48 +-
 .../sis/referencing/crs/DefaultProjectedCRS.java   |  23 +-
 .../sis/referencing/crs/DefaultTemporalCRS.java    |  82 ++-
 .../sis/referencing/crs/DefaultVerticalCRS.java    |  48 +-
 .../sis/referencing/crs/ExplicitParameters.java    |   6 +-
 .../referencing/datum/DefaultDatumEnsemble.java    |  18 +-
 .../apache/sis/referencing/datum/PseudoDatum.java  | 601 +++++++++++++++++++++
 .../referencing/factory/GeodeticObjectFactory.java |  12 +-
 .../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   |  15 +-
 .../DefaultCoordinateOperationFactory.java         |  16 +-
 .../transform/DefaultMathTransformFactory.java     |   5 +-
 .../sis/referencing/privy/DefinitionVerifier.java  |  29 +-
 .../privy/EllipsoidalHeightCombiner.java           |  27 +-
 .../referencing/privy/GeodeticObjectBuilder.java   |  47 +-
 .../referencing/privy/ReferencingUtilities.java    | 132 +++--
 .../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 |  37 +-
 .../sis/storage/netcdf/base/GridMapping.java       |   9 +-
 .../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 +-
 52 files changed, 1535 insertions(+), 524 deletions(-)

diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EllipsoidalHeightSeparator.java
index 4b70ced344,4dfad99d15..366257c40e
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EllipsoidalHeightSeparator.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EllipsoidalHeightSeparator.java
@@@ -39,9 -39,9 +39,12 @@@ import org.apache.sis.util.resources.Er
  import org.apache.sis.referencing.factory.GeodeticObjectFactory;
  import static 
org.apache.sis.referencing.privy.ReferencingUtilities.getPropertiesForModifiedCRS;
  
 +// 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;
+ 
  
  /**
   * Helper class for separating the ellipsoidal height from the horizontal 
part of a CRS.
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java
index 9fafafbdc2,486ff17472..794042176d
--- 
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
@@@ -45,14 -42,13 +42,13 @@@ import org.apache.sis.util.Utilities
  import org.apache.sis.util.ComparisonMode;
  import org.apache.sis.util.resources.Errors;
  
 +// Specific to the main and geoapi-3.1 branches:
 +import org.opengis.referencing.crs.GeneralDerivedCRS;
 +import org.opengis.geometry.MismatchedDimensionException;
 +
  // Specific to the geoapi-3.1 and geoapi-4.0 branches:
  import org.opengis.metadata.Identifier;
- import org.opengis.referencing.datum.DatumEnsemble;
  
 -// Specific to the geoapi-4.0 branch:
 -import org.opengis.referencing.crs.DerivedCRS;
 -import org.opengis.coordinate.MismatchedDimensionException;
 -
  
  /**
   * Coordinate reference system, defined by a {@linkplain AbstractCS 
coordinate system}
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultImageCRS.java
index 1a466619d6,885a1679bd..3f46606280
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultImageCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultImageCRS.java
@@@ -27,12 -26,10 +26,11 @@@ import org.apache.sis.referencing.Abstr
  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.metadata.privy.ImplementationHelper;
  import org.apache.sis.io.wkt.Formatter;
  
 -// Specific to the geoapi-4.0 branch:
 -import org.apache.sis.referencing.datum.DefaultImageDatum;
 +// Specific to the main and geoapi-3.1 branches:
 +import org.opengis.referencing.crs.ImageCRS;
 +import org.opengis.referencing.datum.ImageDatum;
  
  
  /**
@@@ -70,7 -67,7 +68,7 @@@
      "datum"
  })
  @XmlRootElement(name = "ImageCRS")
- public final class DefaultImageCRS extends AbstractCRS implements ImageCRS {
 -public final class DefaultImageCRS extends 
AbstractSingleCRS<DefaultImageDatum> {
++public final class DefaultImageCRS extends AbstractSingleCRS<ImageDatum> 
implements ImageCRS {
      /**
       * Serial number for inter-operability with different versions.
       */
@@@ -125,15 -111,12 +112,14 @@@
       * @param  properties  the properties to be given to the coordinate 
reference system.
       * @param  datum       the datum.
       * @param  cs          the coordinate system.
 +     *
 +     * @see 
org.apache.sis.referencing.factory.GeodeticObjectFactory#createImageCRS(Map, 
ImageDatum, AffineCS)
       */
      public DefaultImageCRS(final Map<String,?> properties,
 -                           final DefaultImageDatum datum,
 -                           final AffineCS cs)
 +                           final ImageDatum    datum,
 +                           final AffineCS      cs)
      {
-         super(properties, cs);
-         this.datum = Objects.requireNonNull(datum);
 -        super(properties, DefaultImageDatum.class, datum, null, cs);
++        super(properties, ImageDatum.class, datum, null, cs);
      }
  
      /**
@@@ -142,56 -125,8 +128,54 @@@
       */
      private DefaultImageCRS(final DefaultImageCRS original, final AbstractCS 
cs) {
          super(original, null, cs);
-         datum = original.datum;
      }
  
 +    /**
 +     * 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>
 +     *
 +     * @param  crs  the coordinate reference system to copy.
 +     *
 +     * @see #castOrCopy(ImageCRS)
 +     */
 +    protected DefaultImageCRS(final ImageCRS crs) {
 +        super(crs);
-         datum = crs.getDatum();
 +    }
 +
 +    /**
 +     * 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 DefaultImageCRS castOrCopy(final ImageCRS object) {
 +        return (object == null) || (object instanceof DefaultImageCRS)
 +                ? (DefaultImageCRS) object : new DefaultImageCRS(object);
 +    }
 +
 +    /**
 +     * Returns the GeoAPI interface implemented by this class.
 +     * The SIS implementation returns {@code ImageCRS.class}.
 +     *
 +     * <h4>Note for implementers</h4>
 +     * Subclasses usually do not need to override this method since GeoAPI 
does not define {@code ImageCRS}
 +     * sub-interface. Overriding possibility is left mostly for implementers 
who wish to extend GeoAPI with
 +     * their own set of interfaces.
 +     *
 +     * @return {@code ImageCRS.class} or a user-defined sub-interface.
 +     */
 +    @Override
 +    public Class<? extends ImageCRS> getInterface() {
 +        return ImageCRS.class;
 +    }
 +
      /**
       * Returns the datum.
       *
@@@ -199,8 -134,8 +183,8 @@@
       */
      @Override
      @XmlElement(name = "imageDatum", required = true)
 -    public DefaultImageDatum getDatum() {
 +    public ImageDatum getDatum() {
-         return datum;
+         return super.getDatum();
      }
  
      /**
@@@ -289,12 -224,8 +273,8 @@@
       *
       * @see #getDatum()
       */
 -    private void setDatum(final DefaultImageDatum value) {
 +    private void setDatum(final ImageDatum value) {
-         if (datum == null) {
-             datum = value;
-         } else {
-             ImplementationHelper.propertyAlreadySet(DefaultImageCRS.class, 
"setDatum", "imageDatum");
-         }
+         setDatum("imageDatum", value);
      }
  
      /**
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
index 663090219d,d5af964f6f..c190cbe749
--- 
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
@@@ -40,12 -40,9 +40,13 @@@ import org.apache.sis.util.ArgumentChec
  import org.apache.sis.util.ComparisonMode;
  import org.apache.sis.util.Workaround;
  
 +// Specific to the main and geoapi-3.1 branches:
 +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;
  
  
  /**
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
index 625064eddc,2d157f8e2b..4eceaac3c7
--- 
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
@@@ -353,6 -323,22 +326,22 @@@ public class DefaultTemporalCRS extend
          return 
super.getCoordinateSystem().getAxis(0).getUnit().asType(Time.class);
      }
  
+     /**
+      * Returns the temporal origin which is indirectly (through a datum) 
associated to this <abbr>CRS</abbr>.
+      * If the {@linkplain #getDatum() datum} is non-null, then this method 
returns the datum origin.
+      * Otherwise, if all members of the {@linkplain #getDatumEnsemble() datum 
ensemble} use the same origin,
+      * then this method returns that origin.
+      *
+      * @return the origin indirectly associated to this <abbr>CRS</abbr>.
+      * @throws NullPointerException if an origin, which are mandatory, is 
null.
+      * @throws GeodeticException if the origin is not the same for all 
members of the datum ensemble.
+      *
+      * @since 1.5
+      */
+     public final Temporal getOrigin() {     // Must be final because invoked 
at construction time.
 -        return PseudoDatum.of(this).getOrigin();
++        return TemporalDate.toTemporal(PseudoDatum.of(this).getOrigin());
+     }
+ 
      /**
       * {@inheritDoc}
       *
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/ExplicitParameters.java
index 5984372e29,c476ed9e01..1a3266fe08
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/ExplicitParameters.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/ExplicitParameters.java
@@@ -59,10 -58,9 +58,9 @@@ final class ExplicitParameters extends 
      /**
       * Creates a new temporary {@code Conversion} elements for the parameters 
of the given CRS.
       */
 -    ExplicitParameters(final AbstractDerivedCRS crs, final String keyword) {
 +    ExplicitParameters(final AbstractDerivedCRS<?> crs, final String keyword) 
{
          conversion = crs.getConversionFromBase();
-         final Datum datum = crs.getDatum();
-         ellipsoid = (datum instanceof GeodeticDatum) ? ((GeodeticDatum) 
datum).getEllipsoid() : null;
+         ellipsoid = ReferencingUtilities.getEllipsoid(crs);
          this.keyword = keyword;
      }
  
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/PseudoDatum.java
index 0000000000,6d13d6200f..4ee7b6837c
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,589 +1,601 @@@
+ /*
+  * 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.metadata.Identifier;
+ 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 and geoapi-3.1 branches:
++import java.util.Date;
++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;
+ 
+     /**
+      * Creates a new pseudo-datum.
+      *
+      * @param ensemble the datum ensemble wrapped by this pseudo-datum.
+      */
+     protected PseudoDatum(final DatumEnsemble<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();
+     }
+ 
+     /**
+      * 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());
+         }
+         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());
+         }
+         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());
+         }
+         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());
+         }
+         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 Identifier getName() {
++    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<Identifier> getIdentifiers() {
++    public Set<ReferenceIdentifier> getIdentifiers() {
+         return ensemble.getIdentifiers();
+     }
+ 
+     /**
+      * Returns the usage of the datum ensemble.
+      *
+      * @return {@code ensemble.getDomains()}.
+      * @hidden
+      */
+     @Override
+     public Collection<ObjectDomain> getDomains() {
+         return ensemble.getDomains();
+     }
+ 
+     /**
+      * 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.
+      *
+      * @return the common anchor definition, or empty if there is no common 
value.
+      */
+     @Override
+     public Optional<InternationalString> getAnchorDefinition() {
+         return getCommonOptionalValue(Datum::getAnchorDefinition);
+     }
+ 
+     /**
+      * Returns an anchor 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.
+      *
+      * @return the common anchor epoch, or empty if there is no common value.
+      */
+     @Override
+     public Optional<Temporal> getAnchorEpoch() {
+         return getCommonOptionalValue(Datum::getAnchorEpoch);
+     }
+ 
+     /**
+      * 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.
+      *
+      * @return the common publication date, or empty if there is no common 
value.
+      */
+     @Override
+     public Optional<Temporal> getPublicationDate() {
+         return getCommonOptionalValue(Datum::getPublicationDate);
+     }
+ 
+     /**
+      * 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.
+      *
+      * @return the common conventional reference system, or empty if there is 
no common value.
+      */
+     @Override
+     public Optional<IdentifiedObject> getConventionalRS() {
+         return getCommonOptionalValue(Datum::getConventionalRS);
+     }
+ 
+     /**
+      * 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 Optional<InternationalString> getRemarks() {
++    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) {
+             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) {
+             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.
+          *
+          * @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()}.
++         */
++        @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) {
+             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 Temporal getOrigin() {
++        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) {
+             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/privy/DefinitionVerifier.java
index 626bfe9463,5e1bd59b0d..d2d82bca6d
--- 
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,9 -306,8 +306,9 @@@ 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 8dfe6c7152,0a9ac3d3c6..fabc5f747a
--- 
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
@@@ -143,13 -144,15 +144,15 @@@ 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);
                      } else {
                          final ProjectedCRS proj = (ProjectedCRS) crs;
 -                        GeodeticCRS base = proj.getBaseCRS();
 +                        GeographicCRS base = proj.getBaseCRS();
                          if (base.getCoordinateSystem().getDimension() == 2) {
 -                            base = (GeodeticCRS) createCompoundCRS(
 +                            base = (GeographicCRS) createCompoundCRS(
                                      IdentifiedObjects.getProperties(base, 
GeographicCRS.IDENTIFIERS_KEY), base, vertical);
                          }
                          /*
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/GeodeticObjectBuilder.java
index 897b375bbd,bd34098df3..df58dc19ca
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/GeodeticObjectBuilder.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/GeodeticObjectBuilder.java
@@@ -63,8 -63,12 +63,9 @@@ import org.apache.sis.parameter.Paramet
  
  // Specific to the geoapi-3.1 and geoapi-4.0 branches:
  import org.opengis.referencing.ObjectDomain;
+ import org.opengis.referencing.datum.DatumEnsemble;
  import org.opengis.referencing.operation.MathTransform;
  
 -// Specific to the geoapi-4.0 branch:
 -import org.opengis.referencing.crs.GeodeticCRS;
 -
  
  /**
   * Helper methods for building Coordinate Reference Systems and related 
objects.


Reply via email to