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
The following commit(s) were added to refs/heads/geoapi-4.0 by this push: new 94d30d99d2 The referencing module needs to check if the datum is null, and fallback on the datum ensemble if needed. This commit does not resolve all cases. In particular, the cases that depend on the EPSG database update are not yet done: EPSGDataAccess, CoordinateOperationRegistry and CoordinateOperationFinder. 94d30d99d2 is described below commit 94d30d99d2bd07a08ee6cd4af5150135ca48537a Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Sat Jul 13 16:38:36 2024 +0200 The referencing module needs to check if the datum is null, and fallback on the datum ensemble if needed. This commit does not resolve all cases. In particular, the cases that depend on the EPSG database update are not yet done: EPSGDataAccess, CoordinateOperationRegistry and CoordinateOperationFinder. --- .../org/apache/sis/portrayal/CanvasContext.java | 12 +- .../gazetteer/GeohashReferenceSystem.java | 6 +- .../gazetteer/MilitaryGridReferenceSystem.java | 2 +- .../org/apache/sis/geometry/CoordinateFormat.java | 7 +- .../main/org/apache/sis/io/wkt/VerticalInfo.java | 14 ++- .../main/org/apache/sis/referencing/CRS.java | 34 +++--- .../main/org/apache/sis/referencing/CommonCRS.java | 41 ++++++- .../referencing/EllipsoidalHeightSeparator.java | 24 +++- .../sis/referencing/MultiRegisterOperations.java | 23 +++- .../apache/sis/referencing/crs/AbstractCRS.java | 4 +- .../sis/referencing/crs/DefaultGeographicCRS.java | 70 +++++++++++ .../sis/referencing/crs/DefaultTemporalCRS.java | 39 +++++- .../sis/referencing/crs/ExplicitParameters.java | 6 +- .../apache/sis/referencing/internal/Resources.java | 2 +- .../sis/referencing/internal/Resources.properties | 2 +- .../referencing/internal/Resources_fr.properties | 2 +- .../referencing/operation/DefaultConversion.java | 15 ++- .../DefaultCoordinateOperationFactory.java | 16 +-- .../transform/DefaultMathTransformFactory.java | 5 +- .../sis/referencing/privy/DefinitionVerifier.java | 26 ++-- .../privy/EllipsoidalHeightCombiner.java | 24 ++-- .../referencing/privy/GeodeticObjectBuilder.java | 36 ++++-- .../referencing/privy/ReferencingUtilities.java | 136 ++++++++++++++------- .../sis/storage/geotiff/reader/CRSBuilder.java | 8 +- .../sis/storage/geotiff/writer/GeoEncoder.java | 62 +++++----- .../apache/sis/storage/netcdf/base/CRSBuilder.java | 37 ++++-- .../sis/storage/netcdf/base/GridMapping.java | 9 +- .../main/org/apache/sis/util/resources/Errors.java | 5 + .../apache/sis/util/resources/Errors.properties | 1 + .../apache/sis/util/resources/Errors_fr.properties | 1 + 30 files changed, 473 insertions(+), 196 deletions(-) diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/CanvasContext.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/CanvasContext.java index aae76518d5..b6c9144308 100644 --- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/CanvasContext.java +++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/CanvasContext.java @@ -19,7 +19,6 @@ package org.apache.sis.portrayal; import java.util.Optional; import java.util.OptionalDouble; import org.opengis.metadata.extent.GeographicBoundingBox; -import org.opengis.referencing.crs.GeodeticCRS; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.datum.Ellipsoid; import org.opengis.referencing.operation.Matrix; @@ -35,6 +34,7 @@ import org.apache.sis.measure.Units; import org.apache.sis.geometry.Envelopes; import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.referencing.privy.Formulas; +import org.apache.sis.referencing.privy.ReferencingUtilities; import org.apache.sis.system.Configuration; @@ -201,10 +201,12 @@ final class CanvasContext extends CoordinateOperationContext { } combined[j] = m; } - final Ellipsoid ellipsoid = ((GeodeticCRS) objectiveToGeographic.getTargetCRS()).getDatum().getEllipsoid(); - double radius = Formulas.radiusOfConformalSphere(ellipsoid, combined[1]); - radius = ellipsoid.getAxisUnit().getConverterTo(Units.METRE).convert(radius); - resolution = MathFunctions.magnitude(combined) * radius; + final Ellipsoid ellipsoid = ReferencingUtilities.getEllipsoid(objectiveToGeographic.getTargetCRS()); + if (ellipsoid != null) { + double radius = Formulas.radiusOfConformalSphere(ellipsoid, combined[1]); + radius = ellipsoid.getAxisUnit().getConverterTo(Units.METRE).convert(radius); + resolution = MathFunctions.magnitude(combined) * radius; + } } } } diff --git a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/GeohashReferenceSystem.java b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/GeohashReferenceSystem.java index 1666a27797..512e608b95 100644 --- a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/GeohashReferenceSystem.java +++ b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/GeohashReferenceSystem.java @@ -60,7 +60,7 @@ import org.opengis.referencing.gazetteer.LocationType; * * @author Chris Mattmann (JPL) * @author Martin Desruisseaux (Geomatys) - * @version 1.4 + * @version 1.5 * * @see <a href="https://en.wikipedia.org/wiki/Geohash">Geohash on Wikipedia</a> * @@ -315,7 +315,7 @@ public class GeohashReferenceSystem extends ReferencingByIdentifiers { */ @Override public Quantity<Length> getPrecision(DirectPosition position) { - final Ellipsoid ellipsoid = normalizedCRS.getDatum().getEllipsoid(); + final Ellipsoid ellipsoid = normalizedCRS.getEllipsoid(); final Unit<Length> unit = ellipsoid.getAxisUnit(); final int latNumBits = (5*length) >>> 1; // Number of bits for latitude value. final int lonNumBits = latNumBits + (length & 1); // Longitude has 1 more bit when length is odd. @@ -353,7 +353,7 @@ public class GeohashReferenceSystem extends ReferencingByIdentifiers { numLat = Latitude .MAX_VALUE / p; numLon = Longitude.MAX_VALUE / p; } else { - final Ellipsoid ellipsoid = normalizedCRS.getDatum().getEllipsoid(); + final Ellipsoid ellipsoid = normalizedCRS.getEllipsoid(); p = unit.getConverterToAny(ellipsoid.getAxisUnit()).convert(p); if (position != null) try { position = toGeographic(position); diff --git a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java index 85906610bc..24bacf8623 100644 --- a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java +++ b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java @@ -576,7 +576,7 @@ public class MilitaryGridReferenceSystem extends ReferencingByIdentifiers { * Returns the ellipsoid of the geodetic reference frame of MGRS identifiers. */ final Ellipsoid getEllipsoid() { - return datum.geographic().getDatum().getEllipsoid(); + return datum.ellipsoid(); } /** diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/CoordinateFormat.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/CoordinateFormat.java index b5752a0760..b2a15f9d9d 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/CoordinateFormat.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/CoordinateFormat.java @@ -51,6 +51,7 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.crs.TemporalCRS; import org.opengis.referencing.datum.Ellipsoid; import org.apache.sis.referencing.CRS; +import org.apache.sis.referencing.crs.DefaultTemporalCRS; import org.apache.sis.referencing.privy.Formulas; import org.apache.sis.referencing.privy.AxisDirections; import org.apache.sis.referencing.privy.ReferencingUtilities; @@ -58,13 +59,13 @@ import org.apache.sis.system.Loggers; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.CharSequences; import org.apache.sis.util.Characters; +import org.apache.sis.util.logging.Logging; +import org.apache.sis.util.resources.Errors; import org.apache.sis.util.privy.LocalizedParseException; import org.apache.sis.util.privy.Constants; import org.apache.sis.util.privy.Numerics; import org.apache.sis.math.DecimalFunctions; import org.apache.sis.math.MathFunctions; -import org.apache.sis.util.logging.Logging; -import org.apache.sis.util.resources.Errors; import org.apache.sis.temporal.TemporalDate; import org.apache.sis.measure.Angle; import org.apache.sis.measure.AngleFormat; @@ -580,7 +581,7 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> { } types [i] = DATE; formats[i] = getFormat(Date.class); - epochs [i] = TemporalDate.toInstant(((TemporalCRS) t).getDatum().getOrigin(), null); + epochs [i] = TemporalDate.toInstant(DefaultTemporalCRS.castOrCopy((TemporalCRS) t).getOrigin(), null); setConverter(dimension, i, unit.asType(Time.class).getConverterTo(Units.SECOND)); if (direction == AxisDirection.PAST) { negate(i); 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 37275e7cc3..a11f49dbec 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,6 +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.metadata.privy.AxisNames; import org.apache.sis.metadata.iso.extent.DefaultExtent; import org.apache.sis.metadata.iso.extent.DefaultVerticalExtent; @@ -107,8 +108,11 @@ final class VerticalInfo { * became empty as a result of this operation. */ final VerticalInfo resolve(final VerticalCRS crs) { - if (crs != null && crs.getDatum().getRealizationMethod().orElse(null) == RealizationMethod.GEOID) { - return resolve(crs, crs.getCoordinateSystem().getAxis(0)); + if (crs != null) { + final VerticalDatum datum = crs.getDatum(); + if (datum != null && datum.getRealizationMethod().orElse(null) == RealizationMethod.GEOID) { + return resolve(crs, crs.getCoordinateSystem().getAxis(0)); + } } return this; } @@ -179,8 +183,10 @@ final class VerticalInfo { * cases the previous name may contain terms like "depth", which are not appropriate for our new CRS. */ final VerticalCS cs = csFactory.createVerticalCS (properties(axis.getName()), axis); - extent.setVerticalCRS(crsFactory.createVerticalCRS( - properties((isUP ? compatibleCRS : axis).getName()), compatibleCRS.getDatum(), cs)); + extent.setVerticalCRS(crsFactory.createVerticalCRS(properties((isUP ? compatibleCRS : axis).getName()), + compatibleCRS.getDatum(), + compatibleCRS.getDatumEnsemble(), + cs)); return next; } diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java index 3f71eab3bf..6174b51d20 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java @@ -45,7 +45,6 @@ import org.opengis.referencing.crs.TemporalCRS; import org.opengis.referencing.crs.VerticalCRS; import org.opengis.referencing.crs.EngineeringCRS; import org.opengis.referencing.datum.Datum; -import org.opengis.referencing.datum.GeodeticDatum; import org.opengis.referencing.operation.Conversion; import org.opengis.referencing.operation.OperationNotFoundException; import org.opengis.referencing.operation.CoordinateOperation; @@ -1124,7 +1123,7 @@ public final class CRS extends Static { { final long current = (Numerics.bitmask(dimension) - 1) << previous; final long intersect = selected & current; - if (intersect != 0) { +choice: if (intersect != 0) { if (intersect == current) { addTo.add(crs); selected &= ~current; @@ -1135,18 +1134,23 @@ public final class CRS extends Static { if ((selected & current) == 0) break; // Stop if it would be useless to continue. previous += dimension; } - } else if (dimension == 3 && crs instanceof SingleCRS) { - final Datum datum = ((SingleCRS) crs).getDatum(); - if (datum instanceof GeodeticDatum) { - final boolean isVertical = Long.bitCount(intersect) == 1; // Presumed for now, verified later. - final int verticalDimension = Long.numberOfTrailingZeros((isVertical ? intersect : ~intersect) >>> previous); - final CoordinateSystemAxis verticalAxis = crs.getCoordinateSystem().getAxis(verticalDimension); - if (AxisDirections.isVertical(verticalAxis.getDirection())) try { - addTo.add(new EllipsoidalHeightSeparator((GeodeticDatum) datum, isVertical).separate((SingleCRS) crs)); - selected &= ~current; - } catch (IllegalArgumentException | ClassCastException e) { - throw new FactoryException(Resources.format(Resources.Keys.CanNotSeparateCRS_1, crs.getName())); - } + } else if (dimension == 3) { + final GeodeticCRS baseCRS; + if (crs instanceof GeodeticCRS) { + baseCRS = (GeodeticCRS) crs; + } else if (crs instanceof ProjectedCRS) { + baseCRS = ((ProjectedCRS) crs).getBaseCRS(); + } else { + break choice; + } + final boolean isVertical = Long.bitCount(intersect) == 1; // Presumed for now, verified later. + final int verticalDimension = Long.numberOfTrailingZeros((isVertical ? intersect : ~intersect) >>> previous); + final CoordinateSystemAxis verticalAxis = crs.getCoordinateSystem().getAxis(verticalDimension); + if (AxisDirections.isVertical(verticalAxis.getDirection())) try { + addTo.add(new EllipsoidalHeightSeparator(baseCRS, isVertical).separate((SingleCRS) crs)); + selected &= ~current; + } catch (IllegalArgumentException | ClassCastException e) { + throw new FactoryException(Resources.format(Resources.Keys.CanNotSeparateCRS_1, crs.getName())); } } } @@ -1552,7 +1556,7 @@ check: while (lower != 0 || upper != dimension) { * @since 0.5 */ public static double getGreenwichLongitude(final GeodeticCRS crs) { - return ReferencingUtilities.getGreenwichLongitude(crs.getDatum().getPrimeMeridian(), Units.DEGREE); + return ReferencingUtilities.getGreenwichLongitude(ReferencingUtilities.getPrimeMeridian(crs), Units.DEGREE); } /** 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 5f54b5e9d5..b8331ff590 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 @@ -69,6 +69,7 @@ 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.system.SystemListener; @@ -87,6 +88,7 @@ import org.apache.sis.measure.Units; import static org.apache.sis.util.privy.Constants.SECONDS_PER_DAY; // Specific to the geoapi-3.1 and geoapi-4.0 branches: +import org.opengis.referencing.datum.DatumEnsemble; import org.opengis.referencing.datum.RealizationMethod; @@ -508,6 +510,9 @@ 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}. + * @return enumeration value for the given datum, or {@code null} if none. */ static CommonCRS forDatum(final GeodeticDatum datum) { /* @@ -828,7 +833,10 @@ public enum CommonCRS { if (cs == null) { cs = (SphericalCS) StandardDefinitions.createCoordinateSystem(StandardDefinitions.SPHERICAL, true); } - object = new DefaultGeocentricCRS(IdentifiedObjects.getProperties(base, exclude()), base.getDatum(), base.getDatumEnsemble(), cs); + object = new DefaultGeocentricCRS(IdentifiedObjects.getProperties(base, exclude()), + base.getDatum(), + base.getDatumEnsemble(), + cs); cachedSpherical = object; } } @@ -854,7 +862,7 @@ public enum CommonCRS { * <tr><td>World Geodetic System 1984</td> <td>{@link #WGS84}</td> <td>6326</td></tr> * </table></blockquote> * - * @return the geodetic reference frame associated to this enum. + * @return the geodetic reference frame associated to this enum, or {@code null} for a datum ensemble. * * @see #forDatum(CoordinateReferenceSystem) * @see org.apache.sis.referencing.datum.DefaultGeodeticDatum @@ -883,6 +891,17 @@ public enum CommonCRS { return object; } + /** + * Returns the datum ensemble associated to this geodetic object. + * + * @return the datum ensemble associated to this enum, or {@code null} if none. + * + * @since 1.5 + */ + public DatumEnsemble<GeodeticDatum> datumEnsemble() { + return geographic().getDatumEnsemble(); + } + /** * Returns the ellipsoid associated to this geodetic object. * The following table summarizes the ellipsoids known to this class, @@ -997,8 +1016,13 @@ public enum CommonCRS { if (object instanceof Ellipsoid) { return (Ellipsoid) object; } - final GeodeticDatum datum = datum(object); - return (datum != null) ? datum.getEllipsoid() : null; + if (object instanceof GeodeticDatum) { + return ((GeodeticDatum) object).getEllipsoid(); + } + if (object instanceof CoordinateReferenceSystem) { + return ReferencingUtilities.getEllipsoid((CoordinateReferenceSystem) object); + } + return null; } /** @@ -1008,8 +1032,13 @@ public enum CommonCRS { if (object instanceof PrimeMeridian) { return (PrimeMeridian) object; } - final GeodeticDatum datum = datum(object); - return (datum != null) ? datum.getPrimeMeridian() : null; + if (object instanceof GeodeticDatum) { + return ((GeodeticDatum) object).getPrimeMeridian(); + } + if (object instanceof CoordinateReferenceSystem) { + return ReferencingUtilities.getPrimeMeridian((CoordinateReferenceSystem) object); + } + return null; } /* 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 d03a744c4f..e4ca8f5200 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 @@ -39,6 +39,9 @@ import org.apache.sis.util.resources.Errors; import org.apache.sis.referencing.factory.GeodeticObjectFactory; import static org.apache.sis.referencing.privy.ReferencingUtilities.getPropertiesForModifiedCRS; +// Specific to the geoapi-3.1 and geoapi-4.0 branches: +import org.opengis.referencing.datum.DatumEnsemble; + /** * Helper class for separating the ellipsoidal height from the horizontal part of a CRS. @@ -54,19 +57,25 @@ final class EllipsoidalHeightSeparator implements AxisFilter { */ private final GeodeticDatum datum; + /** + * The datum ensemble of the <abbr>CRS</abbr> to separate, or {@code null} if none. + */ + private final DatumEnsemble<GeodeticDatum> ensemble; + /** * Whether to extract the vertical component ({@code true}) or the horizontal component ({@code false}). */ private final boolean vertical; /** - * Creates a new separator for a CRS having the given datum. + * Creates a new separator for a CRS having the given base. * - * @param datum the datum of the CRS to separate. + * @param baseCRS the CRS to separate, or the base CRS of the projected CRS to separate. * @param vertical whether to extract the vertical component ({@code true}) or the horizontal component ({@code false}). */ - EllipsoidalHeightSeparator(final GeodeticDatum datum, final boolean vertical) { - this.datum = datum; + EllipsoidalHeightSeparator(final GeodeticCRS baseCRS, final boolean vertical) { + this.datum = baseCRS.getDatum(); + this.ensemble = baseCRS.getDatumEnsemble(); this.vertical = vertical; } @@ -100,7 +109,10 @@ final class EllipsoidalHeightSeparator implements AxisFilter { if (vertical) { VerticalCRS component = CommonCRS.Vertical.ELLIPSOIDAL.crs(); if (!Utilities.equalsIgnoreMetadata(component.getCoordinateSystem(), cs)) { - component = factory().createVerticalCRS(getPropertiesForModifiedCRS(component), component.getDatum(), (VerticalCS) cs); + component = factory().createVerticalCRS(getPropertiesForModifiedCRS(component), + component.getDatum(), + component.getDatumEnsemble(), + (VerticalCS) cs); } return component; } @@ -121,7 +133,7 @@ final class EllipsoidalHeightSeparator implements AxisFilter { final CommonCRS c = CommonCRS.forDatum(datum); if (c != null) return c.normalizedGeographic(); } - return factory().createGeographicCRS(getPropertiesForModifiedCRS(crs), datum, (EllipsoidalCS) cs); + return factory().createGeographicCRS(getPropertiesForModifiedCRS(crs), datum, ensemble, (EllipsoidalCS) cs); } /* * In the projected CRS case, in addition of reducing the number of dimensions in the CartesianCS, 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 2ddcd417b8..25b4aaede9 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 @@ -50,7 +50,8 @@ 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.apache.sis.util.Utilities; +import org.opengis.referencing.crs.CompoundCRS; +import org.apache.sis.referencing.privy.ReferencingUtilities; /** @@ -341,10 +342,22 @@ public class MultiRegisterOperations extends AbstractFactory implements Register public boolean areMembersOfSameEnsemble(CoordinateReferenceSystem source, CoordinateReferenceSystem target) throws FactoryException { - return (source instanceof SingleCRS) && (target instanceof SingleCRS) - && Utilities.equalsIgnoreMetadata( - ((SingleCRS) source).getDatumEnsemble(), - ((SingleCRS) target).getDatumEnsemble()); + if (source instanceof SingleCRS && target instanceof SingleCRS) { + return ReferencingUtilities.areMembersOfSameEnsemble((SingleCRS) source, (SingleCRS) target); + } + 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; + } + } + } + } + return false; } /** 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 8aec007e7b..30f4513010 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 @@ -219,7 +219,7 @@ public class AbstractCRS extends AbstractReferenceSystem implements CoordinateRe * @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 + * @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) { @@ -232,6 +232,8 @@ public class AbstractCRS extends AbstractReferenceSystem implements CoordinateRe } throw new IllegalArgumentException(Resources.format(Resources.Keys.NotAMemberOfDatumEnsemble_2, IdentifiedObjects.getDisplayName(ensemble), IdentifiedObjects.getDisplayName(datum))); + } else { + ArgumentChecks.ensureNonEmpty("ensemble", ensemble.getMembers()); } } 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 a68014ca05..40ff61cca7 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,7 +18,11 @@ package org.apache.sis.referencing.crs; import java.util.Map; import java.util.Arrays; +import java.util.Iterator; 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; import org.opengis.referencing.crs.GeodeticCRS; import org.opengis.referencing.crs.GeographicCRS; @@ -26,10 +30,13 @@ import org.opengis.referencing.cs.EllipsoidalCS; 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.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; @@ -196,6 +203,7 @@ public class DefaultGeographicCRS extends DefaultGeodeticCRS implements Geograph if (!(cs instanceof EllipsoidalCS)) { throw illegalCoordinateSystemType(cs); } + checkDimension(2, 3, cs); } /** @@ -283,6 +291,68 @@ 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/DefaultTemporalCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultTemporalCRS.java index 8080e6010c..b3fbfe6256 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,6 +16,7 @@ */ package org.apache.sis.referencing.crs; +import java.util.Iterator; import java.util.Map; import java.util.Date; import java.time.Instant; @@ -33,6 +34,7 @@ import javax.measure.quantity.Time; import org.opengis.referencing.cs.TimeCS; import org.opengis.referencing.crs.TemporalCRS; import org.opengis.referencing.datum.TemporalDatum; +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; @@ -41,6 +43,7 @@ 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; @@ -118,9 +121,9 @@ public class DefaultTemporalCRS extends AbstractCRS implements TemporalCRS { private transient UnitConverter toSeconds; /** - * The {@linkplain TemporalDatum#getOrigin origin} in seconds since January 1st, 1970. - * This field could be implicit in the {@link #toSeconds} converter, but we still handle - * it explicitly in order to use integer arithmetic. + * The {@linkplain #getOrigin origin} in seconds since January 1st, 1970. + * This field could be implicit in the {@link #toSeconds} converter, + * but we still handle it explicitly in order to use integer arithmetic. */ private transient long origin; @@ -258,7 +261,7 @@ public class DefaultTemporalCRS extends AbstractCRS implements TemporalCRS { */ private void initializeConverter() { toSeconds = getUnit().getConverterTo(Units.SECOND); - final Temporal t = datum.getOrigin(); + final Temporal t = getOrigin(); origin = t.getLong(ChronoField.INSTANT_SECONDS); int r = t.get(ChronoField.NANO_OF_SECOND); if (r != 0) { @@ -350,6 +353,34 @@ public class DefaultTemporalCRS extends AbstractCRS implements TemporalCRS { return super.getCoordinateSystem().getAxis(0).getUnit().asType(Time.class); } + /** + * Returns the temporal origin which is indirectly (through a datum) associated to this <abbr>CRS</abbr>. + * If the {@linkplain #getDatum() datum} is non-null, then this method returns the datum origin. + * Otherwise, if all members of the {@linkplain #getDatumEnsemble() datum ensemble} use the same origin, + * then this method returns that origin. + * + * @return the origin indirectly associated to this <abbr>CRS</abbr>. + * @throws NullPointerException if an origin, which are mandatory, is null. + * @throws GeodeticException if the origin is not the same for all members of the datum ensemble. + * + * @since 1.5 + */ + public final Temporal getOrigin() { // Must be final because invoked at construction time. + 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; + } + /** * {@inheritDoc} * diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/ExplicitParameters.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/ExplicitParameters.java index 65a7624e86..c476ed9e01 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/ExplicitParameters.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/ExplicitParameters.java @@ -19,12 +19,11 @@ package org.apache.sis.referencing.crs; import org.opengis.parameter.ParameterValue; import org.opengis.parameter.GeneralParameterValue; import org.opengis.parameter.GeneralParameterDescriptor; -import org.opengis.referencing.datum.Datum; -import org.opengis.referencing.datum.GeodeticDatum; import org.opengis.referencing.datum.Ellipsoid; import org.opengis.referencing.operation.Conversion; import org.apache.sis.referencing.IdentifiedObjects; import org.apache.sis.referencing.operation.DefaultOperationMethod; +import org.apache.sis.referencing.privy.ReferencingUtilities; import org.apache.sis.referencing.privy.WKTKeywords; import org.apache.sis.referencing.privy.WKTUtilities; import org.apache.sis.util.privy.Constants; @@ -61,8 +60,7 @@ final class ExplicitParameters extends FormattableObject { */ ExplicitParameters(final AbstractDerivedCRS crs, final String keyword) { conversion = crs.getConversionFromBase(); - final Datum datum = crs.getDatum(); - ellipsoid = (datum instanceof GeodeticDatum) ? ((GeodeticDatum) datum).getEllipsoid() : null; + ellipsoid = ReferencingUtilities.getEllipsoid(crs); this.keyword = keyword; } diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.java index 6ed76bff3f..1709cc3110 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.java @@ -438,7 +438,7 @@ public class Resources extends IndexedResourceBundle { /** * The given “{0}” description does not conform to the “{1}” authoritative description. * Differences are found in {2,choice,0#conversion method|1#conversion description|2#coordinate - * system|3#datum|4#prime meridian|5#CRS}. + * system|3#datum|4#prime meridian|5#ellipsoid|6#CRS}. */ public static final short NonConformCRS_3 = 73; diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.properties b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.properties index 41f456f7cf..0f3b751097 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.properties +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.properties @@ -42,7 +42,7 @@ MisnamedParameter_1 = Despite its name, this parameter is effectiv NotFormalProjectionParameter_1 = This parameter borrowed from the \u201c{0}\u201d projection is not formally a parameter of this projection. NonConformAxes_2 = The coordinate system axes in the given \u201c{0}\u201d description do not conform to the expected axes according \u201c{1}\u201d authoritative description. NonConformCRS_3 = The given \u201c{0}\u201d description does not conform to the \u201c{1}\u201d authoritative description. \ - Differences are found in {2,choice,0#conversion method|1#conversion description|2#coordinate system|3#datum|4#prime meridian|5#CRS}. + Differences are found in {2,choice,0#conversion method|1#conversion description|2#coordinate system|3#datum|4#prime meridian|5#ellipsoid|6#CRS}. RestrictedToPoleLatitudes = The only valid entries are \u00b190\u00b0 or equivalent in alternative angle units. # diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources_fr.properties b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources_fr.properties index 163eac2614..245a08e502 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources_fr.properties +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources_fr.properties @@ -47,7 +47,7 @@ MisnamedParameter_1 = Malgr\u00e9 son nom, ce param\u00e8tre produ NotFormalProjectionParameter_1 = Ce param\u00e8tre emprunt\u00e9 \u00e0 la projection \u00ab\u202f{0}\u202f\u00bb n\u2019est pas formellement un param\u00e8tre de cette projection. NonConformAxes_2 = Les axes du syst\u00e8me de coordonn\u00e9es d\u00e9finis dans \u00ab\u202f{0}\u202f\u00bb ne sont pas conformes aux axes attendus d\u2019apr\u00e8s la description officielle de \u00ab\u202f{1}\u202f\u00bb. NonConformCRS_3 = La description donn\u00e9e pour \u00ab\u202f{0}\u202f\u00bb n\u2019est pas conforme \u00e0 la description officielle de \u00ab\u202f{1}\u202f\u00bb. \ - Des diff\u00e9rences ont \u00e9t\u00e9 trouv\u00e9es dans {2,choice,0#la m\u00e9thode de conversion|1#la description de la conversion|2#le syst\u00e8me de coordonn\u00e9es|3#le r\u00e9f\u00e9rentiel|4#le m\u00e9ridien d\u2019origine|5#le CRS}. + Des diff\u00e9rences ont \u00e9t\u00e9 trouv\u00e9es dans {2,choice,0#la m\u00e9thode de conversion|1#la description de la conversion|2#le syst\u00e8me de coordonn\u00e9es|3#le r\u00e9f\u00e9rentiel|4#le m\u00e9ridien d\u2019origine|5#l\u2019ellipso\u00efde|6#le CRS}. RestrictedToPoleLatitudes = Les seules valeurs valides sont \u00b190\u00b0 ou \u00e9quivalent dans d\u2019autres unit\u00e9s. # diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java index af3c6d7ae3..5b6030e34e 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java @@ -23,6 +23,7 @@ import jakarta.xml.bind.annotation.XmlRootElement; import javax.measure.IncommensurableException; import org.opengis.util.FactoryException; import org.opengis.parameter.ParameterValueGroup; +import org.opengis.referencing.IdentifiedObject; import org.opengis.referencing.operation.Conversion; import org.opengis.referencing.operation.OperationMethod; import org.opengis.referencing.operation.MathTransform; @@ -30,7 +31,6 @@ import org.opengis.referencing.operation.MathTransformFactory; import org.opengis.referencing.operation.Matrix; import org.opengis.referencing.crs.SingleCRS; import org.opengis.referencing.crs.CoordinateReferenceSystem; -import org.opengis.referencing.datum.Datum; import org.apache.sis.referencing.cs.CoordinateSystems; import org.apache.sis.referencing.factory.InvalidGeodeticParameterException; import org.apache.sis.referencing.internal.ParameterizedTransformBuilder; @@ -409,8 +409,17 @@ public class DefaultConversion extends AbstractSingleOperation implements Conver final CoordinateReferenceSystem actual) { if ((expected instanceof SingleCRS) && (actual instanceof SingleCRS)) { - final Datum datum = ((SingleCRS) expected).getDatum(); - if (datum != null && !Utilities.equalsIgnoreMetadata(datum, ((SingleCRS) actual).getDatum())) { + final var crs1 = (SingleCRS) expected; + final var crs2 = (SingleCRS) actual; + IdentifiedObject datum = crs1.getDatum(); + IdentifiedObject other; + if (datum != null) { + other = crs2.getDatum(); + } else { + datum = crs1.getDatumEnsemble(); + other = crs2.getDatumEnsemble(); + } + if (datum != null && other != null && !Utilities.equalsIgnoreMetadata(datum, other)) { throw new MismatchedDatumException(Resources.format( Resources.Keys.IncompatibleDatum_2, datum.getName(), param)); } 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 3fcaec2cd3..1573b2ef58 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 @@ -30,24 +30,24 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.crs.SingleCRS; import org.opengis.referencing.crs.CRSFactory; import org.opengis.referencing.cs.CSFactory; -import org.opengis.referencing.datum.Datum; import org.apache.sis.referencing.CRS; import org.apache.sis.referencing.IdentifiedObjects; import org.apache.sis.referencing.AbstractIdentifiedObject; +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.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; import org.apache.sis.util.Debug; import org.apache.sis.util.privy.Constants; -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.util.collection.WeakHashSet; import org.apache.sis.util.collection.Containers; import org.apache.sis.util.collection.Cache; @@ -409,13 +409,13 @@ public class DefaultCoordinateOperationFactory extends AbstractFactory implement { List<SingleCRS> components = CRS.getSingleComponents(sourceCRS); int n = components.size(); // Number of remaining datum from sourceCRS to verify. - final Datum[] datum = new Datum[n]; + final IdentifiedObject[] datum = new IdentifiedObject[n]; for (int i=0; i<n; i++) { - datum[i] = components.get(i).getDatum(); + datum[i] = ReferencingUtilities.getDatumOrEnsemble(components.get(i)); } components = CRS.getSingleComponents(targetCRS); next: for (int i=components.size(); --i >= 0;) { - final Datum d = components.get(i).getDatum(); + final IdentifiedObject d = ReferencingUtilities.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/operation/transform/DefaultMathTransformFactory.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java index e64ab32b0d..84062a6b64 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java @@ -45,6 +45,7 @@ import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.privy.Constants; import org.apache.sis.util.iso.AbstractFactory; import org.apache.sis.util.collection.WeakHashSet; +import org.apache.sis.referencing.privy.ReferencingUtilities; import org.apache.sis.referencing.privy.CoordinateOperations; import org.apache.sis.referencing.operation.DefaultOperationMethod; import org.apache.sis.referencing.internal.ParameterizedTransformBuilder; @@ -552,7 +553,7 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math public void setSource(final GeodeticCRS crs) { if (crs != null) { sourceCS = crs.getCoordinateSystem(); - sourceEllipsoid = crs.getDatum().getEllipsoid(); + sourceEllipsoid = ReferencingUtilities.getEllipsoid(crs); } else { sourceCS = null; sourceEllipsoid = null; @@ -592,7 +593,7 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math public void setTarget(final GeodeticCRS crs) { if (crs != null) { targetCS = crs.getCoordinateSystem(); - targetEllipsoid = crs.getDatum().getEllipsoid(); + targetEllipsoid = ReferencingUtilities.getEllipsoid(crs); } else { targetCS = null; targetEllipsoid = null; 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 866957855b..2bcd77dd12 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 @@ -24,8 +24,6 @@ import org.opengis.util.FactoryException; import org.opengis.metadata.citation.Citation; import org.opengis.referencing.IdentifiedObject; import org.opengis.referencing.NoSuchAuthorityCodeException; -import org.opengis.referencing.datum.Datum; -import org.opengis.referencing.datum.GeodeticDatum; import org.opengis.referencing.crs.CRSAuthorityFactory; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.crs.SingleCRS; @@ -301,13 +299,13 @@ public final class DefinitionVerifier { * Indicates in which part of CRS description a difference has been found. Numerical values must match the number * in the {@code {choice}} instruction in the message associated to {@link Resources.Keys#NonConformCRS_3}. */ - private static final int METHOD=0, CONVERSION=1, CS=2, DATUM=3, PRIME_MERIDIAN=4, OTHER=5; + private static final int METHOD=0, CONVERSION=1, CS=2, DATUM=3, PRIME_MERIDIAN=4, ELLIPSOID=5, OTHER=6; /** * Returns a code indicating in which part the two given CRS differ. The given iterators usually iterate over * exactly one element, but may iterate over more elements if the CRS were instance of {@code CompoundCRS}. * The returned value is one of {@link #METHOD}, {@link #CONVERSION}, {@link #CS}, {@link #DATUM}, - * {@link #PRIME_MERIDIAN} or {@link #OTHER} constants. + * {@link #PRIME_MERIDIAN}, {@link #ELLIPSOID} or {@link #OTHER} constants. */ private static int diffCode(final Iterator<SingleCRS> authoritative, final Iterator<SingleCRS> given) { while (authoritative.hasNext() && given.hasNext()) { @@ -324,15 +322,17 @@ public final class DefinitionVerifier { if (!Utilities.equalsApproximately(crsA.getCoordinateSystem(), crsG.getCoordinateSystem())) { return CS; } - final Datum datumA = crsA.getDatum(); - final Datum datumG = crsG.getDatum(); - if (!Utilities.equalsApproximately(datumA, datumG)) { - if ((datumA instanceof GeodeticDatum) && (datumG instanceof GeodeticDatum) && - !Utilities.equalsApproximately(((GeodeticDatum) datumA).getPrimeMeridian(), - ((GeodeticDatum) datumG).getPrimeMeridian())) - { - return PRIME_MERIDIAN; - } + if (!Utilities.equalsApproximately(ReferencingUtilities.getEllipsoid(crsA), + ReferencingUtilities.getEllipsoid(crsG))) + { + return ELLIPSOID; + } + if (!Utilities.equalsApproximately(ReferencingUtilities.getPrimeMeridian(crsA), + ReferencingUtilities.getPrimeMeridian(crsG))) + { + return PRIME_MERIDIAN; + } + if (!Utilities.equalsApproximately(crsA.getDatum(), crsG.getDatum())) { 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 c8791dbd93..27deaab23a 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 @@ -116,14 +116,14 @@ public final class EllipsoidalHeightCombiner { final VerticalDatum datum = ((VerticalCRS) vertical).getDatum(); if (ReferencingUtilities.isEllipsoidalHeight(datum)) { int axisPosition = 0; - CoordinateSystem cs = null; + CoordinateSystem cs2D; CoordinateReferenceSystem crs = null; - if (i == 0 || (cs = getCsIfHorizontal2D(crs = components[i - 1])) == null) { + if (i == 0 || (cs2D = getCsIfHorizontal2D(crs = components[i - 1])) == null) { /* * GeographicCRS are normally before VerticalCRS. But Apache SIS is tolerant to the * opposite order (note however that such ordering is illegal according ISO 19162). */ - if (i+1 >= components.length || (cs = getCsIfHorizontal2D(crs = components[i + 1])) == null) { + if (i+1 >= components.length || (cs2D = getCsIfHorizontal2D(crs = components[i + 1])) == null) { continue; } axisPosition = 1; @@ -136,15 +136,17 @@ public final class EllipsoidalHeightCombiner { * implementation recycles the properties of the existing two-dimensional CRS. */ final CoordinateSystemAxis[] axes = new CoordinateSystemAxis[3]; - axes[axisPosition++ ] = cs.getAxis(0); - axes[axisPosition++ ] = cs.getAxis(1); + axes[axisPosition++ ] = cs2D.getAxis(0); + axes[axisPosition++ ] = cs2D.getAxis(1); axes[axisPosition %= 3] = vertical.getCoordinateSystem().getAxis(0); - final Map<String,?> csProps = IdentifiedObjects.getProperties(cs, CoordinateSystem.IDENTIFIERS_KEY); + final Map<String,?> csProps = IdentifiedObjects.getProperties(cs2D, CoordinateSystem.IDENTIFIERS_KEY); final Map<String,?> crsProps = (components.length == 2) ? properties : IdentifiedObjects.getProperties(crs, CoordinateReferenceSystem.IDENTIFIERS_KEY); if (crs instanceof GeodeticCRS) { - cs = factories.getCSFactory() .createEllipsoidalCS(csProps, axes[0], axes[1], axes[2]); - crs = factories.getCRSFactory().createGeographicCRS(crsProps, ((GeodeticCRS) crs).getDatum(), (EllipsoidalCS) cs); + final var geod = (GeodeticCRS) crs; + final EllipsoidalCS cs3D; + cs3D = factories.getCSFactory() .createEllipsoidalCS(csProps, axes[0], axes[1], axes[2]); + crs = factories.getCRSFactory().createGeographicCRS(crsProps, geod.getDatum(), geod.getDatumEnsemble(), cs3D); } else { final ProjectedCRS proj = (ProjectedCRS) crs; GeodeticCRS base = proj.getBaseCRS(); @@ -161,8 +163,10 @@ public final class EllipsoidalHeightCombiner { fromBase = factories.getCoordinateOperationFactory().createDefiningConversion( IdentifiedObjects.getProperties(fromBase), fromBase.getMethod(), fromBase.getParameterValues()); - cs = factories.getCSFactory() .createCartesianCS(csProps, axes[0], axes[1], axes[2]); - crs = factories.getCRSFactory().createProjectedCRS(crsProps, base, fromBase, (CartesianCS) cs); + + final CartesianCS cs3D; + cs3D = factories.getCSFactory() .createCartesianCS(csProps, axes[0], axes[1], axes[2]); + crs = factories.getCRSFactory().createProjectedCRS(crsProps, base, fromBase, cs3D); } /* * Remove the VerticalCRS and store the three-dimensional GeographicCRS in place of the previous 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 0c84f2a1eb..1c30eea584 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 @@ -63,6 +63,7 @@ import org.apache.sis.parameter.Parameters; // Specific to the geoapi-3.1 and geoapi-4.0 branches: import org.opengis.referencing.ObjectDomain; +import org.opengis.referencing.datum.DatumEnsemble; import org.opengis.referencing.operation.MathTransform; // Specific to the geoapi-4.0 branch: @@ -86,6 +87,11 @@ public class GeodeticObjectBuilder extends Builder<GeodeticObjectBuilder> { */ private GeodeticDatum datum; + /** + * The datum ensemble, or {@code null} if none. + */ + private DatumEnsemble<GeodeticDatum> ensemble; + /** * The name of the conversion to use for creating a {@code ProjectedCRS} or {@code DerivedCRS}. * This name is for information purpose; its value does not impact the numerical results of coordinate operations. @@ -361,6 +367,7 @@ public class GeodeticObjectBuilder extends Builder<GeodeticObjectBuilder> { method = c.getMethod(); parameters = c.getParameterValues(); datum = crs.getDatum(); + ensemble = crs.getDatumEnsemble(); properties.putAll(IdentifiedObjects.getProperties(crs, ProjectedCRS.IDENTIFIERS_KEY)); return this; } @@ -509,8 +516,9 @@ public class GeodeticObjectBuilder extends Builder<GeodeticObjectBuilder> { */ public ProjectedCRS createProjectedCRS() throws FactoryException { GeographicCRS crs = getBaseCRS(); - if (datum != null) { - crs = factories.getCRSFactory().createGeographicCRS(name(datum), datum, crs.getCoordinateSystem()); + if (datum != null || ensemble != null) { + crs = factories.getCRSFactory().createGeographicCRS( + name(datum != null ? datum : ensemble), datum, ensemble, crs.getCoordinateSystem()); } return createProjectedCRS(crs, factories.getStandardProjectedCS()); } @@ -532,8 +540,12 @@ public class GeodeticObjectBuilder extends Builder<GeodeticObjectBuilder> { */ public GeographicCRS createGeographicCRS() throws FactoryException { final GeographicCRS crs = getBaseCRS(); - if (datum != null) properties.putIfAbsent(GeographicCRS.NAME_KEY, datum.getName()); - return factories.getCRSFactory().createGeographicCRS(properties, datum, crs.getCoordinateSystem()); + IdentifiedObject id = datum; + if (id == null) id = ensemble; + if (id != null) { + properties.putIfAbsent(GeographicCRS.NAME_KEY, id.getName()); + } + return factories.getCRSFactory().createGeographicCRS(properties, datum, ensemble, crs.getCoordinateSystem()); } /** @@ -551,19 +563,19 @@ public class GeodeticObjectBuilder extends Builder<GeodeticObjectBuilder> { * This not only saves a little bit of memory, but also provides better names. */ TimeCS cs = null; - TemporalDatum datum = null; + TemporalDatum td = null; for (final CommonCRS.Temporal c : CommonCRS.Temporal.values()) { - if (datum == null) { + if (td == null) { final TemporalDatum candidate = c.datum(); if (origin.equals(candidate.getOrigin())) { - datum = candidate; + td = candidate; } } if (cs == null) { final TemporalCRS crs = c.crs(); final TimeCS candidate = crs.getCoordinateSystem(); if (unit.equals(candidate.getAxis(0).getUnit())) { - if (datum == candidate && properties.isEmpty()) { + if (td == candidate && properties.isEmpty()) { return crs; } cs = candidate; @@ -586,15 +598,15 @@ public class GeodeticObjectBuilder extends Builder<GeodeticObjectBuilder> { if (properties.get(TemporalCRS.NAME_KEY) == null) { properties.putAll(name(cs)); } - if (datum == null) { + if (td == null) { final Object remarks = properties.remove(TemporalCRS.REMARKS_KEY); final Object identifier = properties.remove(TemporalCRS.IDENTIFIERS_KEY); - datum = factories.getDatumFactory().createTemporalDatum(properties, origin); + td = factories.getDatumFactory().createTemporalDatum(properties, origin); properties.put(TemporalCRS.IDENTIFIERS_KEY, identifier); properties.put(TemporalCRS.REMARKS_KEY, remarks); - properties.put(TemporalCRS.NAME_KEY, datum.getName()); // Share the Identifier instance. + properties.put(TemporalCRS.NAME_KEY, td.getName()); // Share the Identifier instance. } - return factories.getCRSFactory().createTemporalCRS(properties, datum, cs); + return factories.getCRSFactory().createTemporalCRS(properties, td, cs); } finally { onCreate(true); } diff --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 48fae67a4a..07aa3e2bbd 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 @@ -18,6 +18,7 @@ package org.apache.sis.referencing.privy; import java.util.Map; import java.util.HashMap; +import java.util.function.Function; import javax.measure.Unit; import javax.measure.quantity.Angle; import org.opengis.annotation.UML; @@ -52,6 +53,9 @@ import org.apache.sis.referencing.cs.DefaultEllipsoidalCS; import org.apache.sis.referencing.internal.VerticalDatumTypes; import org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory; +// Specific to the geoapi-3.1 and geoapi-4.0 branches: +import org.opengis.referencing.datum.DatumEnsemble; + /** * A set of static methods working on GeoAPI referencing objects. @@ -202,6 +206,38 @@ public final class ReferencingUtilities extends Static { return (types.length != 0) ? types[0] : type; } + /** + * Returns whether the given <abbr>CRS</abbr> use the same datum or the same datum ensemble. + * + * @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. + */ + 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; + } + } + 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(); + } + /** * Returns {@code true} if the type of the given datum is ellipsoidal. A vertical datum is not allowed * to be ellipsoidal according ISO 19111, but Apache SIS relaxes this restriction in some limited cases, @@ -228,66 +264,80 @@ public final class ReferencingUtilities extends Static { * <ul> * <li>If the given CRS is an instance of {@link SingleCRS} and its datum is a {@link GeodeticDatum}, * then this method returns the datum ellipsoid.</li> - * <li>Otherwise if the given CRS is an instance of {@link CompoundCRS}, then this method + * <li>Otherwise, if the given CRS is associated to a {@link DatumEnsemble} and all members of the + * ensemble have equal (ignoring metadata) ellipsoid, then returns that ellipsoid.</li> + * <li>Otherwise, if the given CRS is an instance of {@link CompoundCRS}, then this method * invokes itself recursively for each component until a geodetic reference frame is found.</li> - * <li>Otherwise this method returns {@code null}.</li> + * <li>Otherwise, this method returns {@code null}.</li> * </ul> * - * Note that this method does not check if there is more than one ellipsoid (it should never be the case). + * Note that this method does not check if a compound <abbr>CRS</abbr> contains more than one ellipsoid + * (it should never be the case). Note also that this method may return {@code null} even if the CRS is + * geodetic. * * @param crs the coordinate reference system for which to get the ellipsoid. * @return the ellipsoid, or {@code null} if none. */ public static Ellipsoid getEllipsoid(final CoordinateReferenceSystem crs) { - if (crs != null) { - if (crs instanceof SingleCRS) { - final Datum datum = ((SingleCRS) crs).getDatum(); - if (datum instanceof GeodeticDatum) { - final Ellipsoid e = ((GeodeticDatum) datum).getEllipsoid(); - if (e != null) return e; - } - } - if (crs instanceof CompoundCRS) { - for (final CoordinateReferenceSystem c : ((CompoundCRS) crs).getComponents()) { - final Ellipsoid e = getEllipsoid(c); - if (e != null) return e; - } - } - } - return null; + return getGeodeticProperty(crs, GeodeticDatum::getEllipsoid); } /** - * Returns the ellipsoid used by the specified coordinate reference system, provided that the two first dimensions - * use an instance of {@link GeographicCRS}. Otherwise (i.e. if the two first dimensions are not geographic), - * returns {@code null}. + * Returns the prime meridian used by the given coordinate reference system, or {@code null} if none. + * This method applies the same rules as {@link #getEllipsoid(CoordinateReferenceSystem)}. * - * <p>This method excludes geocentric CRS on intent. Some callers needs this exclusion as a way to identify - * which CRS in a Geographic/Geocentric conversion is the geographic one. Another point of view is to said - * that if this method returns a non-null value, then the coordinates are expected to be either two-dimensional - * or three-dimensional with an ellipsoidal height.</p> + * @param crs the coordinate reference system for which to get the prime meridian. + * @return the prime meridian, or {@code null} if none. + */ + public static PrimeMeridian getPrimeMeridian(final CoordinateReferenceSystem crs) { + return getGeodeticProperty(crs, GeodeticDatum::getPrimeMeridian); + } + + /** + * Implementation of {@code getEllipsoid(CRS)} and {@code getPrimeMeridian(CRS)}. * - * @param crs the coordinate reference system for which to get the ellipsoid. - * @return the ellipsoid in the given CRS, or {@code null} if none. + * @param <P> the type of object to get. + * @param crs the coordinate reference system for which to get the ellipsoid or prime meridian. + * @param getter the method to invoke on {@link GeodeticDatum} instances. + * @return the ellipsoid or prime meridian, or {@code null} if none. */ - public static Ellipsoid getEllipsoidOfGeographicCRS(CoordinateReferenceSystem crs) { - while (!(crs instanceof GeodeticCRS)) { - if (crs instanceof CompoundCRS) { - crs = ((CompoundCRS) crs).getComponents().get(0); - } else { - return null; + private static <P> P getGeodeticProperty(final CoordinateReferenceSystem crs, final Function<GeodeticDatum, P> getter) { +single: if (crs instanceof SingleCRS) { + final SingleCRS scrs = (SingleCRS) crs; + final Datum datum = scrs.getDatum(); + if (datum instanceof GeodeticDatum) { + P property = getter.apply((GeodeticDatum) datum); + if (property != null) { + return property; + } + } + final DatumEnsemble<?> ensemble = scrs.getDatumEnsemble(); + if (ensemble != null) { + P common = null; + for (Datum member : ensemble.getMembers()) { + if (member instanceof GeodeticDatum) { + final P property = getter.apply((GeodeticDatum) member); + if (property != null) { + if (common == null) { + common = property; + } else if (!Utilities.equalsIgnoreMetadata(property, common)) { + break single; + } + } + } + } + return common; } } - /* - * In order to determine if the CRS is geographic, checking the CoordinateSystem type is more reliable - * then checking if the CRS implements the GeographicCRS interface. This is because the GeographicCRS - * interface is GeoAPI-specific, so a CRS may be OGC-compliant without implementing that interface. - */ - if (crs.getCoordinateSystem() instanceof EllipsoidalCS) { - return ((GeodeticCRS) crs).getDatum().getEllipsoid(); - } else { - return null; // Geocentric CRS. + if (crs instanceof CompoundCRS) { + for (final CoordinateReferenceSystem c : ((CompoundCRS) crs).getComponents()) { + final P property = getGeodeticProperty(c, getter); + if (property != null) { + return property; + } + } } + return null; } /** diff --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 d0fa83779a..381d6e5a9a 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 @@ -903,11 +903,11 @@ public final class CRSBuilder extends ReferencingFactoryContainer { name = Strings.toUpperCase(name, Characters.Filter.LETTERS_AND_DIGITS, true); lastName = datum.getName(); try { - final GeodeticDatum predefined = CommonCRS.valueOf(name).datum(); - if (equalsIgnoreMetadata(predefined.getEllipsoid(), ellipsoid) && - equalsIgnoreMetadata(predefined.getPrimeMeridian(), meridian)) + final CommonCRS predefined = CommonCRS.valueOf(name); + if (equalsIgnoreMetadata(predefined.ellipsoid(), ellipsoid) && + equalsIgnoreMetadata(predefined.primeMeridian(), meridian)) { - return predefined; + return predefined.datum(); } } catch (IllegalArgumentException e) { // Not a name that can be mapped to CommonCRS. Ignore. 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 9b5b9df2cc..0519241239 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 @@ -325,38 +325,42 @@ 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(); - 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(); + 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())); } else { - 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)); + 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)); + } } else if (isBaseCRS) { writeUnit(UnitKey.ANGULAR); // Map projection parameters may need this unit. } diff --git a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/CRSBuilder.java b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/CRSBuilder.java index 87352f7e20..b3d4d844fd 100644 --- a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/CRSBuilder.java +++ b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/CRSBuilder.java @@ -56,6 +56,7 @@ import org.apache.sis.storage.DataStoreContentException; import org.apache.sis.storage.DataStoreException; import org.apache.sis.storage.netcdf.internal.Resources; import org.apache.sis.util.ArraysExt; +import org.apache.sis.util.Utilities; import org.apache.sis.util.resources.Errors; import org.apache.sis.measure.NumberRange; import org.apache.sis.measure.Units; @@ -621,8 +622,18 @@ previous: for (int i=components.size(); --i >= 0;) { * This method is invoked only if {@link #setPredefinedComponents(Decoder)} failed to create a datum. */ @Override final void createDatum(DatumFactory factory, Map<String,?> properties) throws FactoryException { - final GeodeticDatum template = defaultCRS.datum(); - datum = factory.createGeodeticDatum(properties, template.getEllipsoid(), template.getPrimeMeridian()); + datum = factory.createGeodeticDatum(properties, defaultCRS.ellipsoid(), defaultCRS.primeMeridian()); + } + + /** + * Sets the datum from the enumeration value of a predefined CRS. + * The predefined CRS is {@link #defaultCRS} or a spherical CRS. + */ + protected final void setDatum(final CommonCRS crs) { + datum = crs.datum(); + if (datum == null) { + datumEnsemble = crs.datumEnsemble(); + } } /** @@ -676,8 +687,9 @@ previous: for (int i=components.size(); --i >= 0;) { referenceSystem = crs; coordinateSystem = (SphericalCS) crs.getCoordinateSystem(); datum = crs.getDatum(); + datumEnsemble = crs.getDatumEnsemble(); } else { - datum = defaultCRS.datum(); + setDatum(defaultCRS); } } @@ -739,8 +751,9 @@ previous: for (int i=components.size(); --i >= 0;) { referenceSystem = crs; coordinateSystem = crs.getCoordinateSystem(); datum = crs.getDatum(); + datumEnsemble = crs.getDatumEnsemble(); } else { - datum = defaultCRS.datum(); + setDatum(defaultCRS); final Integer epsg = epsgCandidateCS(Units.DEGREE); if (epsg != null) try { coordinateSystem = decoder.getCSAuthorityFactory().createEllipsoidalCS(epsg.toString()); @@ -768,7 +781,7 @@ previous: for (int i=components.size(); --i >= 0;) { * This method is invoked under conditions similar to the ones of above {@code createCS(…)} method. */ @Override void createCRS(CRSFactory factory, Map<String,?> properties) throws FactoryException { - referenceSystem = factory.createGeographicCRS(properties, datum, coordinateSystem); + referenceSystem = factory.createGeographicCRS(properties, datum, datumEnsemble, coordinateSystem); } } @@ -817,7 +830,7 @@ previous: for (int i=components.size(); --i >= 0;) { @Override void setPredefinedComponents(final Decoder decoder) throws FactoryException { super.setPredefinedComponents(decoder); sphericalDatum = decoder.convention().defaultHorizontalCRS(true); - datum = sphericalDatum.datum(); + setDatum(sphericalDatum); if (isPredefinedCS(Units.METRE)) { coordinateSystem = decoder.getStandardProjectedCS(); } @@ -843,8 +856,10 @@ previous: for (int i=components.size(); --i >= 0;) { @Override void createCRS(CRSFactory factory, Map<String,?> properties) throws FactoryException { final boolean is3D = (coordinateSystem.getDimension() >= 3); GeographicCRS baseCRS = is3D ? sphericalDatum.geographic3D() : sphericalDatum.geographic(); - if (!baseCRS.getDatum().equals(datum)) { - baseCRS = factory.createGeographicCRS(properties, datum, baseCRS.getCoordinateSystem()); + if (!Utilities.equalsIgnoreMetadata(baseCRS.getDatum(), datum) && + !Utilities.equalsIgnoreMetadata(baseCRS.getDatumEnsemble(), datumEnsemble)) + { + baseCRS = factory.createGeographicCRS(properties, datum, datumEnsemble, baseCRS.getCoordinateSystem()); } referenceSystem = factory.createProjectedCRS(properties, baseCRS, UNKNOWN_PROJECTION, coordinateSystem); } @@ -912,7 +927,7 @@ previous: for (int i=components.size(); --i >= 0;) { * Creates the coordinate reference system from datum and coordinate system computed in previous steps. */ @Override void createCRS(CRSFactory factory, Map<String,?> properties) throws FactoryException { - referenceSystem = factory.createVerticalCRS(properties, datum, coordinateSystem); + referenceSystem = factory.createVerticalCRS(properties, datum, datumEnsemble, coordinateSystem); } } @@ -992,7 +1007,7 @@ previous: for (int i=components.size(); --i >= 0;) { @Override void createCRS(CRSFactory factory, Map<String,?> properties) throws FactoryException { properties = properties(getFirstAxis().coordinates.getUnitsString()); if (datum != null) { - referenceSystem = factory.createTemporalCRS(properties, datum, coordinateSystem); + referenceSystem = factory.createTemporalCRS(properties, datum, datumEnsemble, coordinateSystem); } else { referenceSystem = factory.createEngineeringCRS(properties, CommonCRS.Engineering.TIME.datum(), coordinateSystem); @@ -1058,7 +1073,7 @@ previous: for (int i=components.size(); --i >= 0;) { * Creates the coordinate reference system from datum and coordinate system computed in previous steps. */ @Override void createCRS(CRSFactory factory, Map<String,?> properties) throws FactoryException { - referenceSystem = factory.createEngineeringCRS(properties, datum, coordinateSystem); + referenceSystem = factory.createEngineeringCRS(properties, datum, datumEnsemble, coordinateSystem); } } diff --git a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java index e470fbd1d5..609cc18f5f 100644 --- a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java +++ b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java @@ -78,6 +78,9 @@ import org.apache.sis.io.wkt.WKTFormat; import org.apache.sis.io.wkt.Warnings; import org.apache.sis.measure.Units; +// Specific to the geoapi-3.1 and geoapi-4.0 branches: +import org.opengis.referencing.datum.DatumEnsemble; + /** * Temporary objects for creating a {@link GridGeometry} instance defined by attributes on a variable. @@ -367,6 +370,7 @@ final class GridMapping { */ final Object bursaWolf = definition.remove(Convention.TOWGS84); final GeodeticDatum datum; + DatumEnsemble<GeodeticDatum> ensemble = null; if (isSpecified | bursaWolf != null) { Map<String,Object> properties = properties(definition, Convention.GEODETIC_DATUM_NAME, ellipsoid); if (bursaWolf instanceof BursaWolfParameters) { @@ -377,13 +381,16 @@ final class GridMapping { datum = datumFactory.createGeodeticDatum(properties, ellipsoid, meridian); } else { datum = defaultDefinitions.datum(); + if (datum == null) { + ensemble = defaultDefinitions.datumEnsemble(); + } } /* * Geographic CRS from all above properties. */ if (isSpecified) { final Map<String,?> properties = properties(definition, Convention.GEOGRAPHIC_CRS_NAME, datum); - return decoder.getCRSFactory().createGeographicCRS(properties, datum, + return decoder.getCRSFactory().createGeographicCRS(properties, datum, ensemble, defaultDefinitions.geographic().getCoordinateSystem()); } else { return defaultDefinitions.geographic(); diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.java index 78d747d87c..027b7f1b48 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.java +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.java @@ -739,6 +739,11 @@ public class Errors extends IndexedResourceBundle { */ public static final short NonTemporalUnit_1 = 133; + /** + * Expected the “{0}” value for all members, but found a member with the “{1}” value. + */ + public static final short NonUniformValue_2 = 207; + /** * No element for the “{0}” identifier, or the identifier is a forward reference. */ diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.properties b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.properties index 103ad55d34..9fdefff89d 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.properties +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.properties @@ -158,6 +158,7 @@ NonLinearUnit_1 = \u201c{0}\u201d is not a linear unit. NonScaleUnit_1 = \u201c{0}\u201d is not a scale unit. NonTemporalUnit_1 = \u201c{0}\u201d is not a time unit. NonRatioUnit_1 = The scale of measurement for \u201c{0}\u201d unit is not a ratio scale. +NonUniformValue_2 = Expected the \u201c{0}\u201d value for all members, but found a member with the \u201c{1}\u201d value. NotABackwardReference_1 = No element for the \u201c{0}\u201d identifier, or the identifier is a forward reference. NotADivisorOrMultiple_4 = Value of \u2018{0}\u2019 shall be a {1,choice,0#divisor|1#multiple} of {2} but the given value is {3}. NotAnInteger_1 = {0} is not an integer value. diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties index ef040968eb..ef89339a87 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties @@ -155,6 +155,7 @@ NonLinearUnit_1 = \u00ab\u202f{0}\u202f\u00bb n\u2019est pas u NonScaleUnit_1 = \u00ab\u202f{0}\u202f\u00bb n\u2019est pas une unit\u00e9 d\u2019\u00e9chelles. NonTemporalUnit_1 = \u00ab\u202f{0}\u202f\u00bb n\u2019est pas une unit\u00e9 de temps. NonRatioUnit_1 = L\u2019\u00e9chelle de mesure de l\u2019unit\u00e9 \u00ab\u202f{0}\u202f\u00bb n\u2019est pas une \u00e9chelle de rapports. +NonUniformValue_2 = La valeur \u00ab\u202f{0}\u202f\u00bb \u00e9tait attendue pour tous les membres, mais un membre a la valeur \u00ab\u202f{1}\u202f\u00bb. NotABackwardReference_1 = Il n\u2019y a pas d\u2019\u00e9l\u00e9ment pour l\u2019identifiant \u201c{0}\u201d, ou l\u2019identifiant est une r\u00e9f\u00e9rence vers l\u2019avant. NotADivisorOrMultiple_4 = La valeur de \u2018{0}\u2019 doit \u00eatre un {1,choice,0#diviseur|1#multiple} de {2}, mais la valeur donn\u00e9e est {3}. NotAnInteger_1 = {0} n\u2019est pas un nombre entier.