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 56da92e4a7 Allow `DefaultConcatenatedOperation` to contain steps in
reverse order. https://issues.apache.org/jira/browse/SIS-594
56da92e4a7 is described below
commit 56da92e4a7838a2a4c83d708b9d1e27465a9153b
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Tue Jan 9 19:41:37 2024 +0100
Allow `DefaultConcatenatedOperation` to contain steps in reverse order.
https://issues.apache.org/jira/browse/SIS-594
---
.../apache/sis/referencing/IdentifiedObjects.java | 16 ++-
.../apache/sis/referencing/internal/Resources.java | 6 +
.../sis/referencing/internal/Resources.properties | 3 +-
.../referencing/internal/Resources_fr.properties | 3 +-
.../apache/sis/referencing/operation/CRSPair.java | 17 ++-
.../operation/CoordinateOperationFinder.java | 18 +--
.../operation/CoordinateOperationRegistry.java | 149 +++++++++++++++------
.../operation/DefaultConcatenatedOperation.java | 146 +++++++++++++++-----
.../DefaultCoordinateOperationFactory.java | 30 ++---
.../operation/InverseOperationMethod.java | 8 +-
.../src/org.apache.sis.util/main/module-info.java | 1 +
.../main/org/apache/sis/pending/jdk/JDK21.java | 31 +++++
12 files changed, 312 insertions(+), 116 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
index 22429fc32c..fdb2b1c0d6 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
@@ -32,15 +32,18 @@ import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.crs.CompoundCRS;
import org.opengis.referencing.operation.CoordinateOperation;
import org.opengis.referencing.operation.ConcatenatedOperation;
+import static org.apache.sis.util.Utilities.equalsIgnoreMetadata;
import org.apache.sis.util.Static;
import org.apache.sis.util.CharSequences;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.OptionalCandidate;
import org.apache.sis.util.logging.Logging;
-import org.apache.sis.xml.IdentifierSpace;
import org.apache.sis.util.internal.Strings;
import org.apache.sis.util.internal.Constants;
import org.apache.sis.util.internal.DefinitionURI;
+import static org.apache.sis.util.internal.CollectionsExt.nonNull;
+import org.apache.sis.pending.jdk.JDK21;
+import org.apache.sis.xml.IdentifierSpace;
import org.apache.sis.metadata.internal.Identifiers;
import org.apache.sis.metadata.internal.NameMeaning;
import org.apache.sis.metadata.internal.NameToIdentifier;
@@ -48,7 +51,6 @@ import org.apache.sis.metadata.iso.citation.Citations;
import org.apache.sis.referencing.factory.IdentifiedObjectFinder;
import org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
import org.apache.sis.referencing.factory.NoSuchAuthorityFactoryException;
-import static org.apache.sis.util.internal.CollectionsExt.nonNull;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.referencing.ObjectDomain;
@@ -472,7 +474,15 @@ public final class IdentifiedObjects extends Static {
if (object instanceof CompoundCRS) {
components = CRS.getSingleComponents((CompoundCRS) object);
} else if (object instanceof ConcatenatedOperation) {
- components = ((ConcatenatedOperation) object).getOperations();
+ final var cop = (ConcatenatedOperation) object;
+ final List<? extends CoordinateOperation> steps =
cop.getOperations();
+ if (equalsIgnoreMetadata(cop.getSourceCRS(),
JDK21.getFirst(steps).getSourceCRS()) &&
+ equalsIgnoreMetadata(cop.getTargetCRS(), JDK21.getLast
(steps).getTargetCRS()))
+ {
+ components = steps;
+ } else {
+ return null;
+ }
} else {
return 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 af196a96b9..a40460efdf 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
@@ -350,6 +350,12 @@ public class Resources extends IndexedResourceBundle {
*/
public static final short MismatchedPrimeMeridian_2 = 36;
+ /**
+ * Invalid coordinate operation step {0}, because the reference system
“{1}” cannot be followed
+ * by “{2}”.
+ */
+ public static final short MismatchedSourceTargetCRS_3 = 100;
+
/**
* Despite its name, this parameter is effectively “{0}”.
*/
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 a04045bc48..18b3aba29d 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
@@ -26,6 +26,7 @@ ConformanceMeansDatumShift = This result indicates if
a datum shift metho
ConstantProjParameterValue_1 = This parameter is shown for completeness,
but should never have a value different than {0} for this projection.
DeprecatedCode_3 = Code \u201c{0}\u201d is deprecated and
replaced by code {1}. Reason is: {2}
ElementsOmitted_1 = \u2026 {0} elements omitted \u2026
+FallbackAuthorityNotice = Definitions from public sources. When a
definition corresponds to an EPSG object (ignoring metadata), the EPSG code is
provided as a reference where to find the complete definition.
FallbackDefaultFactoryVersion_2 = There is no local registry for version {1}
of \u201c{0}\u201d authority. Fallback on default version for objects creation.
GeodeticDataBase_4 = {0} geodetic dataset version {1} on
\u201c{2}\u201d version {3}.
IgnoredServiceProvider_3 = More than one service provider of type
\u2018{0}\u2019 are declared for \u201c{1}\u201d. Only the first provider (an
instance of \u2018{2}\u2019) will be used.
@@ -76,7 +77,6 @@ DuplicatedSpatialComponents_1 = Compound coordinate
reference systems cannot
EllipsoidalHeightNotAllowed_1 = Compound coordinate reference systems
should not contain ellipsoidal height. Use a three-dimensional
{0,choice,0#geographic|1#projected} system instead.
FileNotFound_2 = Cannot find {0} file named \u201c{1}\u201d.
FileNotReadable_2 = Cannot parse \u201c{1}\u201d as a file in
the {0} format.
-FallbackAuthorityNotice = Definitions from public sources. When a
definition corresponds to an EPSG object (ignoring metadata), the EPSG code is
provided as a reference where to find the complete definition.
IllegalAxisDirection_2 = Coordinate system of class \u2018{0}\u2019
cannot have axis in the {1} direction.
IllegalOperationDimension_3 = Dimensions of \u201c{0}\u201d operation
cannot be ({1} \u2192 {2}).
IllegalOperationForValueClass_1 = This operation cannot be applied to values
of class \u2018{0}\u2019.
@@ -89,6 +89,7 @@ IncompatibleDatum_2 = Datum of \u201c{1}\u201d
shall be \u201c{0}\
LatitudesAreOpposite_2 = Latitudes {0} and {1} are opposite.
MismatchedParameterDescriptor_1 = Mismatched descriptor for \u201c{0}\u201d
parameter.
MismatchedPrimeMeridian_2 = Expected the \u201c{0}\u201d prime
meridian but found \u201c{1}\u201d.
+MismatchedSourceTargetCRS_3 = Invalid coordinate operation step {0},
because the reference system \u201c{1}\u201d cannot be followed by
\u201c{2}\u201d.
MissingAuthority_1 = No authority was specified for code
\u201c{0}\u201d. The expected syntax is \u201cAUTHORITY:CODE\u201d.
MissingAuthorityCode_1 = Missing or empty \u201cID[\u2026]\u201d
element for \u201c{0}\u201d.
MissingInterpolationOrdinates = Not enough dimension in
\u2018MathTransform\u2019 input or output coordinates for the interpolation
points.
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 92be70805c..98e953dc70 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
@@ -31,6 +31,7 @@ ConformanceMeansDatumShift = Ce r\u00e9sultat indique
si un changement de
ConstantProjParameterValue_1 = Ce param\u00e8tre est montr\u00e9 pour
\u00eatre plus complet, mais sa valeur ne devrait jamais \u00eatre
diff\u00e9rente de {0} pour cette projection.
DeprecatedCode_3 = Le code \u00ab\u202f{0}\u202f\u00bb est
d\u00e9pr\u00e9ci\u00e9 et remplac\u00e9 par le code {1}. La raison est\u00a0:
{2}
ElementsOmitted_1 = \u2026 {0} \u00e9l\u00e9ments omis \u2026
+FallbackAuthorityNotice = D\u00e9finitions de sources publiques.
Quand une d\u00e9finition correspond \u00e0 un objet EPSG (en ignorant les
m\u00e9ta-donn\u00e9es), le code EPSG est fournit comme r\u00e9f\u00e9rence
o\u00f9 trouver une d\u00e9finition compl\u00e8te.
FallbackDefaultFactoryVersion_2 = Il n\u2019y a pas de registre local pour
la version {1} de l\u2019autorit\u00e9 \u00ab\u202f{0}\u202f\u00bb. Les objets
seront cr\u00e9\u00e9s avec la version par d\u00e9faut.
GeodeticDataBase_4 = Base de donn\u00e9es g\u00e9od\u00e9sique
{0} version {1} sur \u00ab\u202f{2}\u202f\u00bb version {3}.
IgnoredServiceProvider_3 = Plusieurs fournisseurs de service de type
\u2018{0}\u2019 sont d\u00e9clar\u00e9s pour \u00ab\u202f{1}\u202f\u00bb. Seul
le premier fournisseur (une instance de \u2018{2}\u2019) sera utilis\u00e9.
@@ -81,7 +82,6 @@ DuplicatedSpatialComponents_1 = Un syst\u00e8me de
r\u00e9f\u00e9rence des c
EllipsoidalHeightNotAllowed_1 = Un syst\u00e8me de r\u00e9f\u00e9rence des
coordonn\u00e9es ne devrait pas contenir une hauteur ellipso\u00efdale.
Utilisez plut\u00f4t un syst\u00e8me
{0,choice,0#g\u00e9ographique|1#projet\u00e9} \u00e0 trois dimensions.
FileNotFound_2 = Ne peut pas trouver le fichier {0}
nomm\u00e9 \u00ab\u202f{1}\u202f\u00bb.
FileNotReadable_2 = Ne peut pas lire
\u00ab\u202f{1}\u202f\u00bb comme un fichier au format {0}.
-FallbackAuthorityNotice = D\u00e9finitions de sources publiques.
Quand une d\u00e9finition correspond \u00e0 un objet EPSG (en ignorant les
m\u00e9ta-donn\u00e9es), le code EPSG est fournit comme r\u00e9f\u00e9rence
o\u00f9 trouver une d\u00e9finition compl\u00e8te.
IllegalAxisDirection_2 = Les syst\u00e8mes de coordonn\u00e9es de
classe \u2018{0}\u2019 ne peuvent pas avoir d\u2019axe dans la direction
\u00ab\u202f{1}\u202f\u00bb.
IllegalOperationDimension_3 = Les dimensions de l\u2019op\u00e9ration
\u00ab\u202f{0}\u202f\u00bb ne peuvent pas \u00eatre ({1} \u2192 {2}).
IllegalOperationForValueClass_1 = Cette op\u00e9ration ne peut pas
s\u2019appliquer aux valeurs de classe \u2018{0}\u2019.
@@ -94,6 +94,7 @@ IncompatibleDatum_2 = Le r\u00e9f\u00e9rentiel
de \u00ab\u202f{1}\
LatitudesAreOpposite_2 = Les latitudes {0} et {1} sont
oppos\u00e9es.
MismatchedParameterDescriptor_1 = Le descripteur du param\u00e8tre
\u00ab\u202f{0}\u202f\u00bb ne correspond pas.
MismatchedPrimeMeridian_2 = Le m\u00e9ridien d\u2019origine
\u00ab\u202f{0}\u202f\u00bb \u00e9tait attendu, mais
\u00ab\u202f{1}\u202f\u00bb a \u00e9t\u00e9 trouv\u00e9.
+MismatchedSourceTargetCRS_3 = L\u2019\u00e9tape {0} de la transformation
de coordonn\u00e9es est invalide, parce que le syst\u00e8me de
r\u00e9f\u00e9rence \u00ab\u202f{1}\u202f\u00bb ne peut pas \u00eatre suivit de
\u00ab\u202f{2}\u202f\u00bb.
MissingAuthority_1 = Aucune autorit\u00e9 n\u2019a
\u00e9t\u00e9 sp\u00e9cifi\u00e9e pour le code \u00ab\u202f{0}\u202f\u00bb. Le
format attendu est \u00ab\u202fAUTORIT\u00c9:CODE\u202f\u00bb.
MissingAuthorityCode_1 = L\u2019\u00e9l\u00e9ment
\u00ab\u202fID[\u2026]\u202f\u00bb est manquant ou vide pour
\u00ab\u202f{0}\u202f\u00bb.
MissingInterpolationOrdinates = La dimension des coordonn\u00e9es en
entr\u00e9 ou en sortie du \u2018MathTransform\u2019 n\u2019est pas suffisante
pour contenir les points d\u2019interpolation.
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CRSPair.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CRSPair.java
index 7f83c44b15..63c872587f 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CRSPair.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CRSPair.java
@@ -16,6 +16,7 @@
*/
package org.apache.sis.referencing.operation;
+import java.util.Locale;
import java.util.Objects;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.cs.EllipsoidalCS;
@@ -77,11 +78,15 @@ final class CRSPair {
}
/**
- * Returns the name of the GeoAPI interface implemented by the specified
object. In the GeographicCRS
- * or EllipsoidalCS cases, the trailing CRS or CS suffix is replaced by
the number of dimensions
- * (e.g. "Geographic3D").
+ * Returns the name of the GeoAPI interface implemented by the specified
object, followed by the object name.
+ * In the GeographicCRS or EllipsoidalCS cases, the trailing CRS or CS
suffix is replaced by the number of
+ * dimensions (e.g. "Geographic3D").
+ *
+ * @param object the object for which to get a label.
+ * @param locale the locale for the object name, or {@code null}.
+ * @return a label for the specified object.
*/
- static String label(final IdentifiedObject object) {
+ static String label(final IdentifiedObject object, final Locale locale) {
if (object == null) {
return null;
}
@@ -98,7 +103,7 @@ final class CRSPair {
label = sb.append(((CoordinateSystem)
cs).getDimension()).append('D').toString();
}
}
- String name = IdentifiedObjects.getDisplayName(object, null);
+ String name = IdentifiedObjects.getDisplayName(object, locale);
if (name != null) {
int i = 30; // Arbitrary
length threshold.
if (name.length() >= i) {
@@ -119,6 +124,6 @@ final class CRSPair {
*/
@Override
public String toString() {
- return label(sourceCRS) + " ⟶ " + label(targetCRS);
+ return label(sourceCRS, null) + " ⟶ " + label(targetCRS, 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 406ccd4db0..53d1602d2b 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
@@ -114,7 +114,7 @@ import static
org.apache.sis.util.Utilities.equalsIgnoreMetadata;
* </ul>
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.4
+ * @version 1.5
*
* @see
DefaultCoordinateOperationFactory#createOperation(CoordinateReferenceSystem,
CoordinateReferenceSystem, CoordinateOperationContext)
*
@@ -238,7 +238,8 @@ public class CoordinateOperationFinder extends
CoordinateOperationRegistry {
CoordinateSystems.swapAndScaleAxes(sourceCRS.getCoordinateSystem(),
targetCRS.getCoordinateSystem())));
} catch (IllegalArgumentException | IncommensurableException e) {
- throw new
FactoryException(Resources.format(Resources.Keys.CanNotInstantiateGeodeticObject_1,
new CRSPair(sourceCRS, targetCRS)), e);
+ final CRSPair key = new CRSPair(sourceCRS, targetCRS);
+ throw new
FactoryException(resources().getString(Resources.Keys.CanNotInstantiateGeodeticObject_1,
key), e);
}
/*
* If this method is invoked recursively, verify if the requested
operation is already in the cache.
@@ -253,7 +254,7 @@ public class CoordinateOperationFinder extends
CoordinateOperationRegistry {
if (op != null) return asList(op); // Must be a modifiable
list as per this method contract.
}
if (previousSearches.put(key, Boolean.TRUE) != null) {
- throw new
FactoryException(Resources.format(Resources.Keys.RecursiveCreateCallForCode_2,
CoordinateOperation.class, key));
+ throw new
FactoryException(resources().getString(Resources.Keys.RecursiveCreateCallForCode_2,
CoordinateOperation.class, key));
}
/*
* If the user did not specified an area of interest, use the domain
of validity of the CRS.
@@ -1237,8 +1238,7 @@ public class CoordinateOperationFinder extends
CoordinateOperationRegistry {
final Map<String,Object> properties = new HashMap<>(4);
properties.put(IdentifiedObject.NAME_KEY, newID);
- properties.put(IdentifiedObject.REMARKS_KEY,
Vocabulary.formatInternational(
- Vocabulary.Keys.DerivedFrom_1,
CRSPair.label(object)));
+ properties.put(IdentifiedObject.REMARKS_KEY,
Vocabulary.formatInternational(Vocabulary.Keys.DerivedFrom_1, label(object)));
return properties;
}
@@ -1268,8 +1268,8 @@ public class CoordinateOperationFinder extends
CoordinateOperationRegistry {
* @param target the target CRS.
* @return a default error message.
*/
- private static String notFoundMessage(final IdentifiedObject source, final
IdentifiedObject target) {
- return Resources.format(Resources.Keys.CoordinateOperationNotFound_2,
CRSPair.label(source), CRSPair.label(target));
+ private String notFoundMessage(final IdentifiedObject source, final
IdentifiedObject target) {
+ return
resources().getString(Resources.Keys.CoordinateOperationNotFound_2,
label(source), label(target));
}
/**
@@ -1279,7 +1279,7 @@ public class CoordinateOperationFinder extends
CoordinateOperationRegistry {
* @param crs the CRS having a conversion that cannot be inverted.
* @return a default error message.
*/
- private static String canNotInvert(final GeneralDerivedCRS crs) {
- return Resources.format(Resources.Keys.NonInvertibleOperation_1,
crs.getConversionFromBase().getName().getCode());
+ private String canNotInvert(final GeneralDerivedCRS crs) {
+ return resources().getString(Resources.Keys.NonInvertibleOperation_1,
label(crs.getConversionFromBase()));
}
}
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 c0ffadcae9..568569454f 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
@@ -24,7 +24,9 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
+import java.util.Arrays;
import java.util.Objects;
+import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.function.Predicate;
@@ -58,8 +60,6 @@ import
org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
import org.apache.sis.referencing.factory.MissingFactoryResourceException;
import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
import org.apache.sis.referencing.factory.NoSuchAuthorityFactoryException;
-import org.apache.sis.metadata.iso.extent.Extents;
-import org.apache.sis.metadata.iso.citation.Citations;
import org.apache.sis.referencing.util.CoordinateOperations;
import org.apache.sis.referencing.util.EllipsoidalHeightCombiner;
import org.apache.sis.referencing.util.PositionalAccuracyConstant;
@@ -68,7 +68,10 @@ import
org.apache.sis.referencing.internal.DeferredCoordinateOperation;
import org.apache.sis.referencing.internal.Resources;
import org.apache.sis.referencing.operation.provider.Affine;
import org.apache.sis.referencing.operation.provider.AbstractProvider;
+import org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.metadata.iso.extent.Extents;
import org.apache.sis.system.Semaphores;
+import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.Utilities;
@@ -236,6 +239,13 @@ class CoordinateOperationRegistry {
*/
private final Map<CoordinateReferenceSystem, List<String>> authorityCodes;
+ /**
+ * The locale for error messages.
+ *
+ * @todo Add a setter method, or make configurable in some way.
+ */
+ private Locale locale;
+
/**
* Creates a new instance for the given factory and context.
*
@@ -506,8 +516,9 @@ class CoordinateOperationRegistry {
return operations;
}
}
- } catch (IllegalArgumentException | IncommensurableException e) {
- String message =
Resources.format(Resources.Keys.CanNotInstantiateGeodeticObject_1, new
CRSPair(sourceCRS, targetCRS));
+ } catch (IllegalArgumentException | IncommensurableException |
NoninvertibleTransformException e) {
+ CRSPair key = new CRSPair(sourceCRS, targetCRS);
+ String message =
resources().getString(Resources.Keys.CanNotInstantiateGeodeticObject_1, key);
String details = e.getLocalizedMessage();
if (details != null) {
message = message + ' ' + details;
@@ -530,11 +541,12 @@ class CoordinateOperationRegistry {
* or {@code null} if no such operation is explicitly defined in
the underlying database.
* @throws IllegalArgumentException if the coordinate systems are not of
the same type or axes do not match.
* @throws IncommensurableException if the units are not compatible or a
unit conversion is non-linear.
+ * @throws NoninvertibleTransformException if a step needs to be inverted
but is not invertible.
* @throws FactoryException if an error occurred while creating the
operation.
*/
private List<CoordinateOperation> search(final CoordinateReferenceSystem
sourceCRS,
final CoordinateReferenceSystem
targetCRS)
- throws IllegalArgumentException, IncommensurableException,
FactoryException
+ throws IncommensurableException, NoninvertibleTransformException,
FactoryException
{
final List<String> sources = findCode(sourceCRS); if
(sources.isEmpty()) return null;
final List<String> targets = findCode(targetCRS); if
(targets.isEmpty()) return null;
@@ -645,14 +657,14 @@ class CoordinateOperationRegistry {
operation = fromDefiningConversion((SingleOperation)
operation,
foundDirectOperations ? sourceCRS :
targetCRS,
foundDirectOperations ? targetCRS :
sourceCRS);
- if (operation == null) {
- it.remove();
- continue;
- }
}
if (!foundDirectOperations) {
operation = inverse(operation);
}
+ if (operation == null) {
+ it.remove();
+ continue;
+ }
} catch (NoninvertibleTransformException |
MissingFactoryResourceException e) {
/*
* If we failed to get the real CoordinateOperation instance,
remove it from
@@ -691,15 +703,13 @@ class CoordinateOperationRegistry {
/**
* Creates the inverse of the given single operation.
- * If this operation succeed, then the returned coordinate operations has
the following properties:
+ * If this operation succeed, then the returned coordinate operation has
the following properties:
*
* <ul>
* <li>Its {@code sourceCRS} is the {@code targetCRS} of the given
operation.</li>
* <li>Its {@code targetCRS} is the {@code sourceCRS} of the given
operation.</li>
* <li>Its {@code interpolationCRS} is {@code null}.</li>
- * <li>Its {@code MathTransform} is the
- * {@linkplain
org.apache.sis.referencing.operation.transform.AbstractMathTransform#inverse()
inverse}
- * of the {@code MathTransform} of this operation.</li>
+ * <li>Its {@code MathTransform} is the inverse of the {@code
MathTransform} of the given operation.</li>
* <li>Its domain of validity and accuracy is the same.</li>
* </ul>
*
@@ -749,20 +759,53 @@ class CoordinateOperationRegistry {
return inverse((SingleOperation) operation);
}
if (operation instanceof ConcatenatedOperation) {
- final List<? extends CoordinateOperation> operations =
((ConcatenatedOperation) operation).getOperations();
- final CoordinateOperation[] inverted = new
CoordinateOperation[operations.size()];
- for (int i=0; i<inverted.length;) {
- final CoordinateOperation op = inverse(operations.get(i));
- if (op == null) {
- return null;
- }
- inverted[inverted.length - ++i] = op;
+ final CoordinateOperation[] inverted =
getSteps((ConcatenatedOperation) operation, true);
+ ArraysExt.reverse(inverted);
+ final Map<String,Object> properties =
properties(INVERSE_OPERATION);
+ final MathTransform transform = operation.getMathTransform();
+ if (transform != null) {
+ properties.put(DefaultConcatenatedOperation.TRANSFORM_KEY,
transform.inverse());
}
- return
factory.createConcatenatedOperation(properties(INVERSE_OPERATION), inverted);
+ return factory.createConcatenatedOperation(properties, inverted);
}
return null;
}
+ /**
+ * Returns all steps of the given concatenated operation, making sure that
they are in the specified direction.
+ * This method returns an array where the source CRS of the first step is
the {@code operation} source CRS, and
+ * where the target CRS of each step is the source CRS of the next step.
If needed, some steps may be inverted
+ * in order to fulfill that requirement.
+ *
+ * <p>If {@code inverse} if {@code true}, then this method inverses all
steps. Note however that the element
+ * order in the returned array is not inverted (this is left to the
caller).</p>
+ *
+ * @param operation the operation for which to get the steps.
+ * @return all steps of the given concatenated operation.
+ * @throws NoninvertibleTransformException if a step needs to be inverted
but is not invertible.
+ * @throws FactoryException if the operation creation failed for another
reason (e.g., inconsistency found).
+ */
+ private CoordinateOperation[] getSteps(final ConcatenatedOperation
operation, final boolean inverse)
+ throws NoninvertibleTransformException, FactoryException
+ {
+ final var steps =
operation.getOperations().toArray(CoordinateOperation[]::new);
+ CoordinateReferenceSystem previous = operation.getSourceCRS();
+ for (int i=0; i<steps.length; i++) {
+ final CoordinateOperation step = steps[i];
+ final CoordinateReferenceSystem source = step.getSourceCRS();
+ final CoordinateReferenceSystem target = step.getTargetCRS();
+ final boolean r =
DefaultConcatenatedOperation.verifyStepChaining(null, i, previous, source,
target);
+ if (r != inverse) {
+ if ((steps[i] = inverse(step)) == null) {
+ throw new
NoninvertibleTransformException(resources().getString(
+ Resources.Keys.NonInvertibleOperation_1,
label(step)));
+ }
+ }
+ previous = r ? source : target;
+ }
+ return steps;
+ }
+
/**
* Completes (if necessary) the given coordinate operation for making sure
that the source CRS
* is the given one and the target CRS is the given one. In principle,
the given CRS shall be
@@ -775,12 +818,13 @@ class CoordinateOperationRegistry {
* @return a coordinate operation for the given source and target CRS.
* @throws IllegalArgumentException if the coordinate systems are not of
the same type or axes do not match.
* @throws IncommensurableException if the units are not compatible or a
unit conversion is non-linear.
+ * @throws NoninvertibleTransformException if a step needs to be inverted
but is not invertible.
* @throws FactoryException if the operation cannot be constructed.
*/
private CoordinateOperation complete(final CoordinateOperation
operation,
final CoordinateReferenceSystem
sourceCRS,
final CoordinateReferenceSystem
targetCRS)
- throws IllegalArgumentException, IncommensurableException,
FactoryException
+ throws IncommensurableException, NoninvertibleTransformException,
FactoryException
{
CoordinateReferenceSystem source = operation.getSourceCRS();
CoordinateReferenceSystem target = operation.getTargetCRS();
@@ -807,7 +851,7 @@ class CoordinateOperationRegistry {
private static MathTransform swapAndScaleAxes(final
CoordinateReferenceSystem sourceCRS,
final
CoordinateReferenceSystem targetCRS,
final MathTransformFactory
mtFactory)
- throws IllegalArgumentException, IncommensurableException,
FactoryException
+ throws IncommensurableException, FactoryException
{
/*
* Assertion: source and target CRS must be equal, ignoring change in
axis order or units.
@@ -837,6 +881,7 @@ class CoordinateOperationRegistry {
* @param mtFactory the math transform factory to use.
* @return a new operation, or {@code operation} if {@code prepend} and
{@code append} were nulls or identity transforms.
* @throws IllegalArgumentException if the operation method cannot have
the desired number of dimensions.
+ * @throws NoninvertibleTransformException if a step needs to be inverted
but is not invertible.
* @throws FactoryException if the operation cannot be constructed.
*/
private CoordinateOperation transform(final CoordinateReferenceSystem
sourceCRS,
@@ -845,7 +890,7 @@ class CoordinateOperationRegistry {
final MathTransform
append,
final CoordinateReferenceSystem
targetCRS,
final MathTransformFactory
mtFactory)
- throws IllegalArgumentException, FactoryException
+ throws NoninvertibleTransformException, FactoryException
{
if ((prepend == null || prepend.isIdentity()) && (append == null ||
append.isIdentity())) {
return operation;
@@ -857,18 +902,17 @@ class CoordinateOperationRegistry {
* with that). Instead, prepend to the first single operation and
append to the last single operation.
*/
if (operation instanceof ConcatenatedOperation) {
- final List<? extends CoordinateOperation> c =
((ConcatenatedOperation) operation).getOperations();
- final CoordinateOperation[] op =
c.toArray(CoordinateOperation[]::new);
- switch (op.length) {
+ final CoordinateOperation[] steps =
getSteps((ConcatenatedOperation) operation, false);
+ switch (steps.length) {
case 0: break; // Illegal, but we
are paranoiac.
- case 1: operation = op[0]; break; // Useless
ConcatenatedOperation.
+ case 1: operation = steps[0]; break; // Useless
ConcatenatedOperation.
default: {
- final int n = op.length - 1;
- final CoordinateOperation first = op[0];
- final CoordinateOperation last = op[n];
- op[0] = transform(sourceCRS, prepend, first, null,
first.getTargetCRS(), mtFactory);
- op[n] = transform(last.getSourceCRS(), null, last, append,
targetCRS, mtFactory);
- return
factory.createConcatenatedOperation(derivedFrom(operation), op);
+ final int n = steps.length - 1;
+ final CoordinateOperation first = steps[0];
+ final CoordinateOperation last = steps[n];
+ steps[0] = transform(sourceCRS, prepend, first, null,
first.getTargetCRS(), mtFactory);
+ steps[n] = transform(last.getSourceCRS(), null, last,
append, targetCRS, mtFactory);
+ return
factory.createConcatenatedOperation(derivedFrom(operation), steps);
}
}
}
@@ -901,7 +945,7 @@ class CoordinateOperationRegistry {
CoordinateReferenceSystem
targetCRS,
final MathTransform
transform,
OperationMethod
method)
- throws IllegalArgumentException, FactoryException
+ throws FactoryException
{
/*
* If the user-provided CRS are approximately equal to the coordinate
operation CRS, keep the latter.
@@ -970,7 +1014,7 @@ class CoordinateOperationRegistry {
}
} else {
// Should never happen because parameters are mandatory, but let
be safe.
- log(Resources.forLocale(null).getLogRecord(Level.WARNING,
Resources.Keys.MissingParameterValues_1,
+ log(resources().getLogRecord(Level.WARNING,
Resources.Keys.MissingParameterValues_1,
IdentifiedObjects.getIdentifierOrName(operation)), null);
}
return null;
@@ -999,17 +1043,18 @@ class CoordinateOperationRegistry {
* @return a coordinate operation with the source and/or target
coordinates made 3D,
* or {@code null} if this method does not know how to create the
operation.
* @throws IllegalArgumentException if the operation method cannot have
the desired number of dimensions.
+ * @throws NoninvertibleTransformException if a step needs to be inverted
but is not invertible.
* @throws FactoryException if an error occurred while creating the
coordinate operation.
*/
private CoordinateOperation propagateVertical(final
CoordinateReferenceSystem sourceCRS,
final
CoordinateReferenceSystem targetCRS,
final CoordinateOperation
operation,
final Decomposition
decompose)
- throws IllegalArgumentException, FactoryException
+ throws NoninvertibleTransformException, FactoryException
{
final List<CoordinateOperation> operations = new ArrayList<>();
if (operation instanceof ConcatenatedOperation) {
- operations.addAll(((ConcatenatedOperation)
operation).getOperations());
+ operations.addAll(Arrays.asList(getSteps((ConcatenatedOperation)
operation, false)));
} else {
operations.add(operation);
}
@@ -1022,7 +1067,7 @@ class CoordinateOperationRegistry {
case 0: return null;
case 1: return operations.get(0);
default: return
factory.createConcatenatedOperation(derivedFrom(operation),
- operations.toArray(CoordinateOperation[]::new));
+
operations.toArray(CoordinateOperation[]::new));
}
}
@@ -1042,14 +1087,14 @@ class CoordinateOperationRegistry {
final CoordinateReferenceSystem target3D,
final ListIterator<CoordinateOperation>
operations,
final boolean forward)
- throws IllegalArgumentException, FactoryException
+ throws FactoryException
{
while (forward ? operations.hasNext() : operations.hasPrevious()) {
final CoordinateOperation op = forward ? operations.next() :
operations.previous();
/*
* We will accept to increase the number of dimensions only for
operations between geographic CRS.
- * We do not increase the number of dimensions for operations
between other kind of CRS because we
- * would not know which value to give to the new dimension.
+ * We do not increase the number of dimensions for operations
between other kinds of CRS because
+ * we would not know which value to give to the new dimension.
*/
CoordinateReferenceSystem sourceCRS, targetCRS;
if ( !((sourceCRS = op.getSourceCRS()) instanceof GeodeticCRS
@@ -1175,7 +1220,7 @@ class CoordinateOperationRegistry {
*
* <h4>Note</h4>
* In the datum shift case, an operation version is mandatory but unknown
at this time.
- * However, we noticed that the EPSG database do not always defines a
version neither.
+ * However, we noticed that the EPSG database does not always define a
version neither.
* Consequently, the Apache SIS implementation relaxes the rule requiring
an operation
* version and we do not try to provide this information here for now.
*
@@ -1296,6 +1341,22 @@ class CoordinateOperationRegistry {
return factorySIS.createSingleOperation(properties, sourceCRS,
targetCRS, null, method, transform);
}
+ /**
+ * {@return the localized resources for error messages}.
+ */
+ final Resources resources() {
+ return Resources.forLocale(locale);
+ }
+
+ /**
+ * {@return a label for identifying the given object in error message}.
+ *
+ * @param object the object of identify.
+ */
+ final String label(final IdentifiedObject object) {
+ return CRSPair.label(object, locale);
+ }
+
/**
* Logs an unexpected but ignorable exception. This method pretends that
the logging
* come from {@link CoordinateOperationFinder} since this is the public
API which
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 cad53a37b7..4b434f18f5 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
@@ -20,11 +20,11 @@ import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import java.util.Objects;
+import java.util.Locale;
import jakarta.xml.bind.annotation.XmlType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import org.opengis.util.FactoryException;
-import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.CoordinateOperation;
import org.opengis.referencing.operation.ConcatenatedOperation;
@@ -32,15 +32,19 @@ import org.opengis.referencing.operation.Conversion;
import org.opengis.referencing.operation.Transformation;
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.operation.transform.DefaultMathTransformFactory;
+import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
import org.apache.sis.referencing.util.PositionalAccuracyConstant;
import org.apache.sis.referencing.internal.Resources;
+import org.apache.sis.util.Utilities;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.collection.Containers;
import org.apache.sis.util.internal.UnmodifiableArrayList;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.io.wkt.Formatter;
-import static org.apache.sis.util.Utilities.deepEquals;
/**
@@ -60,6 +64,23 @@ final class DefaultConcatenatedOperation extends
AbstractCoordinateOperation imp
*/
private static final long serialVersionUID = 4199619838029045700L;
+ /**
+ * Optional key for specifying the {@link #transform} value.
+ * This property should generally not be specified, as the constructor
builds the transform itself.
+ * It may be useful if the resulting transform is already known and we
want to avoid the construction cost.
+ */
+ public static final String TRANSFORM_KEY = "transform";
+
+ /**
+ * The comparison modes to use for determining if two CRS are equal, in
preference order.
+ * This is used for determining if an operation need to be inverted.
+ */
+ private static final ComparisonMode[] CRS_ORDER_CRITERIA = {
+ ComparisonMode.BY_CONTRACT,
+ ComparisonMode.IGNORE_METADATA,
+ ComparisonMode.APPROXIMATE
+ };
+
/**
* The sequence of operations.
*
@@ -84,6 +105,12 @@ final class DefaultConcatenatedOperation extends
AbstractCoordinateOperation imp
* <th>Value type</th>
* <th>Returned by</th>
* </tr><tr>
+ * <td>{@value #TRANSFORM_KEY}</td>
+ * <td>{@link MathTransform}</td>
+ * <td>{@link #getMathTransform()}</td>
+ * </tr><tr>
+ * <th colspan="3" class="hsep">Defined in parent class (reminder)</th>
+ * </tr><tr>
* <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
* <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
* <td>{@link #getName()}</td>
@@ -91,13 +118,21 @@ final class DefaultConcatenatedOperation extends
AbstractCoordinateOperation imp
* <td>{@value
org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
* <td>{@link org.opengis.metadata.Identifier} (optionally as
array)</td>
* <td>{@link #getIdentifiers()}</td>
+ * </tr><tr>
+ * <td>{@value
org.opengis.referencing.operation.CoordinateOperation#COORDINATE_OPERATION_ACCURACY_KEY}</td>
+ * <td>{@link PositionalAccuracy} (optionally as array)</td>
+ * <td>{@link #getCoordinateOperationAccuracy()}</td>
* </tr>
* </table>
*
+ * The {@value #TRANSFORM_KEY} property should generally not be provided,
as it is automatically computed.
+ * That property is available for saving computation cost when the
concatenated transform is known in advance,
+ * or for overriding the automatic concatenation.
+ *
* @param properties the properties to be given to the identified object.
* @param operations the sequence of operations. Shall contain at least
two operations.
* @param mtFactory the math transform factory to use for math
transforms concatenation.
- * @throws FactoryException if the factory cannot concatenate the math
transforms.
+ * @throws FactoryException if this constructor or the factory cannot
concatenate the operation steps.
*/
public DefaultConcatenatedOperation(final Map<String,?> properties, final
CoordinateOperation[] operations,
final MathTransformFactory mtFactory) throws FactoryException
@@ -105,10 +140,11 @@ final class DefaultConcatenatedOperation extends
AbstractCoordinateOperation imp
super(properties);
ArgumentChecks.ensureNonNull("operations", operations);
if (operations.length < 2) {
- throw new
IllegalArgumentException(Errors.getResources(properties).getString(
+ throw new
InvalidGeodeticParameterException(Errors.getResources(properties).getString(
Errors.Keys.TooFewOccurrences_2, 2,
CoordinateOperation.class));
}
- initialize(properties, operations, mtFactory);
+ transform = Containers.property(properties, TRANSFORM_KEY,
MathTransform.class);
+ initialize(properties, operations, (transform == null) ? mtFactory :
null);
checkDimensions(properties);
}
@@ -120,7 +156,7 @@ final class DefaultConcatenatedOperation extends
AbstractCoordinateOperation imp
* @param properties the properties specified at construction time, or
{@code null} if unknown.
* @param operations the operations to concatenate.
* @param mtFactory the math transform factory to use, or {@code null}
for not performing concatenation.
- * @throws FactoryException if the factory cannot concatenate the math
transforms.
+ * @throws FactoryException if this constructor or the factory cannot
concatenate the operation steps.
*/
private void initialize(final Map<String,?> properties,
final CoordinateOperation[] operations,
@@ -129,7 +165,7 @@ final class DefaultConcatenatedOperation extends
AbstractCoordinateOperation imp
{
final List<CoordinateOperation> flattened = new
ArrayList<>(operations.length);
final CoordinateReferenceSystem crs = initialize(properties,
operations, flattened, mtFactory,
- (sourceCRS == null), (coordinateOperationAccuracy == null));
+ sourceCRS, (sourceCRS == null), (coordinateOperationAccuracy
== null));
if (targetCRS == null) {
targetCRS = crs;
}
@@ -175,6 +211,7 @@ final class DefaultConcatenatedOperation extends
AbstractCoordinateOperation imp
* @param operations the operations to concatenate.
* @param flattened the destination list in which to add the {@code
SingleOperation} instances.
* @param mtFactory the math transform factory to use, or {@code null}
for not performing concatenation.
+ * @param previous target CRS of the step before the first {@code
operations} step, or {@code null}.
* @param setSource {@code true} for setting the {@link #sourceCRS} on
the very first CRS (regardless if null or not).
* @param setAccuracy {@code true} for setting the {@link
#coordinateOperationAccuracy} field.
* @return the last target CRS, regardless if null or not.
@@ -185,51 +222,60 @@ final class DefaultConcatenatedOperation extends
AbstractCoordinateOperation imp
final CoordinateOperation[] operations,
final List<CoordinateOperation> flattened,
final MathTransformFactory mtFactory,
+ CoordinateReferenceSystem previous,
boolean setSource,
boolean setAccuracy) throws FactoryException
{
- CoordinateReferenceSystem previous = null;
+ CoordinateReferenceSystem source; // Source CRS of
current iteration.
+ CoordinateReferenceSystem target = null; // Target CRS of
current and last iteration.
for (int i=0; i<operations.length; i++) {
final CoordinateOperation op = operations[i];
ArgumentChecks.ensureNonNullElement("operations", i, op);
/*
- * Verify consistency of user argument: for each coordinate
operation, the number of dimensions of the
- * source CRS shall be equal to the number of dimensions of the
target CRS in the previous operation.
+ * Verify consistency of user argument: for each coordinate
operation, the source CRS
+ * should be equal (ignoring metadata) to the target CRS of the
previous operation.
+ * An exception to this rule is when source and target CRS need to
be swapped.
*/
- final CoordinateReferenceSystem next = op.getSourceCRS();
- if (previous != null && next != null) {
- final int dim1 = previous.getCoordinateSystem().getDimension();
- final int dim2 = next.getCoordinateSystem().getDimension();
- if (dim1 != dim2) {
- throw new
MismatchedDimensionException(Errors.getResources(properties).getString(
- Errors.Keys.MismatchedDimension_3, "operations[" +
i + "].sourceCRS", dim1, dim2));
- }
+ source = op.getSourceCRS();
+ target = op.getTargetCRS();
+ final boolean inverse = verifyStepChaining(properties, i,
previous, source, target);
+ if (inverse) {
+ var t = source;
+ source = target;
+ target = t;
}
if (setSource) {
setSource = false;
- sourceCRS = next;
// Take even if null.
+ sourceCRS = source;
// Take even if null.
}
- previous = op.getTargetCRS();
// For next iteration cycle.
/*
- * Now that we have verified the CRS dimensions, we should be able
to concatenate the transforms.
- * If an operation is a nested ConcatenatedOperation (not allowed
by ISO 19111, but we try to be
- * safe), we will first try to use the
ConcatenatedOperation.transform as a whole. Only if that
- * concatenated operation does not provide a transform we will
concatenate its components. Note
- * however that we traverse nested concatenated operations
unconditionally at least for checking
- * its consistency.
+ * Now that we have verified the CRS chaining, we should be able
to concatenate the transforms.
+ * If an operation is a nested `ConcatenatedOperation` (not
allowed by ISO 19111, but we try to
+ * be safe), we will first try to use the
`ConcatenatedOperation.transform` as a whole. Only if
+ * that concatenated operation does not provide a transform, we
will concatenate its components.
+ * Note however that we traverse nested concatenated operations
unconditionally at least for
+ * checking its consistency.
*/
- final MathTransform step = op.getMathTransform();
+ NoninvertibleTransformException cause = null;
+ MathTransform step = op.getMathTransform();
+ if (step != null && inverse) try {
+ step = step.inverse();
+ } catch (NoninvertibleTransformException e) {
+ step = null;
+ cause = e;
+ }
if (step == null) {
// May happen if the operation is a defining operation.
- throw new IllegalArgumentException(Resources.format(
- Resources.Keys.OperationHasNoTransform_2,
op.getClass(), op.getName()));
+ throw new InvalidGeodeticParameterException(Resources.format(
+ Resources.Keys.OperationHasNoTransform_2,
op.getClass(), op.getName()), cause);
}
if (op instanceof ConcatenatedOperation) {
- final List<? extends CoordinateOperation> children =
((ConcatenatedOperation) op).getOperations();
- final CoordinateOperation[] asArray =
children.toArray(CoordinateOperation[]::new);
- initialize(properties, asArray, flattened, (step == null) ?
mtFactory : null, false, setAccuracy);
+ final var nested = ((ConcatenatedOperation)
op).getOperations().toArray(CoordinateOperation[]::new);
+ previous = initialize(properties, nested, flattened, null,
previous, false, setAccuracy);
} else if (!step.isIdentity()) {
+ // Note: operation (source, target) may be in reverse order,
but it should be taken as metadata.
flattened.add(op);
+ previous = target; // For next iteration cycle.
}
if (mtFactory != null) {
transform = (transform != null) ?
mtFactory.createConcatenatedTransform(transform, step) : step;
@@ -253,9 +299,42 @@ final class DefaultConcatenatedOperation extends
AbstractCoordinateOperation imp
}
}
}
+ verifyStepChaining(properties, operations.length, target, targetCRS,
null);
return previous;
}
+ /**
+ * Verifies if a step of a concatenated operation can be chained after the
previous step.
+ *
+ * @param properties user-specified properties (for the locale of error
message), or {@code null} if none.
+ * @param step index of the operation step, used only in case an
exception it thrown.
+ * @param previous Target CRS of the previous step.
+ * @param source Source CRS of the current step.
+ * @param target Target CRS of the current step, or {@code null} if
none.
+ * @return whether the math transform needs to be inverted.
+ * @throws FactoryException if the current operation cannot be chained
after the previous operation.
+ */
+ static boolean verifyStepChaining(
+ final Map<String,?> properties, final int step,
+ final CoordinateReferenceSystem previous,
+ final CoordinateReferenceSystem source,
+ final CoordinateReferenceSystem target) throws FactoryException
+ {
+ if (previous == null || source == null) {
+ return false;
+ }
+ for (final ComparisonMode mode : CRS_ORDER_CRITERIA) {
+ if (Utilities.deepEquals(previous, source, mode)) return false;
+ if (Utilities.deepEquals(previous, target, mode)) return true;
+ }
+ Resources resources = Resources.forProperties(properties);
+ Locale locale = resources.getLocale();
+ throw new InvalidGeodeticParameterException(resources.getString(
+ Resources.Keys.MismatchedSourceTargetCRS_3, step,
+ IdentifiedObjects.getDisplayName(previous, locale),
+ IdentifiedObjects.getDisplayName(source, locale)));
+ }
+
/**
* Creates a new coordinate operation with the same values than the
specified one.
* This copy constructor provides a way to convert an arbitrary
implementation into a SIS one
@@ -333,7 +412,7 @@ final class DefaultConcatenatedOperation extends
AbstractCoordinateOperation imp
if (mode == ComparisonMode.STRICT) {
return Objects.equals(operations,
((DefaultConcatenatedOperation) object).operations);
} else {
- return deepEquals(getOperations(), ((ConcatenatedOperation)
object).getOperations(), mode);
+ return Utilities.deepEquals(getOperations(),
((ConcatenatedOperation) object).getOperations(), mode);
}
}
return false;
@@ -398,6 +477,7 @@ final class DefaultConcatenatedOperation extends
AbstractCoordinateOperation imp
*/
@XmlElement(name = "coordOperation", required = true)
private CoordinateOperation[] getSteps() {
+ @SuppressWarnings("LocalVariableHidesMemberVariable")
final List<? extends CoordinateOperation> operations = getOperations();
return (operations != null) ?
operations.toArray(CoordinateOperation[]::new) : null;
}
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 112647c2ac..03c8363dc1 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
@@ -662,12 +662,7 @@ next: for (int i=components.size(); --i >= 0;) {
if (operations != null && operations.length == 1) {
return operations[0];
}
- final ConcatenatedOperation op;
- try {
- op = new DefaultConcatenatedOperation(properties, operations,
getMathTransformFactory());
- } catch (IllegalArgumentException exception) {
- throw new
InvalidGeodeticParameterException(exception.getLocalizedMessage(), exception);
- }
+ final var op = new DefaultConcatenatedOperation(properties,
operations, getMathTransformFactory());
/*
* Verifies again the number of single operations. We may have a
singleton if some operations
* were omitted because their associated math transform were identity.
This happen for example
@@ -678,17 +673,22 @@ next: for (int i=components.size(); --i >= 0;) {
return pool.unique(op);
}
final CoordinateOperation single = co.get(0);
- assert op.getMathTransform().equals(single.getMathTransform()) : op;
- if (!Objects.equals(single.getSourceCRS(), op.getSourceCRS()) ||
- !Objects.equals(single.getTargetCRS(), op.getTargetCRS()))
+ if (Objects.equals(single.getSourceCRS(), op.getSourceCRS()) &&
+ Objects.equals(single.getTargetCRS(), op.getTargetCRS()))
{
+ // Verify only if CRS are equal because otherwise, `op` transform
may be the inverse.
+ assert single.getMathTransform().equals(op.getMathTransform()) :
op;
+ } else {
/*
* The CRS of the single operation may be different than the CRS
of the concatenated operation
- * if the first or the last operation was an identity operation.
It happens for example if the
- * sole purpose of an operation step was to change the longitude
range from [-180 … +180]° to
- * [0 … 360]°: the MathTransform is identity (because Apache SIS
does not handle those changes
- * in MathTransform; we handle that elsewhere, for example in the
Envelopes utility class),
- * but omitting the transform should not cause the lost of the CRS
with desired longitude range.
+ * for two reasons: optimization when the first or the last
operation was an identity operation,
+ * or when the operation to apply is the inverse of the single
operation (swapped source/target).
+ *
+ * The first case (optimization) happens, for example, if the sole
purpose of an operation step was
+ * to change the longitude range from [-180 … +180]° to [0 …
360]°. In such case, the `MathTransform`
+ * is identity (because Apache SIS does not handle those changes
in `MathTransform`; we handle that
+ * elsewhere, for example in the Envelopes utility class), but
omitting the transform should not
+ * cause the lost of the original CRS with the desired longitude
range.
*/
if (single instanceof SingleOperation) {
final Map<String,Object> merge = new HashMap<>(
@@ -700,7 +700,7 @@ next: for (int i=components.size(); --i >= 0;) {
merge.putAll(properties);
return createSingleOperation(merge, op.getSourceCRS(),
op.getTargetCRS(),
AbstractCoordinateOperation.getInterpolationCRS(op),
- ((SingleOperation) single).getMethod(),
single.getMathTransform());
+ ((SingleOperation) single).getMethod(),
op.getMathTransform());
}
}
return single;
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/InverseOperationMethod.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/InverseOperationMethod.java
index 640bf41977..e6262b6b24 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/InverseOperationMethod.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/InverseOperationMethod.java
@@ -112,11 +112,11 @@ final class InverseOperationMethod extends
DefaultOperationMethod {
/**
* Infers the properties to give to an inverse coordinate operation.
- * The returned map will contain three kind of information:
+ * The returned map will contain the following kinds of information:
*
* <ul>
- * <li>Metadata (domain of validity, accuracy)</li>
- * <li>Parameter values, if possible</li>
+ * <li>Metadata (domain of validity, accuracy).</li>
+ * <li>Parameter values, if possible.</li>
* </ul>
*
* If the inverse of the given operation can be represented by inverting
the sign of all numerical
@@ -132,7 +132,7 @@ final class InverseOperationMethod extends
DefaultOperationMethod {
* see ISO 19111 for more information).
*
* @param source the operation for which to get the inverse parameters.
- * @param target where to store the inverse parameters.
+ * @param target where to store the properties of the inverse operation.
*/
static void properties(final SingleOperation source, final
Map<String,Object> target) {
target.put(SingleOperation.DOMAIN_OF_VALIDITY_KEY,
source.getDomainOfValidity());
diff --git a/endorsed/src/org.apache.sis.util/main/module-info.java
b/endorsed/src/org.apache.sis.util/main/module-info.java
index 33c1e9dfec..3c846ed3ab 100644
--- a/endorsed/src/org.apache.sis.util/main/module-info.java
+++ b/endorsed/src/org.apache.sis.util/main/module-info.java
@@ -164,6 +164,7 @@ module org.apache.sis.util {
org.apache.sis.referencing.database; // In the "non-free"
sub-project.
exports org.apache.sis.pending.jdk to
+ org.apache.sis.referencing,
org.apache.sis.referencing.gazetteer,
org.apache.sis.feature,
org.apache.sis.storage,
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/jdk/JDK21.java
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/jdk/JDK21.java
index 33580297b4..d1853ad2a5 100644
---
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/jdk/JDK21.java
+++
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/jdk/JDK21.java
@@ -21,6 +21,7 @@ import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
+import java.util.NoSuchElementException;
/**
@@ -35,6 +36,36 @@ public final class JDK21 {
private JDK21() {
}
+ /**
+ * Placeholder for {@code SequencedCollection.getFirst()}.
+ *
+ * @param <E> type of elements in the collection.
+ * @param sequenced the sequenced collection for which to get elements
in reverse order.
+ * @return elements of the given collection in reverse order.
+ */
+ public static <E> E getFirst(final List<E> sequenced) {
+ try {
+ return sequenced.get(0);
+ } catch (IndexOutOfBoundsException e) {
+ throw new NoSuchElementException();
+ }
+ }
+
+ /**
+ * Placeholder for {@code SequencedCollection.getLast()}.
+ *
+ * @param <E> type of elements in the collection.
+ * @param sequenced the sequenced collection for which to get elements
in reverse order.
+ * @return elements of the given collection in reverse order.
+ */
+ public static <E> E getLast(final List<E> sequenced) {
+ try {
+ return sequenced.get(sequenced.size() - 1);
+ } catch (IndexOutOfBoundsException e) {
+ throw new NoSuchElementException();
+ }
+ }
+
/**
* Placeholder for {@code SequencedCollection.reversed()}.
*