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