You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2025/07/03 16:47:01 UTC
(sis) 02/02: `DefaultDatumEnsemble` implements `Datum` with a documentation saying that this is non-standard. Note that the EPSG database does something similar by storing ensembles in the datum table. The `PseudoDatum` class is replaced by `DatumOrEnsemble` with static methods.
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 f4aee41bc4cf6b3a2753e8458901edcc708d0b21
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Thu Jul 3 18:36:20 2025 +0200
`DefaultDatumEnsemble` implements `Datum` with a documentation saying that this is non-standard.
Note that the EPSG database does something similar by storing ensembles in the datum table.
The `PseudoDatum` class is replaced by `DatumOrEnsemble` with static methods.
The purpose of this change is to make easier to handle datum and ensemble with the same code.
This need was already present before, and become stronger for the upgrade to EPSG version 12.
---
.../org/apache/sis/portrayal/CanvasContext.java | 8 +-
.../org/apache/sis/geometry/CoordinateFormat.java | 4 +-
.../main/org/apache/sis/io/wkt/VerticalInfo.java | 4 +-
.../main/org/apache/sis/referencing/CRS.java | 7 +-
.../main/org/apache/sis/referencing/CommonCRS.java | 6 +-
.../apache/sis/referencing/GeodeticCalculator.java | 9 +-
.../sis/referencing/MultiRegisterOperations.java | 4 +-
.../sis/referencing/crs/AbstractSingleCRS.java | 37 +-
.../sis/referencing/crs/DefaultGeographicCRS.java | 6 +-
.../sis/referencing/crs/DefaultTemporalCRS.java | 4 +-
.../sis/referencing/crs/ExplicitParameters.java | 4 +-
.../sis/referencing/datum/DatumOrEnsemble.java | 456 ++++++++++++
.../referencing/datum/DefaultDatumEnsemble.java | 669 +++++++++++++++--
.../referencing/datum/DefaultPrimeMeridian.java | 2 +-
.../apache/sis/referencing/datum/PseudoDatum.java | 802 ---------------------
.../referencing/factory/GeodeticObjectFactory.java | 2 +-
.../internal/ParameterizedTransformBuilder.java | 6 +-
.../apache/sis/referencing/internal/Resources.java | 2 +-
.../sis/referencing/internal/Resources.properties | 2 +-
.../referencing/internal/Resources_fr.properties | 2 +-
.../operation/CoordinateOperationFinder.java | 31 +-
.../operation/CoordinateOperationRegistry.java | 6 +-
.../operation/DefaultConcatenatedOperation.java | 4 +-
.../referencing/operation/DefaultConversion.java | 3 +-
.../DefaultCoordinateOperationFactory.java | 4 +-
.../transform/DefaultMathTransformFactory.java | 6 +-
.../sis/referencing/privy/DefinitionVerifier.java | 38 +-
.../privy/EllipsoidalHeightCombiner.java | 4 +-
.../referencing/privy/ReferencingUtilities.java | 90 ---
.../apache/sis/referencing/privy/WKTUtilities.java | 6 +-
.../sis/referencing/GeodesicsOnEllipsoidTest.java | 4 +-
.../sis/storage/geotiff/reader/CRSBuilder.java | 6 +-
.../sis/storage/geotiff/writer/GeoEncoder.java | 6 +-
.../main/org/apache/sis/storage/csv/Store.java | 4 +-
34 files changed, 1184 insertions(+), 1064 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 b6c9144308..e66994e3f9 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
@@ -20,7 +20,6 @@ import java.util.Optional;
import java.util.OptionalDouble;
import org.opengis.metadata.extent.GeographicBoundingBox;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.datum.Ellipsoid;
import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
@@ -33,8 +32,8 @@ import org.apache.sis.math.MathFunctions;
import org.apache.sis.measure.Units;
import org.apache.sis.geometry.Envelopes;
import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.referencing.datum.DatumOrEnsemble;
import org.apache.sis.referencing.privy.Formulas;
-import org.apache.sis.referencing.privy.ReferencingUtilities;
import org.apache.sis.system.Configuration;
@@ -201,12 +200,11 @@ final class CanvasContext extends CoordinateOperationContext {
}
combined[j] = m;
}
- final Ellipsoid ellipsoid = ReferencingUtilities.getEllipsoid(objectiveToGeographic.getTargetCRS());
- if (ellipsoid != null) {
+ DatumOrEnsemble.getEllipsoid(objectiveToGeographic.getTargetCRS()).ifPresent((ellipsoid) -> {
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/main/org/apache/sis/geometry/CoordinateFormat.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/CoordinateFormat.java
index b2a15f9d9d..2f92163477 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
@@ -52,9 +52,9 @@ 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.datum.DatumOrEnsemble;
import org.apache.sis.referencing.privy.Formulas;
import org.apache.sis.referencing.privy.AxisDirections;
-import org.apache.sis.referencing.privy.ReferencingUtilities;
import org.apache.sis.system.Loggers;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.CharSequences;
@@ -1172,7 +1172,7 @@ abort: if (dimensions != 0 && groundAccuracy != null) try {
*/
Resolution derived(final CoordinateReferenceSystem crs) throws IncommensurableException {
if (isAngular || Units.isLinear(unit)) {
- final Ellipsoid ellipsoid = ReferencingUtilities.getEllipsoid(crs);
+ final Ellipsoid ellipsoid = DatumOrEnsemble.getEllipsoid(crs).orElse(null);
final double radius = Formulas.getAuthalicRadius(ellipsoid);
if (radius > 0) { // Indirectly filter null ellipsoid.
Unit<Length> axisUnit = ellipsoid.getAxisUnit();
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 5db9ddb99c..89bd846962 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.apache.sis.referencing.datum.PseudoDatum;
+import org.apache.sis.referencing.datum.DatumOrEnsemble;
import org.apache.sis.metadata.privy.AxisNames;
import org.apache.sis.metadata.iso.extent.DefaultExtent;
import org.apache.sis.metadata.iso.extent.DefaultVerticalExtent;
@@ -109,7 +109,7 @@ final class VerticalInfo {
*/
final VerticalInfo resolve(final VerticalCRS crs) {
if (crs != null) {
- if (PseudoDatum.of(crs).getRealizationMethod().orElse(null) == RealizationMethod.GEOID) {
+ if (DatumOrEnsemble.asDatum(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/CRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
index 5cf677f6af..84becac5f3 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
@@ -73,6 +73,8 @@ import org.apache.sis.referencing.crs.DefaultProjectedCRS;
import org.apache.sis.referencing.crs.DefaultVerticalCRS;
import org.apache.sis.referencing.crs.DefaultCompoundCRS;
import org.apache.sis.referencing.crs.DefaultEngineeringCRS;
+import org.apache.sis.referencing.datum.DatumOrEnsemble;
+import org.apache.sis.referencing.datum.DefaultPrimeMeridian;
import org.apache.sis.referencing.operation.AbstractCoordinateOperation;
import org.apache.sis.referencing.operation.CoordinateOperationContext;
import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
@@ -1552,12 +1554,13 @@ check: while (lower != 0 || upper != dimension) {
* @param crs the coordinate reference system from which to get the prime meridian.
* @return the Greenwich longitude (in degrees) of the prime meridian of the given CRS.
*
- * @see org.apache.sis.referencing.datum.DefaultPrimeMeridian#getGreenwichLongitude(Unit)
+ * @see DefaultPrimeMeridian#getGreenwichLongitude(Unit)
+ * @see DatumOrEnsemble#getPrimeMeridian(CoordinateReferenceSystem)
*
* @since 0.5
*/
public static double getGreenwichLongitude(final GeodeticCRS crs) {
- return ReferencingUtilities.getGreenwichLongitude(ReferencingUtilities.getPrimeMeridian(crs), Units.DEGREE);
+ return ReferencingUtilities.getGreenwichLongitude(DatumOrEnsemble.getPrimeMeridian(crs).orElse(null), 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 af71e2350d..404beba383 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
@@ -52,6 +52,7 @@ import org.opengis.referencing.datum.VerticalDatum;
import org.opengis.referencing.datum.EngineeringDatum;
import org.opengis.metadata.extent.GeographicBoundingBox;
import static org.opengis.referencing.IdentifiedObject.NAME_KEY;
+import org.apache.sis.referencing.datum.DatumOrEnsemble;
import org.apache.sis.referencing.datum.DefaultVerticalDatum;
import org.apache.sis.referencing.datum.DefaultTemporalDatum;
import org.apache.sis.referencing.datum.DefaultEngineeringDatum;
@@ -68,7 +69,6 @@ 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.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;
@@ -975,7 +975,7 @@ public enum CommonCRS {
return ((GeodeticDatum) object).getEllipsoid();
}
if (object instanceof CoordinateReferenceSystem) {
- return ReferencingUtilities.getEllipsoid((CoordinateReferenceSystem) object);
+ return DatumOrEnsemble.getEllipsoid((CoordinateReferenceSystem) object).orElse(null);
}
return null;
}
@@ -991,7 +991,7 @@ public enum CommonCRS {
return ((GeodeticDatum) object).getPrimeMeridian();
}
if (object instanceof CoordinateReferenceSystem) {
- return ReferencingUtilities.getPrimeMeridian((CoordinateReferenceSystem) object);
+ return DatumOrEnsemble.getPrimeMeridian((CoordinateReferenceSystem) object).orElse(null);
}
return null;
}
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/GeodeticCalculator.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/GeodeticCalculator.java
index 20adac574c..72778a94e4 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/GeodeticCalculator.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/GeodeticCalculator.java
@@ -41,6 +41,7 @@ import org.apache.sis.measure.Units;
import org.apache.sis.measure.Quantities;
import org.apache.sis.geometry.CoordinateFormat;
import org.apache.sis.parameter.Parameters;
+import org.apache.sis.referencing.datum.DatumOrEnsemble;
import org.apache.sis.referencing.operation.transform.MathTransforms;
import org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory;
import org.apache.sis.referencing.operation.provider.MapProjection;
@@ -292,11 +293,9 @@ public class GeodeticCalculator {
* @return a new geodetic calculator using the specified CRS.
*/
public static GeodeticCalculator create(final CoordinateReferenceSystem crs) {
- final Ellipsoid ellipsoid = ReferencingUtilities.getEllipsoid(Objects.requireNonNull(crs));
- if (ellipsoid == null) {
- throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalCRSType_1,
- ReferencingUtilities.getInterface(CoordinateReferenceSystem.class, crs)));
- }
+ final Ellipsoid ellipsoid = DatumOrEnsemble.getEllipsoid(Objects.requireNonNull(crs))
+ .orElseThrow(() -> new IllegalArgumentException(Errors.format(Errors.Keys.IllegalCRSType_1,
+ ReferencingUtilities.getInterface(CoordinateReferenceSystem.class, crs))));
if (ellipsoid.isSphere()) {
return new GeodeticCalculator(crs, ellipsoid);
} else {
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 ca5b60b103..75d861363b 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,7 @@ 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.referencing.datum.PseudoDatum;
+import org.apache.sis.referencing.datum.DatumOrEnsemble;
/**
@@ -351,7 +351,7 @@ public class MultiRegisterOperations extends AbstractFactory implements Register
return false;
}
for (int i=0; i<n; i++) {
- if (PseudoDatum.getDatumOrEnsemble(sources.get(i), targets.get(i)).isEmpty()) {
+ if (DatumOrEnsemble.ofTarget(sources.get(i), targets.get(i)).isEmpty()) {
return false;
}
}
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractSingleCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractSingleCRS.java
index d1fbdcaedf..686ef5b11a 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractSingleCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractSingleCRS.java
@@ -30,7 +30,6 @@ 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;
@@ -103,33 +102,13 @@ class AbstractSingleCRS<D extends Datum> extends AbstractCRS implements SingleCR
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);
+ if (datum instanceof DatumEnsemble<?>) {
+ throw new IllegalArgumentException(Errors.forProperties(properties)
+ .getString(Errors.Keys.IllegalArgumentClass_2, "datum", DatumEnsemble.class.getSimpleName()));
}
+ this.datum = datum;
+ this.ensemble = ensemble;
+ checkDatum(properties);
}
/**
@@ -189,9 +168,9 @@ class AbstractSingleCRS<D extends Datum> extends AbstractCRS implements SingleCR
AbstractSingleCRS(final SingleCRS crs) {
super(crs);
datum = (D) crs.getDatum();
- if (datum instanceof PseudoDatum<?>) {
+ if (datum instanceof DatumEnsemble<?>) {
throw new IllegalArgumentException(
- Errors.format(Errors.Keys.IllegalPropertyValueClass_2, "datum", PseudoDatum.class));
+ Errors.format(Errors.Keys.IllegalPropertyValueClass_2, "datum", DatumEnsemble.class));
}
ensemble = (DatumEnsemble<D>) crs.getDatumEnsemble();
checkDatum(null);
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 d0b69fd740..e366e344f4 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
@@ -32,7 +32,7 @@ import org.apache.sis.metadata.iso.citation.Citations;
import org.apache.sis.referencing.GeodeticException;
import org.apache.sis.referencing.ImmutableIdentifier;
import org.apache.sis.referencing.AbstractReferenceSystem;
-import org.apache.sis.referencing.datum.PseudoDatum;
+import org.apache.sis.referencing.datum.DatumOrEnsemble;
import org.apache.sis.referencing.cs.AxesConvention;
import org.apache.sis.referencing.cs.AbstractCS;
import org.apache.sis.io.wkt.Formatter;
@@ -263,7 +263,7 @@ public class DefaultGeographicCRS extends DefaultGeodeticCRS implements Geograph
* @since 1.5
*/
public PrimeMeridian getPrimeMeridian() {
- return PseudoDatum.of(this).getPrimeMeridian();
+ return DatumOrEnsemble.asDatum(this).getPrimeMeridian();
}
/**
@@ -279,7 +279,7 @@ public class DefaultGeographicCRS extends DefaultGeodeticCRS implements Geograph
* @since 1.5
*/
public Ellipsoid getEllipsoid() {
- return PseudoDatum.of(this).getEllipsoid();
+ return DatumOrEnsemble.asDatum(this).getEllipsoid();
}
/**
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 2d157f8e2b..9941694085 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
@@ -37,7 +37,7 @@ 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.datum.DatumOrEnsemble;
import org.apache.sis.referencing.privy.WKTKeywords;
import org.apache.sis.io.wkt.Formatter;
import org.apache.sis.measure.Units;
@@ -336,7 +336,7 @@ public class DefaultTemporalCRS extends AbstractSingleCRS<TemporalDatum> impleme
* @since 1.5
*/
public final Temporal getOrigin() { // Must be final because invoked at construction time.
- return PseudoDatum.of(this).getOrigin();
+ return DatumOrEnsemble.asDatum(this).getOrigin();
}
/**
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 c476ed9e01..f49c4ab2eb 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
@@ -22,8 +22,8 @@ import org.opengis.parameter.GeneralParameterDescriptor;
import org.opengis.referencing.datum.Ellipsoid;
import org.opengis.referencing.operation.Conversion;
import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.referencing.datum.DatumOrEnsemble;
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;
@@ -60,7 +60,7 @@ final class ExplicitParameters extends FormattableObject {
*/
ExplicitParameters(final AbstractDerivedCRS crs, final String keyword) {
conversion = crs.getConversionFromBase();
- ellipsoid = ReferencingUtilities.getEllipsoid(crs);
+ ellipsoid = DatumOrEnsemble.getEllipsoid(crs).orElse(null);
this.keyword = keyword;
}
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DatumOrEnsemble.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DatumOrEnsemble.java
new file mode 100644
index 0000000000..7cf521849a
--- /dev/null
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DatumOrEnsemble.java
@@ -0,0 +1,456 @@
+/*
+ * 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.ArrayDeque;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Optional;
+import java.util.function.Function;
+import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.crs.EngineeringCRS;
+import org.opengis.referencing.crs.GeodeticCRS;
+import org.opengis.referencing.crs.SingleCRS;
+import org.opengis.referencing.crs.TemporalCRS;
+import org.opengis.referencing.crs.VerticalCRS;
+import org.opengis.referencing.crs.CompoundCRS;
+import org.opengis.referencing.datum.Datum;
+import org.opengis.referencing.datum.EngineeringDatum;
+import org.opengis.referencing.datum.GeodeticDatum;
+import org.opengis.referencing.datum.TemporalDatum;
+import org.opengis.referencing.datum.VerticalDatum;
+import org.opengis.referencing.datum.PrimeMeridian;
+import org.opengis.referencing.datum.Ellipsoid;
+import org.opengis.metadata.quality.PositionalAccuracy;
+import org.apache.sis.util.Static;
+import org.apache.sis.util.Utilities;
+import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.referencing.GeodeticException;
+
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.referencing.crs.ParametricCRS;
+import org.opengis.referencing.datum.DatumEnsemble;
+import org.opengis.referencing.datum.ParametricDatum;
+
+
+/**
+ * Utility methods for working on objects that may be {@code Datum} or {@code DatumEnsemble}.
+ * The methods in this class view an ensemble as if it was a datum for the sake of simplicity.
+ * For example, {@code DatumOrEnsemble.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.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.5
+ * @since 1.5
+ */
+public final class DatumOrEnsemble extends Static {
+ /**
+ * Do not allow instantiation of this class.
+ */
+ private DatumOrEnsemble() {
+ }
+
+ /**
+ * Returns the datum of the given <abbr>CRS</abbr> if presents, or the datum ensemble otherwise.
+ * This is an alternative to the {@code asDatum(…)} methods when the caller does not need to view
+ * the returned 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 of(final SingleCRS crs) {
+ if (crs == null) return null;
+ final Datum datum = crs.getDatum();
+ return (datum != null) ? datum : crs.getDatumEnsemble();
+ }
+
+ /**
+ * Returns the datum or ensemble of a coordinate operation from <var>source</var> to <var>target</var>.
+ * If the two given coordinate reference systems are associated to the same datum, then this method returns
+ * the <var>target</var> datum. Otherwise, this method returns the largest ensemble which fully contains the
+ * datum or datum ensemble of the other <abbr>CRS</abbr>.
+ * That largest common ensemble is interpreted as the new target of the operation result.
+ * If none of the <var>source</var> or <var>target</var> datum ensembles met the above criteria,
+ * then this method returns an empty value.
+ * A non-empty value means that it is okay, for low accuracy requirements, to ignore the datum shift.
+ *
+ * <p>This is an alternative to the {@code asTargetDatum(…)} methods
+ * when the caller does not need to view the returned object as a datum.</p>
+ *
+ * @param source the source <abbr>CRS</abbr> of a coordinate operation.
+ * @param target the target <abbr>CRS</abbr> of a coordinate operation.
+ * @return datum or datum ensemble of the coordinate operation result if it is okay to ignore datum shift.
+ * @throws NullPointerException if any argument is null.
+ */
+ public static Optional<IdentifiedObject> ofTarget(final SingleCRS source, final SingleCRS target) {
+ return asTargetDatum(source, source.getDatum(),
+ target, target.getDatum(),
+ (ensemble) -> ensemble);
+ }
+
+ /**
+ * Returns the datum (preferred) or ensemble (fallback) 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 as a pseudo-datum.
+ *
+ * <h4>Common properties</h4>
+ * If an ensemble is viewed as a pseudo-datum, then the implementation of the {@link GeodeticDatum#getEllipsoid()}
+ * and {@link GeodeticDatum#getPrimeMeridian()} methods will verify that all members have the same ellipsoid or
+ * prime meridian respectively. If this condition does not hold, then a {@link GeodeticException} will be thrown.
+ * Note that this verification is done when the above-cited methods are invoked, not when this {@code asDatum(…)}
+ * method is invoked.
+ *
+ * @param crs the coordinate reference system for which to get the datum or datum ensemble, or {@code null}.
+ * @return the datum or pseudo-datum of the given <abbr>CRS</abbr>, or {@code null}.
+ */
+ public static GeodeticDatum asDatum(final GeodeticCRS crs) {
+ if (crs == null) return null;
+ final GeodeticDatum datum = crs.getDatum();
+ return (datum != null) ? datum : DefaultDatumEnsemble.Geodetic.datum(crs.getDatumEnsemble());
+ }
+
+ /**
+ * Returns the datum (preferred) or ensemble (fallback) 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 as a pseudo-datum.
+ *
+ * @param crs the coordinate reference system for which to get the datum or datum ensemble, or {@code null}.
+ * @return the datum or pseudo-datum of the given <abbr>CRS</abbr>, or {@code null}.
+ */
+ public static VerticalDatum asDatum(final VerticalCRS crs) {
+ if (crs == null) return null;
+ final VerticalDatum datum = crs.getDatum();
+ return (datum != null) ? datum : DefaultDatumEnsemble.Vertical.datum(crs.getDatumEnsemble());
+ }
+
+ /**
+ * Returns the datum (preferred) or ensemble (fallback) 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 as a pseudo-datum.
+ *
+ * <h4>Common property</h4>
+ * If an ensemble is viewed as a pseudo-datum, then the implementation of the {@link TemporalDatum#getOrigin()}
+ * method will verify that all members have the same origin. If this condition does not hold,
+ * then a {@link GeodeticException} will be thrown. Note that this verification is done when
+ * the above-cited method is invoked, not when this {@code asDatum(…)} method is invoked.
+ *
+ * @param crs the coordinate reference system for which to get the datum or datum ensemble, or {@code null}.
+ * @return the datum or pseudo-datum of the given <abbr>CRS</abbr>, or {@code null}.
+ */
+ public static TemporalDatum asDatum(final TemporalCRS crs) {
+ if (crs == null) return null;
+ final TemporalDatum datum = crs.getDatum();
+ return (datum != null) ? datum : DefaultDatumEnsemble.Time.datum(crs.getDatumEnsemble());
+ }
+
+ /**
+ * Returns the datum (preferred) or ensemble (fallback) 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 as a pseudo-datum.
+ *
+ * @param crs the coordinate reference system for which to get the datum or datum ensemble, or {@code null}.
+ * @return the datum or pseudo-datum of the given <abbr>CRS</abbr>, or {@code null}.
+ *
+ * @since 2.0 (temporary version number until this branch is released)
+ */
+ public static ParametricDatum asDatum(final ParametricCRS crs) {
+ if (crs == null) return null;
+ final ParametricDatum datum = crs.getDatum();
+ return (datum != null) ? datum : DefaultDatumEnsemble.Parametric.datum(crs.getDatumEnsemble());
+ }
+
+ /**
+ * Returns the datum (preferred) or ensemble (fallback) 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 as a pseudo-datum.
+ *
+ * @param crs the coordinate reference system for which to get the datum or datum ensemble, or {@code null}.
+ * @return the datum or pseudo-datum of the given <abbr>CRS</abbr>, or {@code null}.
+ */
+ public static EngineeringDatum asDatum(final EngineeringCRS crs) {
+ if (crs == null) return null;
+ final EngineeringDatum datum = crs.getDatum();
+ return (datum != null) ? datum : DefaultDatumEnsemble.Engineering.datum(crs.getDatumEnsemble());
+ }
+
+ /**
+ * Returns the datum or pseudo-datum of the result of an operation between the given geodetic <abbr>CRS</abbr>s.
+ * If the two given coordinate reference systems are associated to the same datum, then this method returns
+ * the <var>target</var> datum. Otherwise, this method returns a pseudo-datum for the largest ensemble which
+ * fully contains the datum or datum ensemble of the other <abbr>CRS</abbr>.
+ * That largest common ensemble is interpreted as the new target of the operation result.
+ * If none of the <var>source</var> or <var>target</var> datum ensembles met the above criteria,
+ * then this method returns an empty value.
+ * A non-empty value means that it is okay, for low accuracy requirements, to ignore the datum shift.
+ *
+ * @param source the source <abbr>CRS</abbr> of a coordinate operation.
+ * @param target the target <abbr>CRS</abbr> of a coordinate operation.
+ * @return datum or pseudo-datum of the coordinate operation result if it is okay to ignore datum shift.
+ * @throws NullPointerException if any argument is null.
+ */
+ public static Optional<GeodeticDatum> asTargetDatum(final GeodeticCRS source, final GeodeticCRS target) {
+ return asTargetDatum(source, source.getDatum(),
+ target, target.getDatum(),
+ DefaultDatumEnsemble.Geodetic::datum);
+ }
+
+ /**
+ * Returns the datum or pseudo-datum of the result of an operation between the given vertical <abbr>CRS</abbr>s.
+ * See {@link #asTargetDatum(GeodeticCRS, GeodeticCRS)} for more information.
+ *
+ * @param source the source <abbr>CRS</abbr> of a coordinate operation.
+ * @param target the target <abbr>CRS</abbr> of a coordinate operation.
+ * @return datum or pseudo-datum of the coordinate operation result if it is okay to ignore datum shift.
+ * @throws NullPointerException if any argument is null.
+ */
+ public static Optional<VerticalDatum> asTargetDatum(final VerticalCRS source, final VerticalCRS target) {
+ return asTargetDatum(source, source.getDatum(),
+ target, target.getDatum(),
+ DefaultDatumEnsemble.Vertical::datum);
+ }
+
+ /**
+ * Returns the datum or pseudo-datum of the result of an operation between the given temporal <abbr>CRS</abbr>s.
+ * See {@link #asTargetDatum(GeodeticCRS, GeodeticCRS)} for more information.
+ *
+ * @param source the source <abbr>CRS</abbr> of a coordinate operation.
+ * @param target the target <abbr>CRS</abbr> of a coordinate operation.
+ * @return datum or pseudo-datum of the coordinate operation result if it is okay to ignore datum shift.
+ * @throws NullPointerException if any argument is null.
+ */
+ public static Optional<TemporalDatum> asTargetDatum(final TemporalCRS source, final TemporalCRS target) {
+ return asTargetDatum(source, source.getDatum(),
+ target, target.getDatum(),
+ DefaultDatumEnsemble.Time::datum);
+ }
+
+ /**
+ * Returns the datum or pseudo-datum of the result of an operation between the given parametric <abbr>CRS</abbr>s.
+ * See {@link #asTargetDatum(GeodeticCRS, GeodeticCRS)} for more information.
+ *
+ * @param source the source <abbr>CRS</abbr> of a coordinate operation.
+ * @param target the target <abbr>CRS</abbr> of a coordinate operation.
+ * @return datum or pseudo-datum of the coordinate operation result if it is okay to ignore datum shift.
+ * @throws NullPointerException if any argument is null.
+ */
+ public static Optional<ParametricDatum> asTargetDatum(final ParametricCRS source, final ParametricCRS target) {
+ return asTargetDatum(source, source.getDatum(),
+ target, target.getDatum(),
+ DefaultDatumEnsemble.Parametric::datum);
+ }
+
+ /**
+ * Returns the datum or pseudo-datum of the result of an operation between the given engineering <abbr>CRS</abbr>s.
+ * See {@link #asTargetDatum(GeodeticCRS, GeodeticCRS)} for more information.
+ *
+ * @param source the source <abbr>CRS</abbr> of a coordinate operation.
+ * @param target the target <abbr>CRS</abbr> of a coordinate operation.
+ * @return datum or pseudo-datum of the coordinate operation result if it is okay to ignore datum shift.
+ * @throws NullPointerException if any argument is null.
+ */
+ public static Optional<EngineeringDatum> asTargetDatum(final EngineeringCRS source, final EngineeringCRS target) {
+ return asTargetDatum(source, source.getDatum(),
+ target, target.getDatum(),
+ DefaultDatumEnsemble.Engineering::datum);
+ }
+
+ /**
+ * Returns the datum or pseudo-datum of a coordinate operation from <var>source</var> to <var>target</var>.
+ * If the two given coordinate reference systems are associated to the same datum, then this method returns
+ * the <var>target</var> datum. Otherwise, this method returns a pseudo-datum for the largest ensemble which
+ * fully contains the datum or datum ensemble of the other <abbr>CRS</abbr>. If none of the <var>source</var>
+ * or <var>target</var> datum ensembles met that criterion, then this method returns an empty value.
+ * A non-empty value means that it is okay, for low accuracy requirements, to ignore the datum shift.
+ *
+ * @param source the source <abbr>CRS</abbr> of a coordinate operation.
+ * @param sourceDatum the datum of the source <abbr>CRS</abbr>.
+ * @param target the target <abbr>CRS</abbr> of a coordinate operation.
+ * @param targetDatum the datum of the target <abbr>CRS</abbr>.
+ * @param constructor function to invoke for wrapping a datum ensemble in a pseudo-datum.
+ * @return datum or pseudo-datum of the coordinate operation result if it is okay to ignore datum shift.
+ */
+ @SuppressWarnings("unchecked") // Casts are safe because callers know the method signature of <D>.
+ private static <C extends SingleCRS, D extends Datum, R extends IdentifiedObject> Optional<R> asTargetDatum(
+ final C source, final R sourceDatum,
+ final C target, final R targetDatum,
+ final Function<DatumEnsemble<D>, R> constructor)
+ {
+ if (sourceDatum != null && Utilities.equalsIgnoreMetadata(sourceDatum, targetDatum)) {
+ return Optional.of(targetDatum);
+ }
+ DatumEnsemble<D> sourceEnsemble;
+ DatumEnsemble<D> targetEnsemble;
+ DatumEnsemble<D> selected;
+ if ((isMember(selected = targetEnsemble = (DatumEnsemble<D>) target.getDatumEnsemble(), sourceDatum)) ||
+ (isMember(selected = sourceEnsemble = (DatumEnsemble<D>) source.getDatumEnsemble(), targetDatum)))
+ {
+ return Optional.of(constructor.apply(selected));
+ }
+ if (sourceEnsemble != null && targetEnsemble != null) {
+ selected = targetEnsemble;
+ Collection<D> large = targetEnsemble.getMembers();
+ Collection<D> small = sourceEnsemble.getMembers();
+ if (small.size() > large.size()) {
+ selected = sourceEnsemble;
+ var t = large;
+ large = small;
+ small = t;
+ }
+ small = new ArrayDeque<>(small);
+ for (final Datum member : large) {
+ final Iterator<D> it = small.iterator();
+ while (it.hasNext()) {
+ if (Utilities.equalsIgnoreMetadata(member, it.next())) {
+ it.remove();
+ if (small.isEmpty()) {
+ /*
+ * Found all members of the smaller ensemble. Take the larger ensemble,
+ * as it contains both ensembles and should have conservative accuracy.
+ */
+ return Optional.of(constructor.apply(selected));
+ }
+ break; // For removing only the first match.
+ }
+ }
+ }
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * Returns whether the given datum is a member of the given ensemble.
+ *
+ * @param datum the datum to test, or {@code null}.
+ * @param ensemble the ensemble to test, or {@code null}.
+ * @return whether the ensemble contains the given datum.
+ */
+ private static boolean isMember(final DatumEnsemble<?> ensemble, final IdentifiedObject datum) {
+ if (ensemble != null) {
+ for (final Datum member : ensemble.getMembers()) {
+ if (Utilities.equalsIgnoreMetadata(datum, member)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the ellipsoid used by the given coordinate reference system.
+ * More specifically:
+ *
+ * <ul>
+ * <li>If the given <abbr>CRS</abbr> is an instance of {@link SingleCRS} and its datum
+ * is a {@link GeodeticDatum}, then this method returns the datum ellipsoid.</li>
+ * <li>Otherwise, if the given <abbr>CRS</abbr> is associated to a {@link DatumEnsemble} and all members
+ * of the ensemble have equal (ignoring metadata) ellipsoid, then returns that ellipsoid.</li>
+ * <li>Otherwise, if the given <abbr>CRS</abbr> is an instance of {@link CompoundCRS}, then this method
+ * searches recursively in each component until a geodetic reference frame is found.</li>
+ * <li>Otherwise, this method returns an empty value.</li>
+ * </ul>
+ *
+ * Note that this method does not check if a compound <abbr>CRS</abbr> contains more than one ellipsoid
+ * (it should never be the case). Note also that this method may return an empty value even
+ * if the <abbr>CRS</abbr> is geodetic.
+ *
+ * @param crs the coordinate reference system for which to get the ellipsoid.
+ * @return the ellipsoid, or an empty value if none or inconsistent.
+ */
+ public static Optional<Ellipsoid> getEllipsoid(final CoordinateReferenceSystem crs) {
+ return getGeodeticProperty(crs, GeodeticDatum::getEllipsoid);
+ }
+
+ /**
+ * Returns the prime meridian used by the given coordinate reference system.
+ * This method applies the same rules as {@link #getEllipsoid(CoordinateReferenceSystem)}.
+ *
+ * @param crs the coordinate reference system for which to get the prime meridian.
+ * @return the prime meridian, or an empty value if none or inconsistent.
+ *
+ * @see org.apache.sis.referencing.CRS#getGreenwichLongitude(GeodeticCRS)
+ */
+ public static Optional<PrimeMeridian> getPrimeMeridian(final CoordinateReferenceSystem crs) {
+ return getGeodeticProperty(crs, GeodeticDatum::getPrimeMeridian);
+ }
+
+ /**
+ * Implementation of {@code getEllipsoid(CRS)} and {@code getPrimeMeridian(CRS)}.
+ *
+ * @param <P> the type of object to get.
+ * @param crs the coordinate reference system for which to get the ellipsoid or prime meridian.
+ * @param getter the method to invoke on {@link GeodeticDatum} instances.
+ * @return the ellipsoid or prime meridian, or an empty value if none of inconsistent.
+ */
+ private static <P> Optional<P> getGeodeticProperty(final CoordinateReferenceSystem crs, final Function<GeodeticDatum, P> getter) {
+single: if (crs instanceof SingleCRS) {
+ final var scrs = (SingleCRS) crs;
+ final Datum datum = scrs.getDatum();
+ if (datum instanceof GeodeticDatum) {
+ P property = getter.apply((GeodeticDatum) datum);
+ if (property != null) {
+ return Optional.of(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 Optional.ofNullable(common);
+ }
+ }
+ if (crs instanceof CompoundCRS) {
+ for (final CoordinateReferenceSystem c : ((CompoundCRS) crs).getComponents()) {
+ final Optional<P> property = getGeodeticProperty(c, getter);
+ if (property.isPresent()) {
+ return property;
+ }
+ }
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * If the given object is a datum ensemble, returns its accuracy.
+ *
+ * @param object the object from which to get the ensemble accuracy, or {@code null}.
+ * @return the datum ensemble accuracy if the given object is a datum ensemble.
+ * @throws NullPointerException if the given object should provide an accuracy but didn't.
+ */
+ public static Optional<PositionalAccuracy> getAccuracy(final IdentifiedObject object) {
+ final DatumEnsemble<?> ensemble;
+ if (object instanceof DatumEnsemble<?>) {
+ ensemble = (DatumEnsemble<?>) object;
+ } else {
+ return Optional.empty();
+ }
+ return Optional.of(ensemble.getEnsembleAccuracy()); // Intentional NullPointerException if this property is null.
+ }
+}
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 ebffe6fdfe..8920687048 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
@@ -19,12 +19,30 @@ package org.apache.sis.referencing.datum;
import java.util.Map;
import java.util.List;
import java.util.Collection;
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Function;
+import java.time.temporal.Temporal;
import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.crs.EngineeringCRS;
+import org.opengis.referencing.crs.GeodeticCRS;
+import org.opengis.referencing.crs.TemporalCRS;
+import org.opengis.referencing.crs.VerticalCRS;
import org.opengis.referencing.datum.Datum;
+import org.opengis.referencing.datum.Ellipsoid;
+import org.opengis.referencing.datum.EngineeringDatum;
+import org.opengis.referencing.datum.GeodeticDatum;
+import org.opengis.referencing.datum.PrimeMeridian;
+import org.opengis.referencing.datum.TemporalDatum;
+import org.opengis.referencing.datum.VerticalDatum;
import org.opengis.metadata.quality.PositionalAccuracy;
+import org.opengis.util.InternationalString;
import org.apache.sis.io.wkt.Formatter;
import org.apache.sis.io.wkt.Convention;
import org.apache.sis.referencing.AbstractIdentifiedObject;
+import org.apache.sis.referencing.GeodeticException;
+import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.referencing.internal.Resources;
import org.apache.sis.referencing.privy.WKTKeywords;
import org.apache.sis.util.ArgumentChecks;
@@ -33,22 +51,38 @@ import org.apache.sis.util.Utilities;
import org.apache.sis.util.resources.Errors;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.Objects;
+import org.opengis.referencing.crs.ParametricCRS;
import org.opengis.referencing.datum.DatumEnsemble;
+import org.opengis.referencing.datum.ParametricDatum;
+import org.opengis.referencing.datum.RealizationMethod;
/**
* Collection of datums which for low accuracy requirements may be considered
* to be insignificantly different from each other.
+ * Coordinate transformations between different {@linkplain #getMembers() members} of an ensemble
+ * can be omitted if the {@linkplain #getEnsembleAccuracy() ensemble accuracy} is sufficient.
+ *
+ * <h2>Ensemble viewed as a datum</h2>
+ * While a {@code DatumEnsemble} is theoretically <em>not</em> a {@code Datum},
+ * it is sometime convenient to handle the ensemble <em>as if</em> it was a datum.
+ * Therefore, Apache <abbr>SIS</abbr> implementation of {@link DatumEnsemble} implements also the {@link Datum} interface.
+ * This is a non-standard approach for making easier to handle {@code Datum} and {@code DatumEnsemble} with the same code.
+ * A similar approach is used in the <abbr>EPSG</abbr> database, which stores ensembles in the same table as the datums.
+ *
+ * <p>It is illegal to use this double inheritance for returning a {@code DatumEnsemble} from
+ * {@link org.opengis.referencing.crs.SingleCRS#getDatum()}, or for creating a {@code DatumEnsemble}
+ * containing a nested {@code DatumEnsemble}. The Apache <abbr>SIS</abbr> constructors verify these constraints.
+ * For getting a single object which may be a datum or an ensemble, see the {@link DatumOrEnsemble} static methods.</p>
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.5
*
- * @param <D> the type of datum contained in this ensemble.
+ * @param <D> the type of datum members contained in this ensemble.
*
* @since 1.5
*/
-public class DefaultDatumEnsemble<D extends Datum> extends AbstractIdentifiedObject implements DatumEnsemble<D> {
+public class DefaultDatumEnsemble<D extends Datum> extends AbstractIdentifiedObject implements DatumEnsemble<D>, Datum {
/**
* Serial number for inter-operability with different versions.
*/
@@ -56,6 +90,8 @@ public class DefaultDatumEnsemble<D extends Datum> extends AbstractIdentifiedObj
/**
* Datum or reference frames which are members of this ensemble.
+ *
+ * @see #getMembers()
*/
@SuppressWarnings("serial") // Standard Java implementations are serializable.
private final List<D> members;
@@ -63,6 +99,8 @@ public class DefaultDatumEnsemble<D extends Datum> extends AbstractIdentifiedObj
/**
* Inaccuracy introduced through use of this ensemble.
* This property is mandatory.
+ *
+ * @see #getEnsembleAccuracy()
*/
@SuppressWarnings("serial") // Most SIS implementations are serializable.
private final PositionalAccuracy ensembleAccuracy;
@@ -104,15 +142,23 @@ public class DefaultDatumEnsemble<D extends Datum> extends AbstractIdentifiedObj
* @param properties the properties to be given to the identified object.
* @param members datum or reference frames which are members of this ensemble.
* @param accuracy inaccuracy introduced through use of this ensemble (mandatory).
- * @throws IllegalArgumentException if at least two different
- * {@linkplain AbstractDatum#getConventionalRS() conventional reference systems} are found.
+ * @param type the base type of datum members contained in this ensemble.
+ * @throws ClassCastException if a member is not an instance of {@code type}.
+ * @throws IllegalArgumentException if a member is an instance of {@link DatumEnsemble}, of if at least two
+ * different {@linkplain AbstractDatum#getConventionalRS() conventional reference systems} are found.
+ *
+ * @see #create(Map, Collection, PositionalAccuracy)
*/
- public DefaultDatumEnsemble(Map<String,?> properties, Collection<? extends D> members, PositionalAccuracy accuracy) {
+ protected DefaultDatumEnsemble(final Map<String,?> properties,
+ final Collection<? extends D> members,
+ final PositionalAccuracy accuracy,
+ final Class<? super D> type)
+ {
super(properties);
ArgumentChecks.ensureNonNull("accuracy", accuracy);
ensembleAccuracy = accuracy;
this.members = List.copyOf(members);
- validate();
+ validate(type);
}
/**
@@ -123,12 +169,71 @@ public class DefaultDatumEnsemble<D extends Datum> extends AbstractIdentifiedObj
* <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
*
* @param ensemble the ensemble to copy.
+ * @param type the base type of datum members contained in this ensemble.
+ * @throws ClassCastException if a member is not an instance of {@code type}.
+ * @throws IllegalArgumentException if a member is an instance of {@link DatumEnsemble}, of if at least two
+ * different {@linkplain AbstractDatum#getConventionalRS() conventional reference systems} are found.
+ *
+ * @see #castOrCopy(DatumEnsemble)
*/
- protected DefaultDatumEnsemble(final DatumEnsemble<? extends D> ensemble) {
+ protected DefaultDatumEnsemble(final DatumEnsemble<? extends D> ensemble, final Class<? super D> type) {
super(ensemble);
members = List.copyOf(ensemble.getMembers());
ensembleAccuracy = Objects.requireNonNull(ensemble.getEnsembleAccuracy());
- validate();
+ validate(type);
+ }
+
+ /**
+ * Verifies this ensemble. All members shall be instances of the specified type and shall have
+ * the same conventional reference system. No member can be an instance of {@link DatumEnsemble}.
+ *
+ * @param type the base type of datum members contained in this ensemble.
+ * @throws ClassCastException if a member is not an instance of {@code type}.
+ * @throws IllegalArgumentException if a member is an instance of {@link DatumEnsemble}, of if at least two
+ * different {@linkplain AbstractDatum#getConventionalRS() conventional reference systems} are found.
+ */
+ private void validate(final Class<? super D> type) {
+ IdentifiedObject rs = null;
+ for (final D datum : members) {
+ if (datum instanceof DatumEnsemble<?>) {
+ throw new IllegalArgumentException(
+ Errors.format(Errors.Keys.IllegalPropertyValueClass_2, "members", DatumEnsemble.class));
+ }
+ if (!type.isInstance(datum)) {
+ throw new ClassCastException(
+ Errors.format(Errors.Keys.IllegalClass_2, type, datum.getClass()));
+ }
+ final IdentifiedObject dr = datum.getConventionalRS().orElse(null);
+ if (dr != null) {
+ if (rs == null) {
+ rs = dr;
+ } else if (!rs.equals(dr)) {
+ throw new IllegalArgumentException(Resources.format(Resources.Keys.ShallHaveSameConventionalRS));
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates a datum ensemble from the given properties. The content of the {@code properties} map is described
+ * {@linkplain #DefaultDatumEnsemble(Map, Collection, PositionalAccuracy, Class) in the constructor}.
+ * The returned ensemble may implement the {@link GeodeticDatum}, {@link VerticalDatum}, {@link TemporalDatum},
+ * {@link ParametricDatum} or {@link EngineeringDatum} interface if all members are instances of the same interface.
+ *
+ * @param <D> the type of datum members contained in this ensemble.
+ * @param properties the properties to be given to the identified object.
+ * @param members datum or reference frames which are members of this ensemble.
+ * @param accuracy inaccuracy introduced through use of this ensemble (mandatory).
+ * @return the datum ensemble.
+ * @throws IllegalArgumentException if a member is an instance of {@link DatumEnsemble}, of if at least two
+ * different {@linkplain AbstractDatum#getConventionalRS() conventional reference systems} are found.
+ */
+ public static <D extends Datum> DefaultDatumEnsemble<D> create(
+ final Map<String,?> properties,
+ final Collection<? extends D> members,
+ final PositionalAccuracy accuracy)
+ {
+ return Factory.forMemberType(null, properties, List.copyOf(members), accuracy);
}
/**
@@ -137,15 +242,18 @@ public class DefaultDatumEnsemble<D extends Datum> extends AbstractIdentifiedObj
*
* <ul>
* <li>If the given object is {@code null}, then this method returns {@code null}.</li>
- * <li>Otherwise if the given object is already an instance of
+ * <li>Otherwise, if the given object is already an instance of
* {@code DefaultDatumEnsemble}, then it is returned unchanged.</li>
- * <li>Otherwise a new {@code DefaultDatumEnsemble} instance is created using the
- * {@linkplain #DefaultDatumEnsemble(DatumEnsemble) copy constructor} and returned.
+ * <li>Otherwise, a new {@code DefaultDatumEnsemble} instance is created using the
+ * {@linkplain #DefaultDatumEnsemble(DatumEnsemble, Class) copy constructor} and returned.
* Note that this is a <em>shallow</em> copy operation,
* because the other properties contained in the given object are not recursively copied.</li>
* </ul>
*
- * @param <D> the type of datum contained in the ensemble.
+ * The returned ensemble may implement the {@link GeodeticDatum}, {@link VerticalDatum}, {@link TemporalDatum},
+ * {@link ParametricDatum} or {@link EngineeringDatum} interface if all members are instances of the same interface.
+ *
+ * @param <D> the type of datum members contained in the ensemble.
* @param object the object to get as a SIS implementation, or {@code null} if none.
* @return a SIS implementation containing the values of the given object (may be the
* given object itself), or {@code null} if the argument was null.
@@ -153,31 +261,8 @@ public class DefaultDatumEnsemble<D extends Datum> extends AbstractIdentifiedObj
public static <D extends Datum> DefaultDatumEnsemble<D> castOrCopy(final DatumEnsemble<D> object) {
if (object == null || object instanceof DefaultDatumEnsemble<?>) {
return (DefaultDatumEnsemble<D>) object;
- } else {
- return new DefaultDatumEnsemble<>(object);
- }
- }
-
- /**
- * Verifies this ensemble. All members shall have the same conventional reference system.
- * No member can be an instance of {@link PseudoDatum}.
- */
- private void validate() {
- IdentifiedObject rs = null;
- for (final D datum : members) {
- if (datum instanceof PseudoDatum<?>) {
- throw new IllegalArgumentException(
- Errors.format(Errors.Keys.IllegalPropertyValueClass_2, "members", PseudoDatum.class));
- }
- final IdentifiedObject dr = datum.getConventionalRS().orElse(null);
- if (dr != null) {
- if (rs == null) {
- rs = dr;
- } else if (!rs.equals(dr)) {
- throw new IllegalArgumentException(Resources.format(Resources.Keys.ShallHaveSameConventionalRS));
- }
- }
}
+ return Factory.forMemberType(object, null, List.copyOf(object.getMembers()), null);
}
/**
@@ -197,6 +282,17 @@ public class DefaultDatumEnsemble<D extends Datum> extends AbstractIdentifiedObj
return (Class) DatumEnsemble.class;
}
+ /*
+ * NOTE: a previous version provided the following method:
+ *
+ * public Class<? super D> getMemberInterface() {
+ * return Datum.class;
+ * }
+ *
+ * It has been removed because, to be safe, it would require a `Class<D>` argument in the static methods.
+ * Even `? super D` is unsafe. See the comment in `Factory.forMemberType(…)` method for more discussion.
+ */
+
/**
* Returns the datum or reference frames which are members of this ensemble.
*
@@ -218,11 +314,104 @@ public class DefaultDatumEnsemble<D extends Datum> extends AbstractIdentifiedObj
return ensembleAccuracy;
}
+ /**
+ * Returns an anchor definition which is common to all members of the datum ensemble.
+ * If the value is not the same for all members, or if at least one member returned
+ * 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, or if at least one member returned
+ * 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, or if at least one member returned
+ * 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,
+ * then 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 empty if there is no common value.
+ */
+ final <V> Optional<V> getCommonOptionalValue(final Function<D, Optional<V>> getter) {
+ final Iterator<D> it = members.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 = members.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;
+ }
+
/**
* Compares the specified object with this ensemble for equality.
- * If the {@code mode} argument value is {@link ComparisonMode#STRICT STRICT} or
- * {@link ComparisonMode#BY_CONTRACT BY_CONTRACT}, then all available properties are compared including the
- * {@linkplain #getDomains() domains}.
*
* @param object the object to compare to {@code this}.
* @param mode {@link ComparisonMode#STRICT STRICT} for performing a strict comparison, or
@@ -233,10 +422,7 @@ public class DefaultDatumEnsemble<D extends Datum> extends AbstractIdentifiedObj
* @hidden because nothing new to said.
*/
@Override
- public boolean equals(Object object, final ComparisonMode mode) {
- if (mode != ComparisonMode.STRICT && object instanceof PseudoDatum<?>) {
- object = ((PseudoDatum<?>) object).ensemble;
- }
+ public boolean equals(final Object object, final ComparisonMode mode) {
if (!super.equals(object, mode)) {
return false;
}
@@ -274,6 +460,8 @@ public class DefaultDatumEnsemble<D extends Datum> extends AbstractIdentifiedObj
* @param formatter the formatter where to format the inner content of this WKT element.
* @return the {@linkplain org.apache.sis.io.wkt.KeywordCase#CAMEL_CASE CamelCase} keyword
* for the WKT element, or {@code null} if unknown.
+ *
+ * @hidden because nothing new to said.
*/
@Override
protected String formatTo(final Formatter formatter) {
@@ -283,4 +471,397 @@ public class DefaultDatumEnsemble<D extends Datum> extends AbstractIdentifiedObj
}
return WKTKeywords.Ensemble;
}
+
+ /**
+ * An ensemble viewed as a low-accuracy geodetic datum.
+ *
+ * @see #create(Map, Collection, PositionalAccuracy)
+ * @see #castOrCopy(DatumEnsemble)
+ * @see DatumOrEnsemble#of(GeodeticCRS)
+ * @see DatumOrEnsemble#asTargetDatum(GeodeticCRS, GeodeticCRS)
+ */
+ static final class Geodetic extends DefaultDatumEnsemble<GeodeticDatum> implements GeodeticDatum {
+ /** For cross-versions compatibility. */
+ private static final long serialVersionUID = 7669230365507661290L;
+
+ /** Returns the given ensemble as a pseudo-datum. */
+ static GeodeticDatum datum(DatumEnsemble<GeodeticDatum> ensemble) {
+ return (ensemble == null || ensemble instanceof GeodeticDatum)
+ ? (GeodeticDatum) ensemble : new Geodetic(ensemble);
+ }
+
+ /** Creates a new ensemble as a copy of the given ensemble. */
+ Geodetic(DatumEnsemble<? extends GeodeticDatum> ensemble) {
+ super(ensemble, GeodeticDatum.class);
+ }
+
+ /** Creates a new ensemble from the given properties. */
+ Geodetic(Map<String,?> properties, List<? extends GeodeticDatum> members, PositionalAccuracy accuracy) {
+ super(properties, members, accuracy, 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);
+ }
+ }
+
+ /**
+ * An ensemble viewed as a low-accuracy vertical datum.
+ *
+ * @see #create(Map, Collection, PositionalAccuracy)
+ * @see #castOrCopy(DatumEnsemble)
+ * @see DatumOrEnsemble#of(VerticalCRS)
+ * @see DatumOrEnsemble#asTargetDatum(VerticalCRS, VerticalCRS)
+ */
+ static final class Vertical extends DefaultDatumEnsemble<VerticalDatum> implements VerticalDatum {
+ /** For cross-versions compatibility. */
+ private static final long serialVersionUID = 7242417944400289818L;
+
+ /** Returns the given ensemble as a pseudo-datum. */
+ static VerticalDatum datum(DatumEnsemble<VerticalDatum> ensemble) {
+ return (ensemble == null || ensemble instanceof VerticalDatum)
+ ? (VerticalDatum) ensemble : new Vertical(ensemble);
+ }
+
+ /** Creates a new ensemble as a copy of the given ensemble. */
+ Vertical(DatumEnsemble<? extends VerticalDatum> ensemble) {
+ super(ensemble, VerticalDatum.class);
+ }
+
+ /** Creates a new ensemble from the given properties. */
+ Vertical(Map<String,?> properties, List<? extends VerticalDatum> members, PositionalAccuracy accuracy) {
+ super(properties, members, accuracy, 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, or if at least one 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);
+ }
+ }
+
+ /**
+ * An ensemble viewed as a low-accuracy temporal datum.
+ *
+ * @see #create(Map, Collection, PositionalAccuracy)
+ * @see #castOrCopy(DatumEnsemble)
+ * @see DatumOrEnsemble#of(TemporalCRS)
+ * @see DatumOrEnsemble#asTargetDatum(TemporalCRS, TemporalCRS)
+ */
+ static final class Time extends DefaultDatumEnsemble<TemporalDatum> implements TemporalDatum {
+ /** For cross-versions compatibility. */
+ private static final long serialVersionUID = -4208563828181087035L;
+
+ /** Returns the given ensemble as a pseudo-datum. */
+ static TemporalDatum datum(DatumEnsemble<TemporalDatum> ensemble) {
+ return (ensemble == null || ensemble instanceof TemporalDatum)
+ ? (TemporalDatum) ensemble : new Time(ensemble);
+ }
+
+ /** Creates a new ensemble as a copy of the given ensemble. */
+ Time(DatumEnsemble<? extends TemporalDatum> ensemble) {
+ super(ensemble, TemporalDatum.class);
+ }
+
+ /** Creates a new ensemble from the given properties. */
+ Time(Map<String,?> properties, List<? extends TemporalDatum> members, PositionalAccuracy accuracy) {
+ super(properties, members, accuracy, 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);
+ }
+ }
+
+ /**
+ * An ensemble viewed as a low-accuracy parametric datum.
+ *
+ * @see #create(Map, Collection, PositionalAccuracy)
+ * @see #castOrCopy(DatumEnsemble)
+ * @see DatumOrEnsemble#of(ParametricCRS)
+ * @see DatumOrEnsemble#asTargetDatum(ParametricCRS, ParametricCRS)
+ */
+ static final class Parametric extends DefaultDatumEnsemble<ParametricDatum> implements ParametricDatum {
+ /** For cross-versions compatibility. */
+ private static final long serialVersionUID = -8277774591738789437L;
+
+ /** Returns the given ensemble as a pseudo-datum. */
+ static ParametricDatum datum(DatumEnsemble<ParametricDatum> ensemble) {
+ return (ensemble == null || ensemble instanceof ParametricDatum)
+ ? (ParametricDatum) ensemble : new Parametric(ensemble);
+ }
+
+ /** Creates a new ensemble as a copy of the given ensemble. */
+ Parametric(DatumEnsemble<? extends ParametricDatum> ensemble) {
+ super(ensemble, ParametricDatum.class);
+ }
+
+ /** Creates a new ensemble from the given properties. */
+ Parametric(Map<String,?> properties, List<? extends ParametricDatum> members, PositionalAccuracy accuracy) {
+ super(properties, members, accuracy, ParametricDatum.class);
+ }
+ }
+
+ /**
+ * An ensemble viewed as a low-accuracy engineering datum.
+ *
+ * @see #create(Map, Collection, PositionalAccuracy)
+ * @see #castOrCopy(DatumEnsemble)
+ * @see DatumOrEnsemble#of(EngineeringCRS)
+ * @see DatumOrEnsemble#asTargetDatum(EngineeringCRS, EngineeringCRS)
+ */
+ static final class Engineering extends DefaultDatumEnsemble<EngineeringDatum> implements EngineeringDatum {
+ /** For cross-versions compatibility. */
+ private static final long serialVersionUID = -8978468990963666861L;
+
+ /** Returns the given ensemble as a pseudo-datum. */
+ static EngineeringDatum datum(DatumEnsemble<EngineeringDatum> ensemble) {
+ return (ensemble == null || ensemble instanceof EngineeringDatum)
+ ? (EngineeringDatum) ensemble : new Engineering(ensemble);
+ }
+
+ /** Creates a new ensemble as a copy of the given ensemble. */
+ Engineering(DatumEnsemble<? extends EngineeringDatum> ensemble) {
+ super(ensemble, EngineeringDatum.class);
+ }
+
+ /** Creates a new ensemble from the given properties. */
+ Engineering(Map<String,?> properties, List<? extends EngineeringDatum> members, PositionalAccuracy accuracy) {
+ super(properties, members, accuracy, EngineeringDatum.class);
+ }
+ }
+
+ /**
+ * Factory methods for finding the base type of all members of a list and instantiate
+ * the {@link DefaultDatumEnsemble} subclass corresponding to the type of all members.
+ * Each instance of {@code Factory} is immutable and thread-safe.
+ * There is one instance for each supported type.
+ *
+ * @param <D> base type of all members in the ensembles constructed by this factory instance.
+ *
+ * @see #create(Map, Collection, PositionalAccuracy)
+ * @see #castOrCopy(DatumEnsemble)
+ */
+ private static abstract class Factory<D extends Datum> {
+ /**
+ * Base type of all members in the ensembles constructed by this factory instance.
+ */
+ private final Class<D> type;
+
+ /**
+ * Creates a new factory for ensembles in which all members are instances of the given type.
+ *
+ * @param type base type of all members in the ensembles constructed by this factory instance.
+ */
+ private Factory(final Class<D> type) {
+ this.type = type;
+ }
+
+ /**
+ * Finds a common type for all members in the given list, then creates a datum ensemble of that type.
+ * In principle, the {@code <D>} type should be restricted to one of the types hard-coded in this class.
+ * However, it is okay if {@code <D>} is a custom subclass because it appears only in the following places:
+ *
+ * <ul>
+ * <li>{@link #getMembers()}, which is a read-only collection (it is safe to cast {@code List<? extends D>}
+ * as {@code List<D>} when no write operation is allowed).</li>
+ * </ul>
+ *
+ * Exactly one of {@code object} and {@code properties} should be non-null.
+ *
+ * @param <D> base type of all members in the ensembles to create.
+ * @param object the source ensemble to copy, or {@code null} if none.
+ * @param properties the properties of the ensemble to create, or {@code null}.
+ * @param members members of the ensemble to copy or create.
+ * @param accuracy inaccuracy of the ensemble. Mandatory if {@code properties} is non-null.
+ * @return the copied or created ensemble.
+ * @throws ClassCastException if at least one member is not a {@link Datum}.
+ * Should never happen if the parameterized type of {@code members} is respected.
+ */
+ static <D extends Datum> DefaultDatumEnsemble<D> forMemberType(
+ final DatumEnsemble<? extends D> object,
+ final Map<String,?> properties,
+ final List<? extends D> members,
+ final PositionalAccuracy accuracy)
+ {
+nextType: for (final Factory<?> factory : FACTORIES) {
+ for (final Object member : members) {
+ if (!factory.type.isInstance(member)) {
+ continue nextType;
+ }
+ }
+ /*
+ * A more correct type would be `Factory<? super D>` because of the use of `isInstance(member)`
+ * instead of strict class equality. However, even `? super D` is not guaranteed to be correct,
+ * because nothing prevent a member from implementing two interfaces. In such case, the type of
+ * the first matching factory could be unrelated to `D` (e.g., `D` is `ParametricDatum` but all
+ * members also implement `VerticalDatum`). However, despite this uncertainty, the cast is okay
+ * because `D` appears only in `getMembers()` which returns a read-only collection. There is no
+ * method returning `Class<D>` and no guarantees that the returned object will implement `D`.
+ */
+ @SuppressWarnings("unchecked")
+ final var selected = (Factory<D>) factory; // See above comment.
+ if (object != null) {
+ return selected.copy(object);
+ } else {
+ return selected.create(properties, members, accuracy);
+ }
+ }
+ throw new ClassCastException();
+ }
+
+ /**
+ * Creates a new ensemble of the type associated with this factory instance.
+ *
+ * @param properties the properties to be given to the identified object.
+ * @param members datum or reference frames which are members of this ensemble.
+ * @param accuracy inaccuracy introduced through use of this ensemble (mandatory).
+ * @return the datum ensemble.
+ * @throws IllegalArgumentException if a member is an instance of {@link DatumEnsemble}, of if at least two
+ * different {@linkplain AbstractDatum#getConventionalRS() conventional reference systems} are found.
+ *
+ * @see #create(Map, Collection, PositionalAccuracy)
+ */
+ abstract DefaultDatumEnsemble<D> create(Map<String,?> properties, List<? extends D> members, PositionalAccuracy accuracy);
+
+ /**
+ * Creates a new ensemble with the same values as the specified one.
+ *
+ * @param ensemble the ensemble to copy.
+ * @throws IllegalArgumentException if a member is an instance of {@link DatumEnsemble}, of if at least two
+ * different {@linkplain AbstractDatum#getConventionalRS() conventional reference systems} are found.
+ *
+ * @see #castOrCopy(DatumEnsemble)
+ */
+ abstract DefaultDatumEnsemble<D> copy(DatumEnsemble<? extends D> object);
+
+ /**
+ * Factories for all datum types supported by this class. The types are (in order) {@link GeodeticDatum},
+ * {@link VerticalDatum}, {@link TemporalDatum}, {@link ParametricDatum} and {@link EngineeringDatum}.
+ * The types are tested in iteration order. For example, if a member implements both {@code VerticalDatum}
+ * {@code ParametricDatum} interface, then {@code VerticalDatum} has precedence.
+ */
+ private static final Factory<?>[] FACTORIES = {
+ new Factory<GeodeticDatum>(GeodeticDatum.class) {
+ @Override
+ DefaultDatumEnsemble<GeodeticDatum> copy(DatumEnsemble<? extends GeodeticDatum> object) {
+ return new Geodetic(object);
+ }
+
+ @Override
+ DefaultDatumEnsemble<GeodeticDatum> create(Map<String,?> properties,
+ List<? extends GeodeticDatum> members, PositionalAccuracy accuracy)
+ {
+ return new Geodetic(properties, members, accuracy);
+ }
+ },
+
+ new Factory<VerticalDatum>(VerticalDatum.class) {
+ @Override
+ DefaultDatumEnsemble<VerticalDatum> copy(DatumEnsemble<? extends VerticalDatum> object) {
+ return new Vertical(object);
+ }
+
+ @Override
+ DefaultDatumEnsemble<VerticalDatum> create(Map<String,?> properties,
+ List<? extends VerticalDatum> members, PositionalAccuracy accuracy)
+ {
+ return new Vertical(properties, members, accuracy);
+ }
+ },
+
+ new Factory<TemporalDatum>(TemporalDatum.class) {
+ @Override
+ DefaultDatumEnsemble<TemporalDatum> copy(DatumEnsemble<? extends TemporalDatum> object) {
+ return new Time(object);
+ }
+
+ @Override
+ DefaultDatumEnsemble<TemporalDatum> create(Map<String,?> properties,
+ List<? extends TemporalDatum> members, PositionalAccuracy accuracy)
+ {
+ return new Time(properties, members, accuracy);
+ }
+ },
+
+ new Factory<ParametricDatum>(ParametricDatum.class) {
+ @Override
+ DefaultDatumEnsemble<ParametricDatum> copy(DatumEnsemble<? extends ParametricDatum> object) {
+ return new Parametric(object);
+ }
+
+ @Override
+ DefaultDatumEnsemble<ParametricDatum> create(Map<String,?> properties,
+ List<? extends ParametricDatum> members, PositionalAccuracy accuracy)
+ {
+ return new Parametric(properties, members, accuracy);
+ }
+ },
+
+ new Factory<EngineeringDatum>(EngineeringDatum.class) {
+ @Override
+ DefaultDatumEnsemble<EngineeringDatum> copy(DatumEnsemble<? extends EngineeringDatum> object) {
+ return new Engineering(object);
+ }
+
+ @Override
+ DefaultDatumEnsemble<EngineeringDatum> create(Map<String,?> properties,
+ List<? extends EngineeringDatum> members, PositionalAccuracy accuracy)
+ {
+ return new Engineering(properties, members, accuracy);
+ }
+ },
+
+ new Factory<Datum>(Datum.class) {
+ @Override
+ DefaultDatumEnsemble<Datum> copy(DatumEnsemble<? extends Datum> object) {
+ return new DefaultDatumEnsemble<>(object, Datum.class);
+ }
+
+ @Override
+ DefaultDatumEnsemble<Datum> create(Map<String,?> properties,
+ List<? extends Datum> members, PositionalAccuracy accuracy)
+ {
+ return new DefaultDatumEnsemble<>(properties, members, accuracy, Datum.class);
+ }
+ }
+ };
+ }
}
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java
index f9dc3e7642..174f7f62a3 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java
@@ -263,7 +263,7 @@ public class DefaultPrimeMeridian extends AbstractIdentifiedObject implements Pr
}
if (super.equals(object, mode)) switch (mode) {
case STRICT: {
- final DefaultPrimeMeridian that = (DefaultPrimeMeridian) object;
+ final var that = (DefaultPrimeMeridian) object;
return Numerics.equals(this.greenwichLongitude, that.greenwichLongitude) &&
Objects.equals(this.angularUnit, that.angularUnit);
}
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
deleted file mode 100644
index 7c4cfef555..0000000000
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/PseudoDatum.java
+++ /dev/null
@@ -1,802 +0,0 @@
-/*
- * 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.ArrayDeque;
-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.io.Serializable;
-import org.opengis.util.GenericName;
-import org.opengis.util.InternationalString;
-import org.opengis.metadata.quality.PositionalAccuracy;
-import org.opengis.referencing.IdentifiedObject;
-import org.opengis.referencing.datum.*;
-import org.opengis.referencing.crs.*;
-import org.apache.sis.util.Utilities;
-import org.apache.sis.util.ComparisonMode;
-import org.apache.sis.util.LenientComparable;
-import org.apache.sis.util.resources.Errors;
-import org.apache.sis.referencing.IdentifiedObjects;
-import org.apache.sis.referencing.GeodeticException;
-
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.time.temporal.Temporal;
-import org.opengis.referencing.ObjectDomain;
-
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.Identifier;
-
-
-/**
- * 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 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.
- *
- * @since 2.0 (temporary version number until this branch is released)
- */
- 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 datum or pseudo-datum of the result of an operation between the given geodetic <abbr>CRS</abbr>s.
- * If the two given coordinate reference systems are associated to the same datum, then this method returns
- * the <var>target</var> datum. Otherwise, this method returns a pseudo-datum for the largest ensemble which
- * fully contains the datum or datum ensemble of the other <abbr>CRS</abbr>. If none of the <var>source</var>
- * or <var>target</var> datum ensembles met that criterion, then this method returns an empty value.
- * A non-empty value means that it is okay, for low accuracy requirements, to ignore the datum shift.
- *
- * @param source the source <abbr>CRS</abbr> of a coordinate operation.
- * @param target the target <abbr>CRS</abbr> of a coordinate operation.
- * @return datum or pseudo-datum of the coordinate operation result if it is okay to ignore datum shift.
- */
- public static Optional<GeodeticDatum> ofOperation(final GeodeticCRS source, final GeodeticCRS target) {
- return ofOperation(source, source.getDatum(),
- target, target.getDatum(),
- Geodetic::new);
- }
-
- /**
- * Returns the datum or pseudo-datum of the result of an operation between the given vertical <abbr>CRS</abbr>s.
- * See {@link #ofOperation(GeodeticCRS, GeodeticCRS)} for more information.
- *
- * @param source the source <abbr>CRS</abbr> of a coordinate operation.
- * @param target the target <abbr>CRS</abbr> of a coordinate operation.
- * @return datum or pseudo-datum of the coordinate operation result if it is okay to ignore datum shift.
- */
- public static Optional<VerticalDatum> ofOperation(final VerticalCRS source, final VerticalCRS target) {
- return ofOperation(source, source.getDatum(),
- target, target.getDatum(),
- Vertical::new);
- }
-
- /**
- * Returns the datum or pseudo-datum of the result of an operation between the given temporal <abbr>CRS</abbr>s.
- * See {@link #ofOperation(GeodeticCRS, GeodeticCRS)} for more information.
- *
- * @param source the source <abbr>CRS</abbr> of a coordinate operation.
- * @param target the target <abbr>CRS</abbr> of a coordinate operation.
- * @return datum or pseudo-datum of the coordinate operation result if it is okay to ignore datum shift.
- */
- public static Optional<TemporalDatum> ofOperation(final TemporalCRS source, final TemporalCRS target) {
- return ofOperation(source, source.getDatum(),
- target, target.getDatum(),
- Time::new);
- }
-
- /**
- * Returns the datum or pseudo-datum of the result of an operation between the given parametric <abbr>CRS</abbr>s.
- * See {@link #ofOperation(GeodeticCRS, GeodeticCRS)} for more information.
- *
- * @param source the source <abbr>CRS</abbr> of a coordinate operation.
- * @param target the target <abbr>CRS</abbr> of a coordinate operation.
- * @return datum or pseudo-datum of the coordinate operation result if it is okay to ignore datum shift.
- */
- public static Optional<ParametricDatum> ofOperation(final ParametricCRS source, final ParametricCRS target) {
- return ofOperation(source, source.getDatum(),
- target, target.getDatum(),
- Parametric::new);
- }
-
- /**
- * Returns the datum or pseudo-datum of the result of an operation between the given engineering <abbr>CRS</abbr>s.
- * See {@link #ofOperation(GeodeticCRS, GeodeticCRS)} for more information.
- *
- * @param source the source <abbr>CRS</abbr> of a coordinate operation.
- * @param target the target <abbr>CRS</abbr> of a coordinate operation.
- * @return datum or pseudo-datum of the coordinate operation result if it is okay to ignore datum shift.
- */
- public static Optional<EngineeringDatum> ofOperation(final EngineeringCRS source, final EngineeringCRS target) {
- return ofOperation(source, source.getDatum(),
- target, target.getDatum(),
- Engineering::new);
- }
-
- /**
- * Returns the datum or pseudo-datum of a coordinate operation from <var>source</var> to <var>target</var>.
- * If the two given coordinate reference systems are associated to the same datum, then this method returns
- * the <var>target</var> datum. Otherwise, this method returns a pseudo-datum for the largest ensemble which
- * fully contains the datum or datum ensemble of the other <abbr>CRS</abbr>. If none of the <var>source</var>
- * or <var>target</var> datum ensembles met that criterion, then this method returns an empty value.
- * A non-empty value means that it is okay, for low accuracy requirements, to ignore the datum shift.
- *
- * @param source the source <abbr>CRS</abbr> of a coordinate operation.
- * @param sourceDatum the datum of the source <abbr>CRS</abbr>.
- * @param target the target <abbr>CRS</abbr> of a coordinate operation.
- * @param targetDatum the datum of the target <abbr>CRS</abbr>.
- * @param constructor function to invoke for wrapping a datum ensemble in a pseudo-datum.
- * @return datum or pseudo-datum of the coordinate operation result if it is okay to ignore datum shift.
- */
- @SuppressWarnings("unchecked") // Casts are safe because callers know the method signature of <D>.
- private static <C extends SingleCRS, D extends Datum, R extends IdentifiedObject> Optional<R> ofOperation(
- final C source, final R sourceDatum,
- final C target, final R targetDatum,
- final Function<DatumEnsemble<D>, R> constructor)
- {
- if (sourceDatum != null && Utilities.equalsIgnoreMetadata(sourceDatum, targetDatum)) {
- return Optional.of(targetDatum);
- }
- DatumEnsemble<D> sourceEnsemble;
- DatumEnsemble<D> targetEnsemble;
- DatumEnsemble<D> selected;
- if ((isMember(selected = targetEnsemble = (DatumEnsemble<D>) target.getDatumEnsemble(), sourceDatum)) ||
- (isMember(selected = sourceEnsemble = (DatumEnsemble<D>) source.getDatumEnsemble(), targetDatum)))
- {
- return Optional.of(constructor.apply(selected));
- }
- if (sourceEnsemble != null && targetEnsemble != null) {
- selected = targetEnsemble;
- Collection<D> large = targetEnsemble.getMembers();
- Collection<D> small = sourceEnsemble.getMembers();
- if (small.size() > large.size()) {
- selected = sourceEnsemble;
- var t = large;
- large = small;
- small = t;
- }
- small = new ArrayDeque<>(small);
- for (final Datum member : large) {
- final Iterator<D> it = small.iterator();
- while (it.hasNext()) {
- if (Utilities.equalsIgnoreMetadata(member, it.next())) {
- it.remove();
- if (small.isEmpty()) {
- /*
- * Found all members of the smaller ensemble. Take the larger ensemble,
- * as it contains both ensembles and should have conservative accuracy.
- */
- return Optional.of(constructor.apply(selected));
- }
- break; // For removing only the first match.
- }
- }
- }
- }
- return Optional.empty();
- }
-
- /**
- * Returns whether the given datum is a member of the given ensemble.
- *
- * @param datum the datum to test, or {@code null}.
- * @param ensemble the ensemble to test, or {@code null}.
- * @return whether the ensemble contains the given datum.
- */
- private static boolean isMember(final DatumEnsemble<?> ensemble, final IdentifiedObject datum) {
- if (ensemble != null) {
- for (final Datum member : ensemble.getMembers()) {
- if (Utilities.equalsIgnoreMetadata(datum, member)) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Returns the datum or ensemble of a coordinate operation from <var>source</var> to <var>target</var>.
- * If the two given coordinate reference systems are associated to the same datum, then this method returns
- * the <var>target</var> datum. Otherwise, this method returns the largest ensemble which fully contains the
- * datum or datum ensemble of the other <abbr>CRS</abbr>. If none of the <var>source</var> or <var>target</var>
- * datum ensembles met that criterion, then this method returns an empty value.
- * A non-empty value means that it is okay, for low accuracy requirements, to ignore the datum shift.
- *
- * <p>This is an alternative to the {@code ofOperation(…)} methods when the caller does not need to view
- * the returned object as a datum.</p>
- *
- * @param source the source <abbr>CRS</abbr> of a coordinate operation, or {@code null}.
- * @param target the target <abbr>CRS</abbr> of a coordinate operation, or {@code null}.
- * @return datum or datum ensemble of the coordinate operation result if it is okay to ignore datum shift.
- */
- public static Optional<IdentifiedObject> getDatumOrEnsemble(final SingleCRS source, final SingleCRS target) {
- if (source == null) return Optional.ofNullable(getDatumOrEnsemble(target));
- if (target == null) return Optional.ofNullable(getDatumOrEnsemble(source));
- return ofOperation(source, source.getDatum(),
- target, target.getDatum(),
- (ensemble) -> 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
- * returned 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();
- }
-
- /**
- * If the given object is a datum ensemble or a wrapper for a datum ensemble, returns its accuracy.
- * This method recognizes the {@link DatumEnsemble} and {@link PseudoDatum} types.
- *
- * @param object the object from which to get the ensemble accuracy, or {@code null}.
- * @return the datum ensemble accuracy if the given object is a datum ensemble or a wrapper.
- * @throws NullPointerException if the given object should provide an accuracy but didn't.
- */
- public static Optional<PositionalAccuracy> getEnsembleAccuracy(final IdentifiedObject object) {
- final DatumEnsemble<?> ensemble;
- if (object instanceof DatumEnsemble<?>) {
- ensemble = (DatumEnsemble<?>) object;
- } else if (object instanceof PseudoDatum<?>) {
- ensemble = ((PseudoDatum<?>) object).ensemble;
- } else {
- return Optional.empty();
- }
- return Optional.of(ensemble.getEnsembleAccuracy()); // Intentional NullPointerException if this property is null.
- }
-
- /**
- * 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 because nothing new to said.
- */
- @Override
- public Identifier getName() {
- return ensemble.getName();
- }
-
- /**
- * Returns alternative names by which the datum ensemble is identified.
- *
- * @return {@code ensemble.getAlias()}.
- *
- * @hidden because nothing new to said.
- */
- @Override
- public Collection<GenericName> getAlias() {
- return ensemble.getAlias();
- }
-
- /**
- * Returns an identifier which references elsewhere the datum ensemble information.
- *
- * @return {@code ensemble.getIdentifiers()}.
- *
- * @hidden because nothing new to said.
- */
- @Override
- public Set<Identifier> getIdentifiers() {
- return ensemble.getIdentifiers();
- }
-
- /**
- * Returns the usage of the datum ensemble.
- *
- * @return {@code ensemble.getDomains()}.
- *
- * @hidden because nothing new to said.
- */
- @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 empty if there is no common value.
- */
- 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 because nothing new to said.
- */
- @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 because nothing new to said.
- */
- @Override
- public String toWKT() {
- return ensemble.toWKT();
- }
-
- /**
- * Returns a string representation of the datum ensemble.
- *
- * @return {@code ensemble.toString()}.
- *
- * @hidden because nothing new to said.
- */
- @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/factory/GeodeticObjectFactory.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
index 31f7617988..d1c78322d9 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
@@ -602,7 +602,7 @@ public class GeodeticObjectFactory extends AbstractFactory implements CRSFactory
{
final DefaultDatumEnsemble<D> ensemble;
try {
- ensemble = new DefaultDatumEnsemble<>(complete(properties), members, accuracy);
+ ensemble = DefaultDatumEnsemble.create(complete(properties), members, accuracy);
} catch (IllegalArgumentException exception) {
throw new InvalidGeodeticParameterException(exception);
}
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/ParameterizedTransformBuilder.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/ParameterizedTransformBuilder.java
index c012b8e7bb..a9b2cda5df 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/ParameterizedTransformBuilder.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/ParameterizedTransformBuilder.java
@@ -50,6 +50,7 @@ import org.apache.sis.util.resources.Errors;
import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.referencing.cs.AxesConvention;
import org.apache.sis.referencing.cs.CoordinateSystems;
+import org.apache.sis.referencing.datum.DatumOrEnsemble;
import org.apache.sis.referencing.operation.matrix.Matrices;
import org.apache.sis.referencing.operation.provider.AbstractProvider;
import org.apache.sis.referencing.operation.provider.Geographic2Dto3D;
@@ -62,7 +63,6 @@ import org.apache.sis.referencing.operation.transform.DefaultMathTransformFactor
import org.apache.sis.referencing.operation.transform.EllipsoidToRadiusTransform;
import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
import org.apache.sis.referencing.privy.CoordinateOperations;
-import org.apache.sis.referencing.privy.ReferencingUtilities;
import org.apache.sis.referencing.privy.Formulas;
import org.apache.sis.parameter.Parameterized;
import org.apache.sis.parameter.Parameters;
@@ -203,7 +203,7 @@ public class ParameterizedTransformBuilder extends MathTransformBuilder implemen
* @param crs the <abbr>CRS</abbr> from which to fetch the hints, or {@code null}.
*/
public final void setSourceAxes(final CoordinateReferenceSystem crs) {
- setSourceAxes(crs != null ? crs.getCoordinateSystem() : null, ReferencingUtilities.getEllipsoid(crs));
+ setSourceAxes(crs != null ? crs.getCoordinateSystem() : null, DatumOrEnsemble.getEllipsoid(crs).orElse(null));
}
/**
@@ -214,7 +214,7 @@ public class ParameterizedTransformBuilder extends MathTransformBuilder implemen
* @param crs the <abbr>CRS</abbr> from which to fetch the hints, or {@code null}.
*/
public final void setTargetAxes(final CoordinateReferenceSystem crs) {
- setTargetAxes(crs != null ? crs.getCoordinateSystem() : null, ReferencingUtilities.getEllipsoid(crs));
+ setTargetAxes(crs != null ? crs.getCoordinateSystem() : null, DatumOrEnsemble.getEllipsoid(crs).orElse(null));
}
/**
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 34eeeae90d..52da6a75c9 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
@@ -448,7 +448,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#ellipsoid|6#CRS}.
+ * system|3#ellipsoid|4#prime meridian|5#datum|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 74149a92a1..67efa76663 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#ellipsoid|6#CRS}.
+ Differences are found in {2,choice,0#conversion method|1#conversion description|2#coordinate system|3#ellipsoid|4#prime meridian|5#datum|6#CRS}.
OperationSameDatumEnsemble = Coordinate operation between reference frames in the same datum ensemble.
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 e63ca97752..eaf5433c7a 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#l\u2019ellipso\u00efde|6#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#l\u2019ellipso\u00efde|4#le m\u00e9ridien d\u2019origine|5#le r\u00e9f\u00e9rentiel|6#le CRS}.
OperationSameDatumEnsemble = Op\u00e9ration sur les coordonn\u00e9es entre des r\u00e9f\u00e9rentiels qui sont dans le m\u00eame ensemble de r\u00e9f\u00e9rentiels.
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/CoordinateOperationFinder.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
index 353eaaa8db..d28975397b 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
@@ -53,7 +53,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.datum.DatumOrEnsemble;
import org.apache.sis.referencing.operation.matrix.Matrices;
import org.apache.sis.referencing.operation.matrix.MatrixSIS;
import org.apache.sis.referencing.operation.provider.DatumShiftMethod;
@@ -322,15 +322,14 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry {
// └────────────────────────────────────────────────────────────────────────┘
if (sourceCRS instanceof SingleCRS && targetCRS instanceof SingleCRS) {
final Optional<IdentifiedObject> datumOrEnsemble =
- PseudoDatum.getDatumOrEnsemble((SingleCRS) sourceCRS,
- (SingleCRS) targetCRS);
+ DatumOrEnsemble.ofTarget((SingleCRS) sourceCRS, (SingleCRS) targetCRS);
if (datumOrEnsemble.isPresent()) try {
/*
* Because the CRS type is determined by the datum type (sometimes completed by the CS type),
* having equivalent datum and compatible CS should be a sufficient criterion.
*/
return asList(createFromAffineTransform(AXIS_CHANGES, sourceCRS, targetCRS,
- PseudoDatum.getEnsembleAccuracy(datumOrEnsemble.get()).orElse(null),
+ DatumOrEnsemble.getAccuracy(datumOrEnsemble.get()).orElse(null),
CoordinateSystems.swapAndScaleAxes(sourceCRS.getCoordinateSystem(),
targetCRS.getCoordinateSystem())));
} catch (IllegalArgumentException | IncommensurableException e) {
@@ -497,8 +496,8 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry {
{
final CoordinateSystem sourceCS = sourceCRS.getCoordinateSystem();
final CoordinateSystem targetCS = targetCRS.getCoordinateSystem();
- final GeodeticDatum sourceDatum = PseudoDatum.of(sourceCRS);
- final GeodeticDatum targetDatum = PseudoDatum.of(targetCRS);
+ final GeodeticDatum sourceDatum = DatumOrEnsemble.asDatum(sourceCRS);
+ final GeodeticDatum targetDatum = DatumOrEnsemble.asDatum(targetCRS);
/*
* Find the type of operation depending on whether there is a change of geodetic reference frame (datum).
* The `DATUM_SHIFT` and `ELLIPSOID_CHANGE` identifiers mean that there is a datum change, and all other
@@ -510,7 +509,7 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry {
final MathTransform transform;
ParameterValueGroup parameters;
final Optional<OperationMethod> method;
- final Optional<GeodeticDatum> commonDatum = PseudoDatum.ofOperation(sourceCRS, targetCRS);
+ final Optional<GeodeticDatum> commonDatum = DatumOrEnsemble.asTargetDatum(sourceCRS, targetCRS);
if (commonDatum.isPresent()) {
/*
* Coordinate system change (including change in the number of dimensions) without datum shift.
@@ -569,7 +568,7 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry {
* specified datum.
*/
final Map<String, Object> properties = properties(identifier);
- PositionalAccuracy accuracy = commonDatum.flatMap(PseudoDatum::getEnsembleAccuracy).orElse(null);
+ PositionalAccuracy accuracy = commonDatum.flatMap(DatumOrEnsemble::getAccuracy).orElse(null);
if (accuracy == null && datumShift instanceof AnnotatedMatrix) {
accuracy = ((AnnotatedMatrix) datumShift).accuracy;
}
@@ -644,7 +643,7 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry {
VerticalCRS heightCRS = targetCRS; // First candidate, will be replaced if it doesn't fit.
VerticalCS heightCS = heightCRS.getCoordinateSystem();
if (Utilities.equalsIgnoreMetadata(heightCS.getAxis(0), expectedAxis)) {
- isEllipsoidalHeight = ReferencingUtilities.isEllipsoidalHeight(PseudoDatum.of(heightCRS));
+ isEllipsoidalHeight = ReferencingUtilities.isEllipsoidalHeight(DatumOrEnsemble.asDatum(heightCRS));
} else {
heightCRS = CommonCRS.Vertical.ELLIPSOIDAL.crs();
heightCS = heightCRS.getCoordinateSystem();
@@ -702,10 +701,10 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry {
final VerticalCRS targetCRS)
throws FactoryException
{
- final Optional<VerticalDatum> commonDatum = PseudoDatum.ofOperation(sourceCRS, targetCRS);
+ final Optional<VerticalDatum> commonDatum = DatumOrEnsemble.asTargetDatum(sourceCRS, targetCRS);
if (commonDatum.isEmpty()) {
- throw new OperationNotFoundException(notFoundMessage(PseudoDatum.getDatumOrEnsemble(sourceCRS),
- PseudoDatum.getDatumOrEnsemble(targetCRS)));
+ throw new OperationNotFoundException(notFoundMessage(DatumOrEnsemble.of(sourceCRS),
+ DatumOrEnsemble.of(targetCRS)));
}
final VerticalCS sourceCS = sourceCRS.getCoordinateSystem();
final VerticalCS targetCS = targetCRS.getCoordinateSystem();
@@ -716,7 +715,7 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry {
throw new OperationNotFoundException(notFoundMessage(sourceCRS, targetCRS), exception);
}
return asList(createFromAffineTransform(AXIS_CHANGES, sourceCRS, targetCRS,
- PseudoDatum.getEnsembleAccuracy(commonDatum.get()).orElse(null), matrix));
+ DatumOrEnsemble.getAccuracy(commonDatum.get()).orElse(null), matrix));
}
/**
@@ -731,7 +730,7 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry {
* @todo The current version performs only unit conversion and change of datum epoch,
* assuming that everything else is equivalent. This is probably not sufficient,
* as the relationship between two temporal datum may be non-linear.
- * We should invoke {@link PseudoDatum#ofOperation(TemporalCRS, TemporalCRS)}
+ * We should invoke {@link DatumOrEnsemble#asTargetDatum(TemporalCRS, TemporalCRS)}
* for checking if the datum are equivalent, but doing so require that we remove
* the origin attribute from {@link TemporalDatum}.
* This change has been proposed to <abbr>OGC</abbr>.
@@ -745,8 +744,8 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry {
final TemporalCRS targetCRS)
throws FactoryException
{
- final TemporalDatum sourceDatum = PseudoDatum.of(sourceCRS);
- final TemporalDatum targetDatum = PseudoDatum.of(targetCRS);
+ final TemporalDatum sourceDatum = DatumOrEnsemble.asDatum(sourceCRS);
+ final TemporalDatum targetDatum = DatumOrEnsemble.asDatum(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 84ae08b5c6..7057a47522 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
@@ -50,7 +50,7 @@ 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.datum.DatumOrEnsemble;
import org.apache.sis.referencing.cs.CoordinateSystems;
import org.apache.sis.referencing.operation.matrix.Matrices;
import org.apache.sis.referencing.operation.provider.Affine;
@@ -1211,8 +1211,8 @@ class CoordinateOperationRegistry {
* to return the existing instance.
*/
if (crs.getClass() == candidate.getClass() && candidate.getCoordinateSystem().getDimension() == 3) {
- if (Utilities.equalsIgnoreMetadata(PseudoDatum.getDatumOrEnsemble((SingleCRS) candidate),
- PseudoDatum.getDatumOrEnsemble((SingleCRS) crs)))
+ if (Utilities.equalsIgnoreMetadata(DatumOrEnsemble.of((SingleCRS) candidate),
+ DatumOrEnsemble.of((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/DefaultConcatenatedOperation.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConcatenatedOperation.java
index 014ea8313c..802e121ad4 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConcatenatedOperation.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConcatenatedOperation.java
@@ -34,10 +34,10 @@ import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransformFactory;
import org.opengis.referencing.operation.NoninvertibleTransformException;
import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.referencing.datum.DatumOrEnsemble;
import org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory;
import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
import org.apache.sis.referencing.privy.CoordinateOperations;
-import org.apache.sis.referencing.privy.ReferencingUtilities;
import org.apache.sis.referencing.internal.PositionalAccuracyConstant;
import org.apache.sis.referencing.internal.Resources;
import org.apache.sis.util.Utilities;
@@ -173,7 +173,7 @@ final class DefaultConcatenatedOperation extends AbstractCoordinateOperation imp
targetCRS = crs;
} else if (mtFactory != null) {
var builder = mtFactory.builder(Constants.COORDINATE_SYSTEM_CONVERSION);
- builder.setSourceAxes(crs.getCoordinateSystem(), ReferencingUtilities.getEllipsoid(crs));
+ builder.setSourceAxes(crs.getCoordinateSystem(), DatumOrEnsemble.getEllipsoid(crs).orElse(null));
builder.setTargetAxes(targetCRS.getCoordinateSystem(), null);
transform = mtFactory.createConcatenatedTransform(transform, builder.create());
}
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 dbf98a5787..e9367587cb 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
@@ -32,6 +32,7 @@ import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.crs.SingleCRS;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.apache.sis.referencing.cs.CoordinateSystems;
+import org.apache.sis.referencing.datum.DatumOrEnsemble;
import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
import org.apache.sis.referencing.internal.ParameterizedTransformBuilder;
import org.apache.sis.referencing.internal.Resources;
@@ -250,7 +251,7 @@ public class DefaultConversion extends AbstractSingleOperation implements Conver
final var builder = new ParameterizedTransformBuilder(factory, null);
builder.setParameters(parameters, true);
builder.setSourceAxes(source);
- builder.setTargetAxes(target.getCoordinateSystem(), isDerived ? null : ReferencingUtilities.getEllipsoid(target));
+ builder.setTargetAxes(target.getCoordinateSystem(), isDerived ? null : DatumOrEnsemble.getEllipsoid(target).orElse(null));
transform = builder.create();
if (builder instanceof MathTransformProvider.Context) {
final var context = (MathTransformProvider.Context) builder;
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 7f12980e6b..7c3a3e067b 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
@@ -39,7 +39,7 @@ 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.datum.DatumOrEnsemble;
import org.apache.sis.referencing.internal.Resources;
import org.apache.sis.referencing.internal.MergedProperties;
import org.apache.sis.referencing.internal.ParameterizedTransformBuilder;
@@ -412,7 +412,7 @@ public class DefaultCoordinateOperationFactory extends AbstractFactory implement
next: for (SingleCRS component : CRS.getSingleComponents(targetCRS)) {
final Iterator<SingleCRS> it = components.iterator();
while (it.hasNext()) {
- if (PseudoDatum.getDatumOrEnsemble(component, it.next()).isPresent()) {
+ if (DatumOrEnsemble.ofTarget(component, it.next()).isPresent()) {
it.remove();
continue next;
}
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 51c858d18c..5e0417e51f 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,7 +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.datum.DatumOrEnsemble;
import org.apache.sis.referencing.privy.CoordinateOperations;
import org.apache.sis.referencing.operation.DefaultOperationMethod;
import org.apache.sis.referencing.internal.ParameterizedTransformBuilder;
@@ -563,7 +563,7 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math
public void setSource(final GeodeticCRS crs) {
if (crs != null) {
sourceCS = crs.getCoordinateSystem();
- sourceEllipsoid = ReferencingUtilities.getEllipsoid(crs);
+ sourceEllipsoid = DatumOrEnsemble.getEllipsoid(crs).orElse(null);
} else {
sourceCS = null;
sourceEllipsoid = null;
@@ -603,7 +603,7 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math
public void setTarget(final GeodeticCRS crs) {
if (crs != null) {
targetCS = crs.getCoordinateSystem();
- targetEllipsoid = ReferencingUtilities.getEllipsoid(crs);
+ targetEllipsoid = DatumOrEnsemble.getEllipsoid(crs).orElse(null);
} 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 8191883518..bb84005056 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
@@ -18,6 +18,7 @@ package org.apache.sis.referencing.privy;
import java.util.Iterator;
import java.util.Locale;
+import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import org.opengis.util.FactoryException;
@@ -32,7 +33,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.datum.DatumOrEnsemble;
import org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
import org.apache.sis.referencing.factory.IdentifiedObjectFinder;
import org.apache.sis.referencing.internal.Resources;
@@ -300,13 +301,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, ELLIPSOID=5, OTHER=6;
+ private static final int METHOD=0, CONVERSION=1, CS=2, ELLIPSOID=3, PRIME_MERIDIAN=4, DATUM=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}, {@link #ELLIPSOID} or {@link #OTHER} constants.
+ * The returned value is one of {@link #METHOD}, {@link #CONVERSION}, {@link #CS}, {@link #PRIME_MERIDIAN},
+ * {@link #ELLIPSOID}, {@link #DATUM} or {@link #OTHER} constants.
*/
private static int diffCode(final Iterator<SingleCRS> authoritative, final Iterator<SingleCRS> given) {
while (authoritative.hasNext() && given.hasNext()) {
@@ -320,23 +321,18 @@ public final class DefinitionVerifier {
return Utilities.equalsApproximately(cnvA.getMethod(), cnvG.getMethod()) ? CONVERSION : METHOD;
}
}
- if (!Utilities.equalsApproximately(crsA.getCoordinateSystem(), crsG.getCoordinateSystem())) {
- return CS;
- }
- 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(PseudoDatum.getDatumOrEnsemble(crsA),
- PseudoDatum.getDatumOrEnsemble(crsG)))
- {
- return DATUM;
+ for (int code = CS; code <= DATUM; code++) {
+ final Function<SingleCRS, ?> getter;
+ switch (code) {
+ case CS: getter = SingleCRS::getCoordinateSystem; break;
+ case ELLIPSOID: getter = DatumOrEnsemble::getEllipsoid; break;
+ case PRIME_MERIDIAN: getter = DatumOrEnsemble::getPrimeMeridian; break;
+ case DATUM: getter = DatumOrEnsemble::of; break;
+ default: throw new AssertionError(code);
+ }
+ if (!Utilities.equalsApproximately(getter.apply(crsA), getter.apply(crsG))) {
+ return code;
+ }
}
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 0a9ac3d3c6..0ee153d30c 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,7 +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.referencing.datum.DatumOrEnsemble;
import org.apache.sis.metadata.iso.extent.Extents;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.ArraysExt;
@@ -114,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 = PseudoDatum.of((VerticalCRS) vertical);
+ final VerticalDatum datum = DatumOrEnsemble.asDatum((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/ReferencingUtilities.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingUtilities.java
index cd5311b564..c58e78f33b 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,7 +18,6 @@ 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;
@@ -30,10 +29,7 @@ import org.opengis.parameter.GeneralParameterDescriptor;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.cs.*;
import org.opengis.referencing.crs.*;
-import org.opengis.referencing.datum.Datum;
-import org.opengis.referencing.datum.Ellipsoid;
import org.opengis.referencing.datum.PrimeMeridian;
-import org.opengis.referencing.datum.GeodeticDatum;
import org.opengis.referencing.datum.VerticalDatum;
import org.opengis.referencing.operation.MathTransformFactory;
import org.apache.sis.util.Static;
@@ -56,7 +52,6 @@ import org.apache.sis.referencing.operation.transform.DefaultMathTransformFactor
import org.apache.sis.parameter.DefaultParameterDescriptorGroup;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.datum.DatumEnsemble;
/**
@@ -227,91 +222,6 @@ public final class ReferencingUtilities extends Static {
return false;
}
- /**
- * Returns the ellipsoid used by the given coordinate reference system, or {@code null} if none.
- * More specifically:
- *
- * <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 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>
- * </ul>
- *
- * Note that this method does not check if a compound <abbr>CRS</abbr> contains more than one ellipsoid
- * (it should never be the case). Note also that this method may return {@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) {
- return getGeodeticProperty(crs, GeodeticDatum::getEllipsoid);
- }
-
- /**
- * 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)}.
- *
- * @param crs the coordinate reference system for which to get the prime meridian.
- * @return the prime meridian, or {@code null} if none.
- */
- public static PrimeMeridian getPrimeMeridian(final CoordinateReferenceSystem crs) {
- return getGeodeticProperty(crs, GeodeticDatum::getPrimeMeridian);
- }
-
- /**
- * Implementation of {@code getEllipsoid(CRS)} and {@code getPrimeMeridian(CRS)}.
- * The difference between this method and {@link org.apache.sis.referencing.datum.PseudoDatum}
- * is that this method ignore null values and does not throw an exception in case of mismatch.
- *
- * @param <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.
- */
- 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;
- }
- }
- if (crs instanceof CompoundCRS) {
- for (final CoordinateReferenceSystem c : ((CompoundCRS) crs).getComponents()) {
- final P property = getGeodeticProperty(c, getter);
- if (property != null) {
- return property;
- }
- }
- }
- return null;
- }
-
/**
* Derives a geographic CRS with (<var>longitude</var>, <var>latitude</var>) axis in the specified
* order and in decimal degrees. If no such CRS can be obtained or created, returns {@code null}.
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTUtilities.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTUtilities.java
index ddb558e8d3..ae07909d91 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTUtilities.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTUtilities.java
@@ -41,6 +41,7 @@ import org.apache.sis.referencing.crs.AbstractCRS;
import org.apache.sis.referencing.cs.AbstractCS;
import org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis;
import org.apache.sis.referencing.datum.AbstractDatum;
+import org.apache.sis.referencing.datum.DatumOrEnsemble;
import org.apache.sis.referencing.datum.DefaultGeodeticDatum;
import org.apache.sis.referencing.datum.DefaultPrimeMeridian;
import org.apache.sis.referencing.datum.DefaultEllipsoid;
@@ -400,8 +401,7 @@ public final class WKTUtilities extends Static {
*/
public static int[] suggestFractionDigits(final CoordinateReferenceSystem crs, final Vector[] points) {
final int[] fractionDigits = suggestFractionDigits(points);
- final Ellipsoid ellipsoid = ReferencingUtilities.getEllipsoid(crs);
- if (ellipsoid != null) {
+ DatumOrEnsemble.getEllipsoid(crs).ifPresent((ellipsoid) -> {
/*
* Use heuristic precisions for geodetic or projected CRS. We do not apply those heuristics
* for other kind of CRS (e.g. engineering) because we do not know what could be the size
@@ -428,7 +428,7 @@ public final class WKTUtilities extends Static {
fractionDigits[i] = f; // Use at least the heuristic precision.
}
}
- }
+ });
return fractionDigits;
}
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/GeodesicsOnEllipsoidTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/GeodesicsOnEllipsoidTest.java
index f50e1b8bd2..d7eb42adcc 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/GeodesicsOnEllipsoidTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/GeodesicsOnEllipsoidTest.java
@@ -22,7 +22,7 @@ import java.util.Arrays;
import static java.lang.StrictMath.*;
import org.opengis.geometry.DirectPosition;
import org.opengis.referencing.crs.GeographicCRS;
-import org.apache.sis.referencing.privy.ReferencingUtilities;
+import org.apache.sis.referencing.datum.DatumOrEnsemble;
import org.apache.sis.referencing.privy.Formulas;
import org.apache.sis.math.MathFunctions;
import org.apache.sis.measure.Units;
@@ -131,7 +131,7 @@ public final class GeodesicsOnEllipsoidTest extends GeodeticCalculatorTest {
/** Creates a new calculator for the given coordinate reference system. */
Calculator(final GeographicCRS crs) {
- super(crs, ReferencingUtilities.getEllipsoid(crs));
+ super(crs, DatumOrEnsemble.getEllipsoid(crs).orElseThrow());
}
/** Invoked when {@link GeodesicsOnEllipsoid} computed an intermediate value. */
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 3e9de28a0f..c24d460a8b 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,7 +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.datum.DatumOrEnsemble;
import org.apache.sis.referencing.privy.WKTKeywords;
import org.apache.sis.referencing.privy.NilReferencingObject;
import org.apache.sis.referencing.privy.ReferencingUtilities;
@@ -1114,7 +1114,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 = PseudoDatum.of(crs);
+ final GeodeticDatum datum = DatumOrEnsemble.asDatum(crs);
verifyIdentifier(crs, datum, GeoKeys.GeodeticDatum);
verify(datum, angularUnit, linearUnit);
geoKeys.remove(GeoKeys.GeodeticCitation);
@@ -1183,7 +1183,7 @@ public final class CRSBuilder extends ReferencingFactoryContainer {
*/
final Unit<Length> linearUnit = createLinearUnit(UnitKey.LINEAR);
final Unit<Angle> angularUnit = createAngularUnit(UnitKey.ANGULAR);
- final GeodeticDatum datum = PseudoDatum.of(crs);
+ final GeodeticDatum datum = DatumOrEnsemble.asDatum(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 ab0ac8cd31..4d0e016107 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
@@ -60,7 +60,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.datum.DatumOrEnsemble;
import org.apache.sis.referencing.cs.CoordinateSystems;
import org.apache.sis.referencing.operation.matrix.Matrices;
import org.apache.sis.referencing.operation.provider.MapProjection;
@@ -391,7 +391,7 @@ public final class GeoEncoder {
if (writeEPSG(GeoKeys.Vertical, crs)) {
writeName(GeoKeys.VerticalCitation, null, crs);
addUnits(UnitKey.VERTICAL, crs.getCoordinateSystem());
- final VerticalDatum datum = PseudoDatum.of(crs);
+ final VerticalDatum datum = DatumOrEnsemble.asDatum(crs);
if (writeEPSG(GeoKeys.VerticalDatum, datum)) {
/*
* OGC requirement 25.5 said "VerticalCitationGeoKey SHALL be populated."
@@ -438,7 +438,7 @@ public final class GeoEncoder {
writeModelType(isBaseCRS ? GeoCodes.ModelTypeProjected : type);
if (writeEPSG(GeoKeys.GeodeticCRS, crs)) {
writeName(GeoKeys.GeodeticCitation, "GCS Name", isPseudoProjection ? null : crs);
- writeDatum(PseudoDatum.of(crs));
+ writeDatum(DatumOrEnsemble.asDatum(crs));
} 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 5b59bf4f25..b0d43afec4 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
@@ -46,7 +46,7 @@ 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.datum.DatumOrEnsemble;
import org.apache.sis.referencing.privy.GeodeticObjectBuilder;
import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.CharSequences;
@@ -445,7 +445,7 @@ final class Store extends URIDataStore implements FeatureSet {
timeEncoding = TimeEncoding.ABSOLUTE;
} else {
temporal = builder.createTemporalCRS(startTime, timeUnit);
- timeEncoding = new TimeEncoding(PseudoDatum.of(temporal), timeUnit);
+ timeEncoding = new TimeEncoding(DatumOrEnsemble.asDatum(temporal), timeUnit);
}
components[count++] = temporal;
name = name + " + " + temporal.getName().getCode();