sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Add a GridGeometry.getExtent(Envelope) method. This require an improvement in TransformSeparator class.
Date Mon, 26 Nov 2018 19:45:53 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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 648d58a  Add a GridGeometry.getExtent(Envelope) method. This require an improvement
in TransformSeparator class.
648d58a is described below

commit 648d58af16fbcd68c0a357deb6b03ea936288944
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Nov 26 20:45:25 2018 +0100

    Add a GridGeometry.getExtent(Envelope) method. This require an improvement in TransformSeparator
class.
---
 .../org/apache/sis/coverage/grid/GridExtent.java   |  55 ++++++---
 .../org/apache/sis/coverage/grid/GridGeometry.java |  78 ++++++++++++-
 .../org/apache/sis/internal/raster/Resources.java  |   5 +
 .../sis/internal/raster/Resources.properties       |   1 +
 .../sis/internal/raster/Resources_fr.properties    |   1 +
 .../apache/sis/coverage/grid/GridExtentTest.java   |   8 +-
 .../apache/sis/coverage/grid/GridGeometryTest.java |  47 +++++++-
 .../operation/transform/TransformSeparator.java    | 123 ++++++++++++++++++++-
 .../transform/TransformSeparatorTest.java          |  54 +++++++++
 .../apache/sis/internal/util/CollectionsExt.java   |   2 +
 .../org/apache/sis/util/resources/Vocabulary.java  |   5 +
 .../sis/util/resources/Vocabulary.properties       |   1 +
 .../sis/util/resources/Vocabulary_fr.properties    |   1 +
 13 files changed, 345 insertions(+), 36 deletions(-)

diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
index b14edd8..61f28de 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
@@ -289,15 +289,18 @@ public class GridExtent implements Serializable {
      * The envelope CRS is ignored, except for identifying dimension names for information
purpose.
      * The way floating point values are rounded to integers may be adjusted in any future
version.
      *
-     * <p><b>NOTE:</b> this constructor is not public because its contract
is a bit approximative.</p>
+     * <p><b>NOTE:</b> this constructor is not public because its contract
is a bit approximate.</p>
      *
-     * @param  envelope  the envelope containing cell indices to store in a {@code GridExtent}.
+     * @param  envelope            the envelope containing cell indices to store in a {@code
GridExtent}.
+     * @param  enclosing           if the new grid is a sub-grid of a larger grid, that larger
grid. Otherwise {@code null}.
+     * @param  modifiedDimensions  if {@code enclosing} is non-null, the grid dimensions
to set from the envelope.
+     *                             The length of this array shall be equal to the {@code
envelope} dimension.
      *
      * @see #toCRS(MathTransform, MathTransform)
      */
-    GridExtent(final AbstractEnvelope envelope) {
+    GridExtent(final AbstractEnvelope envelope, final GridExtent enclosing, final int[] modifiedDimensions)
{
         final int dimension = envelope.getDimension();
-        coordinates = allocate(dimension);
+        coordinates = (enclosing != null) ? enclosing.coordinates.clone() : allocate(dimension);
         for (int i=0; i<dimension; i++) {
             final double min = envelope.getLower(i);
             final double max = envelope.getUpper(i);
@@ -328,8 +331,20 @@ public class GridExtent implements Serializable {
                         }
                     }
                 }
-                coordinates[i] = lower;
-                coordinates[i + dimension] = upper;
+                /*
+                 * At this point the grid range has been computed (lower to upper).
+                 * Update the coordinates accordingly.
+                 */
+                final int m = getDimension();
+                if (enclosing != null) {
+                    final int lo = (modifiedDimensions != null) ? modifiedDimensions[i] :
i;
+                    final int hi = lo + m;
+                    if (lower > coordinates[lo]) coordinates[lo] = Math.min(coordinates[hi],
lower);
+                    if (upper < coordinates[hi]) coordinates[hi] = Math.max(coordinates[lo],
upper);
+                } else {
+                    coordinates[i]   = lower;
+                    coordinates[i+m] = upper;
+                }
             } else {
                 throw new IllegalArgumentException(Resources.format(
                         Resources.Keys.IllegalGridEnvelope_3, i, min, max));
@@ -340,21 +355,25 @@ public class GridExtent implements Serializable {
          * Now try to infer dimension types from the CRS axes.
          * This is only for information purpose.
          */
-        DimensionNameType[] axisTypes = null;
-        final CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem();
-        if (crs != null) {
-            final CoordinateSystem cs = crs.getCoordinateSystem();
-            for (int i=0; i<dimension; i++) {
-                final DimensionNameType type = AXIS_DIRECTIONS.get(AxisDirections.absolute(cs.getAxis(i).getDirection()));
-                if (type != null) {
-                    if (axisTypes == null) {
-                        axisTypes = new DimensionNameType[dimension];
+        if (enclosing != null) {
+            types = enclosing.types;
+        } else {
+            DimensionNameType[] axisTypes = null;
+            final CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem();
+            if (crs != null) {
+                final CoordinateSystem cs = crs.getCoordinateSystem();
+                for (int i=0; i<dimension; i++) {
+                    final DimensionNameType type = AXIS_DIRECTIONS.get(AxisDirections.absolute(cs.getAxis(i).getDirection()));
+                    if (type != null) {
+                        if (axisTypes == null) {
+                            axisTypes = new DimensionNameType[dimension];
+                        }
+                        axisTypes[i] = type;
                     }
-                    axisTypes[i] = type;
                 }
             }
+            types = validateAxisTypes(axisTypes);
         }
-        types = validateAxisTypes(axisTypes);
     }
 
     /**
@@ -534,7 +553,7 @@ public class GridExtent implements Serializable {
      *                      If different, then this is assumed to map pixel centers instead
than pixel corners.
      * @return this grid extent in real world coordinates.
      *
-     * @see #GridExtent(AbstractEnvelope)
+     * @see #GridExtent(AbstractEnvelope, GridExtent, int[])
      */
     final GeneralEnvelope toCRS(final MathTransform cornerToCRS, final MathTransform gridToCRS)
throws TransformException {
         final int dimension = getDimension();
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
index e19eb8f..9e6b715 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
@@ -33,6 +33,7 @@ import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.util.FactoryException;
 import org.apache.sis.math.MathFunctions;
 import org.apache.sis.geometry.Envelopes;
 import org.apache.sis.geometry.GeneralEnvelope;
@@ -41,6 +42,8 @@ import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.referencing.operation.transform.PassThroughTransform;
+import org.apache.sis.referencing.operation.transform.TransformSeparator;
+import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.raster.Resources;
 import org.apache.sis.util.resources.Vocabulary;
@@ -48,9 +51,12 @@ import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.Utilities;
 import org.apache.sis.util.Debug;
 import org.apache.sis.io.TableAppender;
 
+import static org.apache.sis.referencing.CRS.findOperation;
+
 
 /**
  * Valid extent of grid coordinates together with the transform from those grid coordinates
@@ -339,7 +345,7 @@ public class GridGeometry implements Serializable {
         int numToIgnore = 1;
         if (envelope != null && cornerToCRS != null) {
             GeneralEnvelope env = Envelopes.transform(cornerToCRS.inverse(), envelope);
-            extent = new GridExtent(env);
+            extent = new GridExtent(env, null, null);
             env = extent.toCRS(cornerToCRS, gridToCRS);         // 'gridToCRS' specified
by the user, not 'this.gridToCRS'.
             env.setCoordinateReferenceSystem(envelope.getCoordinateReferenceSystem());
             this.envelope = new ImmutableEnvelope(env);
@@ -499,6 +505,70 @@ public class GridGeometry implements Serializable {
         throw incomplete(EXTENT, Resources.Keys.UnspecifiedGridExtent);
     }
 
+    /**
+     * Returns the coordinate range of the grid intersecting the given spatiotemporal region.
+     * The given envelope can be expressed in any coordinate reference system (CRS).
+     * That envelope CRS may have fewer dimensions than this grid geometry CRS,
+     * in which case grid dimensions not mapped to envelope dimensions will be returned unchanged.
+     * In other words, the dimensions not found in the given {@code areaOfInterest} will
be unfiltered (not discarded).
+     *
+     * <p>If the envelope CRS is not specified, then it is assumed the same than the
CRS of this grid geometry.
+     * In such case the envelope needs to contain all dimensions.</p>
+     *
+     * @param  areaOfInterest  the desired spatiotemporal region in any CRS (transformations
will be applied as needed).
+     * @return a grid extent of the same dimension than the grid geometry which intersects
the given area of interest.
+     * @throws IncompleteGridGeometryException if this grid geometry has no extent or no
"grid to CRS" transform.
+     * @throws TransformException if an error occurred while converting the envelope coordinates
to grid coordinates.
+     */
+    public GridExtent getExtent(final Envelope areaOfInterest) throws IncompleteGridGeometryException,
TransformException {
+        ArgumentChecks.ensureNonNull("areaOfInterest", areaOfInterest);
+        if (extent == null) {
+            throw incomplete(EXTENT, Resources.Keys.UnspecifiedGridExtent);
+        }
+        MathTransform gridToAOI = cornerToCRS;
+        if (gridToAOI == null) {
+            throw incomplete(GRID_TO_CRS, Resources.Keys.UnspecifiedTransform);
+        }
+        int[] modifiedDimensions = null;
+        try {
+            /*
+             * If the envelope CRS is different than the expected CRS, concatenate the envelope
transformation
+             * to the 'gridToCRS' transform.  We should not transform the envelope here -
only concatenate the
+             * transforms - because transforming envelopes twice add errors.
+             */
+            if (envelope != null) {
+                final CoordinateReferenceSystem sourceCRS = envelope.getCoordinateReferenceSystem();
+                if (sourceCRS != null) {
+                    final CoordinateReferenceSystem targetCRS = areaOfInterest.getCoordinateReferenceSystem();
+                    if (targetCRS != null && !Utilities.equalsIgnoreMetadata(sourceCRS,
targetCRS)) {
+                        final DefaultGeographicBoundingBox bbox = new DefaultGeographicBoundingBox();
+                        bbox.setBounds(areaOfInterest);
+                        gridToAOI = MathTransforms.concatenate(gridToAOI, findOperation(sourceCRS,
targetCRS, bbox).getMathTransform());
+                    }
+                }
+            }
+            /*
+             * If the envelope dimensions does not encompass all grid dimensions, the envelope
is probably non-invertible.
+             * We need to reduce the number of grid dimensions in the transform for having
a one-to-one relationship.
+             */
+            final int modifiedDimensionCount = gridToAOI.getTargetDimensions();
+            ArgumentChecks.ensureDimensionMatches("areaOfInterest", modifiedDimensionCount,
areaOfInterest);
+            if (modifiedDimensionCount < gridToAOI.getSourceDimensions()) {
+                final TransformSeparator sep = new TransformSeparator(gridToAOI);
+                sep.setTrimSourceDimensions(true);
+                gridToAOI = sep.separate();
+                modifiedDimensions = sep.getSourceDimensions();
+                if (modifiedDimensions.length != modifiedDimensionCount) {
+                    throw new TransformException(Resources.format(Resources.Keys.CanNotMapToGridDimensions));
+                }
+            }
+        } catch (FactoryException e) {
+            throw new TransformException(Resources.format(Resources.Keys.CanNotMapToGridDimensions),
e);
+        }
+        final GridExtent sub = new GridExtent(Envelopes.transform(gridToAOI.inverse(), areaOfInterest),
extent, modifiedDimensions);
+        return sub.equals(extent) ? extent : sub;
+    }
+
     /*
      * Do not provide a convenience 'getGridToCRS()' method without PixelInCell or PixelOrientation
argument.
      * Experience shows that 0.5 pixel offset in image localization is a recurrent problem.
We really want to
@@ -999,7 +1069,11 @@ public class GridGeometry implements Serializable {
          */
         private boolean section(final int property, final short title, final Object value)
{
             if ((bitmask & property) != 0) {
-                buffer.append(vocabulary.getString(title)).append(lineSeparator);
+                buffer.append(vocabulary.getString(title));
+                if (title == Vocabulary.Keys.Conversion) {
+                    buffer.append(" (").append(vocabulary.getString(Vocabulary.Keys.OriginInCellCenter).toLowerCase(locale)).append(')');
+                }
+                buffer.append(lineSeparator);
                 if (value != null) {
                     return true;
                 }
diff --git a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.java b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.java
index edabe79..8a61f30 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.java
@@ -59,6 +59,11 @@ public final class Resources extends IndexedResourceBundle {
         }
 
         /**
+         * Some envelope dimensions can not be mapped to grid dimensions.
+         */
+        public static final short CanNotMapToGridDimensions = 12;
+
+        /**
          * The ({0}, {1}) pixel coordinate is outside iterator domain.
          */
         public static final short CoordinateOutsideDomain_2 = 1;
diff --git a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.properties
b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.properties
index 811b045..6640488 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.properties
+++ b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.properties
@@ -19,6 +19,7 @@
 # Resources in this file are for "sis-raster" usage only and should not be used by any other
module.
 # For resources shared by all modules in the Apache SIS project, see "org.apache.sis.util.resources"
package.
 #
+CanNotMapToGridDimensions         = Some envelope dimensions can not be mapped to grid dimensions.
 CoordinateOutsideDomain_2         = The ({0}, {1}) pixel coordinate is outside iterator domain.
 IllegalGridEnvelope_3             = Illegal grid envelope [{1} \u2026 {2}] for dimension
{0}.
 IncompatibleTile_2                = The ({0}, {1}) tile has an unexpected size, number of
bands or sample layout.
diff --git a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources_fr.properties
b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources_fr.properties
index 901bb99..c94eac6 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources_fr.properties
+++ b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources_fr.properties
@@ -24,6 +24,7 @@
 #   U+202F NARROW NO-BREAK SPACE  before  ; ! and ?
 #   U+00A0 NO-BREAK SPACE         before  :
 #
+CanNotMapToGridDimensions         = Certaines dimensions de l\u2019enveloppe ne correspondent
pas \u00e0 des dimensions de la grille.
 CoordinateOutsideDomain_2         = La coordonn\u00e9e pixel ({0}, {1}) est en dehors du
domaine de l\u2019it\u00e9rateur.
 IllegalGridEnvelope_3             = La plage d\u2019index [{1} \u2026 {2}] de la dimension
{0} n\u2019est pas valide.
 IncompatibleTile_2                = La tuile ({0}, {1}) a une taille, un nombre de bandes
ou une disposition des valeurs inattendu.
diff --git a/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
b/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
index a1a55f3..673766d 100644
--- a/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
+++ b/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
@@ -44,14 +44,14 @@ public final strictfp class GridExtentTest extends TestCase {
     }
 
     /**
-     * Tests the {@link GridExtent#GridExtent(AbstractEnvelope)} constructor.
+     * Tests the {@link GridExtent#GridExtent(AbstractEnvelope, GridExtent, int[])} constructor.
      */
     @Test
     public void testCreateFromEnvelope() {
         final GeneralEnvelope env = new GeneralEnvelope(HardCodedCRS.IMAGE);
         env.setRange(0, -23.01, 30.107);
         env.setRange(1,  12.97, 18.071);
-        GridExtent extent = new GridExtent(env);
+        GridExtent extent = new GridExtent(env, null, null);
         assertExtentEquals(extent, 0, -23, 29);
         assertExtentEquals(extent, 1,  13, 17);
         assertEquals(DimensionNameType.COLUMN, extent.getAxisType(0).get());
@@ -59,7 +59,7 @@ public final strictfp class GridExtentTest extends TestCase {
     }
 
     /**
-     * Tests the rounding performed by the {@link GridExtent#GridExtent(AbstractEnvelope)}
constructor.
+     * Tests the rounding performed by the {@link GridExtent#GridExtent(AbstractEnvelope,
GridExtent, int[])} constructor.
      */
     @Test
     public void testRoundings() {
@@ -69,7 +69,7 @@ public final strictfp class GridExtentTest extends TestCase {
         env.setRange(2, 1.49998, 3.50001);      // Round to [1…4), stored as [1…2] (not
[1…3]) because the span is close to 2.
         env.setRange(3, 1.49999, 3.50002);      // Round to [1…4), stored as [2…3] because
the upper part is closer to integer.
         env.setRange(4, 1.2,     3.8);          // Round to [1…4), stores as [1…3] because
the span is not close enough to integer.
-        GridExtent extent = new GridExtent(env);
+        GridExtent extent = new GridExtent(env, null, null);
         assertExtentEquals(extent, 0, 1, 2);
         assertExtentEquals(extent, 1, 1, 2);
         assertExtentEquals(extent, 2, 1, 2);
diff --git a/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
b/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
index 360cb20..2da4f77 100644
--- a/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
+++ b/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
@@ -26,6 +26,7 @@ import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.referencing.crs.HardCodedCRS;
 import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
@@ -177,10 +178,10 @@ public final strictfp class GridGeometryTest extends TestCase {
                 },
                 new long[] {0,     0, 2, 6},
                 new long[] {100, 200, 3, 9}, false);
-        final MathTransform horizontal = MathTransforms.linear(Matrices.create(3, 3, new
double[] {
+        final MathTransform horizontal = MathTransforms.linear(new Matrix3(
                 0.5, 0,    12,
                 0,   0.25, -2,
-                0,   0,     1}));
+                0,   0,     1));
         final MathTransform vertical  = MathTransforms.interpolate(null, new double[] {1,
2, 4, 10});
         final MathTransform temporal  = MathTransforms.linear(3600, 60);
         final MathTransform gridToCRS = MathTransforms.compound(horizontal, vertical, temporal);
@@ -201,10 +202,10 @@ public final strictfp class GridGeometryTest extends TestCase {
         final GeneralEnvelope envelope = new GeneralEnvelope(HardCodedCRS.WGS84_φλ);
         envelope.setRange(0, -70.001, +80.002);
         envelope.setRange(1,   4.997,  15.003);
-        final MathTransform gridToCRS = MathTransforms.linear(Matrices.create(3, 3, new double[]
{
+        final MathTransform gridToCRS = MathTransforms.linear(new Matrix3(
             0,   0.5, -90,
             0.5, 0,  -180,
-            0,   0,     1}));
+            0,   0,     1));
         final GridGeometry grid = new GridGeometry(PixelInCell.CELL_CORNER, gridToCRS, envelope);
         assertExtentEquals(
                 new long[] {370, 40},
@@ -213,9 +214,43 @@ public final strictfp class GridGeometryTest extends TestCase {
                 new double[] {-70,  5},
                 new double[] {+80, 15}), grid.getEnvelope(), STRICT);
         assertArrayEquals("resolution", new double[] {0.5, 0.5}, grid.getResolution(false),
STRICT);
-        assertMatrixEquals("gridToCRS", Matrices.create(3, 3, new double[] {
+        assertMatrixEquals("gridToCRS", new Matrix3(
                 0,   0.5, -89.75,
                 0.5, 0,  -179.75,
-                0,   0,     1}), MathTransforms.getMatrix(grid.getGridToCRS(PixelInCell.CELL_CENTER)),
STRICT);
+                0,   0,     1), MathTransforms.getMatrix(grid.getGridToCRS(PixelInCell.CELL_CENTER)),
STRICT);
+    }
+
+    /**
+     * Tests {@link GridGeometry#getExtent(Envelope)}.
+     *
+     * @throws TransformException if an error occurred while using the "grid to CRS" transform.
+     */
+    @Test
+    @DependsOnMethod("testFromGeospatialEnvelope")
+    public void testGetExtent() throws TransformException {
+        GeneralEnvelope envelope = new GeneralEnvelope(HardCodedCRS.WGS84_3D);
+        envelope.setRange(0, -80, 120);
+        envelope.setRange(1, -12,  21);
+        envelope.setRange(2,  10,  25);
+        final MathTransform gridToCRS = MathTransforms.linear(new Matrix4(
+            0,   0.5, 0,  -90,
+            0.5, 0,   0, -180,
+            0,   0,   2,    3,
+            0,   0,   0,    1));
+        final GridGeometry grid = new GridGeometry(PixelInCell.CELL_CORNER, gridToCRS, envelope);
+        assertExtentEquals(
+                new long[] {336,  20,  4},
+                new long[] {401, 419, 10}, grid.getExtent());
+        /*
+         * Set the region of interest as a two-dimensional envelope. The vertical dimension
is omitted.
+         * The result should be that all grid indices in the vertical dimension are kept
unchanged.
+         */
+        envelope = new GeneralEnvelope(HardCodedCRS.WGS84);
+        envelope.setRange(0, -70.001, +80.002);
+        envelope.setRange(1,   4.997,  15.003);
+        final GridExtent extent = grid.getExtent(envelope);
+        assertExtentEquals(
+                new long[] {370,  40,  4},
+                new long[] {389, 339, 10}, extent);
     }
 }
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 64823c4..cf3f613 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
@@ -21,6 +21,7 @@ import org.opengis.util.FactoryException;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransformFactory;
+import org.apache.sis.referencing.operation.matrix.MatrixSIS;
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.referencing.Resources;
@@ -96,6 +97,14 @@ public class TransformSeparator {
     protected final MathTransformFactory factory;
 
     /**
+     * Whether to remove unused source dimensions. If {@code true}, then {@link #separate()}
will try to
+     * reduce the set of source dimensions to the smallest set required for computing the
target dimensions.
+     *
+     * @see #getTrimSourceDimensions()
+     */
+    private boolean trimSourceDimensions;
+
+    /**
      * Constructs a separator for the given transform.
      *
      * @param transform  the transform to separate.
@@ -118,13 +127,16 @@ public class TransformSeparator {
     }
 
     /**
-     * Clears any {@linkplain #getSourceDimensions() source} and {@linkplain #getTargetDimensions()
target dimension}
-     * settings. 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.
+     * 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 together with the {@linkplain #trimSourceDimensions trim source dimensions}
flag.
+     * 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;
+        trimSourceDimensions = false;
     }
 
     /**
@@ -276,7 +288,9 @@ public class TransformSeparator {
      * <ol class="verbose">
      *   <li>Source dimensions have been explicitly set by at least one call to {@link
#addSourceDimensions(int...)}
      *       or {@link #addSourceDimensionRange(int, int)} since construction or since last
call to {@link #clear()}.
-     *       In such case, this method returns all specified source dimensions.</li>
+     *       In such case, this method returns all specified source dimensions if {@link
#getTrimSourceDimensions()}
+     *       is {@code false}, or a subset of the specified source dimensions if {@code getTrimSourceDimensions()}
+     *       is {@code true} and {@link #separate()} has been invoked.</li>
      *
      *   <li>No source dimensions were set but {@link #separate()} has been invoked.
      *       In such case, this method returns the sequence of source dimensions that {@code
separate()} chooses to retain.
@@ -358,6 +372,31 @@ public class TransformSeparator {
     }
 
     /**
+     * Returns whether to remove unused source dimensions. If {@code true}, then {@link #separate()}
will try
+     * to reduce the set of source dimensions to the smallest set required for computing
the target dimensions.
+     * The default value is {@code false}.
+     *
+     * @return whether to remove unused source dimensions after transform separation.
+     *
+     * @since 1.0
+     */
+    public boolean getTrimSourceDimensions() {
+        return trimSourceDimensions;
+    }
+
+    /**
+     * Sets whether to remove unused source dimensions.
+     * The default value is {@code false}.
+     *
+     * @param trim whether to remove unused source dimensions after transform separation.
+     *
+     * @since 1.0
+     */
+    public void setTrimSourceDimensions(final boolean trim) {
+        trimSourceDimensions = trim;
+    }
+
+    /**
      * 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}.
@@ -420,7 +459,8 @@ public class TransformSeparator {
             }
         }
         /*
-         * We are done. But do a final verification on the number of dimensions.
+         * We are done for the separation based on specified dimensions. Do a final verification
on the number of dimensions.
+         * Then, if the user asked the minimal set of source dimensions, verify if we can
remove some of those dimensions.
          */
         int side     = 0;
         int expected = sourceDimensions.length;
@@ -430,6 +470,9 @@ public class TransformSeparator {
             expected = targetDimensions.length;
             actual   = tr.getTargetDimensions();
             if (actual == expected) {
+                if (trimSourceDimensions) {
+                    tr = removeUnusedSourceDimensions(tr);
+                }
                 return tr;
             }
         }
@@ -627,7 +670,7 @@ reduce:     for (int j=0; j <= numTgt; j++) {
      * The number and nature of inputs stay unchanged. For example if the supplied {@code
transform}
      * has (<var>longitude</var>, <var>latitude</var>, <var>height</var>)
outputs, then a filtered
      * transform may keep only the (<var>longitude</var>, <var>latitude</var>)
part for the same inputs.
-     * In most cases, the filtered transform is non-invertible since it loose informations.
+     * In most cases, the filtered transform is non-invertible since it looses information.
      *
      * @param  step        the transform for which to retain only a subset of the target
dimensions.
      * @param  dimensions  indices of the target dimensions of {@code step} to retain.
@@ -679,6 +722,74 @@ 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 #trimSourceDimensions} is {@code true}.
+     * 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
+     * this one.</p>
+     *
+     * @param  head  the first transform of a transformation chain.
+     * @return the reduced transform, or {@code head} if this method did not reduced the
transform.
+     */
+    private MathTransform removeUnusedSourceDimensions(final MathTransform head) {
+        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.
+            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 (retainedDimensions.length != dimension) {
+                /*
+                 * 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;) {
+                    final int keep = (i >= 0) ? retainedDimensions[i] : -1;
+                    final int lower = keep + 1;                                     // First
column to exclude.
+                    if (lower != upper) {
+                        // Remove source dimensions that are not retained.
+                        m = MatrixSIS.castOrCopy(m).removeColumns(lower, upper);
+                    }
+                    upper = keep;
+                }
+                /*
+                 * 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++) {
+                    retainedDimensions[i] = sourceDimensions[retainedDimensions[i]];
+                }
+                sourceDimensions = retainedDimensions;
+                return MathTransforms.linear(m);
+            }
+        } else if (head instanceof ConcatenatedTransform) {
+            final MathTransform transform1 = ((ConcatenatedTransform) head).transform1;
+            final MathTransform reduced = removeUnusedSourceDimensions(transform1);
+            if (reduced != transform1) {
+                return MathTransforms.concatenate(reduced, ((ConcatenatedTransform) head).transform2);
+            }
+        }
+        return head;
+    }
+
+    /**
      * Returns {@code true} if the given sequence contains all index in the range {@code
lower} inclusive
      * to {@code upper} exclusive.
      *
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 1ca57a5..3c88372 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
@@ -309,4 +309,58 @@ public final strictfp class TransformSeparatorTest extends TestCase {
         assertEquals("firstAffectedOrdinate", 1, ((PassThroughTransform) r).firstAffectedOrdinate);
         assertEquals("numTrailingOrdinates",  2, ((PassThroughTransform) r).numTrailingOrdinates);
     }
+
+    /**
+     * Tests {@link TransformSeparator#getTrimSourceDimensions()}.
+     *
+     * @throws FactoryException if an error occurred while creating a new transform.
+     */
+    @Test
+    @DependsOnMethod("testLinearTransform")
+    public void testGetTrimSourceDimensions() throws FactoryException {
+        MathTransform tr = MathTransforms.linear(Matrices.create(3, 4, new double[] {
+            0,   0.5, 0,  -90,
+            0.5, 0,   0, -180,
+            0,   0,   0,    1}));
+        /*
+         * Verify that TransformSeparator does not trim anything if not requested so.
+         */
+        TransformSeparator s = new TransformSeparator(tr);
+        assertFalse("trimSourceDimensions", s.getTrimSourceDimensions());
+        assertSame("No source dimensions should be trimmed if not requested.", tr, s.separate());
+        assertArrayEquals(new int[] {0, 1, 2}, s.getSourceDimensions());
+        assertArrayEquals(new int[] {0, 1   }, s.getTargetDimensions());
+        /*
+         * Trim the last dimension (most common case).
+         */
+        final Matrix expected = new Matrix3(
+            0,   0.5, -90,
+            0.5, 0,  -180,
+            0,   0,     1);
+        s.setTrimSourceDimensions(true);
+        assertTrue("trimSourceDimensions", s.getTrimSourceDimensions());
+        MathTransform reduced = s.separate();
+        assertNotEquals("separate()", tr, reduced);
+        assertArrayEquals(new int[] {0, 1}, s.getSourceDimensions());
+        assertArrayEquals(new int[] {0, 1}, s.getTargetDimensions());
+        assertMatrixEquals("separate()", expected, MathTransforms.getMatrix(reduced), STRICT);
+        /*
+         * Trim the first dimension.
+         */
+        tr = MathTransforms.linear(Matrices.create(3, 4, new double[] {
+            0, 0,   0.5, -90,
+            0, 0.5, 0,  -180,
+            0, 0,   0,     1}));
+
+        s = new TransformSeparator(tr);
+        s.setTrimSourceDimensions(true);
+        reduced = s.separate();
+        assertNotEquals("separate()", tr, reduced);
+        assertArrayEquals(new int[] {1, 2}, s.getSourceDimensions());
+        assertArrayEquals(new int[] {0, 1}, s.getTargetDimensions());
+        assertMatrixEquals("separate()", new Matrix3(
+            0,   0.5, -90,
+            0.5, 0,  -180,
+            0,   0,     1), MathTransforms.getMatrix(reduced), STRICT);
+    }
 }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java
b/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java
index 7e0ca8e..b469435 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java
@@ -797,6 +797,8 @@ public final class CollectionsExt extends Static {
      * @return the collection elements as an array, or {@code null} if {@code collection}
is null.
      *
      * @since 0.6
+     *
+     * @todo Remove after migration to JDK11.
      */
     @SuppressWarnings("unchecked")
     public static <T> T[] toArray(final Collection<T> collection, final Class<T>
valueClass) {
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
index 8cffb6f..887e3be 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
@@ -592,6 +592,11 @@ public final class Vocabulary extends IndexedResourceBundle {
         public static final short Options = 83;
 
         /**
+         * Origin in a cell center
+         */
+        public static final short OriginInCellCenter = 155;
+
+        /**
          * Other surface
          */
         public static final short OtherSurface = 84;
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
index c2c24a8..b254cca 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
@@ -121,6 +121,7 @@ OperatingSystem         = Operating system
 Operations              = Operations
 Optional                = Optional
 Options                 = Options
+OriginInCellCenter      = Origin in a cell center
 Others                  = Others
 OtherSurface            = Other surface
 Parenthesis_2           = {0} ({1})
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
index 7dab7e5..1e9a140 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
@@ -128,6 +128,7 @@ OperatingSystem         = Syst\u00e8me d\u2019exploitation
 Operations              = Op\u00e9rations
 Optional                = Optionnel
 Options                 = Options
+OriginInCellCenter      = Origine au centre d\u2019une cellule
 Others                  = Autres
 OtherSurface            = Autre surface
 Parenthesis_2           = {0} ({1})


Mime
View raw message