sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 03/03: Fix a MismatchedDimensionException when a datum shift is applied between a two-dimensional GeographicCRS and a three-dimensional GeographicCRS. This is the first part of https://issues.apache.org/jira/browse/SIS-462
Date Mon, 06 Jul 2020 14:56:31 GMT
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit db63678c02ffc47c8f54f1a8229353e6390fe600
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Jul 6 16:54:53 2020 +0200

    Fix a MismatchedDimensionException when a datum shift is applied between a two-dimensional
GeographicCRS and a three-dimensional GeographicCRS.
    This is the first part of https://issues.apache.org/jira/browse/SIS-462
---
 .../java/org/apache/sis/gui/map/StatusBar.java     | 51 +++++++++++++++-------
 .../org/apache/sis/gui/referencing/MenuSync.java   | 16 ++++---
 .../referencing/EllipsoidalHeightCombiner.java     |  6 +--
 .../referencing/provider/GeocentricAffine.java     |  8 ++--
 .../GeocentricAffineBetweenGeographic.java         |  8 ++--
 .../operation/CoordinateOperationFinder.java       | 48 +++++++++++++-------
 .../transform/DefaultMathTransformFactory.java     | 37 +++++++---------
 .../transform/DefaultMathTransformFactoryTest.java | 12 +++++
 8 files changed, 117 insertions(+), 69 deletions(-)

diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
index c10986d..8cb0823 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
@@ -78,6 +78,7 @@ import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.gui.Widget;
 import org.apache.sis.gui.referencing.RecentReferenceSystems;
+import org.apache.sis.internal.referencing.ReferencingUtilities;
 import org.apache.sis.internal.gui.BackgroundThreads;
 import org.apache.sis.internal.gui.ExceptionReporter;
 import org.apache.sis.internal.gui.GUIUtilities;
@@ -262,12 +263,14 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
     private Predicate<MapCanvas> fullOperationSearchRequired;
 
     /**
-     * The source local indices before conversion to geospatial coordinates.
-     * The number of dimensions is often {@value #BIDIMENSIONAL}.
-     * Shall never be {@code null}.
+     * The source local indices before conversion to geospatial coordinates (never {@code
null}).
+     * The number of dimensions is often {@value #BIDIMENSIONAL}. May be the same array than
+     * <code>{@linkplain #targetCoordinates}.coordinates</code> because some
transforms are
+     * faster when the source and destination arrays are the same.
      *
      * @see #targetCoordinates
      * @see #position
+     * @see #setTargetCRS(CoordinateReferenceSystem)
      */
     private double[] sourceCoordinates;
 
@@ -635,7 +638,11 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
             }
         }
         final boolean sameCRS = Utilities.equalsIgnoreMetadata(objectiveCRS, crs);
-        ((LocalToObjective) localToObjectiveCRS).setNoCheck(localToCRS);
+        if (localToCRS == null) {
+            localToCRS = MathTransforms.identity(BIDIMENSIONAL);
+        }
+        final int srcDim = Math.max(localToCRS.getSourceDimensions(), BIDIMENSIONAL);
+        final int tgtDim = localToCRS.getTargetDimensions();
         /*
          * Remaining code should not fail, so we can start modifying the `StatusBar` fields.
          * The buffers for source and target coordinates are recreated because the number
of
@@ -645,14 +652,9 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
          * So we can not use those values for updating the coordinates shown in status bar.
          * Instead we will wait for the next mouse event to provide new local coordinates.
          */
-        if (localToCRS != null) {
-            sourceCoordinates = new double[Math.max(localToCRS.getSourceDimensions(), BIDIMENSIONAL)];
-            targetCoordinates = new GeneralDirectPosition(localToCRS.getTargetDimensions());
-        } else {
-            localToCRS        = MathTransforms.identity(BIDIMENSIONAL);
-            targetCoordinates = new GeneralDirectPosition(BIDIMENSIONAL);
-            sourceCoordinates = targetCoordinates.coordinates;      // Okay to share array
if same dimension.
-        }
+        ((LocalToObjective) localToObjectiveCRS).setNoCheck(localToCRS);
+        targetCoordinates   = new GeneralDirectPosition(tgtDim);
+        sourceCoordinates   = (srcDim == tgtDim) ? targetCoordinates.coordinates : new double[srcDim];
         objectiveCRS        = crs;
         localToPositionCRS  = localToCRS;                           // May be updated again
below.
         inflatePrecisions   = inflate;
@@ -689,7 +691,24 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
         if (objectiveToPositionCRS != null) {
             localToPositionCRS = MathTransforms.concatenate(localToObjectiveCRS.get(), objectiveToPositionCRS);
         }
-        targetCoordinates.setCoordinateReferenceSystem(format.getDefaultCRS());
+        setTargetCRS(format.getDefaultCRS());
+    }
+
+    /**
+     * Sets the CRS of {@link #targetCoordinates}.
+     * This method creates a new position if the number of dimensions changed.
+     */
+    private void setTargetCRS(final CoordinateReferenceSystem crs) {
+        final int tgtDim = ReferencingUtilities.getDimension(crs);
+        if (tgtDim != 0 && tgtDim != targetCoordinates.getDimension()) {
+            precisions = null;
+            targetCoordinates = new GeneralDirectPosition(tgtDim);
+            if (sourceCoordinates.length == tgtDim) {
+                // Sharing the same array make some transforms faster.
+                sourceCoordinates = targetCoordinates.coordinates;
+            }
+        }
+        targetCoordinates.setCoordinateReferenceSystem(crs);
     }
 
     /**
@@ -892,7 +911,7 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
             position.setText(text);
         }
         outsideText = text;
-        targetCoordinates.setCoordinateReferenceSystem(crs);
+        setTargetCRS(crs);
         ((PositionSystem) positionReferenceSystem).fireValueChangedEvent();
     }
 
@@ -933,7 +952,9 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
 
         /**
          * Overwrite previous value without any check. This method is invoked when the {@link
#objectiveCRS}
-         * is changed in same time the {@link #localToObjectiveCRS} transform.
+         * is changed in same time than the {@link #localToObjectiveCRS} transform, so the
number of dimensions
+         * may be temporarily mismatched. This method does not invoke {@link #updateLocalToPositionCRS()};
+         * that call must be done by the caller when ready.
          */
         final void setNoCheck(final MathTransform newValue) {
             super.set(newValue);
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/MenuSync.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/MenuSync.java
index e841231..4ba7c6f 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/MenuSync.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/MenuSync.java
@@ -30,6 +30,8 @@ import javafx.scene.control.MenuItem;
 import javafx.scene.control.RadioMenuItem;
 import javafx.scene.control.ToggleGroup;
 import org.opengis.referencing.ReferenceSystem;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.apache.sis.internal.referencing.ReferencingUtilities;
 import org.apache.sis.internal.gui.GUIUtilities;
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.util.resources.Vocabulary;
@@ -102,16 +104,18 @@ final class MenuSync extends SimpleObjectProperty<ReferenceSystem>
implements Ev
     }
 
     /**
-     * Sets the initial value to the first item in the {@code systems} list, if any.
-     * This method is invoked in JavaFX thread at construction time or, if it didn't
-     * work, at some later time when the systems list may contain an element.
+     * Sets the initial value to the first two-dimensional item in the {@code systems} list,
if any.
+     * This method is invoked in JavaFX thread at construction time or, if it didn't work,
+     * at some later time when the systems list may contain an element.
      * This method should not be invoked anymore after initialization succeeded.
      */
     private void initialize(final ObservableList<? extends ReferenceSystem> systems)
{
         for (final ReferenceSystem system : systems) {
-            if (system != RecentReferenceSystems.OTHER) {
-                set(system);
-                break;
+            if (system instanceof CoordinateReferenceSystem) {
+                if (ReferencingUtilities.getDimension((CoordinateReferenceSystem) system)
== 2) {
+                    set(system);
+                    break;
+                }
             }
         }
     }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/EllipsoidalHeightCombiner.java
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/EllipsoidalHeightCombiner.java
index 18d551e..c67accc 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/EllipsoidalHeightCombiner.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/EllipsoidalHeightCombiner.java
@@ -142,9 +142,9 @@ public final class EllipsoidalHeightCombiner {
                     axes[axisPosition %= 3] = vertical.getCoordinateSystem().getAxis(0);
                     final Map<String,?> csProps  = IdentifiedObjects.getProperties(cs,
CoordinateSystem.IDENTIFIERS_KEY);
                     final Map<String,?> crsProps = (components.length == 2) ? properties
-                                                   : IdentifiedObjects.getProperties(crs,
CoordinateReferenceSystem.IDENTIFIERS_KEY);
+                                                 : IdentifiedObjects.getProperties(crs, CoordinateReferenceSystem.IDENTIFIERS_KEY);
                     if (crs instanceof GeodeticCRS) {
-                        cs = factories.getCSFactory().createEllipsoidalCS(csProps, axes[0],
axes[1], axes[2]);
+                        cs  = factories.getCSFactory() .createEllipsoidalCS(csProps, axes[0],
axes[1], axes[2]);
                         crs = factories.getCRSFactory().createGeographicCRS(crsProps, ((GeodeticCRS)
crs).getDatum(), (EllipsoidalCS) cs);
                     } else {
                         final ProjectedCRS proj = (ProjectedCRS) crs;
@@ -162,7 +162,7 @@ public final class EllipsoidalHeightCombiner {
                         fromBase = factories.getCoordinateOperationFactory().createDefiningConversion(
                                     IdentifiedObjects.getProperties(fromBase),
                                     fromBase.getMethod(), fromBase.getParameterValues());
-                        cs = factories.getCSFactory().createCartesianCS(csProps, axes[0],
axes[1], axes[2]);
+                        cs  = factories.getCSFactory() .createCartesianCS(csProps, axes[0],
axes[1], axes[2]);
                         crs = factories.getCRSFactory().createProjectedCRS(crsProps, base,
fromBase, (CartesianCS) cs);
                     }
                     /*
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java
index e2b3c06..05b9de1 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java
@@ -56,7 +56,7 @@ import org.apache.sis.util.logging.Logging;
  * "Geocentric translations" is an operation name defined by EPSG.</div>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.8
+ * @version 1.1
  * @since   0.7
  * @module
  */
@@ -394,14 +394,14 @@ public abstract class GeocentricAffine extends GeodeticOperation {
          * Following lines will set all Bursa-Wolf parameter values (scale, translation
          * and rotation terms). In the particular case of Molodensky method, we have an
          * additional parameter for the number of source and target dimensions (2 or 3).
+         * If the number of source and target dimensions are not the same, set to 3 and
+         * let the caller adds or removes an ellipsoidal height as needed.
          */
         final Parameters values = createParameters(descriptor, parameters, isTranslation);
         switch (method) {
             case MOLODENSKY:
             case ABRIDGED_MOLODENSKY: {
-                if (dimension <= 3) {
-                    values.getOrCreate(Molodensky.DIMENSION).setValue(dimension);
-                }
+                values.getOrCreate(Molodensky.DIMENSION).setValue(Math.min(dimension, 3));
                 break;
             }
         }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffineBetweenGeographic.java
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffineBetweenGeographic.java
index 5c9d580..96a2e34 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffineBetweenGeographic.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffineBetweenGeographic.java
@@ -59,9 +59,7 @@ public abstract class GeocentricAffineBetweenGeographic extends GeocentricAffine
      * in this class) because this parameter is used with both two- and three-dimensional
operation methods.
      * If we want to provide a default value, we could but it would complicate a little bit
the code since we
      * could no longer reuse the same {@code PARAMETERS} constant for operation methods of
any number of dimensions.
-     * Furthermore it would not solve the case where the number of input dimensions is different
than the number of
-     * output dimensions. We can not afford to have wrong default values since it would confuse
our interpretation
-     * of user's parameters in {@link #createMathTransform(MathTransformFactory, ParameterValueGroup)}.</p>
+     * TODO: this paragraph will not apply anymore after {@link #redimensioned} is removed.</p>
      *
      * @see GeographicToGeocentric#DIMENSION
      *
@@ -171,8 +169,8 @@ public abstract class GeocentricAffineBetweenGeographic extends GeocentricAffine
     }
 
     /**
-     * Notifies {@code DefaultMathTransformFactory} that map projections require values for
the
-     * {@code "src_semi_major"}, {@code "src_semi_minor"} , {@code "tgt_semi_major"} and
+     * Notifies {@code DefaultMathTransformFactory} that this operation requires values for
+     * the {@code "src_semi_major"}, {@code "src_semi_minor"}, {@code "tgt_semi_major"} and
      * {@code "tgt_semi_minor"} parameters.
      *
      * @return 3, meaning that the operation requires source and target ellipsoids.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
index 30016a5..0e98480 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
@@ -585,11 +585,15 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry
{
              *    - [Abridged] Molodensky          (as an approximation of geocentric translation)
              *    - Identity                       (if the desired accuracy is so large than
we can skip datum shift)
              *
-             * TODO: if both CS are ellipsoidal but with different number of dimensions,
then we should use
-             * an intermediate 3D geographic CRS in order to enable the use of Molodensky
method if desired.
+             * If both CS are ellipsoidal but with different number of dimensions, then a
three-dimensional
+             * operation is used and `DefaultMathTransformFactory` will add an ellipsoidal
height on-the-fly.
+             * We let the transform factory do this work instead than adding an intermediate
"geographic 2D
+             * to 3D" operation here because the transform factory works with normalized
CS, which avoid the
+             * need the search in which dimension to add the ellipsoidal height (it should
always be last,
+             * but SIS is tolerant to unusual axis order).
              */
-            final DatumShiftMethod preferredMethod = DatumShiftMethod.forAccuracy(desiredAccuracy);
-            parameters = GeocentricAffine.createParameters(sourceCS, targetCS, datumShift,
preferredMethod);
+            parameters = GeocentricAffine.createParameters(sourceCS, targetCS, datumShift,
+                                            DatumShiftMethod.forAccuracy(desiredAccuracy));
             if (parameters == null) {
                 /*
                  * Failed to select a coordinate operation. Maybe because the coordinate
system types are not the same.
@@ -634,18 +638,15 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry
{
              */
             final int sourceDim = sourceCS.getDimension();
             final int targetDim = targetCS.getDimension();
-            if ((sourceDim & ~1) == 2                           // sourceDim == 2 or
3.
-                    && (sourceDim ^ targetDim) == 1             // abs(sourceDim
- targetDim) == 1.
-                    && (sourceCS instanceof EllipsoidalCS)
-                    && (targetCS instanceof EllipsoidalCS))
-            {
-                parameters = (sourceDim == 2 ? Geographic2Dto3D.PARAMETERS
-                                             : Geographic3Dto2D.PARAMETERS).createValue();
+            if (sourceDim == 2 && targetDim == 3 && sourceCS instanceof EllipsoidalCS)
{
+                parameters = Geographic2Dto3D.PARAMETERS.createValue();
+            } else if (sourceDim == 3 && targetDim == 2 && targetCS instanceof
EllipsoidalCS) {
+                parameters = Geographic3Dto2D.PARAMETERS.createValue();
             } else {
                 /*
                  * TODO: instead than creating parameters for an identity operation, we should
create the
                  *       CoordinateOperation directly from the MathTransform created by mtFactory
below.
-                 *       The intent if to get the correct OperationMethod, which should not
be "Affine"
+                 *       The intent is to get the correct OperationMethod, which should not
be "Affine"
                  *       if there is a CS type change.
                  */
                 parameters = Affine.identity(targetDim);
@@ -758,7 +759,7 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry
{
                         .createVerticalCS(derivedFrom(heightCS), expectedAxis));
             }
         }
-        if (!isEllipsoidalHeight) {                     // 'false' if we need to change datum,
unit or axis direction.
+        if (!isEllipsoidalHeight) {                     // `false` if we need to change datum,
unit or axis direction.
             heightCRS = toAuthorityDefinition(VerticalCRS.class, factorySIS.getCRSFactory()
                     .createVerticalCRS(derivedFrom(heightCRS), CommonCRS.Vertical.ELLIPSOIDAL.datum(),
heightCS));
         }
@@ -967,8 +968,7 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry
{
             } else if (stepComponents.length == 1) {
                 stepTargetCRS = target;                 // Slight optimization of the next
block.
             } else {
-                final EllipsoidalHeightCombiner c = new EllipsoidalHeightCombiner(factorySIS.getCRSFactory(),
factorySIS.getCSFactory(), factory);
-                stepTargetCRS = toAuthorityDefinition(CoordinateReferenceSystem.class, c.createCompoundCRS(derivedFrom(target),
stepComponents));
+                stepTargetCRS = createCompoundCRS(target, stepComponents);
             }
             int delta = source.getCoordinateSystem().getDimension();
             final int startAtDimension = endAtDimension;
@@ -1041,6 +1041,24 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry
{
     }
 
     /**
+     * Creates a compound CRS, but we special processing for (two-dimensional Geographic
+ ellipsoidal heights) tuples.
+     * If any such tuple is found, a three-dimensional geographic CRS is created instead
than the compound CRS.
+     *
+     * @param  template    the CRS from which to inherit properties.
+     * @param  components  ordered array of {@code CoordinateReferenceSystem} objects.
+     * @return the coordinate reference system for the given properties.
+     * @throws FactoryException if the object creation failed.
+     *
+     * @see EllipsoidalHeightCombiner#createCompoundCRS(Map, CoordinateReferenceSystem...)
+     */
+    private CoordinateReferenceSystem createCompoundCRS(final CoordinateReferenceSystem template,
+            final CoordinateReferenceSystem[] components) throws FactoryException
+    {
+        EllipsoidalHeightCombiner c = new EllipsoidalHeightCombiner(factorySIS.getCRSFactory(),
factorySIS.getCSFactory(), factory);
+        return toAuthorityDefinition(CoordinateReferenceSystem.class, c.createCompoundCRS(derivedFrom(template),
components));
+    }
+
+    /**
      * Concatenates two operation steps.
      * The new concatenated operation gets an automatically generated name.
      *
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
index ca1e6e6..ea43943 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
@@ -1236,25 +1236,24 @@ public class DefaultMathTransformFactory extends AbstractFactory implements
Math
          * Conversely if the source CS is missing a height, add a height with NaN values.
          * After this block, the dimensions of `step1` and `step2` should match.
          *
-         * Note: when adding an ellipsoidal height, we set the height value to NaN instead
than 0 (Earth surface)
-         * because the given `parameterized` transform may be a Molodensky transform or anything
else that could
-         * use the height in its calculation. If we have to add a height, maybe the parameterized
transform is a
-         * 2D Molodensky instead than a 3D Molodensky and the height is just propagated to
the output CS by a
-         * "passthrough" transform. The result is not the same as if a 3D Molodensky was
used in the first place.
+         * When adding an ellipsoidal height, there is two scenarios: the ellipsoidal height
may be used by the
+         * parameterized operation, or it may be passed through (in which case the operation
ignores the height).
+         * If the height is expected as operation input, set the height to 0. Otherwise (the
pass through case),
+         * set the height to NaN. We do that way because the given `parameterized` transform
may be a Molodensky
+         * transform or anything else that could use the height in its calculation. If we
have to add a height as
+         * a pass through dimension, maybe the parameterized transform is a 2D Molodensky
instead than a 3D Molodensky.
+         * The result of passing through the height is not the same as if a 3D Molodensky
was used in the first place.
          * A NaN value avoid to give a false sense of accuracy.
          */
         final int sourceDim = step1.getTargetDimensions();
         final int targetDim = step2.getSourceDimensions();
-        final int change    = targetDim - sourceDim;
-        if (change != 0) {
-            if (change > numTrailingCoordinates) {
-                // If we add dimensions, they must be passthrough dimensions.
-                throw new InvalidGeodeticParameterException(canNotAssociateToCS(parameterized,
context));
-            }
-            ensureDimensionChangeAllowed(parameterized, context, change, targetDim);
+        int insertCount = targetDim - sourceDim;
+        if (insertCount != 0) {
+            ensureDimensionChangeAllowed(parameterized, context, insertCount, targetDim);
             final Matrix resize = Matrices.createZero(targetDim+1, sourceDim+1);
             for (int j=0; j<targetDim; j++) {
-                resize.setElement(j, Math.min(j, sourceDim), (j < sourceDim) ? 1 : Double.NaN);
+                resize.setElement(j, Math.min(j, sourceDim), (j < sourceDim) ? 1 :
+                        ((--insertCount >= numTrailingCoordinates) ? 0 : Double.NaN));
       // See above note.
             }
             resize.setElement(targetDim, sourceDim, 1);     // Element in the lower-right
corner.
             step1 = createConcatenatedTransform(step1, createAffineTransform(resize));
@@ -1303,13 +1302,9 @@ public class DefaultMathTransformFactory extends AbstractFactory implements
Math
                 return;
             }
         }
-        throw new InvalidGeodeticParameterException(canNotAssociateToCS(parameterized, context));
-    }
-
-    /**
-     * Creates the error message for a transform that can not be associated with given coordinate
systems.
-     */
-    private static String canNotAssociateToCS(final MathTransform parameterized, final Context
context) {
+        /*
+         * Creates the error message for a transform that can not be associated with given
coordinate systems.
+         */
         String name = null;
         if (parameterized instanceof Parameterized) {
             name = IdentifiedObjects.getDisplayName(((Parameterized) parameterized).getParameterDescriptors(),
null);
@@ -1324,7 +1319,7 @@ public class DefaultMathTransformFactory extends AbstractFactory implements
Math
                      .append(parameterized.getTargetDimensions()).append("D)");
         cs = context.getTargetCS();
         if (cs != null) b.append(" → ").append(cs.getDimension()).append('D');
-        return Resources.format(Resources.Keys.CanNotAssociateToCS_2, name, b);
+        throw new InvalidGeodeticParameterException(Resources.format(Resources.Keys.CanNotAssociateToCS_2,
name, b));
     }
 
     /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactoryTest.java
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactoryTest.java
index 7203e55..f117804 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactoryTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactoryTest.java
@@ -314,6 +314,7 @@ public final strictfp class DefaultMathTransformFactoryTest extends TestCase
{
         }), MathTransforms.getMatrix(mt), STRICT);
         /*
          * Transform from 2D to 3D. Coordinate values in the height dimension are unknown
(NaN).
+         * This case happen when the third dimension is handled as a "pass through" dimension.
          */
         context.setSource(HardCodedCS.GEODETIC_2D);
         context.setTarget(HardCodedCS.GEODETIC_3D);
@@ -325,6 +326,17 @@ public final strictfp class DefaultMathTransformFactoryTest extends TestCase
{
             0, 0, 1
         }), MathTransforms.getMatrix(mt), STRICT);
         /*
+         * Same transform from 2D to 3D, but this time with the height consumed by the parameterized
operation.
+         * This is differentiated from the previous case by the fact that the parameterized
operation is three-dimensional.
+         */
+        mt = factory.swapAndScaleAxes(MathTransforms.identity(3), context);
+        assertMatrixEquals("2D → 3D", Matrices.create(4, 3, new double[] {
+            1, 0, 0,
+            0, 1, 0,
+            0, 0, 0,
+            0, 0, 1
+        }), MathTransforms.getMatrix(mt), STRICT);
+        /*
          * Test error message when adding a dimension that is not ellipsoidal height.
          */
         context.setSource(HardCodedCS.CARTESIAN_2D);


Mime
View raw message