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 03e05e5  Allow `ResampledGridCoverage` to work (under some conditions) 
even if the coordinate operation can not be reduced to 2 dimensions.
03e05e5 is described below

commit 03e05e58c5b5b7c93e41d843b3591e2f2900ef5b
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Thu May 28 14:10:04 2020 +0200

    Allow `ResampledGridCoverage` to work (under some conditions) even if the 
coordinate operation can not be reduced to 2 dimensions.
---
 .../sis/coverage/grid/ResampledGridCoverage.java   |  42 ++++++++-
 .../java/org/apache/sis/image/PixelIterator.java   |   4 +
 .../java/org/apache/sis/image/ResampledImage.java  |   4 +
 .../org/apache/sis/internal/feature/Resources.java |  10 ++
 .../sis/internal/feature/Resources.properties      |   2 +
 .../sis/internal/feature/Resources_fr.properties   |   2 +
 .../coverage/grid/ResampledGridCoverageTest.java   |   7 +-
 .../referencing/factory/sql/EPSGDataAccess.java    |   2 +-
 .../operation/transform/TransformSeparator.java    | 103 +++++++++++++++------
 .../transform/TransformSeparatorTest.java          |  12 +++
 .../java/org/apache/sis/measure/AngleFormat.java   |   4 +-
 11 files changed, 158 insertions(+), 34 deletions(-)

diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
index f63b4c1..0a171fb 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
@@ -31,6 +31,7 @@ import org.apache.sis.geometry.Envelopes;
 import org.apache.sis.image.ImageProcessor;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.internal.feature.Resources;
 import org.apache.sis.internal.util.DoubleDouble;
 import org.apache.sis.internal.referencing.ExtendedPrecisionMatrix;
 import org.apache.sis.referencing.operation.transform.LinearTransform;
@@ -376,7 +377,7 @@ final class ResampledGridCoverage extends GridCoverage {
                 GeneralEnvelope bounds = new 
GeneralEnvelope(resampled.getEnvelope());
                 bounds.intersect(target.getEnvelope());
                 bounds = Envelopes.transform(targetCornerToCRS.inverse(), 
bounds);
-                targetExtent = new GridExtent(bounds, 
GridRoundingMode.ENCLOSING, null, targetExtent, null);
+                targetExtent = new GridExtent(bounds, 
GridRoundingMode.NEAREST, null, targetExtent, null);
                 resampled = new GridGeometry(targetExtent, 
PixelInCell.CELL_CENTER, targetCenterToCRS, targetCRS);
                 isGeometryExplicit = true;
             }
@@ -456,7 +457,44 @@ final class ResampledGridCoverage extends GridCoverage {
             final TransformSeparator sep = new 
TransformSeparator(toSourceCenter);
             sep.addSourceDimensions(resampledDimensions);
             sep.addTargetDimensions(sourceDimensions);
-            final MathTransform toSourceSlice = sep.separate();
+            sep.setSourceExpandable(true);
+            MathTransform toSourceSlice = sep.separate();
+            final int[] requiredSources = sep.getSourceDimensions();
+            if (requiredSources.length > BIDIMENSIONAL) {
+                /*
+                 * If we enter in this block, TransformSeparator can not 
create a MathTransform with only the 2
+                 * requested source dimensions; it needs more sources. In such 
case, if coordinates in missing
+                 * dimensions can be set to constant values (grid low == grid 
high), create a transform which
+                 * will add new dimensions with coordinates set to those 
constant values. The example below
+                 * passes the two first dimensions as-is and set the third 
dimensions to constant value 7:
+                 *
+                 *     ┌   ┐   ┌         ┐┌   ┐
+                 *     │ x │   │ 1  0  0 ││ x │
+                 *     │ y │ = │ 0  1  0 ││ y │
+                 *     │ z │   │ 0  0  7 ││ 1 │
+                 *     │ 1 │   │ 0  0  1 │└   ┘
+                 *     └   ┘   └         ┘
+                 */
+                final MatrixSIS m = Matrices.createZero(requiredSources.length 
+ 1, BIDIMENSIONAL + 1);
+                m.setElement(requiredSources.length, BIDIMENSIONAL, 1);
+                for (int j=0; j < requiredSources.length; j++) {
+                    final int r = requiredSources[j];
+                    final int i = Arrays.binarySearch(resampledDimensions, r);
+                    if (i >= 0) {
+                        m.setElement(j, i, 1);
+                    } else {
+                        final long low = sliceExtent.getLow(r);
+                        if (low == sliceExtent.getHigh(r)) {
+                            m.setElement(j, BIDIMENSIONAL, low);
+                        } else {
+                            throw new CannotEvaluateException(Resources.format(
+                                    
Resources.Keys.TransformDependsOnDimension_1,
+                                    sliceExtent.getAxisIdentification(r, r)));
+                        }
+                    }
+                }
+                toSourceSlice = 
MathTransforms.concatenate(MathTransforms.linear(m), toSourceSlice);
+            }
             /*
              * `this.toSource` is a transform from source cell coordinates to 
target cell coordinates.
              * We need a transform from source pixel coordinates to target 
pixel coordinates (in images).
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java 
b/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java
index cbc7d48..f3e53fd 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java
@@ -36,6 +36,7 @@ import org.opengis.coverage.grid.SequenceType;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.measure.NumberRange;
+import org.apache.sis.internal.feature.Resources;
 
 import static java.lang.Math.floorDiv;
 import static org.apache.sis.internal.util.Numerics.ceilDiv;
@@ -185,6 +186,9 @@ public abstract class PixelIterator {
      */
     private static Rectangle intersection(int x, int y, int width, int height, 
Rectangle subArea, Dimension window) {
         if (window != null) {
+            if (width <= 0 || height <= 0) {
+                throw new 
IllegalArgumentException(Resources.format(Resources.Keys.EmptyImage));
+            }
             ArgumentChecks.ensureBetween("window.width",  1, width,  
window.width);
             ArgumentChecks.ensureBetween("window.height", 1, height, 
window.height);
             width  -= (window.width  - 1);
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java 
b/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java
index 6418239..ab915da 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java
@@ -34,6 +34,7 @@ import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.internal.coverage.j2d.ImageLayout;
 import org.apache.sis.internal.coverage.j2d.ImageUtilities;
+import org.apache.sis.internal.feature.Resources;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.ArgumentChecks;
@@ -144,6 +145,9 @@ public class ResampledImage extends ComputedImage {
                              final Interpolation interpolation, final Number[] 
fillValues)
     {
         super(ImageLayout.DEFAULT.createCompatibleSampleModel(source, bounds), 
source);
+        if (source.getWidth() <= 0 || source.getHeight() <= 0) {
+            throw new 
IllegalArgumentException(Resources.format(Resources.Keys.EmptyImage));
+        }
         ArgumentChecks.ensureNonNull("interpolation", interpolation);
         ArgumentChecks.ensureStrictlyPositive("width",  width  = bounds.width);
         ArgumentChecks.ensureStrictlyPositive("height", height = 
bounds.height);
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java
index 5a790bf..5a0b823 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java
@@ -148,6 +148,11 @@ public final class Resources extends IndexedResourceBundle 
{
         public static final short DependencyNotFound_3 = 8;
 
         /**
+         * Image has zero pixel.
+         */
+        public static final short EmptyImage = 73;
+
+        /**
          * Empty tile or image region.
          */
         public static final short EmptyTileOrImageRegion = 67;
@@ -360,6 +365,11 @@ public final class Resources extends IndexedResourceBundle 
{
         public static final short TooManyQualitatives = 48;
 
         /**
+         * Coordinate operation depends on grid dimension {0}.
+         */
+        public static final short TransformDependsOnDimension_1 = 74;
+
+        /**
          * The {0} geometry library is not available in current runtime 
environment.
          */
         public static final short UnavailableGeometryLibrary_1 = 21;
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.properties
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.properties
index 3cb001c..fa83b8a 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.properties
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.properties
@@ -37,6 +37,7 @@ CategoryRangeOverlap_4            = The two categories 
\u201c{0}\u201d and \u201
 CharacteristicsAlreadyExists_2    = Characteristics \u201c{1}\u201d already 
exists in attribute \u201c{0}\u201d.
 CharacteristicsNotFound_2         = No characteristics named \u201c{1}\u201d 
has been found in \u201c{0}\u201d attribute.
 DependencyNotFound_3              = Operation \u201c{0}\u201d requires a 
\u201c{1}\u201d property, but no such property has been found in 
\u201c{2}\u201d.
+EmptyImage                        = Image has zero pixel.
 EmptyTileOrImageRegion            = Empty tile or image region.
 GridCoordinateOutsideCoverage_4   = Indices ({3}) are outside grid coverage. 
The value in dimension {0} shall be between {1,number} and {2,number} inclusive.
 GridEnvelopeMustBeNDimensional_1  = The grid envelope must have at least {0} 
dimensions.
@@ -77,6 +78,7 @@ PropertyAlreadyExists_2           = Property \u201c{1}\u201d 
already exists in f
 PropertyNotFound_2                = No property named \u201c{1}\u201d has been 
found in \u201c{0}\u201d feature.
 TileErrorFlagSet_2                = Tile ({0}, {1}) has the error flag set.
 TooManyQualitatives               = Too many qualitative categories.
+TransformDependsOnDimension_1     = Coordinate operation depends on grid 
dimension {0}.
 UnavailableGeometryLibrary_1      = The {0} geometry library is not available 
in current runtime environment.
 UnconvertibleGridCoordinate_2     = Can not convert grid coordinate {1} to 
type \u2018{0}\u2019.
 UnexpectedNumberOfBands_2         = Expected {0} bands but got {1}.
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources_fr.properties
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources_fr.properties
index 324af81..0e78e35 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources_fr.properties
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources_fr.properties
@@ -42,6 +42,7 @@ CategoryRangeOverlap_4            = Les deux cat\u00e9gories 
\u00ab\u202f{0}\u20
 CharacteristicsAlreadyExists_2    = La caract\u00e9ristique 
\u00ab\u202f{1}\u202f\u00bb existe d\u00e9j\u00e0 dans l\u2019attribut 
\u00ab\u202f{0}\u202f\u00bb.
 CharacteristicsNotFound_2         = Aucune caract\u00e9ristique nomm\u00e9e 
\u00ab\u202f{1}\u202f\u00bb n\u2019a \u00e9t\u00e9 trouv\u00e9e dans 
l\u2019attribut \u00ab\u202f{0}\u202f\u00bb.
 DependencyNotFound_3              = L\u2019op\u00e9ration 
\u00ab\u202f{0}\u202f\u00bb n\u00e9cessite une propri\u00e9t\u00e9 
\u00ab\u202f{1}\u202f\u00bb, mais cette propri\u00e9t\u00e9 n\u2019a pas 
\u00e9t\u00e9 trouv\u00e9e dans \u00ab\u202f{2}\u202f\u00bb.
+EmptyImage                        = L\u2019image a z\u00e9ro pixel.
 EmptyTileOrImageRegion            = La tuile ou la r\u00e9gion de l\u2019image 
est vide.
 GridCoordinateOutsideCoverage_4   = Les indices ({3}) sont en dehors du 
domaine de la grille. La valeur dans la dimension {0} doit \u00eatre entre 
{1,number} et {2,number} inclusivement.
 GridEnvelopeMustBeNDimensional_1  = L\u2019enveloppe de la grille doit avoir 
au moins {0} dimensions.
@@ -83,6 +84,7 @@ PropertyAlreadyExists_2           = La propri\u00e9t\u00e9 
\u00ab\u202f{1}\u202f
 PropertyNotFound_2                = Aucune propri\u00e9t\u00e9 nomm\u00e9e 
\u00ab\u202f{1}\u202f\u00bb n\u2019a \u00e9t\u00e9 trouv\u00e9e dans 
l\u2019entit\u00e9 \u00ab\u202f{0}\u202f\u00bb.
 TileErrorFlagSet_2                = La tuile ({0}, {1}) est marqu\u00e9e comme 
ayant une erreur.
 TooManyQualitatives               = Trop de cat\u00e9gories qualitatives.
+TransformDependsOnDimension_1     = L\u2019op\u00e9ration sur les 
coordonn\u00e9es d\u00e9pend de la dimension {0} de la grille.
 UnavailableGeometryLibrary_1      = La biblioth\u00e8que de 
g\u00e9om\u00e9tries {0} n\u2019est pas disponible dans l\u2019environnement 
d\u2019ex\u00e9cution actuel.
 UnconvertibleGridCoordinate_2     = Ne peut pas convertir la coordonn\u00e9e 
de grille {1} vers le type \u2018{0}\u2019.
 UnexpectedNumberOfBands_2         = On attendait {0} bandes mais {1} ont 
\u00e9t\u00e9 sp\u00e9cifi\u00e9es.
diff --git 
a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
 
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
index 5459f18..ae250e7 100644
--- 
a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
+++ 
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
@@ -464,7 +464,6 @@ public final strictfp class ResampledGridCoverageTest 
extends TestCase {
      * @throws TransformException if some coordinates can not be transformed 
to the target grid geometry.
      */
     @Test
-    @org.junit.Ignore("Needs more development")
     public void testNonSeparableGridToCRS() throws TransformException {
         final GridCoverage source = createCoverageND(false);
         final MatrixSIS nonSeparableMatrix = Matrices.createDiagonal(4, 4);
@@ -489,14 +488,16 @@ public final strictfp class ResampledGridCoverageTest 
extends TestCase {
             }
         }
         final GridGeometry targetGeom = new GridGeometry(
-                source.getGridGeometry().getExtent(),
+                null,           // Let the resample operation compute the 
extent automatically.
                 CELL_CENTER, nonSeparableG2C,
                 source.getCoordinateReferenceSystem());
         /*
          * Real test is below (above code was only initialization).
+         * The source image is 6×6 but the target image is 7×7 with
+         * the last row and column left black.
          */
         final GridCoverage result = resample(source, targetGeom);
-        assertPixelsEqual(source.render(null), null, result.render(null), 
null);
+        assertPixelsEqual(source.render(null), null, result.render(null), new 
Rectangle(2*QS, 2*QS));
     }
 
     /**
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index 472bda7..7dc1e3b 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
@@ -428,7 +428,7 @@ public class EPSGDataAccess extends 
GeodeticAuthorityFactory implements CRSAutho
         try {
             /*
              * Get the most recent version number from the history table. We 
get the date in local timezone
-             * instead then UTC because the date is for information purpose 
only, and the local timezone is
+             * instead than UTC because the date is for information purpose 
only, and the local timezone is
              * more likely to be shown nicely (without artificial hours) to 
the user.
              */
             final String query = translator.apply("SELECT VERSION_NUMBER, 
VERSION_DATE FROM [Version History]" +
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/TransformSeparator.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/TransformSeparator.java
index 3e26722..72e38f8 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/TransformSeparator.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/TransformSeparator.java
@@ -49,7 +49,7 @@ import org.apache.sis.util.ArraysExt;
  * The output dimensions can be verified with a call to {@link 
#getTargetDimensions()}.</div>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   0.7
  * @module
  */
@@ -97,6 +97,15 @@ public class TransformSeparator {
     private final MathTransformsOrFactory factory;
 
     /**
+     * Whether {@link #separate()} is allowed to add new dimensions in {@link 
#sourceDimensions}
+     * if this is required for computing all values specified in {@link 
#targetDimensions}.
+     *
+     * @see #isSourceExpandable()
+     * @see #setSourceExpandable(boolean)
+     */
+    private boolean isSourceExpandable;
+
+    /**
      * Constructs a separator for the given transform.
      *
      * @param transform  the transform to separate.
@@ -119,13 +128,15 @@ public class TransformSeparator {
 
     /**
      * Resets this transform separator in the same state than after 
construction. This method clears any
-     * {@linkplain #getSourceDimensions() source dimensions} and {@linkplain 
#getTargetDimensions() target dimensions} settings.
+     * {@linkplain #getSourceDimensions() source} and {@linkplain 
#getTargetDimensions() target dimensions}
+     * settings and disables {@linkplain #isSourceExpandable() source 
expansion}.
      * This method can be invoked when the same {@code MathTransform} needs to 
be separated in more than one part,
      * for example an horizontal and a vertical component.
      */
     public void clear() {
-        sourceDimensions = null;
-        targetDimensions = null;
+        sourceDimensions   = null;
+        targetDimensions   = null;
+        isSourceExpandable = false;
     }
 
     /**
@@ -351,6 +362,37 @@ public class TransformSeparator {
     }
 
     /**
+     * Returns whether {@code separate()} is allowed to expand the list of 
source dimensions.
+     * The default value is {@code false}, which means that {@link 
#separate()} either returns
+     * a {@link MathTransform} having exactly the requested {@linkplain 
#getSourceDimensions()
+     * source dimensions}, or throws a {@link FactoryException}.
+     *
+     * @return whether {@code separate()} is allowed to add new source 
dimensions
+     *         instead of throwing a {@link FactoryException}.
+     *
+     * @since 1.1
+     */
+    public boolean isSourceExpandable() {
+        return isSourceExpandable;
+    }
+
+    /**
+     * Sets whether {@code separate()} is allowed to expand the list of source 
dimensions.
+     * The default value is {@code false}, which means that {@code separate()} 
will throw a {@link FactoryException}
+     * if some {@linkplain #getTargetDimensions() target dimensions} can not 
be computed without inputs that are not
+     * in the list of {@linkplain #getSourceDimensions() source dimensions}. 
If this flag is set to {@code true},
+     * then {@link #separate()} will be allowed to augment the list of source 
dimensions with any inputs that are
+     * essential for producing all requested outputs.
+     *
+     * @param  enabled  whether to allow source dimensions expansion.
+     *
+     * @since 1.1
+     */
+    public void setSourceExpandable(final boolean enabled) {
+        isSourceExpandable = enabled;
+    }
+
+    /**
      * Separates the math transform specified at construction time for given 
dimension indices.
      * This method creates a math transform that use only the {@linkplain 
#addSourceDimensions(int...) specified
      * source dimensions} and return only the {@linkplain 
#addTargetDimensions(int...) specified target dimensions}.
@@ -377,7 +419,10 @@ public class TransformSeparator {
      */
     public MathTransform separate() throws FactoryException {
         MathTransform tr = transform;
-        final boolean isSourceSpecified = (sourceDimensions != null);
+        final int[] specifiedSources = sourceDimensions;
+        if (isSourceExpandable) {
+            sourceDimensions = null;                        // Take all 
sources for now, will filter later.
+        }
         if (sourceDimensions == null || containsAll(sourceDimensions, 0, 
tr.getSourceDimensions())) {
             if (targetDimensions != null && !containsAll(targetDimensions, 0, 
tr.getTargetDimensions())) {
                 tr = filterTargetDimensions(tr, targetDimensions);
@@ -395,7 +440,7 @@ public class TransformSeparator {
              */
             final int[] requested = targetDimensions;
             tr = filterSourceDimensions(tr, sourceDimensions);            // 
May update targetDimensions.
-            assert ArraysExt.isSorted(targetDimensions, true) : 
"targetDimensions";
+            assert ArraysExt.isSorted(targetDimensions, true);
             if (requested != null) {
                 final int[] inferred = targetDimensions;
                 targetDimensions = requested;
@@ -428,8 +473,8 @@ public class TransformSeparator {
             expected = targetDimensions.length;
             actual   = tr.getTargetDimensions();
             if (actual == expected) {
-                if (!isSourceSpecified) {
-                    tr = removeUnusedSourceDimensions(tr);
+                if (specifiedSources == null || isSourceExpandable) {
+                    tr = removeUnusedSourceDimensions(tr, specifiedSources);
                 }
                 return tr;
             }
@@ -683,44 +728,50 @@ reduce:     for (int j=0; j <= numTgt; j++) {
 
     /**
      * Removes the sources dimensions that are not required for computing the 
target dimensions.
-     * This method is invoked only if {@link #sourceDimensions} is non-null at 
{@link #separate()} invocation time.
+     * This method is invoked only if {@link #sourceDimensions} is null at 
{@link #separate()} invocation time.
      * This method can operate only on the first transform of a transformation 
chain.
      * If this method succeed, then {@link #sourceDimensions} will be updated.
      *
      * <p>This method can process only linear transforms (potentially 
indirectly through a concatenated transform).
      * Actually it would be possible to also process pass-through transform 
followed by a linear transform, but this
      * case should have been optimized during transform concatenation. If it 
is not the case, consider improving the
-     * {@link PassThroughTransform#tryConcatenate(boolean, MathTransform, 
MathTransformFactory)} method instead then
+     * {@link PassThroughTransform#tryConcatenate(boolean, MathTransform, 
MathTransformFactory)} method instead than
      * this one.</p>
      *
-     * @param  head  the first transform of a transformation chain.
+     * @param  head      the first transform of a transformation chain.
+     * @param  required  sources to keep even if not necessary, or {@code 
null} if none.
      * @return the reduced transform, or {@code head} if this method did not 
reduced the transform.
      */
-    private MathTransform removeUnusedSourceDimensions(final MathTransform 
head) {
+    private MathTransform removeUnusedSourceDimensions(final MathTransform 
head, final int[] required) {
         Matrix m = MathTransforms.getMatrix(head);
         if (m != null) {
-            int[] retainedDimensions = ArraysExt.EMPTY_INT;
-            final int dimension = m.getNumCol() - 1;            // Number of 
source dimensions (ignore translations column).
-            final int numRows   = m.getNumRow();                // Number of 
target dimensions + 1.
+            final int numRows   = m.getNumRow();            // Number of 
target dimensions + 1.
+            final int dimension = m.getNumCol() - 1;        // Number of 
source dimensions (ignore translations column).
+            int   retainedCount = 0;                        // Number of 
source dimensions to keep.
+            int[] retainedDimensions = new int[dimension];
             for (int i=0; i<dimension; i++) {
-                for (int j=0; j<numRows; j++) {
-                    if (m.getElement(j,i) != 0) {
-                        // Found a source dimension which is required by 
target dimension.
-                        final int length = retainedDimensions.length;
-                        retainedDimensions = Arrays.copyOf(retainedDimensions, 
length+1);
-                        retainedDimensions[length] = i;
-                        break;
+                if (required != null && Arrays.binarySearch(required, i) >= 0) 
{
+                    // Dimension to retain unconditionally.
+                    retainedDimensions[retainedCount++] = i;
+                } else {
+                    for (int j=0; j<numRows; j++) {
+                        if (m.getElement(j,i) != 0) {
+                            // Found a source dimension which is required by 
target dimension.
+                            retainedDimensions[retainedCount++] = i;
+                            break;
+                        }
                     }
                 }
             }
-            if (retainedDimensions.length != dimension) {
+            if (retainedCount != dimension) {
+                retainedDimensions = Arrays.copyOf(retainedDimensions, 
retainedCount);
                 /*
                  * If we do not retain all dimensions, remove the matrix 
columns corresponding to the excluded
                  * source dimensions and create a new transform. We remove 
consecutive columns in single calls
                  * to 'removeColumns', from 'lower' inclusive to 'upper' 
exclusive.
                  */
                 int upper = dimension;
-                for (int i = retainedDimensions.length; --i >= -1;) {
+                for (int i = retainedCount; --i >= -1;) {
                     final int keep = (i >= 0) ? retainedDimensions[i] : -1;
                     final int lower = keep + 1;                                
     // First column to exclude.
                     if (lower != upper) {
@@ -733,7 +784,7 @@ reduce:     for (int j=0; j <= numTgt; j++) {
                  * If the user specified source dimensions, the indices need 
to be adjusted.
                  * This loop has no effect if all source dimensions were kept 
before this method call.
                  */
-                for (int i=0; i<retainedDimensions.length; i++) {
+                for (int i=0; i<retainedCount; i++) {
                     retainedDimensions[i] = 
sourceDimensions[retainedDimensions[i]];
                 }
                 sourceDimensions = retainedDimensions;
@@ -741,7 +792,7 @@ reduce:     for (int j=0; j <= numTgt; j++) {
             }
         } else if (head instanceof ConcatenatedTransform) {
             final MathTransform transform1 = ((ConcatenatedTransform) 
head).transform1;
-            final MathTransform reduced = 
removeUnusedSourceDimensions(transform1);
+            final MathTransform reduced = 
removeUnusedSourceDimensions(transform1, required);
             if (reduced != transform1) {
                 return MathTransforms.concatenate(reduced, 
((ConcatenatedTransform) head).transform2);
             }
diff --git 
a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformSeparatorTest.java
 
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformSeparatorTest.java
index 806ecfb..fbc6664 100644
--- 
a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformSeparatorTest.java
+++ 
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformSeparatorTest.java
@@ -186,6 +186,18 @@ public final strictfp class TransformSeparatorTest extends 
TestCase {
             // This is the expected exception.
             assertNotNull(e.getMessage());
         }
+        /*
+         * Try again, but allow TransformSeparator to expand the list of 
source dimensions.
+         */
+        s.setSourceExpandable(true);
+        matrix = Matrices.create(3, 4, new double[] {
+            2, 0, 0, 7,
+            0, 5, 0, 6,
+            0, 0, 0, 1
+        });
+        assertMatrixEquals("transform", matrix, ((LinearTransform) 
s.separate()).getMatrix(), STRICT);
+        assertArrayEquals("sourceDimensions", new int[] {0, 1, 2}, 
s.getSourceDimensions());
+        assertArrayEquals("targetDimensions", new int[] {0, 1},    
s.getTargetDimensions());
     }
 
     /**
diff --git 
a/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java 
b/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java
index c09d269..2e49157 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java
@@ -1531,7 +1531,7 @@ BigBoss:    switch (skipSuffix(source, pos, 
DEGREES_FIELD)) {
                 /* ------------------------------------------
                  * STRING ANALYSIS FOLLOWING PRESUMED DEGREES
                  * ------------------------------------------
-                 * Found the seconds suffix instead then the degrees suffix. 
Move 'degrees'
+                 * Found the seconds suffix instead than the degrees suffix. 
Move 'degrees'
                  * value to 'seconds' and stop parsing, since seconds are the 
last field.
                  */
                 case SECONDS_FIELD: {
@@ -1585,7 +1585,7 @@ BigBoss:    switch (skipSuffix(source, pos, 
DEGREES_FIELD)) {
                         /* ------------------------------------------
                          * STRING ANALYSIS FOLLOWING PRESUMED MINUTES
                          * ------------------------------------------
-                         * Found the seconds suffix instead then the minutes 
suffix. Move 'minutes'
+                         * Found the seconds suffix instead than the minutes 
suffix. Move 'minutes'
                          * value to 'seconds' and stop parsing, since seconds 
are the last field.
                          */
                         case SECONDS_FIELD: {

Reply via email to