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 3bbf94a0042b0a2e40bf1623eeda0bdd448eca0c Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Wed Jul 17 12:50:56 2024 +0200 Modification in the implementation strategy of datum ensemble: allow a datum ensemble to be viewed as a pseudo-datum. This is trick for reducing the amount of null checks. --- .../main/org/apache/sis/io/wkt/VerticalInfo.java | 5 +- .../sis/referencing/AbstractIdentifiedObject.java | 7 +- .../main/org/apache/sis/referencing/CommonCRS.java | 37 +- .../referencing/EllipsoidalHeightSeparator.java | 4 +- .../sis/referencing/MultiRegisterOperations.java | 31 +- .../apache/sis/referencing/crs/AbstractCRS.java | 81 +-- .../sis/referencing/crs/AbstractSingleCRS.java | 314 +++++++++++ .../sis/referencing/crs/DefaultDerivedCRS.java | 22 +- .../sis/referencing/crs/DefaultEngineeringCRS.java | 46 +- .../sis/referencing/crs/DefaultGeocentricCRS.java | 2 +- .../sis/referencing/crs/DefaultGeodeticCRS.java | 44 +- .../sis/referencing/crs/DefaultGeographicCRS.java | 102 ++-- .../sis/referencing/crs/DefaultImageCRS.java | 28 +- .../sis/referencing/crs/DefaultParametricCRS.java | 46 +- .../sis/referencing/crs/DefaultProjectedCRS.java | 23 +- .../sis/referencing/crs/DefaultTemporalCRS.java | 71 +-- .../sis/referencing/crs/DefaultVerticalCRS.java | 46 +- .../referencing/datum/DefaultDatumEnsemble.java | 14 +- .../apache/sis/referencing/datum/PseudoDatum.java | 589 +++++++++++++++++++++ .../operation/CoordinateOperationFinder.java | 21 +- .../operation/CoordinateOperationRegistry.java | 12 +- .../DefaultCoordinateOperationFactory.java | 6 +- .../sis/referencing/privy/DefinitionVerifier.java | 5 +- .../privy/EllipsoidalHeightCombiner.java | 3 +- .../referencing/privy/GeodeticObjectBuilder.java | 21 +- .../referencing/privy/ReferencingUtilities.java | 44 +- .../sis/storage/geotiff/reader/CRSBuilder.java | 5 +- .../sis/storage/geotiff/writer/GeoEncoder.java | 65 ++- .../main/org/apache/sis/storage/csv/Store.java | 9 +- .../org/apache/sis/gui/map/OperationFinder.java | 5 +- .../main/org/apache/sis/gui/referencing/Utils.java | 6 +- 31 files changed, 1219 insertions(+), 495 deletions(-) diff --git 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 index a11f49dbec..5db9ddb99c 100644 --- 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 @@ -27,7 +27,7 @@ import org.opengis.referencing.cs.CSFactory; import org.opengis.referencing.cs.VerticalCS; import org.opengis.referencing.crs.CRSFactory; import org.opengis.referencing.crs.VerticalCRS; -import org.opengis.referencing.datum.VerticalDatum; +import org.apache.sis.referencing.datum.PseudoDatum; import org.apache.sis.metadata.privy.AxisNames; import org.apache.sis.metadata.iso.extent.DefaultExtent; import org.apache.sis.metadata.iso.extent.DefaultVerticalExtent; @@ -109,8 +109,7 @@ final class VerticalInfo { */ final VerticalInfo resolve(final VerticalCRS crs) { if (crs != null) { - final VerticalDatum datum = crs.getDatum(); - if (datum != null && datum.getRealizationMethod().orElse(null) == RealizationMethod.GEOID) { + if (PseudoDatum.of(crs).getRealizationMethod().orElse(null) == RealizationMethod.GEOID) { return resolve(crs, crs.getCoordinateSystem().getAxis(0)); } } diff --git 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 index 679b1ed56e..aefc4a31d1 100644 --- 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 @@ -449,10 +449,13 @@ public class AbstractIdentifiedObject extends FormattableObject implements Ident * * <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()); diff --git 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 index b8331ff590..6d4457e1bd 100644 --- 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 @@ -67,11 +67,11 @@ import org.apache.sis.referencing.crs.DefaultGeocentricCRS; import org.apache.sis.referencing.crs.DefaultEngineeringCRS; import org.apache.sis.referencing.factory.GeodeticAuthorityFactory; import org.apache.sis.referencing.factory.UnavailableFactoryException; -import org.apache.sis.metadata.iso.citation.Citations; import org.apache.sis.referencing.operation.provider.TransverseMercator; import org.apache.sis.referencing.privy.ReferencingUtilities; import org.apache.sis.referencing.privy.Formulas; import org.apache.sis.referencing.internal.Resources; +import org.apache.sis.metadata.iso.citation.Citations; import org.apache.sis.system.SystemListener; import org.apache.sis.system.Modules; import org.apache.sis.util.OptionalCandidate; @@ -501,7 +501,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()); if (c != null) return c; } throw new IllegalArgumentException(Errors.format( @@ -511,10 +511,11 @@ public enum CommonCRS { /** * 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 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) { /* * 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 @@ -531,7 +532,15 @@ public enum CommonCRS { } } for (final CommonCRS c : values()) { - if ((epsg != 0) ? c.datum == epsg : Utilities.equalsIgnoreMetadata(c.datum(), datum)) { + final boolean filter; + if (epsg != 0) { + filter = c.datum == epsg; + } else if (datum != null) { + filter = Utilities.equalsIgnoreMetadata(c.datum(), datum); + } else { + filter = Utilities.equalsIgnoreMetadata(c.datumEnsemble(), ensemble); + } + if (filter) { return c; } } @@ -2063,6 +2072,24 @@ public enum CommonCRS { public EngineeringDatum datum() { return datum; } + + /** + * Returns {@code true} is the given <abbr>CRS</abbr> uses the datum identified by this enumeration value. + * The association may be direct through {@link SingleCRS#getDatum()}, or indirect throw at least one of + * the members of {@link SingleCRS#getDatumEnsemble()}. + * + * @param crs the CRS to compare against the datum of this enumeration value. May be {@code null}. + * @return whether the given <abbr>CRS</abbr> uses the datum, directly or indirectly. + * @since 1.5 + */ + public boolean datumUsedBy(final CoordinateReferenceSystem crs) { + for (final SingleCRS component : CRS.getSingleComponents(crs)) { + if (ReferencingUtilities.uses(component, datum)) { + return true; + } + } + return false; + } } /** diff --git 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 index e4ca8f5200..4dfad99d15 100644 --- 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 @@ -127,10 +127,10 @@ final class EllipsoidalHeightSeparator implements AxisFilter { } 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, ensemble, (EllipsoidalCS) cs); diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/MultiRegisterOperations.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/MultiRegisterOperations.java index 25b4aaede9..9b8ebfef7e 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/MultiRegisterOperations.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/MultiRegisterOperations.java @@ -38,11 +38,13 @@ import org.opengis.referencing.operation.CoordinateOperation; import org.opengis.referencing.operation.CoordinateOperationFactory; import org.opengis.referencing.operation.CoordinateOperationAuthorityFactory; import org.opengis.referencing.operation.MathTransformFactory; +import org.apache.sis.referencing.datum.PseudoDatum; import org.apache.sis.referencing.factory.GeodeticObjectFactory; import org.apache.sis.referencing.factory.MultiAuthoritiesFactory; import org.apache.sis.referencing.factory.NoSuchAuthorityFactoryException; import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory; import org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory; +import org.apache.sis.util.Utilities; import org.apache.sis.util.logging.Logging; import org.apache.sis.util.resources.Errors; import org.apache.sis.util.iso.AbstractFactory; @@ -50,7 +52,6 @@ import org.apache.sis.util.iso.AbstractFactory; // Specific to the geoapi-3.1 and geoapi-4.0 branches: import org.opengis.referencing.RegisterOperations; import org.opengis.referencing.crs.SingleCRS; -import org.opengis.referencing.crs.CompoundCRS; import org.apache.sis.referencing.privy.ReferencingUtilities; @@ -342,22 +343,24 @@ public class MultiRegisterOperations extends AbstractFactory implements Register public boolean areMembersOfSameEnsemble(CoordinateReferenceSystem source, CoordinateReferenceSystem target) throws FactoryException { - if (source instanceof SingleCRS && target instanceof SingleCRS) { - return ReferencingUtilities.areMembersOfSameEnsemble((SingleCRS) source, (SingleCRS) target); + final List<SingleCRS> sources = CRS.getSingleComponents(source); + final List<SingleCRS> targets = CRS.getSingleComponents(target); + final int n = targets.size(); + if (sources.size() != n) { + return false; } - if (source instanceof CompoundCRS && target instanceof CompoundCRS) { - final List<SingleCRS> sources = ((CompoundCRS) source).getSingleComponents(); - final List<SingleCRS> targets = ((CompoundCRS) target).getSingleComponents(); - final int n = targets.size(); - if (sources.size() == n) { - for (int i=0; i<n; i++) { - if (!ReferencingUtilities.areMembersOfSameEnsemble(sources.get(i), targets.get(i))) { - return false; - } - } + for (int i=0; i<n; i++) { + final var crs1 = sources.get(i); + final var crs2 = targets.get(i); + if (!(Utilities.equalsIgnoreMetadata(PseudoDatum.getDatumOrEnsemble(crs1), + PseudoDatum.getDatumOrEnsemble(crs2)) + || ReferencingUtilities.uses(crs1, crs2.getDatum()) + || ReferencingUtilities.uses(crs2, crs1.getDatum()))) + { + return false; } } - return false; + return true; } /** 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 30f4513010..486ff17472 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 @@ -31,16 +31,13 @@ import org.opengis.referencing.cs.CoordinateSystem; import org.opengis.referencing.crs.SingleCRS; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.apache.sis.referencing.AbstractReferenceSystem; -import org.apache.sis.referencing.IdentifiedObjects; import org.apache.sis.referencing.cs.AbstractCS; import org.apache.sis.referencing.cs.AxesConvention; -import org.apache.sis.referencing.internal.Resources; import org.apache.sis.referencing.privy.WKTUtilities; import org.apache.sis.referencing.privy.ReferencingUtilities; 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.util.ArgumentChecks; import org.apache.sis.util.Utilities; import org.apache.sis.util.ComparisonMode; import org.apache.sis.util.resources.Errors; @@ -50,7 +47,6 @@ import org.opengis.metadata.Identifier; // Specific to the geoapi-4.0 branch: import org.opengis.referencing.crs.DerivedCRS; -import org.opengis.referencing.datum.DatumEnsemble; import org.opengis.coordinate.MismatchedDimensionException; @@ -96,13 +92,7 @@ import org.opengis.coordinate.MismatchedDimensionException; @XmlType(name = "AbstractCRSType") @XmlRootElement(name = "AbstractCRS") @XmlSeeAlso({ - AbstractDerivedCRS.class, - DefaultGeodeticCRS.class, - DefaultVerticalCRS.class, - DefaultTemporalCRS.class, - DefaultParametricCRS.class, - DefaultEngineeringCRS.class, - DefaultImageCRS.class, + AbstractSingleCRS.class, DefaultCompoundCRS.class }) public class AbstractCRS extends AbstractReferenceSystem implements CoordinateReferenceSystem { @@ -115,7 +105,7 @@ public class AbstractCRS extends AbstractReferenceSystem implements CoordinateRe * The coordinate system. * * <p><b>Consider this field as final!</b> - * This field is modified only at unmarshalling time by {@link #setCoordinateSystem(String, CoordinateSystem)}</p> + * This field is modified only at unmarshalling time by {@link #setCoordinateSystem(String, CoordinateSystem)}.</p> * * @see #getCoordinateSystem() */ @@ -134,13 +124,13 @@ public class AbstractCRS extends AbstractReferenceSystem implements CoordinateRe /** * Creates the value to assign to the {@link #forConvention} map by constructors. + * {@code this} instance will be the <abbr>CRS</abbr> to declare as the original one. * - * @param original the coordinate system to declare as the original one. * @return map to assign to the {@link #forConvention} field. */ - private static EnumMap<AxesConvention,AbstractCRS> forConvention(final AbstractCRS original) { + private EnumMap<AxesConvention,AbstractCRS> forConvention() { var m = new EnumMap<AxesConvention,AbstractCRS>(AxesConvention.class); - m.put(AxesConvention.ORIGINAL, original); + m.put(AxesConvention.ORIGINAL, this); return m; } @@ -186,7 +176,7 @@ public class AbstractCRS extends AbstractReferenceSystem implements CoordinateRe public AbstractCRS(final Map<String,?> properties, final CoordinateSystem cs) { super(properties); coordinateSystem = Objects.requireNonNull(cs); - forConvention = forConvention(this); + forConvention = forConvention(); } /** @@ -211,36 +201,10 @@ public class AbstractCRS extends AbstractReferenceSystem implements CoordinateRe Errors.Keys.MismatchedDimension_3, "cs", expected, actual)); } - /** - * Verifies the consistency between the datum and the ensemble. - * At least one of the {@code datum} and {@code ensemble} arguments shall be non-null. - * - * @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. - * @throws NullPointerException if both arguments are null. - * @throws IllegalArgumentException if the datum is not a member of the ensemble. - */ - static <D extends Datum> void checkDatum(final D datum, final DatumEnsemble<D> ensemble) { - 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.format(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 coordinate system from which to derive a new one. + * @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. * @@ -249,7 +213,7 @@ public class AbstractCRS extends AbstractReferenceSystem implements CoordinateRe AbstractCRS(final AbstractCRS original, final Identifier 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(); } /** @@ -267,7 +231,10 @@ public class AbstractCRS extends AbstractReferenceSystem implements CoordinateRe protected AbstractCRS(final CoordinateReferenceSystem crs) { super(crs); coordinateSystem = crs.getCoordinateSystem(); - forConvention = forConvention(this); + if (coordinateSystem == null) { + throw new IllegalArgumentException(Errors.format(Errors.Keys.MissingValueForProperty_1, "coordinateSystem")); + } + forConvention = forConvention(); } /** @@ -318,10 +285,8 @@ 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. But Apache SIS does not define an {@code AbstractSingleCRS} class - * in order to simplify our class hierarchy, so we provide a datum getter in this class has a hidden property. - * Subclasses implementing {@code SingleCRS} (basically all SIS subclasses except {@link DefaultCompoundCRS}) - * will override this method with public access and more specific return type. + * {@link SingleCRS} sub-interface instead. This method is defined here for the convenience of the + * {@link #formatTo(Formatter)} method implementation. * * @return the datum, or {@code null} if none. */ @@ -380,8 +345,8 @@ public class AbstractCRS extends AbstractReferenceSystem implements CoordinateRe } /** - * Returns a coordinate reference system equivalent to this one but with axes rearranged according the given - * convention. If this CRS is already compatible with the given convention, then this method returns {@code this}. + * Returns a <abbr>CRS</abbr> equivalent to this one but with axes rearranged according the given convention. + * If this <abbr>CRS</abbr> is already compatible with the given convention, then this method returns {@code this}. * * @param convention the axes convention for which a coordinate reference system is desired. * @return a coordinate reference system compatible with the given convention (may be {@code this}). @@ -434,16 +399,14 @@ public class AbstractCRS extends AbstractReferenceSystem implements CoordinateRe @Override public boolean equals(final Object object, final ComparisonMode mode) { if (super.equals(object, mode)) { - final Datum datum = getDatum(); switch (mode) { case STRICT: { - final AbstractCRS that = (AbstractCRS) object; - return Objects.equals(datum, that.getDatum()) && - Objects.equals(coordinateSystem, that.coordinateSystem); + final var that = (AbstractCRS) object; + return Objects.equals(coordinateSystem, that.coordinateSystem); } default: { - return Utilities.deepEquals(datum, (object instanceof SingleCRS) ? ((SingleCRS) object).getDatum() : null, mode) && - Utilities.deepEquals(getCoordinateSystem(), ((CoordinateReferenceSystem) object).getCoordinateSystem(), mode); + final var that = (CoordinateReferenceSystem) object; + return Utilities.deepEquals(getCoordinateSystem(), that.getCoordinateSystem(), mode); } } } @@ -459,7 +422,7 @@ public class AbstractCRS extends AbstractReferenceSystem implements CoordinateRe */ @Override protected long computeHashCode() { - return super.computeHashCode() + Objects.hash(getDatum(), coordinateSystem); + return super.computeHashCode() + coordinateSystem.hashCode(); } /** @@ -590,7 +553,7 @@ public class AbstractCRS extends AbstractReferenceSystem implements CoordinateRe */ AbstractCRS() { super(org.apache.sis.referencing.privy.NilReferencingObject.INSTANCE); - forConvention = forConvention(this); + forConvention = forConvention(); /* * 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 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 new file mode 100644 index 0000000000..6cff7e3c74 --- /dev/null +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractSingleCRS.java @@ -0,0 +1,314 @@ +/* + * 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; + + +/** + * 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; + + /** + * 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 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) { + 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(); + 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() { + 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); + } + } + } + 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 --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 edf9df9a2f..75c85fc31c 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 @@ -397,15 +397,33 @@ public class DefaultDerivedCRS extends AbstractDerivedCRS implements DerivedCRS } /** - * Returns the datum of the {@linkplain #getBaseCRS() base CRS}. + * Returns the datum 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 #getDatumEnsemble() datum ensemble}. * - * @return the datum of the base CRS. + * @return the datum of the {@linkplain #getBaseCRS() base CRS}, or {@code null} if this <abbr>CRS</abbr> + * is related to an object identified only by a {@linkplain #getDatumEnsemble() datum ensemble}. */ @Override public Datum getDatum() { 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(); + } + /** * Returns the CRS on which the conversion 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/DefaultEngineeringCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java index aaae14d88e..e08f83a07a 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,7 +26,6 @@ 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.metadata.privy.ImplementationHelper; import org.apache.sis.referencing.privy.WKTKeywords; import org.apache.sis.xml.bind.referencing.CS_CoordinateSystem; import org.apache.sis.io.wkt.Formatter; @@ -81,31 +80,11 @@ import org.opengis.referencing.datum.DatumEnsemble; "datum" }) @XmlRootElement(name = "EngineeringCRS") -public class DefaultEngineeringCRS extends AbstractCRS implements EngineeringCRS { +public class DefaultEngineeringCRS extends AbstractSingleCRS<EngineeringDatum> implements EngineeringCRS { /** * Serial number for inter-operability with different versions. */ - private static final long serialVersionUID = 6695541732063382701L; - - /** - * The datum, or {@code null} if the CRS is associated only to a datum ensemble. - * - * <p><b>Consider this field as final!</b> - * This field is modified only at unmarshalling time by {@link #setDatum(EngineeringDatum)}</p> - * - * @see #getDatum() - */ - @SuppressWarnings("serial") // Most SIS implementations are serializable. - private EngineeringDatum 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<EngineeringDatum> ensemble; + private static final long serialVersionUID = -5716016061569447341L; /** * Creates a coordinate reference system from the given properties, datum and coordinate system. @@ -158,10 +137,7 @@ public class DefaultEngineeringCRS extends AbstractCRS implements EngineeringCRS final DatumEnsemble<EngineeringDatum> ensemble, final CoordinateSystem cs) { - super(properties, cs); - this.datum = datum; - this.ensemble = ensemble; - checkDatum(datum, ensemble); + super(properties, EngineeringDatum.class, datum, ensemble, cs); } /** @@ -181,8 +157,6 @@ public class DefaultEngineeringCRS extends AbstractCRS implements EngineeringCRS */ private DefaultEngineeringCRS(final DefaultEngineeringCRS original, final AbstractCS cs) { super(original, null, cs); - datum = original.datum; - ensemble = original.ensemble; } /** @@ -198,9 +172,6 @@ public class DefaultEngineeringCRS extends AbstractCRS implements EngineeringCRS */ protected DefaultEngineeringCRS(final EngineeringCRS crs) { super(crs); - datum = crs.getDatum(); - ensemble = crs.getDatumEnsemble(); - checkDatum(datum, ensemble); } /** @@ -245,7 +216,7 @@ public class DefaultEngineeringCRS extends AbstractCRS implements EngineeringCRS @Override @XmlElement(name = "engineeringDatum", required = true) public EngineeringDatum getDatum() { - return datum; + return super.getDatum(); } /** @@ -261,7 +232,7 @@ public class DefaultEngineeringCRS extends AbstractCRS implements EngineeringCRS */ @Override public DatumEnsemble<EngineeringDatum> getDatumEnsemble() { - return ensemble; + return super.getDatumEnsemble(); } /** @@ -322,7 +293,6 @@ public class DefaultEngineeringCRS extends AbstractCRS implements EngineeringCRS * reserved to JAXB, which will assign values to the fields using reflection. */ private DefaultEngineeringCRS() { - ensemble = null; /* * The datum and the coordinate system are mandatory for SIS working. We do not verify their presence * here because the verification would have to be done in an 'afterMarshal(…)' method and throwing an @@ -337,11 +307,7 @@ public class DefaultEngineeringCRS extends AbstractCRS implements EngineeringCRS * @see #getDatum() */ private void setDatum(final EngineeringDatum value) { - if (datum == null) { - datum = value; - } else { - ImplementationHelper.propertyAlreadySet(DefaultEngineeringCRS.class, "setDatum", "engineeringDatum"); - } + setDatum("engineeringDatum", value); } /** diff --git 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 index 7685aa9720..d33a7d34d8 100644 --- 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 @@ -275,7 +275,7 @@ public class DefaultGeocentricCRS extends DefaultGeodeticCRS { */ @Override public DatumEnsemble<GeodeticDatum> getDatumEnsemble() { - return ensemble; + return super.getDatumEnsemble(); } /** 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 7f706ab223..c46575f586 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 @@ -39,7 +39,6 @@ import org.apache.sis.referencing.privy.AxisDirections; import org.apache.sis.referencing.privy.WKTKeywords; import org.apache.sis.referencing.privy.WKTUtilities; import org.apache.sis.referencing.privy.ReferencingUtilities; -import org.apache.sis.metadata.privy.ImplementationHelper; import org.apache.sis.util.resources.Errors; import org.apache.sis.io.wkt.Convention; import org.apache.sis.io.wkt.Formatter; @@ -71,31 +70,11 @@ import org.opengis.referencing.datum.DatumEnsemble; "datum" }) @XmlRootElement(name = "GeodeticCRS") -class DefaultGeodeticCRS extends AbstractCRS implements GeodeticCRS { // If made public, see comment in getDatum(). +class DefaultGeodeticCRS extends AbstractSingleCRS<GeodeticDatum> implements GeodeticCRS { /** * Serial number for inter-operability with different versions. */ - private static final long serialVersionUID = -6205678223972395910L; - - /** - * The datum, or {@code null} if the CRS is associated only to a datum ensemble. - * - * <p><b>Consider this field as final!</b> - * This field is modified only at unmarshalling time by {@link #setDatum(GeodeticDatum)}</p> - * - * @see #getDatum() - */ - @SuppressWarnings("serial") // Most SIS implementations are serializable. - private GeodeticDatum 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. - final DatumEnsemble<GeodeticDatum> ensemble; + private static final long serialVersionUID = -1634312292667977126L; /** * Creates a coordinate reference system from the given properties, datum and coordinate system. @@ -115,10 +94,7 @@ class DefaultGeodeticCRS extends AbstractCRS implements GeodeticCRS { // If ma final DatumEnsemble<GeodeticDatum> ensemble, final CoordinateSystem cs) { - super(properties, cs); - this.datum = datum; - this.ensemble = ensemble; - checkDatum(datum, ensemble); + super(properties, GeodeticDatum.class, datum, ensemble, cs); } /** @@ -127,8 +103,6 @@ class DefaultGeodeticCRS extends AbstractCRS implements GeodeticCRS { // If ma */ DefaultGeodeticCRS(final DefaultGeodeticCRS original, final Identifier id, final AbstractCS cs) { super(original, id, cs); - datum = original.datum; - ensemble = original.ensemble; } /** @@ -142,9 +116,6 @@ class DefaultGeodeticCRS extends AbstractCRS implements GeodeticCRS { // If ma */ protected DefaultGeodeticCRS(final GeodeticCRS crs) { super(crs); - datum = crs.getDatum(); - ensemble = crs.getDatumEnsemble(); - checkDatum(datum, ensemble); } /** @@ -184,7 +155,7 @@ class DefaultGeodeticCRS extends AbstractCRS implements GeodeticCRS { // If ma @Override @XmlElement(name = "geodeticDatum", required = true) public GeodeticDatum getDatum() { - return datum; + return super.getDatum(); } /** @@ -329,7 +300,6 @@ class DefaultGeodeticCRS extends AbstractCRS implements GeodeticCRS { // If ma * reserved to JAXB, which will assign values to the fields using reflection. */ DefaultGeodeticCRS() { - ensemble = null; /* * The datum and the coordinate system are mandatory for SIS working. We do not verify their presence * here because the verification would have to be done in an `afterMarshal(…)` method and throwing an @@ -344,11 +314,7 @@ class DefaultGeodeticCRS extends AbstractCRS implements GeodeticCRS { // If ma * @see #getDatum() */ private void setDatum(final GeodeticDatum value) { - if (datum == null) { - datum = value; - } else { - ImplementationHelper.propertyAlreadySet(DefaultGeodeticCRS.class, "setDatum", "geodeticDatum"); - } + setDatum("geodeticDatum", value); } /** diff --git 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 index 40ff61cca7..fd685ca0b9 100644 --- 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 @@ -18,9 +18,8 @@ package org.apache.sis.referencing.crs; import java.util.Map; import java.util.Arrays; -import java.util.Iterator; +import java.util.NoSuchElementException; import jakarta.xml.bind.annotation.XmlTransient; -import org.opengis.referencing.IdentifiedObject; import org.opengis.referencing.datum.Ellipsoid; import org.opengis.referencing.datum.PrimeMeridian; import org.opengis.referencing.datum.GeodeticDatum; @@ -31,12 +30,11 @@ import org.opengis.referencing.cs.CoordinateSystem; import org.opengis.referencing.cs.CoordinateSystemAxis; import org.apache.sis.metadata.iso.citation.Citations; import org.apache.sis.referencing.GeodeticException; -import org.apache.sis.referencing.IdentifiedObjects; import org.apache.sis.referencing.ImmutableIdentifier; import org.apache.sis.referencing.AbstractReferenceSystem; +import org.apache.sis.referencing.datum.PseudoDatum; import org.apache.sis.referencing.cs.AxesConvention; import org.apache.sis.referencing.cs.AbstractCS; -import org.apache.sis.util.resources.Errors; import org.apache.sis.io.wkt.Formatter; import org.apache.sis.measure.Longitude; import static org.apache.sis.util.privy.Constants.CRS; @@ -252,6 +250,38 @@ public class DefaultGeographicCRS extends DefaultGeodeticCRS implements Geograph return GeographicCRS.class; } + /** + * Returns the prime meridian which is indirectly (through a datum) associated to this <abbr>CRS</abbr>. + * If the {@linkplain #getDatum() datum} is non-null, then this method returns the datum prime meridian. + * Otherwise, if all members of the {@linkplain #getDatumEnsemble() datum ensemble} use the same prime meridian, + * then this method returns that meridian. + * + * @return the prime meridian indirectly associated to this <abbr>CRS</abbr>. + * @throws NoSuchElementException if there is no datum and 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. + * + * @since 1.5 + */ + public PrimeMeridian getPrimeMeridian() { + return PseudoDatum.of(this).getPrimeMeridian(); + } + + /** + * Returns the ellipsoid which is indirectly (through a datum) associated to this <abbr>CRS</abbr>. + * If the {@linkplain #getDatum() datum} is non-null, then this method returns the datum ellipsoid. + * Otherwise, if all members of the {@linkplain #getDatumEnsemble() datum ensemble} use the same ellipsoid, + * then this method returns that ellipsoid. + * + * @return the ellipsoid indirectly associated to this <abbr>CRS</abbr>. + * @throws NoSuchElementException if there is no datum and 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. + * + * @since 1.5 + */ + public Ellipsoid getEllipsoid() { + return PseudoDatum.of(this).getEllipsoid(); + } + /** * Returns the geodetic reference frame associated to this geographic CRS. * This property may be null if this <abbr>CRS</abbr> is related to an object @@ -278,7 +308,7 @@ public class DefaultGeographicCRS extends DefaultGeodeticCRS implements Geograph */ @Override public DatumEnsemble<GeodeticDatum> getDatumEnsemble() { - return ensemble; + return super.getDatumEnsemble(); } /** @@ -291,68 +321,6 @@ public class DefaultGeographicCRS extends DefaultGeodeticCRS implements Geograph return (EllipsoidalCS) super.getCoordinateSystem(); } - /** - * Returns the ellipsoid which is indirectly (through a datum) associated to this <abbr>CRS</abbr>. - * If the {@linkplain #getDatum() datum} is non-null, then this method returns the datum ellipsoid. - * Otherwise, if all members of the {@linkplain #getDatumEnsemble() datum ensemble} use the same ellipsoid, - * then this method returns that ellipsoid. - * - * @return the ellipsoid indirectly associated to this <abbr>CRS</abbr>. - * @throws NullPointerException if an ellipsoid, which are mandatory in the context of geographic <abbr>CRS</abbr>, is null. - * @throws GeodeticException if the ellipsoid is not the same for all members of the datum ensemble. - * - * @since 1.5 - */ - public Ellipsoid getEllipsoid() { - final GeodeticDatum datum = super.getDatum(); - if (datum != null) { - return datum.getEllipsoid(); // Has precedence regardless the value. - } - // If the datum is null, then the datum ensemble must be non-null. - final Iterator<GeodeticDatum> it = ensemble.getMembers().iterator(); - final Ellipsoid ellipsoid = it.next().getEllipsoid(); // Mandatory - while (it.hasNext()) { - checkDatumConsistency(ellipsoid, it.next().getEllipsoid()); - } - return ellipsoid; - } - - /** - * Returns the prime meridian which is indirectly (through a datum) associated to this <abbr>CRS</abbr>. - * If the {@linkplain #getDatum() datum} is non-null, then this method returns the datum prime meridian. - * Otherwise, if all members of the {@linkplain #getDatumEnsemble() datum ensemble} use the same prime meridian, - * then this method returns that meridian. - * - * @return the prime meridian indirectly associated to this <abbr>CRS</abbr>. - * @throws NullPointerException if a prime meridian, which are mandatory, is null. - * @throws GeodeticException if the prime meridian is not the same for all members of the datum ensemble. - * - * @since 1.5 - */ - public PrimeMeridian getPrimeMeridian() { - final GeodeticDatum datum = super.getDatum(); - if (datum != null) { - return datum.getPrimeMeridian(); // Has precedence regardless the value. - } - // If the datum is null, then the datum ensemble must be non-null. - final Iterator<GeodeticDatum> it = ensemble.getMembers().iterator(); - final PrimeMeridian pm = it.next().getPrimeMeridian(); // Mandatory - while (it.hasNext()) { - checkDatumConsistency(pm, it.next().getPrimeMeridian()); - } - return pm; - } - - /** - * Ensures that the ellipsoid or prime meridian has the same value in all members of a datum ensemble. - */ - private static void checkDatumConsistency(final IdentifiedObject expected, final IdentifiedObject actual) { - if (!expected.equals(actual)) { - throw new GeodeticException(Errors.format(Errors.Keys.NonUniformValue_2, - IdentifiedObjects.getDisplayName(expected), IdentifiedObjects.getDisplayName(actual))); - } - } - /** * {@inheritDoc} * diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultImageCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultImageCRS.java index e9e58b45d8..885a1679bd 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultImageCRS.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultImageCRS.java @@ -17,7 +17,6 @@ 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.XmlElement; import jakarta.xml.bind.annotation.XmlRootElement; @@ -27,7 +26,6 @@ import org.apache.sis.referencing.AbstractReferenceSystem; import org.apache.sis.referencing.privy.WKTKeywords; import org.apache.sis.referencing.cs.AxesConvention; import org.apache.sis.referencing.cs.AbstractCS; -import org.apache.sis.metadata.privy.ImplementationHelper; import org.apache.sis.io.wkt.Formatter; // Specific to the geoapi-4.0 branch: @@ -69,21 +67,11 @@ import org.apache.sis.referencing.datum.DefaultImageDatum; "datum" }) @XmlRootElement(name = "ImageCRS") -public final class DefaultImageCRS extends AbstractCRS { +public final class DefaultImageCRS extends AbstractSingleCRS<DefaultImageDatum> { /** * Serial number for inter-operability with different versions. */ - private static final long serialVersionUID = 7312452786096397847L; - - /** - * The datum. - * - * <p><b>Consider this field as final!</b> - * This field is modified only at unmarshalling time.</p> - * - * @see #getDatum() - */ - private DefaultImageDatum datum; + private static final long serialVersionUID = 7222610270977351462L; /** * Creates a coordinate reference system from the given properties, datum and coordinate system. @@ -128,8 +116,7 @@ public final class DefaultImageCRS extends AbstractCRS { final DefaultImageDatum datum, final AffineCS cs) { - super(properties, cs); - this.datum = Objects.requireNonNull(datum); + super(properties, DefaultImageDatum.class, datum, null, cs); } /** @@ -138,7 +125,6 @@ public final class DefaultImageCRS extends AbstractCRS { */ private DefaultImageCRS(final DefaultImageCRS original, final AbstractCS cs) { super(original, null, cs); - datum = original.datum; } /** @@ -149,7 +135,7 @@ public final class DefaultImageCRS extends AbstractCRS { @Override @XmlElement(name = "imageDatum", required = true) public DefaultImageDatum getDatum() { - return datum; + return super.getDatum(); } /** @@ -239,11 +225,7 @@ public final class DefaultImageCRS extends AbstractCRS { * @see #getDatum() */ private void setDatum(final DefaultImageDatum value) { - if (datum == null) { - datum = value; - } else { - ImplementationHelper.propertyAlreadySet(DefaultImageCRS.class, "setDatum", "imageDatum"); - } + setDatum("imageDatum", value); } /** 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 0f37ee6d7b..adcee10299 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 @@ -20,7 +20,6 @@ import java.util.Map; import jakarta.xml.bind.annotation.XmlElement; import jakarta.xml.bind.annotation.XmlRootElement; import jakarta.xml.bind.annotation.XmlType; -import org.apache.sis.metadata.privy.ImplementationHelper; import org.apache.sis.referencing.privy.WKTKeywords; import org.apache.sis.referencing.cs.AxesConvention; import org.apache.sis.referencing.cs.AbstractCS; @@ -63,31 +62,11 @@ import org.opengis.referencing.datum.DatumEnsemble; "datum" }) @XmlRootElement(name = "ParametricCRS") -public class DefaultParametricCRS extends AbstractCRS implements ParametricCRS { +public class DefaultParametricCRS extends AbstractSingleCRS<ParametricDatum> implements ParametricCRS { /** * Serial number for inter-operability with different versions. */ - private static final long serialVersionUID = 4013698133331342649L; - - /** - * The datum, or {@code null} if the CRS is associated only to a datum ensemble. - * - * <p><b>Consider this field as final!</b> - * This field is modified only at unmarshalling time by {@link #setDatum(ParametricDatum)}</p> - * - * @see #getDatum() - */ - @SuppressWarnings("serial") // Most SIS implementations are serializable. - private ParametricDatum 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<ParametricDatum> ensemble; + private static final long serialVersionUID = -5443671973122639841L; /** * Creates a coordinate reference system from the given properties, datum and coordinate system. @@ -140,10 +119,7 @@ public class DefaultParametricCRS extends AbstractCRS implements ParametricCRS { final DatumEnsemble<ParametricDatum> ensemble, final ParametricCS cs) { - super(properties, cs); - this.datum = datum; - this.ensemble = ensemble; - checkDatum(datum, ensemble); + super(properties, ParametricDatum.class, datum, ensemble, cs); checkDimension(1, 1, cs); } @@ -164,8 +140,6 @@ public class DefaultParametricCRS extends AbstractCRS implements ParametricCRS { */ private DefaultParametricCRS(final DefaultParametricCRS original, final AbstractCS cs) { super(original, null, cs); - datum = original.datum; - ensemble = original.ensemble; } /** @@ -181,9 +155,6 @@ public class DefaultParametricCRS extends AbstractCRS implements ParametricCRS { */ protected DefaultParametricCRS(final ParametricCRS crs) { super(crs); - datum = crs.getDatum(); - ensemble = crs.getDatumEnsemble(); - checkDatum(datum, ensemble); } /** @@ -228,7 +199,7 @@ public class DefaultParametricCRS extends AbstractCRS implements ParametricCRS { @Override @XmlElement(name = "parametricDatum", required = true) public ParametricDatum getDatum() { - return datum; + return super.getDatum(); } /** @@ -244,7 +215,7 @@ public class DefaultParametricCRS extends AbstractCRS implements ParametricCRS { */ @Override public DatumEnsemble<ParametricDatum> getDatumEnsemble() { - return ensemble; + return super.getDatumEnsemble(); } /** @@ -321,7 +292,6 @@ public class DefaultParametricCRS extends AbstractCRS implements ParametricCRS { * reserved to JAXB, which will assign values to the fields using reflection. */ private DefaultParametricCRS() { - ensemble = null; /* * The datum and the coordinate system are mandatory for SIS working. We do not verify their presence * here because the verification would have to be done in an 'afterMarshal(…)' method and throwing an @@ -336,11 +306,7 @@ public class DefaultParametricCRS extends AbstractCRS implements ParametricCRS { * @see #getDatum() */ private void setDatum(final ParametricDatum value) { - if (datum == null) { - datum = value; - } else { - ImplementationHelper.propertyAlreadySet(DefaultParametricCRS.class, "setDatum", "parametricDatum"); - } + setDatum("parametricDatum", value); } /** 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 abac077c16..d5af964f6f 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 @@ -42,6 +42,7 @@ import org.apache.sis.util.Workaround; // Specific to the geoapi-3.1 and geoapi-4.0 branches: import org.opengis.coordinate.MismatchedDimensionException; +import org.opengis.referencing.datum.DatumEnsemble; /** @@ -209,15 +210,33 @@ public class DefaultProjectedCRS extends AbstractDerivedCRS implements Projected } /** - * Returns the datum of the {@linkplain #getBaseCRS() base CRS}. + * Returns the datum 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 #getDatumEnsemble() datum ensemble}. * - * @return the datum of the base CRS. + * @return the datum of the {@linkplain #getBaseCRS() base CRS}, or {@code null} if this <abbr>CRS</abbr> + * is related to an object identified only by a {@linkplain #getDatumEnsemble() datum ensemble}. */ @Override public GeodeticDatum getDatum() { 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(); + } + /** * 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 b3fbfe6256..2d157f8e2b 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 @@ -16,7 +16,6 @@ */ package org.apache.sis.referencing.crs; -import java.util.Iterator; import java.util.Map; import java.util.Date; import java.time.Instant; @@ -38,12 +37,11 @@ import org.apache.sis.referencing.GeodeticException; 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.PseudoDatum; import org.apache.sis.referencing.privy.WKTKeywords; -import org.apache.sis.metadata.privy.ImplementationHelper; import org.apache.sis.io.wkt.Formatter; import org.apache.sis.measure.Units; import org.apache.sis.math.Fraction; -import org.apache.sis.util.resources.Errors; import static org.apache.sis.util.privy.Constants.NANOS_PER_SECOND; import static org.apache.sis.util.privy.Constants.MILLIS_PER_SECOND; @@ -85,31 +83,11 @@ import org.opengis.referencing.datum.DatumEnsemble; "datum" }) @XmlRootElement(name = "TemporalCRS") -public class DefaultTemporalCRS extends AbstractCRS implements TemporalCRS { +public class DefaultTemporalCRS extends AbstractSingleCRS<TemporalDatum> implements TemporalCRS { /** * Serial number for inter-operability with different versions. */ - private static final long serialVersionUID = 3000119849197222007L; - - /** - * The datum, or {@code null} if the CRS is associated only to a datum ensemble. - * - * <p><b>Consider this field as final!</b> - * This field is modified only at unmarshalling time by {@link #setDatum(TemporalDatum)}</p> - * - * @see #getDatum() - */ - @SuppressWarnings("serial") // Most SIS implementations are serializable. - private TemporalDatum 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<TemporalDatum> ensemble; + private static final long serialVersionUID = 369537220141768472L; /** * Conversion factor from values in this CRS to values in seconds. We use {@link UnitConverter} @@ -179,10 +157,7 @@ public class DefaultTemporalCRS extends AbstractCRS implements TemporalCRS { final DatumEnsemble<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(); } @@ -204,8 +179,6 @@ public class DefaultTemporalCRS extends AbstractCRS implements TemporalCRS { */ private DefaultTemporalCRS(final DefaultTemporalCRS original, final AbstractCS cs) { super(original, null, cs); - datum = original.datum; - ensemble = original.ensemble; initializeConverter(); } @@ -223,9 +196,6 @@ public class DefaultTemporalCRS extends AbstractCRS implements TemporalCRS { @SuppressWarnings("this-escape") protected DefaultTemporalCRS(final TemporalCRS crs) { super(crs); - datum = crs.getDatum(); - ensemble = crs.getDatumEnsemble(); - checkDatum(datum, ensemble); initializeConverter(); } @@ -257,7 +227,7 @@ public class DefaultTemporalCRS extends AbstractCRS implements TemporalCRS { } /** - * Initialize the fields required for {@link #toInstant(double)} and {@link #toValue(Instant)} operations. + * Initializes the fields required for {@link #toInstant(double)} and {@link #toValue(Instant)} operations. */ private void initializeConverter() { toSeconds = getUnit().getConverterTo(Units.SECOND); @@ -302,7 +272,7 @@ public class DefaultTemporalCRS extends AbstractCRS implements TemporalCRS { @Override @XmlElement(name = "temporalDatum", required = true) public TemporalDatum getDatum() { - return datum; + return super.getDatum(); } /** @@ -318,7 +288,7 @@ public class DefaultTemporalCRS extends AbstractCRS implements TemporalCRS { */ @Override public DatumEnsemble<TemporalDatum> getDatumEnsemble() { - return ensemble; + return super.getDatumEnsemble(); } /** @@ -366,19 +336,7 @@ public class DefaultTemporalCRS extends AbstractCRS implements TemporalCRS { * @since 1.5 */ public final Temporal getOrigin() { // Must be final because invoked at construction time. - if (datum != null) { - return datum.getOrigin(); // Has precedence regardless the value. - } - // If the datum is null, then the datum ensemble must be non-null. - final Iterator<TemporalDatum> it = ensemble.getMembers().iterator(); - final Temporal origin = it.next().getOrigin(); - while (it.hasNext()) { - final Temporal actual = it.next().getOrigin(); - if (!origin.equals(actual)) { - throw new GeodeticException(Errors.format(Errors.Keys.NonUniformValue_2, origin, actual)); - } - } - return origin; + return PseudoDatum.of(this).getOrigin(); } /** @@ -575,7 +533,6 @@ public class DefaultTemporalCRS extends AbstractCRS implements TemporalCRS { * reserved to JAXB, which will assign values to the fields using reflection. */ private DefaultTemporalCRS() { - ensemble = null; /* * The datum and the coordinate system are mandatory for SIS working. We do not verify their presence * here because the verification would have to be done in an 'afterMarshal(…)' method and throwing an @@ -590,13 +547,9 @@ public class DefaultTemporalCRS extends AbstractCRS implements TemporalCRS { * @see #getDatum() */ private void setDatum(final TemporalDatum value) { - if (datum == null) { - datum = value; - if (super.getCoordinateSystem() != null) { - initializeConverter(); - } - } else { - ImplementationHelper.propertyAlreadySet(DefaultVerticalCRS.class, "setDatum", "temporalDatum"); + setDatum("temporalDatum", value); + if (super.getCoordinateSystem() != null) { + initializeConverter(); } } @@ -607,7 +560,7 @@ public class DefaultTemporalCRS extends AbstractCRS implements TemporalCRS { */ private void setCoordinateSystem(final TimeCS cs) { setCoordinateSystem("timeCS", cs); - if (toSeconds == null && datum != null) { + if (toSeconds == null && super.getDatum() != null) { initializeConverter(); } } 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 cb11c46b70..41ef613856 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 @@ -27,7 +27,6 @@ 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.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: @@ -64,31 +63,11 @@ import org.opengis.referencing.datum.DatumEnsemble; "datum" }) @XmlRootElement(name = "VerticalCRS") -public class DefaultVerticalCRS extends AbstractCRS implements VerticalCRS { +public class DefaultVerticalCRS extends AbstractSingleCRS<VerticalDatum> implements VerticalCRS { /** * Serial number for inter-operability with different versions. */ - private static final long serialVersionUID = 3565878468719941800L; - - /** - * The datum, or {@code null} if the CRS is associated only to a datum ensemble. - * - * <p><b>Consider this field as final!</b> - * This field is modified only at unmarshalling time by {@link #setDatum(VerticalDatum)}</p> - * - * @see #getDatum() - */ - @SuppressWarnings("serial") // Most SIS implementations are serializable. - private VerticalDatum 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<VerticalDatum> ensemble; + private static final long serialVersionUID = 5807645386129942811L; /** * Creates a coordinate reference system from the given properties, datum and coordinate system. @@ -141,10 +120,7 @@ public class DefaultVerticalCRS extends AbstractCRS implements VerticalCRS { final DatumEnsemble<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); } @@ -165,8 +141,6 @@ public class DefaultVerticalCRS extends AbstractCRS implements VerticalCRS { */ private DefaultVerticalCRS(final DefaultVerticalCRS original, final AbstractCS cs) { super(original, null, cs); - datum = original.datum; - ensemble = original.ensemble; } /** @@ -182,9 +156,6 @@ public class DefaultVerticalCRS extends AbstractCRS implements VerticalCRS { */ protected DefaultVerticalCRS(final VerticalCRS crs) { super(crs); - datum = crs.getDatum(); - ensemble = crs.getDatumEnsemble(); - checkDatum(datum, ensemble); } /** @@ -229,7 +200,7 @@ public class DefaultVerticalCRS extends AbstractCRS implements VerticalCRS { @Override @XmlElement(name = "verticalDatum", required = true) public VerticalDatum getDatum() { - return datum; + return super.getDatum(); } /** @@ -245,7 +216,7 @@ public class DefaultVerticalCRS extends AbstractCRS implements VerticalCRS { */ @Override public DatumEnsemble<VerticalDatum> getDatumEnsemble() { - return ensemble; + return super.getDatumEnsemble(); } /** @@ -314,7 +285,6 @@ public class DefaultVerticalCRS extends AbstractCRS implements VerticalCRS { * reserved to JAXB, which will assign values to the fields using reflection. */ private DefaultVerticalCRS() { - ensemble = null; /* * The datum and the coordinate system are mandatory for SIS working. We do not verify their presence * here because the verification would have to be done in an 'afterMarshal(…)' method and throwing an @@ -329,11 +299,7 @@ public class DefaultVerticalCRS extends AbstractCRS implements VerticalCRS { * @see #getDatum() */ private void setDatum(final VerticalDatum value) { - if (datum == null) { - datum = value; - } else { - ImplementationHelper.propertyAlreadySet(DefaultVerticalCRS.class, "setDatum", "verticalDatum"); - } + setDatum("verticalDatum", value); } /** diff --git 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 index 12b563e81c..0b03e1ba43 100644 --- 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 @@ -32,14 +32,14 @@ import org.apache.sis.referencing.privy.WKTKeywords; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.ComparisonMode; import org.apache.sis.util.Utilities; +import org.apache.sis.util.resources.Errors; /** * Collection of datums which for low accuracy requirements may be considered * to be insignificantly different from each other. * - * @author OGC Topic 2 (for abstract model and documentation) - * @author Martin Desruisseaux (IRD, Geomatys) + * @author Martin Desruisseaux (Geomatys) * @version 1.5 * * @param <D> the type of datum contained in this ensemble. @@ -158,10 +158,15 @@ public class DefaultDatumEnsemble<D extends Datum> extends AbstractIdentifiedObj /** * 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 (dr != null) { if (rs == null) { @@ -224,7 +229,10 @@ public class DefaultDatumEnsemble<D extends Datum> extends AbstractIdentifiedObj * @return {@code true} if both objects are equal. */ @Override - public boolean equals(final Object object, final ComparisonMode mode) { + public boolean equals(Object object, final ComparisonMode mode) { + if (mode != ComparisonMode.STRICT && object instanceof PseudoDatum<?>) { + object = ((PseudoDatum<?>) object).ensemble; + } if (!super.equals(object, mode)) { return false; } diff --git 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 new file mode 100644 index 0000000000..6d13d6200f --- /dev/null +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/PseudoDatum.java @@ -0,0 +1,589 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.referencing.datum; + +import java.util.Set; +import java.util.Collection; +import java.util.Iterator; +import java.util.Objects; +import java.util.Optional; +import java.util.NoSuchElementException; +import java.util.function.Function; +import java.time.temporal.Temporal; +import java.io.Serializable; +import org.opengis.util.GenericName; +import org.opengis.util.InternationalString; +import org.opengis.metadata.Identifier; +import org.opengis.referencing.IdentifiedObject; +import org.opengis.referencing.ObjectDomain; +import org.opengis.referencing.datum.*; +import org.opengis.referencing.crs.*; +import org.apache.sis.util.Utilities; +import org.apache.sis.util.ComparisonMode; +import org.apache.sis.util.LenientComparable; +import org.apache.sis.util.resources.Errors; +import org.apache.sis.referencing.IdentifiedObjects; +import org.apache.sis.referencing.GeodeticException; + + +/** + * A datum ensemble viewed as if it was a single datum for the sake of simplicity. + * This pseudo-datum is a non-standard mechanism used by the Apache <abbr>SIS</abbr> implementation + * for handling datum and datum ensemble in a uniform way. For example, {@code PseudoDatum.of(crs)} + * allows to {@linkplain IdentifiedObjects#isHeuristicMatchForName compare the datum name} without + * the need to check which one of the {@code getDatum()} or {@code getDatumEnsemble()} methods + * returns a non-null value. + * + * <p>{@code PseudoDatum} instances should live only for a short time. + * They should not be stored as {@link SingleCRS} properties. + * If a {@code PseudoDatum} instances is given to the constructor of an Apache <abbr>SIS</abbr> class, + * the constructor will automatically unwraps the {@linkplain #ensemble}.</p> + * + * <h2>Default method implementations</h2> + * Unless otherwise specified in the Javadoc, all methods in this class delegate + * to the same method in the wrapper datum {@linkplain #ensemble}. + * + * <h2>Object comparisons</h2> + * The {@link #equals(Object)} method returns {@code true} only if the two compared objects are instances + * of the same class, which implies that the {@code Object} argument must be a {@code PseudoDatum}. + * The {@link #equals(Object, ComparisonMode)} method with a non-strict comparison mode compares + * the wrapped datum ensemble, which implies that: + * + * <ul> + * <li>A pseudo-datum is never equal to a real datum, regardless the names and identifiers of the compared objects.</li> + * <li>A pseudo-datum can be equal to another pseudo-datum or to a datum ensemble.</li> + * </ul> + * + * @author Martin Desruisseaux (Geomatys) + * @version 1.5 + * + * @param <D> the type of datum contained in this ensemble. + * + * @since 1.5 + */ +public abstract class PseudoDatum<D extends Datum> implements Datum, LenientComparable, Serializable { + /** + * For cross-versions compatibility. + */ + private static final long serialVersionUID = 3889895625961827486L; + + /** + * The datum ensemble wrapped by this pseudo-datum. + */ + @SuppressWarnings("serial") // Most SIS implementations are serializable. + public final DatumEnsemble<D> ensemble; + + /** + * Creates a new pseudo-datum. + * + * @param ensemble the datum ensemble wrapped by this pseudo-datum. + */ + protected PseudoDatum(final DatumEnsemble<D> ensemble) { + this.ensemble = Objects.requireNonNull(ensemble); + } + + /** + * Returns the datum of the given <abbr>CRS</abbr> if presents, or the datum ensemble otherwise. + * This is an alternative to the {@code of(…)} methods when the caller does not need to view the + * object as a datum. + * + * @param crs the <abbr>CRS</abbr> from which to get the datum or ensemble, or {@code null}. + * @return the datum if present, or the datum ensemble otherwise, or {@code null}. + */ + public static IdentifiedObject getDatumOrEnsemble(final SingleCRS crs) { + if (crs == null) return null; + final Datum datum = crs.getDatum(); + if (datum != null) { + if (datum instanceof PseudoDatum<?>) { + return ((PseudoDatum) datum).ensemble; + } + return datum; + } + return crs.getDatumEnsemble(); + } + + /** + * Returns the datum or pseudo-datum of the given geodetic <abbr>CRS</abbr>. + * If the given <abbr>CRS</abbr> is associated to a non-null datum, then this method returns that datum. + * Otherwise, this method returns the <abbr>CRS</abbr> datum ensemble wrapped in a pseudo-datum. + * In the latter case, the pseudo-datum implementations of the {@link GeodeticDatum#getEllipsoid()} + * and {@link GeodeticDatum#getPrimeMeridian()} methods expect an ellipsoid or prime meridian which + * is the same for all {@linkplain #ensemble} members. + * If this condition does not hold, a {@link GeodeticException} will be thrown. + * + * @param crs the coordinate reference system for which to get the datum or datum ensemble. + * @return the datum or pseudo-datum of the given <abbr>CRS</abbr>. + * @throws NullPointerException if the given argument is {@code null}, + * or if both the datum and datum ensemble are null. + */ + public static GeodeticDatum of(final GeodeticCRS crs) { + GeodeticDatum datum = crs.getDatum(); + if (datum == null) { + datum = new PseudoDatum.Geodetic(crs.getDatumEnsemble()); + } + return datum; + } + + /** + * Returns the datum or pseudo-datum of the given vertical <abbr>CRS</abbr>. + * If the given <abbr>CRS</abbr> is associated to a non-null datum, then this method returns that datum. + * Otherwise, this method returns the <abbr>CRS</abbr> datum ensemble wrapped in a pseudo-datum. + * + * @param crs the coordinate reference system for which to get the datum or datum ensemble. + * @return the datum or pseudo-datum of the given <abbr>CRS</abbr>. + * @throws NullPointerException if the given argument is {@code null}, + * or if both the datum and datum ensemble are null. + */ + public static VerticalDatum of(final VerticalCRS crs) { + VerticalDatum datum = crs.getDatum(); + if (datum == null) { + datum = new PseudoDatum.Vertical(crs.getDatumEnsemble()); + } + return datum; + } + + /** + * Returns the datum or pseudo-datum of the given temporal <abbr>CRS</abbr>. + * If the given <abbr>CRS</abbr> is associated to a non-null datum, then this method returns that datum. + * Otherwise, this method returns the <abbr>CRS</abbr> datum ensemble wrapped in a pseudo-datum. + * In the latter case, the pseudo-datum implementations of the {@link TemporalDatum#getOrigin()} + * expects a temporal origin which is the same for all {@linkplain #ensemble} members. + * If this condition does not hold, a {@link GeodeticException} will be thrown. + * + * @param crs the coordinate reference system for which to get the datum or datum ensemble. + * @return the datum or pseudo-datum of the given <abbr>CRS</abbr>. + * @throws NullPointerException if the given argument is {@code null}, + * or if both the datum and datum ensemble are null. + */ + public static TemporalDatum of(final TemporalCRS crs) { + TemporalDatum datum = crs.getDatum(); + if (datum == null) { + datum = new PseudoDatum.Time(crs.getDatumEnsemble()); + } + return datum; + } + + /** + * Returns the datum or pseudo-datum of the given parametric <abbr>CRS</abbr>. + * If the given <abbr>CRS</abbr> is associated to a non-null datum, then this method returns that datum. + * Otherwise, this method returns the <abbr>CRS</abbr> datum ensemble wrapped in a pseudo-datum. + * + * @param crs the coordinate reference system for which to get the datum or datum ensemble. + * @return the datum or pseudo-datum of the given <abbr>CRS</abbr>. + * @throws NullPointerException if the given argument is {@code null}, + * or if both the datum and datum ensemble are null. + */ + public static ParametricDatum of(final ParametricCRS crs) { + ParametricDatum datum = crs.getDatum(); + if (datum == null) { + datum = new PseudoDatum.Parametric(crs.getDatumEnsemble()); + } + return datum; + } + + /** + * Returns the datum or pseudo-datum of the given engineering <abbr>CRS</abbr>. + * If the given <abbr>CRS</abbr> is associated to a non-null datum, then this method returns that datum. + * Otherwise, this method returns the <abbr>CRS</abbr> datum ensemble wrapped in a pseudo-datum. + * + * @param crs the coordinate reference system for which to get the datum or datum ensemble. + * @return the datum or pseudo-datum of the given <abbr>CRS</abbr>. + * @throws NullPointerException if the given argument is {@code null}, + * or if both the datum and datum ensemble are null. + */ + public static EngineeringDatum of(final EngineeringCRS crs) { + EngineeringDatum datum = crs.getDatum(); + if (datum == null) { + datum = new PseudoDatum.Engineering(crs.getDatumEnsemble()); + } + return datum; + } + + /** + * Returns the GeoAPI interface of the ensemble members. + * It should also be the interface implemented by this class. + * + * @return the GeoAPI interface of the ensemble members. + */ + public abstract Class<D> getInterface(); + + /** + * Returns the primary name by which the datum ensemble is identified. + * + * @return {@code ensemble.getName()}. + * @hidden + */ + @Override + public Identifier getName() { + return ensemble.getName(); + } + + /** + * Returns alternative names by which the datum ensemble is identified. + * + * @return {@code ensemble.getAlias()}. + * @hidden + */ + @Override + public Collection<GenericName> getAlias() { + return ensemble.getAlias(); + } + + /** + * Returns an identifier which references elsewhere the datum ensemble information. + * + * @return {@code ensemble.getIdentifiers()}. + * @hidden + */ + @Override + public Set<Identifier> getIdentifiers() { + return ensemble.getIdentifiers(); + } + + /** + * Returns the usage of the datum ensemble. + * + * @return {@code ensemble.getDomains()}. + * @hidden + */ + @Override + public Collection<ObjectDomain> getDomains() { + return ensemble.getDomains(); + } + + /** + * Returns an anchor definition which is common to all members of the datum ensemble. + * If the value is not the same for all members (including the case where a member + * has an empty value), then this method returns an empty value. + * + * @return the common anchor definition, or empty if there is no common value. + */ + @Override + public Optional<InternationalString> getAnchorDefinition() { + return getCommonOptionalValue(Datum::getAnchorDefinition); + } + + /** + * Returns an anchor epoch which is common to all members of the datum ensemble. + * If the value is not the same for all members (including the case where a member + * has an empty value), then this method returns an empty value. + * + * @return the common anchor epoch, or empty if there is no common value. + */ + @Override + public Optional<Temporal> getAnchorEpoch() { + return getCommonOptionalValue(Datum::getAnchorEpoch); + } + + /** + * Returns a publication date which is common to all members of the datum ensemble. + * If the value is not the same for all members (including the case where a member + * has an empty value), then this method returns an empty value. + * + * @return the common publication date, or empty if there is no common value. + */ + @Override + public Optional<Temporal> getPublicationDate() { + return getCommonOptionalValue(Datum::getPublicationDate); + } + + /** + * Returns a conventional reference system which is common to all members of the datum ensemble. + * The returned value should never be empty, because it is illegal for a datum ensemble to have + * members with different conventional reference system. If this case nevertheless happens, + * this method returns an empty value. + * + * @return the common conventional reference system, or empty if there is no common value. + */ + @Override + public Optional<IdentifiedObject> getConventionalRS() { + return getCommonOptionalValue(Datum::getConventionalRS); + } + + /** + * Returns an optional value which is common to all ensemble members. + * If all members do not have the same value, returns an empty value. + * + * @param <V> type of value. + * @param getter method to invoke on each member for getting the value. + * @return a value common to all members, or {@code null} if none. + */ + final <V> Optional<V> getCommonOptionalValue(final Function<D, Optional<V>> getter) { + final Iterator<D> it = ensemble.getMembers().iterator(); +check: if (it.hasNext()) { + final Optional<V> value = getter.apply(it.next()); + if (value.isPresent()) { + while (it.hasNext()) { + if (!value.equals(getter.apply(it.next()))) { + break check; + } + } + return value; + } + } + return Optional.empty(); + } + + /** + * Returns a mandatory value which is common to all ensemble members. + * + * @param <V> type of value. + * @param getter method to invoke on each member for getting the value. + * @return a value common to all members. + * @throws NoSuchElementException if the ensemble does not contain at least one member. + * @throws GeodeticException if the value is not the same for all members of the datum ensemble. + */ + final <V> V getCommonMandatoryValue(final Function<D, V> getter) { + final Iterator<D> it = ensemble.getMembers().iterator(); + final V value = getter.apply(it.next()); // Mandatory. + if (it.hasNext()) { + final V other = getter.apply(it.next()); + if (!Objects.equals(value, other)) { + throw new GeodeticException(Errors.format(Errors.Keys.NonUniformValue_2, + (value instanceof IdentifiedObject) ? IdentifiedObjects.getDisplayName((IdentifiedObject) value) : value, + (other instanceof IdentifiedObject) ? IdentifiedObjects.getDisplayName((IdentifiedObject) other) : other)); + } + } + return value; + } + + /** + * Returns comments on or information about the datum ensemble. + * + * @return {@code ensemble.getRemarks()}. + * @hidden + */ + @Override + public Optional<InternationalString> getRemarks() { + return ensemble.getRemarks(); + } + + /** + * Formats a <i>Well-Known Text</i> (WKT) for the datum ensemble. + * + * @return {@code ensemble.toWKT()}. + * @hidden + */ + @Override + public String toWKT() { + return ensemble.toWKT(); + } + + /** + * Returns a string representation of the datum ensemble. + * + * @return {@code ensemble.toString()}. + * @hidden + */ + @Override + public String toString() { + return ensemble.toString(); + } + + /** + * Returns a hash-code value of this pseudo-datum. + * + * @return a hash-code value of this pseudo-datum. + */ + @Override + public int hashCode() { + return ensemble.hashCode() ^ getClass().hashCode(); + } + + /** + * Compares this pseudo-datum to the given object for equality. + * The two objects are equal if they are of the same classes and + * the wrapped {@link #ensemble} are equal. + * + * @param other the object to compare with this pseudo-datum. + * @return whether the two objects are equal. + */ + @Override + public boolean equals(final Object other) { + return (other != null) && other.getClass() == getClass() && ensemble.equals(((PseudoDatum<?>) other).ensemble); + } + + /** + * Compares this object with the given object for equality. + * If the comparison mode is strict, then this method delegates to {@link #equals(Object)}. + * Otherwise, this method unwrap the ensembles, then compare the ensembles. + * + * @param other the object to compare to {@code this}. + * @param mode the strictness level of the comparison. + * @return {@code true} if both objects are equal according the given comparison mode. + */ + @Override + public boolean equals(Object other, final ComparisonMode mode) { + if (mode == ComparisonMode.STRICT) { + return equals(other); + } + if (other instanceof PseudoDatum<?>) { + other = ((PseudoDatum<?>) other).ensemble; + } + return Utilities.deepEquals(ensemble, other, mode); + } + + /** + * A pseudo-datum for an ensemble of geodetic datum. + */ + private static final class Geodetic extends PseudoDatum<GeodeticDatum> implements GeodeticDatum { + /** For cross-versions compatibility. */ + private static final long serialVersionUID = 7669230365507661290L; + + /** Creates a new pseudo-datum wrapping the given ensemble. */ + Geodetic(final DatumEnsemble<GeodeticDatum> ensemble) { + super(ensemble); + } + + /** + * Returns the GeoAPI interface implemented by this pseudo-datum. + */ + @Override + public Class<GeodeticDatum> getInterface() { + return GeodeticDatum.class; + } + + /** + * Returns the ellipsoid which is indirectly (through a datum) associated to this datum ensemble. + * If all members of the ensemble use the same ellipsoid, then this method returns that ellipsoid. + * + * @return the ellipsoid indirectly associated to this datum ensemble. + * @throws NoSuchElementException if the ensemble does not contain at least one member. + * @throws GeodeticException if the ellipsoid is not the same for all members of the datum ensemble. + */ + @Override + public Ellipsoid getEllipsoid() { + return getCommonMandatoryValue(GeodeticDatum::getEllipsoid); + } + + /** + * Returns the prime meridian which is indirectly (through a datum) associated to this datum ensemble. + * If all members of the ensemble use the same prime meridian, then this method returns that meridian. + * + * @return the prime meridian indirectly associated to this datum ensemble. + * @throws NoSuchElementException if the ensemble does not contain at least one member. + * @throws GeodeticException if the prime meridian is not the same for all members of the datum ensemble. + */ + @Override + public PrimeMeridian getPrimeMeridian() { + return getCommonMandatoryValue(GeodeticDatum::getPrimeMeridian); + } + } + + /** + * A pseudo-datum for an ensemble of vertical datum. + */ + private static final class Vertical extends PseudoDatum<VerticalDatum> implements VerticalDatum { + /** For cross-versions compatibility. */ + private static final long serialVersionUID = 7242417944400289818L; + + /** Creates a new pseudo-datum wrapping the given ensemble. */ + Vertical(final DatumEnsemble<VerticalDatum> ensemble) { + super(ensemble); + } + + /** + * Returns the GeoAPI interface implemented by this pseudo-datum. + */ + @Override + public Class<VerticalDatum> getInterface() { + return VerticalDatum.class; + } + + /** + * Returns a realization method which is common to all members of the datum ensemble. + * If the value is not the same for all members (including the case where a member + * has an empty value), then this method returns an empty value. + * + * @return the common realization method, or empty if there is no common value. + */ + @Override + public Optional<RealizationMethod> getRealizationMethod() { + return getCommonOptionalValue(VerticalDatum::getRealizationMethod); + } + } + + /** + * A pseudo-datum for an ensemble of temporal datum. + */ + private static final class Time extends PseudoDatum<TemporalDatum> implements TemporalDatum { + /** For cross-versions compatibility. */ + private static final long serialVersionUID = -4208563828181087035L; + + /** Creates a new pseudo-datum wrapping the given ensemble. */ + Time(final DatumEnsemble<TemporalDatum> ensemble) { + super(ensemble); + } + + /** + * Returns the GeoAPI interface implemented by this pseudo-datum. + */ + @Override + public Class<TemporalDatum> getInterface() { + return TemporalDatum.class; + } + + /** + * Returns the temporal origin which is indirectly (through a datum) associated to this datum ensemble. + * If all members of the ensemble use the same temporal origin, then this method returns that origin. + * + * @return the temporal origin indirectly associated to this datum ensemble. + * @throws NoSuchElementException if the ensemble does not contain at least one member. + * @throws GeodeticException if the temporal origin is not the same for all members of the datum ensemble. + */ + @Override + public Temporal getOrigin() { + return getCommonMandatoryValue(TemporalDatum::getOrigin); + } + } + + /** + * A pseudo-datum for an ensemble of parametric datum. + */ + private static final class Parametric extends PseudoDatum<ParametricDatum> implements ParametricDatum { + /** For cross-versions compatibility. */ + private static final long serialVersionUID = -8277774591738789437L; + + /** Creates a new pseudo-datum wrapping the given ensemble. */ + Parametric(final DatumEnsemble<ParametricDatum> ensemble) { + super(ensemble); + } + + /** Returns the GeoAPI interface implemented by this pseudo-datum. */ + @Override public Class<ParametricDatum> getInterface() { + return ParametricDatum.class; + } + } + + /** + * A pseudo-datum for an ensemble of engineering datum. + */ + private static final class Engineering extends PseudoDatum<EngineeringDatum> implements EngineeringDatum { + /** For cross-versions compatibility. */ + private static final long serialVersionUID = -8978468990963666861L; + + /** Creates a new pseudo-datum wrapping the given ensemble. */ + Engineering(final DatumEnsemble<EngineeringDatum> ensemble) { + super(ensemble); + } + + /** Returns the GeoAPI interface implemented by this pseudo-datum. */ + @Override public Class<EngineeringDatum> getInterface() { + return EngineeringDatum.class; + } + } +} diff --git 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 index 6200fb4977..a01cbe8868 100644 --- 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 @@ -54,6 +54,7 @@ import org.apache.sis.referencing.internal.Resources; import org.apache.sis.referencing.cs.CoordinateSystems; import org.apache.sis.referencing.datum.BursaWolfParameters; import org.apache.sis.referencing.datum.DefaultGeodeticDatum; +import org.apache.sis.referencing.datum.PseudoDatum; import org.apache.sis.referencing.operation.matrix.Matrices; import org.apache.sis.referencing.operation.matrix.MatrixSIS; import org.apache.sis.referencing.operation.provider.Affine; @@ -338,8 +339,8 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry { //// //// //////////////////////////////////////////////////////////////////////////////// if (sourceCRS instanceof SingleCRS && targetCRS instanceof SingleCRS) { - final Datum sourceDatum = ((SingleCRS) sourceCRS).getDatum(); - final Datum targetDatum = ((SingleCRS) targetCRS).getDatum(); + final IdentifiedObject sourceDatum = PseudoDatum.getDatumOrEnsemble((SingleCRS) sourceCRS); + final IdentifiedObject targetDatum = PseudoDatum.getDatumOrEnsemble((SingleCRS) targetCRS); if (equalsIgnoreMetadata(sourceDatum, targetDatum)) try { /* * Because the CRS type is determined by the datum type (sometimes completed by the CS type), @@ -514,8 +515,8 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry { { final CoordinateSystem sourceCS = sourceCRS.getCoordinateSystem(); final CoordinateSystem targetCS = targetCRS.getCoordinateSystem(); - final GeodeticDatum sourceDatum = sourceCRS.getDatum(); - final GeodeticDatum targetDatum = targetCRS.getDatum(); + final GeodeticDatum sourceDatum = PseudoDatum.of(sourceCRS); + final GeodeticDatum targetDatum = PseudoDatum.of(targetCRS); Matrix datumShift = null; /* * If the prime meridian is not the same, we will concatenate a longitude rotation before or after datum shift @@ -741,7 +742,7 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry { 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); step1 = createOperation(sourceCRS, toAuthorityDefinition(GeographicCRS.class, stepCRS)); interpolationCRS = step1.getTargetCRS(); interpolationCS = interpolationCRS.getCoordinateSystem(); @@ -765,7 +766,7 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry { VerticalCRS heightCRS = targetCRS; // First candidate, will be replaced if it doesn't fit. VerticalCS heightCS = heightCRS.getCoordinateSystem(); if (equalsIgnoreMetadata(heightCS.getAxis(0), expectedAxis)) { - isEllipsoidalHeight = ReferencingUtilities.isEllipsoidalHeight(heightCRS.getDatum()); + isEllipsoidalHeight = ReferencingUtilities.isEllipsoidalHeight(PseudoDatum.of(heightCRS)); } else { heightCRS = CommonCRS.Vertical.ELLIPSOIDAL.crs(); heightCS = heightCRS.getCoordinateSystem(); @@ -823,8 +824,8 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry { final VerticalCRS targetCRS) throws FactoryException { - final VerticalDatum sourceDatum = sourceCRS.getDatum(); - final VerticalDatum targetDatum = targetCRS.getDatum(); + final VerticalDatum sourceDatum = PseudoDatum.of(sourceCRS); + final VerticalDatum targetDatum = PseudoDatum.of(targetCRS); if (!equalsIgnoreMetadata(sourceDatum, targetDatum)) { throw new OperationNotFoundException(notFoundMessage(sourceDatum, targetDatum)); } @@ -857,8 +858,8 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry { final TemporalCRS targetCRS) throws FactoryException { - final TemporalDatum sourceDatum = sourceCRS.getDatum(); - final TemporalDatum targetDatum = targetCRS.getDatum(); + final TemporalDatum sourceDatum = PseudoDatum.of(sourceCRS); + final TemporalDatum targetDatum = PseudoDatum.of(targetCRS); final TimeCS sourceCS = sourceCRS.getCoordinateSystem(); final TimeCS targetCS = targetCRS.getCoordinateSystem(); /* diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java index 7edfc6f93f..136f8b010b 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java @@ -51,8 +51,11 @@ import org.apache.sis.referencing.CRS; import org.apache.sis.referencing.CommonCRS; import org.apache.sis.referencing.NamedIdentifier; import org.apache.sis.referencing.IdentifiedObjects; +import org.apache.sis.referencing.datum.PseudoDatum; import org.apache.sis.referencing.cs.CoordinateSystems; import org.apache.sis.referencing.operation.matrix.Matrices; +import org.apache.sis.referencing.operation.provider.Affine; +import org.apache.sis.referencing.operation.provider.AbstractProvider; import org.apache.sis.referencing.operation.transform.MathTransforms; import org.apache.sis.referencing.factory.IdentifiedObjectFinder; import org.apache.sis.referencing.factory.GeodeticAuthorityFactory; @@ -67,8 +70,6 @@ import org.apache.sis.referencing.privy.ReferencingUtilities; import org.apache.sis.referencing.internal.ParameterizedTransformBuilder; import org.apache.sis.referencing.internal.DeferredCoordinateOperation; import org.apache.sis.referencing.internal.Resources; -import org.apache.sis.referencing.operation.provider.Affine; -import org.apache.sis.referencing.operation.provider.AbstractProvider; import org.apache.sis.metadata.iso.citation.Citations; import org.apache.sis.metadata.iso.extent.Extents; import org.apache.sis.system.Semaphores; @@ -1194,6 +1195,9 @@ class CoordinateOperationRegistry { /** * If the given CRS is two-dimensional, appends an ellipsoidal height to it. * It is caller's responsibility to ensure that the given CRS is geographic. + * + * @param crs the two-dimensional CRS to replace by a three-dimensional CRS. + * @param candidate an existing three-dimensional instance that may be suitable, or {@code null}. */ private CoordinateReferenceSystem toGeodetic3D(CoordinateReferenceSystem crs, final CoordinateReferenceSystem candidate) throws FactoryException @@ -1208,7 +1212,9 @@ class CoordinateOperationRegistry { * to return the existing instance. */ if (crs.getClass() == candidate.getClass() && candidate.getCoordinateSystem().getDimension() == 3) { - if (Utilities.equalsIgnoreMetadata(((SingleCRS) crs).getDatum(), ((SingleCRS) candidate).getDatum())) { + if (Utilities.equalsIgnoreMetadata(PseudoDatum.getDatumOrEnsemble((SingleCRS) candidate), + PseudoDatum.getDatumOrEnsemble((SingleCRS) crs))) + { return candidate; // Keep the existing instance since it may contain useful metadata. } } diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java index 1573b2ef58..40c87d28a8 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java @@ -37,12 +37,12 @@ import org.apache.sis.referencing.factory.GeodeticObjectFactory; import org.apache.sis.referencing.factory.InvalidGeodeticParameterException; import org.apache.sis.referencing.operation.transform.AbstractMathTransform; import org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory; +import org.apache.sis.referencing.datum.PseudoDatum; import org.apache.sis.referencing.internal.Resources; import org.apache.sis.referencing.internal.MergedProperties; import org.apache.sis.referencing.internal.ParameterizedTransformBuilder; import org.apache.sis.referencing.privy.CoordinateOperations; import org.apache.sis.referencing.privy.ReferencingFactoryContainer; -import org.apache.sis.referencing.privy.ReferencingUtilities; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.Classes; import org.apache.sis.util.Utilities; @@ -411,11 +411,11 @@ public class DefaultCoordinateOperationFactory extends AbstractFactory implement int n = components.size(); // Number of remaining datum from sourceCRS to verify. final IdentifiedObject[] datum = new IdentifiedObject[n]; for (int i=0; i<n; i++) { - datum[i] = ReferencingUtilities.getDatumOrEnsemble(components.get(i)); + datum[i] = PseudoDatum.getDatumOrEnsemble(components.get(i)); } components = CRS.getSingleComponents(targetCRS); next: for (int i=components.size(); --i >= 0;) { - final IdentifiedObject d = ReferencingUtilities.getDatumOrEnsemble(components.get(i)); + final IdentifiedObject d = PseudoDatum.getDatumOrEnsemble(components.get(i)); for (int j=n; --j >= 0;) { if (Utilities.equalsIgnoreMetadata(d, datum[j])) { System.arraycopy(datum, j+1, datum, j, --n - j); // Remove the datum from the list. diff --git 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 index 2bcd77dd12..5e1bd59b0d 100644 --- 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 @@ -32,6 +32,7 @@ import org.apache.sis.referencing.CRS; import org.apache.sis.referencing.IdentifiedObjects; import org.apache.sis.referencing.crs.AbstractCRS; import org.apache.sis.referencing.cs.AxesConvention; +import org.apache.sis.referencing.datum.PseudoDatum; import org.apache.sis.referencing.factory.GeodeticAuthorityFactory; import org.apache.sis.referencing.factory.IdentifiedObjectFinder; import org.apache.sis.referencing.internal.Resources; @@ -332,7 +333,9 @@ public final class DefinitionVerifier { { return PRIME_MERIDIAN; } - if (!Utilities.equalsApproximately(crsA.getDatum(), crsG.getDatum())) { + if (!Utilities.equalsApproximately(PseudoDatum.getDatumOrEnsemble(crsA), + PseudoDatum.getDatumOrEnsemble(crsG))) + { return DATUM; } break; diff --git 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 index 27deaab23a..0a9ac3d3c6 100644 --- 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 @@ -35,6 +35,7 @@ import org.opengis.referencing.datum.VerticalDatum; import org.opengis.referencing.operation.Conversion; import org.opengis.referencing.operation.CoordinateOperationFactory; import org.apache.sis.referencing.IdentifiedObjects; +import org.apache.sis.referencing.datum.PseudoDatum; import org.apache.sis.metadata.iso.extent.Extents; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.ArraysExt; @@ -113,7 +114,7 @@ public final class EllipsoidalHeightCombiner { for (int i=0; i<components.length; i++) { final CoordinateReferenceSystem vertical = components[i]; if (vertical instanceof VerticalCRS) { - final VerticalDatum datum = ((VerticalCRS) vertical).getDatum(); + final VerticalDatum datum = PseudoDatum.of((VerticalCRS) vertical); if (ReferencingUtilities.isEllipsoidalHeight(datum)) { int axisPosition = 0; CoordinateSystem cs2D; diff --git 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 index 1c30eea584..bd34098df3 100644 --- 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 @@ -84,11 +84,15 @@ import org.opengis.referencing.crs.GeodeticCRS; public class GeodeticObjectBuilder extends Builder<GeodeticObjectBuilder> { /** * The geodetic reference frame, or {@code null} if none. + * + * @see #getDatumOrEnsemble() */ private GeodeticDatum datum; /** * The datum ensemble, or {@code null} if none. + * + * @see #getDatumOrEnsemble() */ private DatumEnsemble<GeodeticDatum> ensemble; @@ -516,13 +520,21 @@ public class GeodeticObjectBuilder extends Builder<GeodeticObjectBuilder> { */ public ProjectedCRS createProjectedCRS() throws FactoryException { GeographicCRS crs = getBaseCRS(); - if (datum != null || ensemble != null) { - crs = factories.getCRSFactory().createGeographicCRS( - name(datum != null ? datum : ensemble), datum, ensemble, crs.getCoordinateSystem()); + final IdentifiedObject id = getDatumOrEnsemble(); + if (id != null) { + crs = factories.getCRSFactory().createGeographicCRS(name(id), datum, ensemble, 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; + } + /** * Returns the CRS to use as the base of a projected CRS. * @@ -540,8 +552,7 @@ public class GeodeticObjectBuilder extends Builder<GeodeticObjectBuilder> { */ public GeographicCRS createGeographicCRS() throws FactoryException { final GeographicCRS crs = getBaseCRS(); - IdentifiedObject id = datum; - if (id == null) id = ensemble; + final IdentifiedObject id = getDatumOrEnsemble(); if (id != null) { properties.putIfAbsent(GeographicCRS.NAME_KEY, id.getName()); } diff --git 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 index 07aa3e2bbd..6cb1e5cbb8 100644 --- 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 @@ -207,35 +207,27 @@ public final class ReferencingUtilities extends Static { } /** - * Returns whether the given <abbr>CRS</abbr> use the same datum or the same datum ensemble. + * Returns whether the given <abbr>CRS</abbr> uses the given datum. * - * @param crs1 the first <abbr>CRS</abbr>. - * @param crs2 the second <abbr>CRS</abbr>. - * @return whether the two reference systems use the same datum or the same datum ensemble. + * @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 areMembersOfSameEnsemble(final SingleCRS crs1, final SingleCRS crs2) { - IdentifiedObject d1 = crs1.getDatum(); - IdentifiedObject d2 = crs2.getDatum(); - if (d1 == null && d2 == null) { - d1 = crs1.getDatumEnsemble(); - d2 = crs2.getDatumEnsemble(); - if (d1 == null && d2 == null) { - return false; + 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(); + if (ensemble != null) { + for (final Datum member : ensemble.getMembers()) { + if (Utilities.equalsIgnoreMetadata(member, datum)) { + return true; + } + } } } - return Utilities.equalsIgnoreMetadata(d1, d2); - } - - /** - * Returns the datum of the given <abbr>CRS</abbr> if presents, or the datum ensemble otherwise. - * - * @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. - */ - public static IdentifiedObject getDatumOrEnsemble(final SingleCRS crs) { - if (crs == null) return null; - final Datum datum = crs.getDatum(); - return (datum != null) ? datum : crs.getDatumEnsemble(); + return false; } /** @@ -295,6 +287,8 @@ public final class ReferencingUtilities extends Static { /** * 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 <P> the type of object to get. * @param crs the coordinate reference system for which to get the ellipsoid or prime meridian. diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/CRSBuilder.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/CRSBuilder.java index 381d6e5a9a..041a34a5ad 100644 --- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/CRSBuilder.java +++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/CRSBuilder.java @@ -66,6 +66,7 @@ import org.apache.sis.referencing.cs.AxesConvention; import org.apache.sis.referencing.cs.CoordinateSystems; import org.apache.sis.referencing.crs.DefaultProjectedCRS; import org.apache.sis.referencing.crs.DefaultGeographicCRS; +import org.apache.sis.referencing.datum.PseudoDatum; import org.apache.sis.referencing.privy.WKTKeywords; import org.apache.sis.referencing.privy.NilReferencingObject; import org.apache.sis.referencing.privy.ReferencingUtilities; @@ -1105,7 +1106,7 @@ public final class CRSBuilder extends ReferencingFactoryContainer { * were specified in the GeoTIFF file or if we got the default values. We do not compare units for that reason. */ final Unit<Length> linearUnit = createLinearUnit(UnitKey.LINEAR); - final GeodeticDatum datum = crs.getDatum(); + final GeodeticDatum datum = PseudoDatum.of(crs); verifyIdentifier(crs, datum, GeoKeys.GeodeticDatum); verify(datum, angularUnit, linearUnit); geoKeys.remove(GeoKeys.GeodeticCitation); @@ -1174,7 +1175,7 @@ public final class CRSBuilder extends ReferencingFactoryContainer { */ final Unit<Length> linearUnit = createLinearUnit(UnitKey.LINEAR); final Unit<Angle> angularUnit = createAngularUnit(UnitKey.ANGULAR); - final GeodeticDatum datum = crs.getDatum(); + final GeodeticDatum datum = PseudoDatum.of(crs); verifyIdentifier(crs, datum, GeoKeys.GeodeticDatum); verify(datum, angularUnit, linearUnit); } diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java index 0519241239..a88c8b21d4 100644 --- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java +++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java @@ -53,6 +53,7 @@ import org.apache.sis.util.privy.Strings; import org.apache.sis.util.privy.CollectionsExt; import org.apache.sis.referencing.CRS; import org.apache.sis.referencing.IdentifiedObjects; +import org.apache.sis.referencing.datum.PseudoDatum; import org.apache.sis.referencing.cs.CoordinateSystems; import org.apache.sis.referencing.operation.matrix.Matrices; import org.apache.sis.referencing.operation.transform.MathTransforms; @@ -276,7 +277,7 @@ public final class GeoEncoder { if (writeEPSG(GeoKeys.Vertical, crs)) { writeName(GeoKeys.VerticalCitation, null, crs); addUnits(UnitKey.VERTICAL, crs.getCoordinateSystem()); - final VerticalDatum datum = crs.getDatum(); + final VerticalDatum datum = PseudoDatum.of(crs); if (writeEPSG(GeoKeys.VerticalDatum, datum)) { /* * OGC requirement 25.5 said "VerticalCitationGeoKey SHALL be populated." @@ -325,41 +326,37 @@ public final class GeoEncoder { writeModelType(isBaseCRS ? GeoCodes.ModelTypeProjected : type); if (writeEPSG(GeoKeys.GeodeticCRS, crs)) { writeName(GeoKeys.GeodeticCitation, "GCS Name", crs); - final GeodeticDatum datum = crs.getDatum(); - final Ellipsoid ellipsoid = ReferencingUtilities.getEllipsoid(crs); - if (datum == null && ellipsoid != null) { - // Case of a datum ensemble instead of a single datum. - writeShort(GeoKeys.GeodeticDatum, GeoCodes.userDefined); - } else if (!writeEPSG(GeoKeys.GeodeticDatum, datum)) { - return true; - } - appendName(WKTKeywords.Datum, datum); - final PrimeMeridian primem = ReferencingUtilities.getPrimeMeridian(crs); - final double longitude; - if (writeEPSG(GeoKeys.PrimeMeridian, primem)) { - appendName(WKTKeywords.PrimeM, primem); - longitude = primem.getGreenwichLongitude(); - } else { - longitude = 0; // Means "do not write prime meridian". - } - final Unit<Length> axisUnit = ellipsoid.getAxisUnit(); - final Unit<?> linearUnit = units.putIfAbsent(UnitKey.LINEAR, axisUnit); - final UnitConverter toLinear = axisUnit.getConverterToAny(linearUnit != null ? linearUnit : axisUnit); - writeUnit(UnitKey.LINEAR); // Must be after the `units` map have been updated. - writeUnit(UnitKey.ANGULAR); - if (writeEPSG(GeoKeys.Ellipsoid, ellipsoid)) { - appendName(WKTKeywords.Ellipsoid, ellipsoid); - writeDouble(GeoKeys.SemiMajorAxis, toLinear.convert(ellipsoid.getSemiMajorAxis())); - if (ellipsoid.isSphere() || !ellipsoid.isIvfDefinitive()) { - writeDouble(GeoKeys.SemiMinorAxis, toLinear.convert(ellipsoid.getSemiMinorAxis())); + final GeodeticDatum datum = PseudoDatum.of(crs); + if (writeEPSG(GeoKeys.GeodeticDatum, datum)) { + appendName(WKTKeywords.Datum, datum); + final PrimeMeridian primem = datum.getPrimeMeridian(); + final double longitude; + if (writeEPSG(GeoKeys.PrimeMeridian, primem)) { + appendName(WKTKeywords.PrimeM, datum); + longitude = primem.getGreenwichLongitude(); } else { - writeDouble(GeoKeys.InvFlattening, ellipsoid.getInverseFlattening()); + longitude = 0; // Means "do not write prime meridian". + } + final Ellipsoid ellipsoid = datum.getEllipsoid(); + final Unit<Length> axisUnit = ellipsoid.getAxisUnit(); + final Unit<?> linearUnit = units.putIfAbsent(UnitKey.LINEAR, axisUnit); + final UnitConverter toLinear = axisUnit.getConverterToAny(linearUnit != null ? linearUnit : axisUnit); + writeUnit(UnitKey.LINEAR); // Must be after the `units` map have been updated. + writeUnit(UnitKey.ANGULAR); + if (writeEPSG(GeoKeys.Ellipsoid, ellipsoid)) { + appendName(WKTKeywords.Ellipsoid, ellipsoid); + writeDouble(GeoKeys.SemiMajorAxis, toLinear.convert(ellipsoid.getSemiMajorAxis())); + if (ellipsoid.isSphere() || !ellipsoid.isIvfDefinitive()) { + writeDouble(GeoKeys.SemiMinorAxis, toLinear.convert(ellipsoid.getSemiMinorAxis())); + } else { + writeDouble(GeoKeys.InvFlattening, ellipsoid.getInverseFlattening()); + } + } + if (longitude != 0) { + Unit<Angle> unit = primem.getAngularUnit(); + UnitConverter c = unit.getConverterToAny(units.getOrDefault(UnitKey.ANGULAR, Units.DEGREE)); + writeDouble(GeoKeys.PrimeMeridianLongitude, c.convert(longitude)); } - } - if (longitude != 0) { - Unit<Angle> unit = primem.getAngularUnit(); - UnitConverter c = unit.getConverterToAny(units.getOrDefault(UnitKey.ANGULAR, Units.DEGREE)); - writeDouble(GeoKeys.PrimeMeridianLongitude, c.convert(longitude)); } } else if (isBaseCRS) { writeUnit(UnitKey.ANGULAR); // Map projection parameters may need this unit. diff --git 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 index 549224ca0e..8ac1db8491 100644 --- 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 @@ -47,11 +47,13 @@ import org.apache.sis.feature.DefaultFeatureType; import org.apache.sis.feature.FoliationRepresentation; import org.apache.sis.referencing.CRS; import org.apache.sis.referencing.CommonCRS; +import org.apache.sis.referencing.datum.PseudoDatum; import org.apache.sis.referencing.privy.GeodeticObjectBuilder; import org.apache.sis.util.ArraysExt; import org.apache.sis.util.CharSequences; import org.apache.sis.util.privy.UnmodifiableArrayList; import org.apache.sis.util.privy.Numerics; +import org.apache.sis.util.resources.Errors; import org.apache.sis.temporal.LenientDateFormat; import org.apache.sis.storage.DataOptionKey; import org.apache.sis.storage.DataStoreException; @@ -61,17 +63,16 @@ import org.apache.sis.storage.StorageConnector; import org.apache.sis.storage.FeatureSet; import org.apache.sis.storage.base.MetadataBuilder; import org.apache.sis.storage.base.URIDataStore; -import org.apache.sis.io.InvalidSeekException; -import org.apache.sis.io.stream.IOUtilities; import org.apache.sis.storage.internal.RewindableLineReader; import org.apache.sis.storage.internal.Resources; +import org.apache.sis.io.InvalidSeekException; +import org.apache.sis.io.stream.IOUtilities; import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.geometry.ImmutableEnvelope; import org.apache.sis.geometry.wrapper.Geometries; 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: @@ -450,7 +451,7 @@ final class Store extends URIDataStore implements FeatureSet { timeEncoding = TimeEncoding.ABSOLUTE; } else { temporal = builder.createTemporalCRS(startTime, timeUnit); - timeEncoding = new TimeEncoding(temporal.getDatum(), timeUnit); + timeEncoding = new TimeEncoding(PseudoDatum.of(temporal), timeUnit); } components[count++] = temporal; name = name + " + " + temporal.getName().getCode(); diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/OperationFinder.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/OperationFinder.java index 5ac6b8de43..61de906f91 100644 --- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/OperationFinder.java +++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/OperationFinder.java @@ -20,18 +20,17 @@ import java.util.function.Predicate; import javafx.concurrent.Task; import org.opengis.geometry.Envelope; import org.opengis.referencing.crs.CoordinateReferenceSystem; -import org.opengis.referencing.crs.SingleCRS; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.CoordinateOperation; import org.opengis.referencing.operation.TransformException; import org.apache.sis.referencing.CRS; import org.apache.sis.referencing.CommonCRS; +import org.apache.sis.referencing.operation.transform.MathTransforms; import org.apache.sis.coverage.grid.PixelInCell; import org.apache.sis.coverage.grid.GridCoverage; import org.apache.sis.coverage.grid.GridGeometry; import org.apache.sis.gui.coverage.CoverageCanvas; import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox; -import org.apache.sis.referencing.operation.transform.MathTransforms; import org.apache.sis.util.logging.Logging; import static org.apache.sis.gui.internal.LogHandler.LOGGER; @@ -173,7 +172,7 @@ abstract class OperationFinder extends Task<MathTransform> { * We use the {@link org.apache.sis.referencing.CommonCRS.Engineering#GRID} datum as a signature. */ private static boolean isGridCRS(final CoordinateReferenceSystem crs) { - return (crs instanceof SingleCRS) && CommonCRS.Engineering.GRID.datum().equals(((SingleCRS) crs).getDatum()); + return CommonCRS.Engineering.GRID.datumUsedBy(crs); } /** diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/Utils.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/Utils.java index ecf7ceaf8e..4a1e07554f 100644 --- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/Utils.java +++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/Utils.java @@ -20,7 +20,7 @@ import org.opengis.geometry.Envelope; import org.opengis.util.FactoryException; import org.opengis.metadata.extent.GeographicBoundingBox; import org.opengis.referencing.ReferenceSystem; -import org.opengis.referencing.crs.SingleCRS; +import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.crs.CRSAuthorityFactory; import org.opengis.referencing.operation.TransformException; import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox; @@ -103,7 +103,7 @@ final class Utils { * Returns {@code true} if the given reference system should be ignored. */ static boolean isIgnoreable(final ReferenceSystem system) { - return (system instanceof SingleCRS) - && CommonCRS.Engineering.DISPLAY.datum().equals(((SingleCRS) system).getDatum()); + return (system instanceof CoordinateReferenceSystem) && + CommonCRS.Engineering.DISPLAY.datumUsedBy((CoordinateReferenceSystem) system); } }