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 2b1ed5b6c5 Find a coordinate operation path between two CRS when a 
step requires a change from spherical coordinates to geodetic coordinates. 
Note: when the spherical coordinates are two-dimensional, current version 
assumes a radius of zero, which is incorrect. The missing radius problem will 
be adressed in a separated commit.
2b1ed5b6c5 is described below

commit 2b1ed5b6c5a55cd03dababda10a9ce49eb75106f
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Sun Jun 1 21:57:41 2025 +0200

    Find a coordinate operation path between two CRS when a step requires a 
change from spherical coordinates to geodetic coordinates.
    Note: when the spherical coordinates are two-dimensional, current version 
assumes a radius of zero, which is incorrect.
    The missing radius problem will be adressed in a separated commit.
---
 .../sis/referencing/cs/CoordinateSystems.java      | 63 +++++++++++++++-------
 .../sis/referencing/cs/DefaultCompoundCS.java      |  7 ++-
 .../operation/MathTransformContext.java            |  2 +-
 .../operation/matrix/GeneralMatrix.java            | 22 ++++----
 .../operation/provider/AbstractProvider.java       | 12 ++++-
 .../GeocentricAffineBetweenGeographic.java         |  2 +-
 .../operation/provider/GeocentricToGeographic.java |  4 +-
 .../operation/provider/GeographicToGeocentric.java | 32 +++++++++--
 .../CoordinateSystemTransformBuilder.java          | 45 ++++------------
 .../transform/EllipsoidToCentricTransform.java     | 42 +++++++++++----
 .../operation/CoordinateOperationFinderTest.java   | 50 +++++++++++++++++
 .../provider/GeocentricTranslationTest.java        |  4 +-
 .../transform/EllipsoidToCentricTransformTest.java |  4 +-
 .../EllipsoidToSphericalTransformTest.java         |  2 +-
 14 files changed, 196 insertions(+), 95 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/CoordinateSystems.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/CoordinateSystems.java
index 8a5743bd7e..61f84e9ee0 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/CoordinateSystems.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/CoordinateSystems.java
@@ -56,7 +56,7 @@ import org.apache.sis.referencing.operation.matrix.MatrixSIS;
  * between two coordinate systems.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  * @since   0.4
  */
 public final class CoordinateSystems extends Static {
@@ -74,6 +74,7 @@ public final class CoordinateSystems extends Static {
      * @param  cs  the coordinate system to test (can be {@code null}).
      * @return whether the given coordinate system can be associated to a 
geodetic CRS.
      *
+     * @see #getSingleComponents(CoordinateSystem)
      * @since 1.3
      */
     public static boolean isGeodetic(final CoordinateSystem cs) {
@@ -280,20 +281,6 @@ public final class CoordinateSystems extends Static {
         return null;
     }
 
-    /**
-     * Adds all single coordinate systems in the given list.
-     * This method is used for decomposing a coordinate systems into its 
components.
-     */
-    private static void components(final CoordinateSystem cs, final 
List<CoordinateSystem> addTo) {
-        if (cs instanceof DefaultCompoundCS) {
-            for (final CoordinateSystem c : ((DefaultCompoundCS) 
cs).getComponents()) {
-                components(c, addTo);
-            }
-        } else {
-            addTo.add(cs);
-        }
-    }
-
     /**
      * Returns {@code true} if all {@code CoordinateSystem} interfaces of 
{@code targetCS} have a counterpart in
      * {@code sourceCS}. This method is equivalent to {@link 
Classes#implementSameInterfaces(Class, Class, Class)}
@@ -307,10 +294,8 @@ public final class CoordinateSystems extends Static {
      * then this method returns {@code false}.
      */
     static boolean hasAllTargetTypes(final CoordinateSystem sourceCS, final 
CoordinateSystem targetCS) {
-        final List<CoordinateSystem> sources = new 
ArrayList<>(sourceCS.getDimension());
-        final List<CoordinateSystem> targets = new 
ArrayList<>(targetCS.getDimension());
-        components(sourceCS, sources);
-        components(targetCS, targets);
+        final List<CoordinateSystem> sources = getSingleComponents(sourceCS);
+        final List<CoordinateSystem> targets = getSingleComponents(targetCS);
 next:   for (final CoordinateSystem cs : targets) {
             for (int i=0; i<sources.size(); i++) {
                 if (Classes.implementSameInterfaces(sources.get(i).getClass(), 
cs.getClass(), CoordinateSystem.class)) {
@@ -551,6 +536,46 @@ next:   for (final CoordinateSystem cs : targets) {
         });
     }
 
+    /**
+     * Returns all components of the given coordinate system.
+     * If the given coordinate system (<abbr>CS</abbr>) is null, then this 
method returns an empty list.
+     * Otherwise, if the given <abbr>CS</abbr> is <em>not</em> an instance of 
{@link DefaultCompoundCS},
+     * then this method adds that <abbr>CS</abbr> as the singleton element of 
the returned list.
+     * Otherwise, this method returns all {@linkplain 
DefaultCompoundCS#getComponents() components}.
+     * If a component is itself a {@link DefaultCompoundCS}, its is 
recursively decomposed into its
+     * singleton component.
+     *
+     * <h4>Implementation note</h4>
+     * This method always returns a modifiable list.
+     * Callers can freely modify that list without impacting the coordinate 
system.
+     *
+     * @param  cs  the coordinate system to decompose into singleton 
components, or {@code null}.
+     * @return the components, or an empty list if {@code cs} is null.
+     *
+     * @see #isGeodetic(CoordinateSystem)
+     * @since 1.5
+     */
+    public static List<CoordinateSystem> getSingleComponents(final 
CoordinateSystem cs) {
+        final var addTo = new ArrayList<CoordinateSystem>(3);
+        getSingleComponents(cs, addTo);
+        return addTo;
+    }
+
+    /**
+     * Recursively adds all single coordinate systems in the given list.
+     */
+    private static void getSingleComponents(final CoordinateSystem cs, final 
List<CoordinateSystem> addTo) {
+        if (cs != null) {
+            if (cs instanceof DefaultCompoundCS) {
+                for (final CoordinateSystem c : ((DefaultCompoundCS) 
cs).getComponents()) {
+                    getSingleComponents(c, addTo);
+                }
+            } else {
+                addTo.add(cs);
+            }
+        }
+    }
+
     /**
      * Returns the axis directions for the specified coordinate system.
      * This method guarantees that the returned array is non-null and does not 
contain any null direction.
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCompoundCS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCompoundCS.java
index 5ccec08324..b77b1d65eb 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCompoundCS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCompoundCS.java
@@ -103,6 +103,7 @@ public class DefaultCompoundCS extends AbstractCS {
     public DefaultCompoundCS(final Map<String,?> properties, 
CoordinateSystem... components) {
         super(properties, getAxes(components = clone(components)));
         this.components = UnmodifiableArrayList.wrap(components);
+        // TODO: replace by List.of(components) after RFE #4093999.
     }
 
     /**
@@ -112,6 +113,7 @@ public class DefaultCompoundCS extends AbstractCS {
     private DefaultCompoundCS(final DefaultCompoundCS original, final 
CoordinateSystem[] components) {
         super(original, null, getAxes(components));
         this.components = UnmodifiableArrayList.wrap(components);
+        // TODO: replace by List.of(components) after RFE #4093999.
     }
 
     /**
@@ -134,6 +136,7 @@ public class DefaultCompoundCS extends AbstractCS {
     private DefaultCompoundCS(final CoordinateSystem[] components, final 
CoordinateSystemAxis[] axes) {
         super(Map.of(NAME_KEY, AxisDirections.appendTo(new 
StringBuilder(60).append("Compound CS"), axes)), axes);
         this.components = UnmodifiableArrayList.wrap(components);
+        // TODO: replace by List.of(components) after RFE #4093999.
     }
 
     /**
@@ -156,7 +159,7 @@ public class DefaultCompoundCS extends AbstractCS {
         for (int i=0; i<components.length; i++) {
             count += components[i].getDimension();
         }
-        final CoordinateSystemAxis[] axis = new CoordinateSystemAxis[count];
+        final var axis = new CoordinateSystemAxis[count];
         count = 0;
         for (final CoordinateSystem c : components) {
             final int dim = c.getDimension();
@@ -191,7 +194,7 @@ public class DefaultCompoundCS extends AbstractCS {
             if (cs == null) {
                 cs = this;
                 boolean changed = false;
-                final CoordinateSystem[] newComponents = new 
CoordinateSystem[components.size()];
+                final var newComponents = new 
CoordinateSystem[components.size()];
                 for (int i=0; i<newComponents.length; i++) {
                     CoordinateSystem component = components.get(i);
                     AbstractCS m = castOrCopy(component);
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/MathTransformContext.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/MathTransformContext.java
index d1c5bdf710..bac42900d7 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/MathTransformContext.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/MathTransformContext.java
@@ -130,7 +130,7 @@ final class MathTransformContext extends 
ParameterizedTransformBuilder {
             MatrixSIS cm = MatrixSIS.castOrCopy(matrix);
             if (CartesianCS.class.isAssignableFrom(userCS)) {
                 rotation = Math.toRadians(rotation);
-                final Matrix4 rot = new Matrix4();
+                final var rot = new Matrix4();
                 rot.m00 =   rot.m11 = Math.cos(rotation);
                 rot.m01 = -(rot.m10 = Math.sin(rotation));
                 if (inverse) {
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/matrix/GeneralMatrix.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/matrix/GeneralMatrix.java
index 70b6055ea5..cbd4c57b8d 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/matrix/GeneralMatrix.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/matrix/GeneralMatrix.java
@@ -153,8 +153,8 @@ class GeneralMatrix extends MatrixSIS implements 
ExtendedPrecisionMatrix {
      * @throws NullPointerException if the {@link #elements} array is null.
      */
     private boolean isValid() {
-        final int numRow = this.numRow;
-        final int numCol = this.numCol;
+        @SuppressWarnings("LocalVariableHidesMemberVariable")
+        final int numRow = this.numRow, numCol = this.numCol;       // 
Protection against accidental changes.
         final int length = elements.length;
         int i = numRow * numCol;                // Cannot overflow.
         if ((numRow |  numCol) < 0 || (length != i) ||
@@ -320,8 +320,8 @@ class GeneralMatrix extends MatrixSIS implements 
ExtendedPrecisionMatrix {
      * @param  square  {@code true} if the matrix must be square, or {@code 
false} for allowing non-square matrices.
      */
     final boolean isAffine(final boolean square) {
-        final int numRow = this.numRow;                     // Protection 
against accidental changes.
-        final int numCol = this.numCol;
+        @SuppressWarnings("LocalVariableHidesMemberVariable")
+        final int numRow = this.numRow, numCol = this.numCol;       // 
Protection against accidental changes.
         if (numRow == numCol || !square) {
             int i = numRow * numCol;
             if (Arithmetic.isOne(elements[--i])) {
@@ -342,8 +342,8 @@ class GeneralMatrix extends MatrixSIS implements 
ExtendedPrecisionMatrix {
      */
     @Override
     public final boolean isIdentity() {
-        final int numRow = this.numRow;             // Protection against 
accidental changes.
-        final int numCol = this.numCol;
+        @SuppressWarnings("LocalVariableHidesMemberVariable")
+        final int numRow = this.numRow, numCol = this.numCol;       // 
Protection against accidental changes.
         if (numRow != numCol) {
             return false;
         }
@@ -370,8 +370,8 @@ class GeneralMatrix extends MatrixSIS implements 
ExtendedPrecisionMatrix {
      */
     @Override
     public void transpose() {
-        final int numRow = this.numRow;                                 // 
Protection against accidental changes.
-        final int numCol = this.numCol;
+        @SuppressWarnings("LocalVariableHidesMemberVariable")
+        final int numRow = this.numRow, numCol = this.numCol;       // 
Protection against accidental changes.
         for (int j=0; j<numRow; j++) {
             for (int i=0; i<j; i++) {
                 final int lo = j*numCol + i;
@@ -386,8 +386,8 @@ class GeneralMatrix extends MatrixSIS implements 
ExtendedPrecisionMatrix {
      * The matrix sizes much match - this is not verified unless assertions 
are enabled.
      */
     final void setToProduct(final Matrix A, final Matrix B) {
-        final int numRow = this.numRow;         // Protection against 
accidental changes.
-        final int numCol = this.numCol;
+        @SuppressWarnings("LocalVariableHidesMemberVariable")
+        final int numRow = this.numRow, numCol = this.numCol;       // 
Protection against accidental changes.
         final int nc = A.getNumCol();
         assert B.getNumRow() == nc;
         assert numRow == A.getNumRow() && numCol == B.getNumCol();
@@ -435,7 +435,7 @@ class GeneralMatrix extends MatrixSIS implements 
ExtendedPrecisionMatrix {
     @Override
     public final boolean equals(final Object object) {
         if (object instanceof GeneralMatrix) {
-            final GeneralMatrix that = (GeneralMatrix) object;
+            final var that = (GeneralMatrix) object;
             return numRow == that.numRow &&
                    numCol == that.numCol &&
                    Arrays.equals(elements, that.elements);
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/AbstractProvider.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/AbstractProvider.java
index d322c0631a..e97912cf4a 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/AbstractProvider.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/AbstractProvider.java
@@ -69,8 +69,16 @@ public abstract class AbstractProvider extends 
DefaultOperationMethod implements
     private final Class<? extends SingleOperation> operationType;
 
     /**
-     * The base interface of the coordinate system of source/target 
coordinates.
-     * This is used for resolving some ambiguities at WKT parsing time.
+     * The base interface of the coordinate system of source coordinates.
+     * This is used for resolving some ambiguities at <abbr>WKT</abbr> parsing 
time.
+     *
+     * <h4>Deprecation</h4>
+     * This field is not formally annotated as deprecated, but should be 
considered as such.
+     * The coordinate system type can be specified by the {@code context} 
argument given in
+     * calls to the {@code create(…)} method. In particular, {@link 
GeographicToGeocentric}
+     * accepts Cartesian and spherical coordinate systems. In such cases, this 
field should
+     * be set to the type expected in Well-Known Text (<abbr>WKT</abbr>) 
definitions.
+     * This is not necessarily the only type accepted by this operation method.
      */
     public final Class<? extends CoordinateSystem> sourceCSType, targetCSType;
 
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/GeocentricAffineBetweenGeographic.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/GeocentricAffineBetweenGeographic.java
index fd561617da..f648f99548 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/GeocentricAffineBetweenGeographic.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/GeocentricAffineBetweenGeographic.java
@@ -40,7 +40,7 @@ import org.apache.sis.measure.Units;
  *
  * <h2>Default values to verify</h2>
  * This class assumes the following default values.
- * Subclasses should verify if those default values are suitable from them:
+ * Subclasses should verify if those default values are suitable for them:
  *
  * <ul>
  *   <li>{@link #getOperationType()} defaults to {@link 
org.opengis.referencing.operation.Transformation}.</li>
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/GeocentricToGeographic.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/GeocentricToGeographic.java
index e546342c0a..c89250e67d 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/GeocentricToGeographic.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/GeocentricToGeographic.java
@@ -61,7 +61,7 @@ public final class GeocentricToGeographic extends 
AbstractProvider {
      */
     public GeocentricToGeographic() {
         super(Conversion.class, PARAMETERS,
-              CartesianCS.class,  false,
+              CartesianCS.class,  false,    // Type expected in WKT, but not 
the only type accepted by this operation.
               EllipsoidalCS.class, true,
               (byte) 3);
     }
@@ -75,7 +75,7 @@ public final class GeocentricToGeographic extends 
AbstractProvider {
      */
     @Override
     public MathTransform createMathTransform(final Context context) throws 
FactoryException {
-        MathTransform tr = GeographicToGeocentric.create(context, 
context.getTargetDimensions());
+        MathTransform tr = GeographicToGeocentric.create(context, 
context.getTargetDimensions(), context.getSourceCSType());
         try {
             tr = tr.inverse();
         } catch (NoninvertibleTransformException e) {
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/GeographicToGeocentric.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/GeographicToGeocentric.java
index cdaaf291bb..0f1e21e7e4 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/GeographicToGeocentric.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/GeographicToGeocentric.java
@@ -22,11 +22,14 @@ import javax.measure.quantity.Length;
 import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterValue;
 import org.opengis.parameter.ParameterDescriptorGroup;
+import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.cs.CartesianCS;
 import org.opengis.referencing.cs.EllipsoidalCS;
 import org.opengis.referencing.operation.Conversion;
 import org.opengis.referencing.operation.MathTransform;
 import 
org.apache.sis.referencing.operation.transform.EllipsoidToCentricTransform;
+import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
+import org.apache.sis.referencing.internal.Resources;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.parameter.Parameters;
 import org.apache.sis.util.privy.Constants;
@@ -71,7 +74,7 @@ public final class GeographicToGeocentric extends 
AbstractProvider {
     public GeographicToGeocentric() {
         super(Conversion.class, PARAMETERS,
               EllipsoidalCS.class, true,
-              CartesianCS.class, false,
+              CartesianCS.class, false,     // Type expected in WKT, but not 
the only type accepted by this operation.
               (byte) 2);
     }
 
@@ -108,18 +111,37 @@ public final class GeographicToGeocentric extends 
AbstractProvider {
      */
     @Override
     public MathTransform createMathTransform(final Context context) throws 
FactoryException {
-        return create(context, context.getSourceDimensions());
+        return create(context, context.getSourceDimensions(), 
context.getTargetCSType());
     }
 
     /**
      * Implementation of {@link #createMathTransform(Context)} shared with 
{@link GeocentricToGeographic}.
+     * This method creates the "geographic to geocentric" operation. Callers 
is responsible to invert the
+     * transform if the "geocentric to geographic" operation is desired.
+     *
+     * @param  context     the parameter values together with its context.
+     * @param  dimension   the number of dimension of the geographic 
<abbr>CRS</abbr>.
+     * @param  geocentric  the coordinate system type of the geocentric 
<abbr>CRS</abbr>.
+     * @return the conversion from geographic to geocentric coordinates.
+     * @throws FactoryException if an error occurred while creating a 
transform.
      */
-    static MathTransform create(final Context context, final OptionalInt 
dimension) throws FactoryException {
+    static MathTransform create(final Context context, final OptionalInt 
dimension,
+                                final Class<? extends CoordinateSystem> 
geocentric)
+            throws FactoryException
+    {
         final Parameters values = 
Parameters.castOrWrap(context.getCompletedParameters());
         final ParameterValue<?> semiMajor = 
values.parameter(Constants.SEMI_MAJOR);
         final Unit<Length> unit = semiMajor.getUnit().asType(Length.class);
+        final EllipsoidToCentricTransform.TargetType type;
+        if (geocentric == CoordinateSystem.class) {
+            type = EllipsoidToCentricTransform.TargetType.CARTESIAN;    // 
Default value.
+        } else try {
+            type = EllipsoidToCentricTransform.TargetType.of(geocentric);
+        } catch (IllegalArgumentException e) {
+            throw new InvalidGeodeticParameterException(
+                    
Resources.format(Resources.Keys.IncompatibleCoordinateSystemTypes), e);
+        }
         return 
EllipsoidToCentricTransform.createGeodeticConversion(context.getFactory(), 
semiMajor.doubleValue(),
-                values.parameter(Constants.SEMI_MINOR).doubleValue(unit), 
unit, dimension.orElse(3) >= 3,
-                EllipsoidToCentricTransform.TargetType.CARTESIAN);
+                values.parameter(Constants.SEMI_MINOR).doubleValue(unit), 
unit, dimension.orElse(3) >= 3, type);
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/CoordinateSystemTransformBuilder.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/CoordinateSystemTransformBuilder.java
index 3229427266..1476165cc0 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/CoordinateSystemTransformBuilder.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/CoordinateSystemTransformBuilder.java
@@ -17,7 +17,6 @@
 package org.apache.sis.referencing.operation.transform;
 
 import java.util.List;
-import java.util.ArrayList;
 import javax.measure.IncommensurableException;
 import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterValueGroup;
@@ -33,7 +32,6 @@ import org.opengis.referencing.operation.MathTransformFactory;
 import org.opengis.referencing.operation.OperationNotFoundException;
 import org.apache.sis.referencing.cs.AxesConvention;
 import org.apache.sis.referencing.cs.CoordinateSystems;
-import org.apache.sis.referencing.cs.DefaultCompoundCS;
 import org.apache.sis.referencing.internal.Resources;
 import org.apache.sis.referencing.operation.provider.GeocentricToGeographic;
 import org.apache.sis.referencing.operation.provider.GeographicToGeocentric;
@@ -106,19 +104,6 @@ final class CoordinateSystemTransformBuilder extends 
MathTransformBuilder {
         throw new 
IllegalStateException(Errors.format(Errors.Keys.MissingValueForProperty_1, 
"method"));
     }
 
-    /**
-     * Adds the components of the given coordinate system in the specified 
list.
-     * This method may invoke itself recursively if there is nested compound 
CS.
-     * The returned list is always a copy and can be safely modified.
-     */
-    private static void getComponents(final CoordinateSystem cs, final 
List<CoordinateSystem> addTo) {
-        if (cs instanceof DefaultCompoundCS) {
-            addTo.addAll(((DefaultCompoundCS) cs).getComponents());
-        } else {
-            addTo.add(cs);
-        }
-    }
-
     /**
      * Creates the change of coordinate system.
      *
@@ -134,31 +119,19 @@ final class CoordinateSystemTransformBuilder extends 
MathTransformBuilder {
                     Errors.Keys.MissingValueForProperty_1,
                     (source == null) ? "source" : "target"));
         }
-        if (ellipsoid != null) {
+        final List<CoordinateSystem> sources = 
CoordinateSystems.getSingleComponents(source);
+        final List<CoordinateSystem> targets = 
CoordinateSystems.getSingleComponents(target);
+        final int count = sources.size();
+        if (ellipsoid != null && (count | targets.size()) == 1) {
             final boolean isEllipsoidalSource = (source instanceof 
EllipsoidalCS);
             if (isEllipsoidalSource != (target instanceof EllipsoidalCS)) {
-                /*
-                 * For now we support only conversion between EllipsoidalCS 
and CartesianCS.
-                 * But future Apache SIS versions could add support for 
conversions between
-                 * EllipsoidalCS and SphericalCS or other coordinate systems.
-                 */
-                if ((isEllipsoidalSource ? target : source) instanceof 
CartesianCS) {
-                    final var context = factory.builder(isEllipsoidalSource ? 
GeographicToGeocentric.NAME
-                                                                            : 
GeocentricToGeographic.NAME);
-                    if (isEllipsoidalSource) {
-                        context.setSourceAxes(source, ellipsoid);
-                        context.setTargetAxes(target, null);
-                    } else {
-                        context.setSourceAxes(source, null);
-                        context.setTargetAxes(target, ellipsoid);
-                    }
-                    return context.create();
-                }
+                final var context = factory.builder(isEllipsoidalSource ? 
GeographicToGeocentric.NAME
+                                                                        : 
GeocentricToGeographic.NAME);
+                context.setSourceAxes(source, isEllipsoidalSource? ellipsoid : 
null);
+                context.setTargetAxes(target, isEllipsoidalSource ? null : 
ellipsoid);
+                return context.create();
             }
         }
-        final var sources = new ArrayList<CoordinateSystem>(3); 
getComponents(source, sources);
-        final var targets = new ArrayList<CoordinateSystem>(3); 
getComponents(target, targets);
-        final int count   = sources.size();
         /*
          * Current implementation expects the same number of components, in 
the same order
          * and with the same number of dimensions in each component. A future 
version will
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransform.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransform.java
index 1fe9a70238..e2960d274f 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransform.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransform.java
@@ -36,6 +36,9 @@ import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransformFactory;
 import org.opengis.referencing.operation.TransformException;
+import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.cs.CartesianCS;
+import org.opengis.referencing.cs.SphericalCS;
 import org.apache.sis.util.Debug;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.ArgumentChecks;
@@ -61,6 +64,7 @@ import static 
org.apache.sis.referencing.operation.provider.MapProjection.SEMI_M
 import static 
org.apache.sis.referencing.operation.provider.MapProjection.SEMI_MINOR;
 import static 
org.apache.sis.referencing.operation.provider.MapProjection.ECCENTRICITY;
 import static 
org.apache.sis.referencing.operation.provider.GeocentricAffineBetweenGeographic.DIMENSION;
+import org.apache.sis.util.resources.Errors;
 
 
 /**
@@ -154,7 +158,23 @@ public class EllipsoidToCentricTransform extends 
AbstractMathTransform implement
          *
          * @since 1.5
          */
-        SPHERICAL
+        SPHERICAL;
+
+        /**
+         * Returns the enumeration value for the given type of coordinate 
system.
+         * The {@code cs} argument should be {@code CartesianCS.class}, {@code 
SphericalCS.class}
+         * or a subclass of those types.
+         *
+         * @param  cs  the coordinate system type.
+         * @return enumeration value associated to the given type.
+         * @throws IllegalArgumentException if the given {@code cs} is not one 
of the above-documented types.
+         * @since  1.5
+         */
+        public static TargetType of(final Class<? extends CoordinateSystem> 
cs) {
+            if (CartesianCS.class.isAssignableFrom(cs)) return CARTESIAN;
+            if (SphericalCS.class.isAssignableFrom(cs)) return SPHERICAL;
+            throw new 
IllegalArgumentException(Errors.format(Errors.Keys.UnsupportedType_1, cs));
+        }
     }
 
     /**
@@ -310,17 +330,17 @@ public class EllipsoidToCentricTransform extends 
AbstractMathTransform implement
      * @param unit        the unit of measurement for the semi-axes and the 
ellipsoidal height.
      * @param withHeight  {@code true} if source geographic coordinates 
include an ellipsoidal height
      *                    (i.e. are 3-D), or {@code false} if they are only 
2-D.
-     * @param target      whether the target coordinate system shall be 
Cartesian or spherical.
+     * @param csType      whether the target coordinate system shall be 
Cartesian or spherical.
      *
      * @see #createGeodeticConversion(MathTransformFactory, double, double, 
Unit, boolean, TargetType)
      */
     public EllipsoidToCentricTransform(final double semiMajor, final double 
semiMinor,
-            final Unit<Length> unit, final boolean withHeight, final 
TargetType target)
+            final Unit<Length> unit, final boolean withHeight, final 
TargetType csType)
     {
         ArgumentChecks.ensureStrictlyPositive("semiMajor", semiMajor);
         ArgumentChecks.ensureStrictlyPositive("semiMinor", semiMinor);
-        ArgumentChecks.ensureNonNull("target", target);
-        toSphericalCS = (target == TargetType.SPHERICAL);
+        ArgumentChecks.ensureNonNull("csType", csType);
+        toSphericalCS = (csType == TargetType.SPHERICAL);
         axisRatio = semiMinor / semiMajor;
         eccentricitySquared = 1 - (axisRatio * axisRatio);
         useIterations = (eccentricitySquared >= ECCENTRICITY_THRESHOLD * 
ECCENTRICITY_THRESHOLD);
@@ -494,7 +514,7 @@ public class EllipsoidToCentricTransform extends 
AbstractMathTransform implement
     public ParameterValueGroup getParameterValues() {
         final Parameters pg = 
Parameters.castOrWrap(getParameterDescriptors().createValue());
         pg.getOrCreate(ECCENTRICITY).setValue(sqrt(eccentricitySquared));
-        pg.parameter("target").setValue(getTargetType());
+        pg.parameter("csType").setValue(getTargetType());
         pg.getOrCreate(DIMENSION).setValue(getSourceDimensions());
         return pg;
     }
@@ -512,7 +532,7 @@ public class EllipsoidToCentricTransform extends 
AbstractMathTransform implement
             if (DESCRIPTOR == null) {
                 final ParameterBuilder builder = new 
ParameterBuilder().setCodeSpace(Citations.SIS, Constants.SIS);
                 final ParameterDescriptor<TargetType> target = 
builder.setRequired(true)
-                        .addName("target").create(TargetType.class, 
TargetType.CARTESIAN);
+                        .addName("csType").create(TargetType.class, 
TargetType.CARTESIAN);
                 DESCRIPTOR = builder.addName("Ellipsoid (radians domain) to 
centric")
                         .createGroup(1, 1, ECCENTRICITY, target, DIMENSION);
             }
@@ -783,12 +803,12 @@ public class EllipsoidToCentricTransform extends 
AbstractMathTransform implement
         while (--numPts >= 0) {
             final double p, λ, Z;
             if (toSphericalCS) {
-                final double Ω, r;
+                final double Ω, R;
                 λ = srcPts[srcOff++];       // Spherical longitude
                 Ω = srcPts[srcOff++];       // Spherical latitude
-                r = srcPts[srcOff++];       // Spherical radius
-                p = r * cos(Ω);
-                Z = r * sin(Ω);
+                R = srcPts[srcOff++];       // Spherical radius
+                p = R * cos(Ω);
+                Z = R * sin(Ω);
             } else {
                 final double X, Y;
                 X = srcPts[srcOff++];      // Toward prime meridian
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java
index d6ce33d92e..be6446c47e 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java
@@ -653,6 +653,56 @@ public final class CoordinateOperationFinderTest extends 
MathTransformTestCase {
         validate();
     }
 
+    /**
+     * Tests a conversion from geocentric spherical coordinates to an 
arbitrary map projections.
+     * This is used in astronomy.
+     *
+     * @throws ParseException if a CRS used in this test cannot be parsed.
+     * @throws FactoryException if the operation cannot be created.
+     * @throws TransformException if an error occurred while converting the 
test points.
+     */
+    @Test
+    public void testSphericalToProjection() throws ParseException, 
FactoryException, TransformException {
+        final CoordinateReferenceSystem sourceCRS = parse(
+                "GEODCRS[\"Mars (2015) / Ocentric\",\n" +
+                "  DATUM[\"Mars (2015)\",\n" +
+                "    ELLIPSOID[\"Mars (2015)\", 3396190, 169.8944472236118,\n" 
+
+                "      LENGTHUNIT[\"metre\", 1, ID[\"EPSG\", 9001]]],\n" +
+                "    ANCHOR[\"Viking 1 lander : 47.95137 W\"]],\n" +
+                "    PRIMEM[\"Reference Meridian\", 0,\n" +
+                "      ANGLEUNIT[\"degree\", 0.0174532925199433, ID[\"EPSG\", 
9122]]],\n" +
+                "  CS[spherical, 2],\n" +
+                "    AXIS[\"planetocentric latitude (U)\", north,\n" +
+                "      ANGLEUNIT[\"degree\", 0.0174532925199433]],\n" +
+                "    AXIS[\"planetocentric longitude (V)\", east,\n" +
+                "      ANGLEUNIT[\"degree\", 0.0174532925199433]],\n" +
+                "  ID[\"IAU\", 49902, 2015],\n" +
+                "  REMARK[\"Source of IAU Coordinate systems: 
doi:10.1007/s10569-017-9805-5\"]]");
+
+        final CoordinateReferenceSystem targetCRS = parse(
+                "PROJCRS[\"Mars (2015) / Ocentric / Equirectangular, clon = 
0\",\n" +
+                "  BASEGEODCRS[\"Mars (2015) / Ocentric\",\n" +
+                "    DATUM[\"Mars (2015)\",\n" +
+                "      ELLIPSOID[\"Mars (2015)\", 3396190, 
169.8944472236118,\n" +
+                "        LENGTHUNIT[\"metre\", 1, ID[\"EPSG\", 9001]]],\n" +
+                "      ANCHOR[\"Viking 1 lander : 47.95137 W\"]],\n" +
+                "      PRIMEM[\"Reference Meridian\", 0,\n" +
+                "        ANGLEUNIT[\"degree\", 0.0174532925199433, 
ID[\"EPSG\", 9122]]],\n" +
+                "    ID[\"IAU\", 49902, 2015]],\n" +
+                "  CONVERSION[\"Equirectangular, clon = 0\",\n" +
+                "    METHOD[\"Equidistant Cylindrical\", ID[\"EPSG\", 
1028]]],\n" +
+                "  CS[Cartesian, 2],\n" +
+                "    AXIS[\"Easting (E)\", east,\n" +
+                "      LENGTHUNIT[\"metre\", 1]],\n" +
+                "    AXIS[\"Northing (N)\", north,\n" +
+                "      LENGTHUNIT[\"metre\", 1]],\n" +
+                "  ID[\"IAU\", 49912, 2015]]");
+
+        final CoordinateOperation operation = 
finder().createOperation(sourceCRS, targetCRS);
+        assertSame(sourceCRS, operation.getSourceCRS());
+        assertSame(targetCRS, operation.getTargetCRS());
+    }
+
 
 
 
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/GeocentricTranslationTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/GeocentricTranslationTest.java
index 986da2a0d2..5ac44cedde 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/GeocentricTranslationTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/GeocentricTranslationTest.java
@@ -378,7 +378,7 @@ public final class GeocentricTranslationTest extends 
MathTransformTestCase {
                 "    Parameter[“elt_2_2”, 1.567855942887398E-7]],\n" +
                 "  Param_MT[“Ellipsoid (radians domain) to centric”,\n" +
                 "    Parameter[“eccentricity”, 0.08181919084262157],\n" +
-                "    Parameter[“target”, “CARTESIAN”],\n" +
+                "    Parameter[“csType”, “CARTESIAN”],\n" +
                 "    Parameter[“dim”, 3]],\n" +
                 "  Param_MT[“Affine”,\n" +
                 "    Parameter[“num_row”, 4],\n" +
@@ -391,7 +391,7 @@ public final class GeocentricTranslationTest extends 
MathTransformTestCase {
                 "    Parameter[“elt_2_3”, 1.8335353697517302E-5]],\n" +
                 "  Param_MT[“Centric to ellipsoid (radians domain)”,\n" +
                 "    Parameter[“eccentricity”, 0.08199188997902956],\n" +
-                "    Parameter[“target”, “CARTESIAN”],\n" +
+                "    Parameter[“csType”, “CARTESIAN”],\n" +
                 "    Parameter[“dim”, 3]],\n" +
                 "  Param_MT[“Affine”,\n" +
                 "    Parameter[“num_row”, 4],\n" +
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransformTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransformTest.java
index 9d710a5993..87f8dc8b13 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransformTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransformTest.java
@@ -405,7 +405,7 @@ public class EllipsoidToCentricTransformTest extends 
MathTransformTestCase {
                 "    Parameter[“elt_2_2”, 1.567855942887398E-7]],\n" +
                 "  Param_MT[“Ellipsoid (radians domain) to centric”,\n" +
                 "    Parameter[“eccentricity”, 0.08181919084262157],\n" +
-                "    Parameter[“target”, “CARTESIAN”],\n" +
+                "    Parameter[“csType”, “CARTESIAN”],\n" +
                 "    Parameter[“dim”, 3]],\n" +
                 "  Param_MT[“Affine”,\n" +
                 "    Parameter[“num_row”, 4],\n" +
@@ -425,7 +425,7 @@ public class EllipsoidToCentricTransformTest extends 
MathTransformTestCase {
                 "    Parameter[“elt_2_2”, 1.567855942887398E-7]],\n" +
                 "  Param_MT[“Centric to ellipsoid (radians domain)”,\n" +
                 "    Parameter[“eccentricity”, 0.08181919084262157],\n" +
-                "    Parameter[“target”, “CARTESIAN”],\n" +
+                "    Parameter[“csType”, “CARTESIAN”],\n" +
                 "    Parameter[“dim”, 3]],\n" +
                 "  Param_MT[“Affine”,\n" +
                 "    Parameter[“num_row”, 4],\n" +
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/EllipsoidToSphericalTransformTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/EllipsoidToSphericalTransformTest.java
index 8c7a71f120..aad896d86d 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/EllipsoidToSphericalTransformTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/EllipsoidToSphericalTransformTest.java
@@ -126,7 +126,7 @@ public final class EllipsoidToSphericalTransformTest 
extends EllipsoidToCentricT
                 "    Parameter[“elt_2_2”, 1.567855942887398E-7]],\n" +
                 "  Param_MT[“Ellipsoid (radians domain) to centric”,\n" +
                 "    Parameter[“eccentricity”, 0.08181919084262157],\n" +
-                "    Parameter[“target”, “SPHERICAL”],\n" +
+                "    Parameter[“csType”, “SPHERICAL”],\n" +
                 "    Parameter[“dim”, 3]],\n" +
                 "  Param_MT[“Affine”,\n" +
                 "    Parameter[“num_row”, 4],\n" +


Reply via email to