This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new 2c396c8526 Make use, or prepare for the use, of `DatumEnsemble` in
`CoordinateOperationFinder`. The following pattern:
2c396c8526 is described below
commit 2c396c852690acc967efb5d20bdadf3176e846a9
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri Jul 19 17:20:17 2024 +0200
Make use, or prepare for the use, of `DatumEnsemble` in
`CoordinateOperationFinder`.
The following pattern:
if (equalsIgnoreMetadata(source.getDatum(), target.getDatum())) {...}
is replaced by
if (PseudoDatum.ofOperation(source, target).isPresent()) {...}
The `ofOperation` result is used for determining the accuracy of the
coordinate operation.
---
.../sis/referencing/MultiRegisterOperations.java | 2 +-
.../apache/sis/referencing/datum/PseudoDatum.java | 266 +++++++++++++++------
.../operation/CoordinateOperationFinder.java | 71 ++++--
.../operation/CoordinateOperationRegistry.java | 9 +-
.../DefaultCoordinateOperationFactory.java | 23 +-
5 files changed, 247 insertions(+), 124 deletions(-)
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 8eed014ccc..4b46c7a044 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
@@ -348,7 +348,7 @@ public class MultiRegisterOperations extends
AbstractFactory implements Register
return false;
}
for (int i=0; i<n; i++) {
- if (PseudoDatum.getOperationAccuracy(sources.get(i),
targets.get(i)).isEmpty()) {
+ if (PseudoDatum.getDatumOrEnsemble(sources.get(i),
targets.get(i)).isEmpty()) {
return false;
}
}
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/PseudoDatum.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/PseudoDatum.java
index 87c9fe4f43..75ac98142e 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/PseudoDatum.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/PseudoDatum.java
@@ -16,6 +16,7 @@
*/
package org.apache.sis.referencing.datum;
+import java.util.ArrayDeque;
import java.util.Set;
import java.util.Collection;
import java.util.Iterator;
@@ -39,7 +40,6 @@ 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;
-import org.apache.sis.referencing.internal.PositionalAccuracyConstant;
/**
@@ -198,89 +198,136 @@ public abstract class PseudoDatum<D extends Datum>
implements Datum, LenientComp
}
/**
- * Returns the datum of the given <abbr>CRS</abbr> if presents, or the
datum ensemble otherwise.
- * This is an alternative to the {@code of(…)} methods when the caller
does not need to view the
- * object as a datum.
+ * 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 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();
+ * @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 inaccuracy that would have an operation using datum
ensembles.
- * This method makes the following choice:
+ * 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.
*
- * <ul>
- * <li>If the two reference systems are associated to the same datum,
returns an arbitrary value.</li>
- * <li>Otherwise, if the datum of one <abbr>CRS</abbr> is a member of
the datum ensemble of the other
- * <abbr>CRS</abbr>, returns the ensemble accuracy.</li>
- * <li>OTherwise, if the datum ensemble of one <abbr>CRS</abbr> is fully
contained in the datum ensemble
- * of the other <abbr>CRS</abbr>, returns the accuracy of the larger
ensemble.</li>
- * <li>Otherwise, returns an empty value.</li>
- * </ul>
+ * @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.
*
- * An empty value means that the two <abbr>CRS</abbr> are not compatible
according the datum and datum ensemble
- * properties. However, a transformation path may exist in a geodetic
registry such as <abbr>EPSG</abbr>.
+ * @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 first <abbr>CRS</abbr> for which to compare the
datum.
- * @param target the second <abbr>CRS</abbr> for which to compare the
datum.
- * @return a non-null value if it is okay, for low accuracy requirements,
to ignore the datum shift.
- */
- public static Optional<PositionalAccuracy> getOperationAccuracy(final
SingleCRS source, final SingleCRS target) {
- final Datum sourceDatum = source.getDatum();
- final Datum targetDatum = target.getDatum();
- if (sourceDatum != null && targetDatum != null &&
Utilities.equalsIgnoreMetadata(sourceDatum, targetDatum)) {
- return Optional.of(PositionalAccuracyConstant.SAME_DATUM_ENSEMBLE);
+ * @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<?> sourceEnsemble;
- DatumEnsemble<?> targetEnsemble;
- PositionalAccuracy accuracy;
- if ((accuracy = accuracyIfMember(sourceDatum, targetEnsemble =
target.getDatumEnsemble())) != null ||
- (accuracy = accuracyIfMember(targetDatum, sourceEnsemble =
source.getDatumEnsemble())) != null ||
- (sourceEnsemble == null || targetEnsemble == null))
+ 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(accuracy);
- }
- var sources = sourceEnsemble.getMembers();
- var targets = targetEnsemble.getMembers();
- if (sources.size() > targets.size()) {
- var tmp = targets; // Want as if transforming from
smaller ensemble to larger ensemble.
- targets = sources;
- sources = tmp;
-
- var te = targetEnsemble;
- targetEnsemble = sourceEnsemble;
- sourceEnsemble = te;
+ return Optional.of(constructor.apply(selected));
}
- final Datum[] remaining = sources.toArray(Datum[]::new);
- int count = remaining.length;
- for (final Datum member : targets) {
- for (int i=0; i<count; i++) {
- if (Utilities.equalsIgnoreMetadata(member, remaining[i])) {
- System.arraycopy(remaining, i+1, remaining, i, --count -
i);
- if (count == 0) {
- /*
- * Found all members of the smaller ensemble. Take the
accuracy
- * of the larger ensemble, as it contains the smaller
ensemble.
- */
- if ((accuracy = targetEnsemble.getEnsembleAccuracy())
== null &&
- (accuracy = sourceEnsemble.getEnsembleAccuracy())
== null) {
- accuracy =
PositionalAccuracyConstant.SAME_DATUM_ENSEMBLE;
+ 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));
}
- return Optional.of(accuracy);
+ break; // For removing only the first match.
}
- break; // For removing only the first match.
}
}
}
@@ -288,23 +335,84 @@ public abstract class PseudoDatum<D extends Datum>
implements Datum, LenientComp
}
/**
- * If the given datum is a member of the given ensemble, returns the
ensemble accuracy.
- * Otherwise, returns {@code null}.
+ * 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 a non-null value if the datum is a member of the given ensemble.
+ * @return whether the ensemble contains the given datum.
*/
- private static PositionalAccuracy accuracyIfMember(final Datum datum,
final DatumEnsemble<?> ensemble) {
+ private static boolean isMember(final DatumEnsemble<?> ensemble, final
IdentifiedObject datum) {
if (ensemble != null) {
for (final Datum member : ensemble.getMembers()) {
if (Utilities.equalsIgnoreMetadata(datum, member)) {
- PositionalAccuracy accuracy =
ensemble.getEnsembleAccuracy();
- return (accuracy != null) ? accuracy :
PositionalAccuracyConstant.SAME_DATUM_ENSEMBLE;
+ return true;
}
}
}
- return null;
+ 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.
}
/**
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 a01cbe8868..9df0ff8833 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
@@ -22,6 +22,7 @@ import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.ListIterator;
+import java.util.Optional;
import java.time.Duration;
import javax.measure.Unit;
import javax.measure.IncommensurableException;
@@ -64,11 +65,11 @@ import
org.apache.sis.referencing.operation.provider.Geographic3Dto2D;
import org.apache.sis.referencing.operation.provider.GeographicToGeocentric;
import org.apache.sis.referencing.operation.provider.GeocentricToGeographic;
import org.apache.sis.referencing.operation.provider.GeocentricAffine;
+import org.apache.sis.util.Utilities;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.privy.Constants;
import org.apache.sis.util.privy.DoubleDouble;
import org.apache.sis.util.resources.Vocabulary;
-import static org.apache.sis.util.Utilities.equalsIgnoreMetadata;
/**
@@ -233,8 +234,8 @@ public class CoordinateOperationFinder extends
CoordinateOperationRegistry {
{
ArgumentChecks.ensureNonNull("sourceCRS", sourceCRS);
ArgumentChecks.ensureNonNull("targetCRS", targetCRS);
- if (equalsIgnoreMetadata(sourceCRS, targetCRS)) try {
- return asList(createFromAffineTransform(AXIS_CHANGES, sourceCRS,
targetCRS,
+ if (Utilities.equalsIgnoreMetadata(sourceCRS, targetCRS)) try {
+ return asList(createFromAffineTransform(AXIS_CHANGES, sourceCRS,
targetCRS, null,
CoordinateSystems.swapAndScaleAxes(sourceCRS.getCoordinateSystem(),
targetCRS.getCoordinateSystem())));
} catch (IllegalArgumentException | IncommensurableException e) {
@@ -339,14 +340,16 @@ public class CoordinateOperationFinder extends
CoordinateOperationRegistry {
////
////
////////////////////////////////////////////////////////////////////////////////
if (sourceCRS instanceof SingleCRS && targetCRS instanceof SingleCRS) {
- final IdentifiedObject sourceDatum =
PseudoDatum.getDatumOrEnsemble((SingleCRS) sourceCRS);
- final IdentifiedObject targetDatum =
PseudoDatum.getDatumOrEnsemble((SingleCRS) targetCRS);
- if (equalsIgnoreMetadata(sourceDatum, targetDatum)) try {
+ final Optional<IdentifiedObject> datumOrEnsemble =
+ PseudoDatum.getDatumOrEnsemble((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),
CoordinateSystems.swapAndScaleAxes(sourceCRS.getCoordinateSystem(),
targetCRS.getCoordinateSystem())));
} catch (IllegalArgumentException | IncommensurableException e) {
@@ -539,7 +542,8 @@ public class CoordinateOperationFinder extends
CoordinateOperationRegistry {
*/
Identifier identifier;
boolean isGeographicToGeocentric = false;
- if (equalsIgnoreMetadata(sourceDatum, targetDatum)) {
+ final Optional<GeodeticDatum> commonDatum =
PseudoDatum.ofOperation(sourceCRS, targetCRS);
+ if (commonDatum.isPresent()) {
final boolean isGeocentricToGeographic;
isGeographicToGeocentric = (sourceCS instanceof EllipsoidalCS &&
targetCS instanceof CartesianCS);
isGeocentricToGeographic = (sourceCS instanceof CartesianCS &&
targetCS instanceof EllipsoidalCS);
@@ -563,7 +567,7 @@ public class CoordinateOperationFinder extends
CoordinateOperationRegistry {
}
}
/*
- * Conceptually, all transformations below could done by first
converting from the source coordinate
+ * Conceptually, all transformations below could be done by first
converting from source coordinate
* system to geocentric Cartesian coordinates (X,Y,Z), apply an affine
transform represented by the
* datum shift matrix, then convert from the (X′,Y′,Z′) coordinates to
the target coordinate system.
* However, there are two exceptions to this path:
@@ -693,10 +697,12 @@ public class CoordinateOperationFinder extends
CoordinateOperationRegistry {
* specified datum.
*/
final Map<String, Object> properties = properties(identifier);
- if (datumShift instanceof AnnotatedMatrix) {
-
properties.put(CoordinateOperation.COORDINATE_OPERATION_ACCURACY_KEY, new
PositionalAccuracy[] {
- ((AnnotatedMatrix) datumShift).accuracy
- });
+ PositionalAccuracy accuracy =
commonDatum.flatMap(PseudoDatum::getEnsembleAccuracy).orElse(null);
+ if (accuracy == null && datumShift instanceof AnnotatedMatrix) {
+ accuracy = ((AnnotatedMatrix) datumShift).accuracy;
+ }
+ if (accuracy != null) {
+
properties.put(CoordinateOperation.COORDINATE_OPERATION_ACCURACY_KEY, accuracy);
}
return asList(createFromMathTransform(properties, sourceCRS,
targetCRS, transform, method, parameters, null));
}
@@ -740,7 +746,7 @@ public class CoordinateOperationFinder extends
CoordinateOperationRegistry {
CoordinateSystem interpolationCS =
interpolationCRS.getCoordinateSystem();
if (!(interpolationCS instanceof EllipsoidalCS)) {
final EllipsoidalCS cs =
CommonCRS.WGS84.geographic3D().getCoordinateSystem();
- if (!equalsIgnoreMetadata(interpolationCS, cs)) {
+ if (!Utilities.equalsIgnoreMetadata(interpolationCS, cs)) {
final GeographicCRS stepCRS = factorySIS.crsFactory
.createGeographicCRS(derivedFrom(sourceCRS),
sourceCRS.getDatum(), sourceCRS.getDatumEnsemble(), cs);
step1 = createOperation(sourceCRS,
toAuthorityDefinition(GeographicCRS.class, stepCRS));
@@ -765,12 +771,12 @@ public class CoordinateOperationFinder extends
CoordinateOperationRegistry {
final boolean isEllipsoidalHeight; // Whether heightCRS is okay
or need to be recreated.
VerticalCRS heightCRS = targetCRS; // First candidate, will be
replaced if it doesn't fit.
VerticalCS heightCS = heightCRS.getCoordinateSystem();
- if (equalsIgnoreMetadata(heightCS.getAxis(0), expectedAxis)) {
+ if (Utilities.equalsIgnoreMetadata(heightCS.getAxis(0), expectedAxis))
{
isEllipsoidalHeight =
ReferencingUtilities.isEllipsoidalHeight(PseudoDatum.of(heightCRS));
} else {
heightCRS = CommonCRS.Vertical.ELLIPSOIDAL.crs();
heightCS = heightCRS.getCoordinateSystem();
- isEllipsoidalHeight = equalsIgnoreMetadata(heightCS.getAxis(0),
expectedAxis);
+ isEllipsoidalHeight =
Utilities.equalsIgnoreMetadata(heightCS.getAxis(0), expectedAxis);
if (!isEllipsoidalHeight) {
heightCS = toAuthorityDefinition(VerticalCS.class,
factorySIS.csFactory
.createVerticalCS(derivedFrom(heightCS),
expectedAxis));
@@ -800,7 +806,7 @@ public class CoordinateOperationFinder extends
CoordinateOperationRegistry {
final Matrix matrix = Matrices.createZero(tgtDim + 1, srcDim + 1);
matrix.setElement(0, i, 1);
// Scale factor for height.
matrix.setElement(tgtDim, srcDim, 1);
// Always 1 for affine transform.
- step2 = createFromAffineTransform(AXIS_CHANGES, interpolationCRS,
heightCRS, matrix);
+ step2 = createFromAffineTransform(AXIS_CHANGES, interpolationCRS,
heightCRS, null, matrix);
return asList(concatenate(step1, step2, step3));
}
@@ -824,10 +830,10 @@ public class CoordinateOperationFinder extends
CoordinateOperationRegistry {
final VerticalCRS
targetCRS)
throws FactoryException
{
- final VerticalDatum sourceDatum = PseudoDatum.of(sourceCRS);
- final VerticalDatum targetDatum = PseudoDatum.of(targetCRS);
- if (!equalsIgnoreMetadata(sourceDatum, targetDatum)) {
- throw new OperationNotFoundException(notFoundMessage(sourceDatum,
targetDatum));
+ final Optional<VerticalDatum> commonDatum =
PseudoDatum.ofOperation(sourceCRS, targetCRS);
+ if (commonDatum.isEmpty()) {
+ throw new
OperationNotFoundException(notFoundMessage(PseudoDatum.getDatumOrEnsemble(sourceCRS),
+
PseudoDatum.getDatumOrEnsemble(targetCRS)));
}
final VerticalCS sourceCS = sourceCRS.getCoordinateSystem();
final VerticalCS targetCS = targetCRS.getCoordinateSystem();
@@ -837,7 +843,8 @@ public class CoordinateOperationFinder extends
CoordinateOperationRegistry {
} catch (IllegalArgumentException | IncommensurableException
exception) {
throw new OperationNotFoundException(notFoundMessage(sourceCRS,
targetCRS), exception);
}
- return asList(createFromAffineTransform(AXIS_CHANGES, sourceCRS,
targetCRS, matrix));
+ return asList(createFromAffineTransform(AXIS_CHANGES, sourceCRS,
targetCRS,
+
PseudoDatum.getEnsembleAccuracy(commonDatum.get()).orElse(null), matrix));
}
/**
@@ -849,6 +856,14 @@ public class CoordinateOperationFinder extends
CoordinateOperationRegistry {
* But a list is returned because the same step may be implemented by
different operation methods. Only one element
* in the returned list should be selected (usually the first one).</p>
*
+ * @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)}
+ * 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>.
+ *
* @param sourceCRS input coordinate reference system.
* @param targetCRS output coordinate reference system.
* @return a coordinate operation from {@code sourceCRS} to {@code
targetCRS}.
@@ -889,7 +904,7 @@ public class CoordinateOperationFinder extends
CoordinateOperationRegistry {
final int translationColumn = matrix.getNumCol() - 1; //
Paranoiac check: should always be 1.
final var translation = DoubleDouble.of(matrix.getNumber(0,
translationColumn), true);
matrix.setNumber(0, translationColumn, translation.add(epochShift));
- return asList(createFromAffineTransform(AXIS_CHANGES, sourceCRS,
targetCRS, matrix));
+ return asList(createFromAffineTransform(AXIS_CHANGES, sourceCRS,
targetCRS, null, matrix));
}
/**
@@ -955,7 +970,7 @@ public class CoordinateOperationFinder extends
CoordinateOperationRegistry {
CompoundCRS crs =
factorySIS.crsFactory.createCompoundCRS(derivedFrom(sourceCRS), stepComponents);
stepSourceCRS =
toAuthorityDefinition(CoordinateReferenceSystem.class, crs);
}
- operation = createFromAffineTransform(AXIS_CHANGES, sourceCRS,
stepSourceCRS, select);
+ operation = createFromAffineTransform(AXIS_CHANGES, sourceCRS,
stepSourceCRS, null, select);
}
/*
* For each sub-operation, create a PassThroughOperation for the
(stepSourceCRS → stepTargetCRS) operation.
@@ -1016,7 +1031,7 @@ public class CoordinateOperationFinder extends
CoordinateOperationRegistry {
final Matrix m = SubOperationInfo.createConstantOperation(infos,
stepComponents.length,
stepSourceCRS.getCoordinateSystem().getDimension(),
targetCRS.getCoordinateSystem().getDimension());
- operation = concatenate(operation,
createFromAffineTransform(CONSTANTS, stepSourceCRS, targetCRS, m));
+ operation = concatenate(operation,
createFromAffineTransform(CONSTANTS, stepSourceCRS, targetCRS, null, m));
}
return asList(operation);
}
@@ -1042,6 +1057,7 @@ public class CoordinateOperationFinder extends
CoordinateOperationRegistry {
* @param name the identifier for the operation to be created.
* @param sourceCRS the source coordinate reference system.
* @param targetCRS the target coordinate reference system.
+ * @param accuracy the positional accuracy, or {@code null} if
unspecified.
* @param matrix the matrix which describe an affine transform
operation.
* @return the conversion or transformation.
* @throws FactoryException if the operation cannot be created.
@@ -1049,11 +1065,16 @@ public class CoordinateOperationFinder extends
CoordinateOperationRegistry {
private CoordinateOperation createFromAffineTransform(final Identifier
name,
final
CoordinateReferenceSystem sourceCRS,
final
CoordinateReferenceSystem targetCRS,
+ final
PositionalAccuracy accuracy,
final Matrix
matrix)
throws FactoryException
{
final MathTransform transform =
factorySIS.getMathTransformFactory().createAffineTransform(matrix);
- return createFromMathTransform(properties(name), sourceCRS, targetCRS,
transform, null, null, null);
+ final Map<String, Object> properties = properties(name);
+ if (accuracy != null) {
+
properties.put(CoordinateOperation.COORDINATE_OPERATION_ACCURACY_KEY, accuracy);
+ }
+ return createFromMathTransform(properties, sourceCRS, targetCRS,
transform, null, null, null);
}
/**
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 c8bf82dacf..35a95e6a39 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
@@ -36,7 +36,6 @@ import org.opengis.util.NoSuchIdentifierException;
import org.opengis.metadata.Identifier;
import org.opengis.metadata.extent.Extent;
import org.opengis.metadata.citation.Citation;
-import org.opengis.metadata.quality.PositionalAccuracy;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.IdentifiedObject;
@@ -1249,9 +1248,9 @@ class CoordinateOperationRegistry {
final Map<String,Object> properties = new HashMap<>(4);
properties.put(CoordinateOperation.NAME_KEY, name);
if ((name == DATUM_SHIFT) || (name == ELLIPSOID_CHANGE)) {
-
properties.put(CoordinateOperation.COORDINATE_OPERATION_ACCURACY_KEY, new
PositionalAccuracy[] {
- (name == DATUM_SHIFT) ?
PositionalAccuracyConstant.DATUM_SHIFT_APPLIED
- :
PositionalAccuracyConstant.DATUM_SHIFT_OMITTED});
+
properties.put(CoordinateOperation.COORDINATE_OPERATION_ACCURACY_KEY,
+ (name == DATUM_SHIFT) ?
PositionalAccuracyConstant.DATUM_SHIFT_APPLIED
+ :
PositionalAccuracyConstant.DATUM_SHIFT_OMITTED);
}
return properties;
}
@@ -1330,7 +1329,7 @@ class CoordinateOperationRegistry {
* source and target CRS) are compatible with the specified ones, then
that operation is returned as-is.
*/
if (transform instanceof CoordinateOperation) {
- final CoordinateOperation operation = (CoordinateOperation)
transform;
+ final var operation = (CoordinateOperation) transform;
if (Objects.equals(operation.getSourceCRS(), sourceCRS) &&
Objects.equals(operation.getTargetCRS(), targetCRS) &&
Objects.equals(operation.getMathTransform(), transform) &&
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 40c87d28a8..9a09d6cb98 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
@@ -19,6 +19,8 @@ package org.apache.sis.referencing.operation;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
+import java.util.ArrayDeque;
+import java.util.Iterator;
import java.util.Objects;
import org.opengis.util.FactoryException;
import org.opengis.parameter.ParameterValueGroup;
@@ -45,7 +47,6 @@ import org.apache.sis.referencing.privy.CoordinateOperations;
import org.apache.sis.referencing.privy.ReferencingFactoryContainer;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.Classes;
-import org.apache.sis.util.Utilities;
import org.apache.sis.util.Debug;
import org.apache.sis.util.privy.Constants;
import org.apache.sis.util.collection.WeakHashSet;
@@ -407,22 +408,16 @@ public class DefaultCoordinateOperationFactory extends
AbstractFactory implement
private static boolean isConversion(final CoordinateReferenceSystem
sourceCRS,
final CoordinateReferenceSystem
targetCRS)
{
- List<SingleCRS> components = CRS.getSingleComponents(sourceCRS);
- int n = components.size(); // Number of remaining
datum from sourceCRS to verify.
- final IdentifiedObject[] datum = new IdentifiedObject[n];
- for (int i=0; i<n; i++) {
- datum[i] = PseudoDatum.getDatumOrEnsemble(components.get(i));
- }
- components = CRS.getSingleComponents(targetCRS);
-next: for (int i=components.size(); --i >= 0;) {
- final IdentifiedObject d =
PseudoDatum.getDatumOrEnsemble(components.get(i));
- for (int j=n; --j >= 0;) {
- if (Utilities.equalsIgnoreMetadata(d, datum[j])) {
- System.arraycopy(datum, j+1, datum, j, --n - j); //
Remove the datum from the list.
+ final var components = new
ArrayDeque<>(CRS.getSingleComponents(sourceCRS));
+next: for (SingleCRS component : CRS.getSingleComponents(targetCRS)) {
+ final Iterator<SingleCRS> it = components.iterator();
+ while (it.hasNext()) {
+ if (PseudoDatum.getDatumOrEnsemble(component,
it.next()).isPresent()) {
+ it.remove();
continue next;
}
}
- return false; // Datum from
`targetCRS` not found in `sourceCRS`.
+ return false; // Datum from `targetCRS` not found in
`sourceCRS`.
}
return true;
}