You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2024/07/17 15:37:27 UTC
(sis) branch geoapi-3.1 updated (c4eceb3ef1 -> d3af997759)
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a change to branch geoapi-3.1
in repository https://gitbox.apache.org/repos/asf/sis.git
from c4eceb3ef1 Merge branch 'geoapi-4.0' into geoapi-3.1
add 58dee5a542 Documentation updates.
add 94d30d99d2 The referencing module needs to check if the datum is null, and fallback on the datum ensemble if needed. This commit does not resolve all cases. In particular, the cases that depend on the EPSG database update are not yet done: EPSGDataAccess, CoordinateOperationRegistry and CoordinateOperationFinder.
add 3bbf94a004 Modification in the implementation strategy of datum ensemble: allow a datum ensemble to be viewed as a pseudo-datum. This is trick for reducing the amount of null checks.
add f8421abcf5 Add "the" in some English error messages.
new d3af997759 Merge branch 'geoapi-4.0' into geoapi-3.1
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../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(-)
create mode 100644 endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractSingleCRS.java
create mode 100644 endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/PseudoDatum.java
(sis) 01/01: Merge branch 'geoapi-4.0' into geoapi-3.1
Posted by de...@apache.org.
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 <ma...@geomatys.com>
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.