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