sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 04/04: Replace some GridGeometry methods by a Modifier inner class.
Date Sat, 12 Jan 2019 16:55:21 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 8a7eee34f4845590c48eda42df3e07554e871ed4
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Sat Jan 12 17:54:43 2019 +0100

    Replace some GridGeometry methods by a Modifier inner class.
---
 .../org/apache/sis/coverage/grid/GridCoverage.java |   5 +-
 .../org/apache/sis/coverage/grid/GridExtent.java   |   7 +-
 .../org/apache/sis/coverage/grid/GridGeometry.java | 384 ++++++++++++---------
 .../sis/coverage/grid/SubgridCalculator.java       |  16 +-
 .../apache/sis/coverage/grid/GridGeometryTest.java |  26 +-
 5 files changed, 250 insertions(+), 188 deletions(-)

diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
index d921ef2..52e9709 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
@@ -146,8 +146,9 @@ public abstract class GridCoverage {
      *
      * Then:
      *
-     * <blockquote><code>sliceExtent = {@linkplain #getGridGeometry()}.{@linkplain
GridGeometry#subExtent(DirectPosition)
-     * subExtent}(slicePoint);</code></blockquote>
+     * <blockquote><code>sliceExtent = {@linkplain #getGridGeometry()}.{@linkplain
GridGeometry#modify()
+     * modify()}.{@linkplain GridGeometry.Modifier#slice(DirectPosition)
+     * slice}(slicePoint).{@linkplain GridGeometry.Modifier#apply() apply()};</code></blockquote>
      *
      * If the {@code slicePoint} CRS is different than this grid coverage CRS (except for
the number of dimensions),
      * a coordinate transformation will be applied as needed.
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 e325a5b..c1d7230 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
@@ -68,7 +68,8 @@ import org.opengis.coverage.PointOutsideCoverageException;
  * We follow this specification for all getters methods, but developers should keep in mind
  * that this is the opposite of Java2D usage where {@link java.awt.Rectangle} maximal values
are exclusive.</div>
  *
- * {@code GridExtent} instances are unmodifiable, so they can be shared between different
{@link GridGeometry} instances.
+ * <p>{@code GridExtent} instances are immutable and thread-safe.
+ * The same instance can be shared by different {@link GridGeometry} instances.</p>
  *
  * <div class="note"><b>Upcoming API generalization:</b>
  * this class may implement the {@code GridEnvelope} interface in a future Apache SIS version.
@@ -890,7 +891,7 @@ public class GridExtent implements Serializable {
      * @return the sub-envelope, or {@code this} if the given array contains all dimensions
of this grid extent.
      * @throws IndexOutOfBoundsException if an index is out of bounds.
      *
-     * @see GridGeometry#reduce(int...)
+     * @see GridGeometry.Modifier#reduce(int...)
      */
     public GridExtent reduce(final int... dimensions) {
         final int sd = getDimension();
@@ -934,7 +935,7 @@ public class GridExtent implements Serializable {
      * @return the subsampled extent, or {@code this} is subsampling results in the same
extent.
      * @throws IllegalArgumentException if a period is not greater than zero.
      *
-     * @see GridGeometry#subgrid(Envelope, GridRoundingMode, double...)
+     * @see GridGeometry.Modifier#subgrid(Envelope, double...)
      */
     public GridExtent subsample(final int... periods) {
         ArgumentChecks.ensureNonNull("periods", periods);
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 6e13e5f..cfd50af 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
@@ -90,6 +90,9 @@ import org.opengis.coverage.PointOutsideCoverageException;
  * By default, any request for an undefined property will throw an {@link IncompleteGridGeometryException}.
  * In order to check if a property is defined, use {@link #isDefined(int)}.
  *
+ * <p>{@code GridGeometry} instances are immutable and thread-safe.
+ * The same instance can be shared by different {@link GridCoverage} instances.</p>
+ *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @version 1.0
  * @since   1.0
@@ -239,7 +242,7 @@ public class GridGeometry implements Serializable {
     /**
      * Creates a new grid geometry derived from the given grid geometry with a new extent
and a modified transform.
      * This constructor is used for creating a grid geometry over a subregion (for example
with the grid extent
-     * computed by {@link #subExtent(Envelope, GridRoundingMode)}) or grid geometry for a
subsampled raster.
+     * computed by {@link Modifier#subgrid(Envelope, double...)}) or grid geometry for a
subsampled raster.
      *
      * <p>If {@code toOther} is non-null, it should be a transform from the given {@code
extent} coordinates to the
      * {@code other} grid coordinates. That transform should be merely a {@linkplain MathTransforms#scale(double...)
@@ -258,8 +261,7 @@ public class GridGeometry implements Serializable {
      * @throws NullPointerException if {@code extent} is {@code null} and the other grid
geometry contains no other information.
      * @throws TransformException if the math transform can not compute the geospatial envelope
from the grid extent.
      *
-     * @see #subExtent(Envelope, GridRoundingMode)
-     * @see #subgrid(Envelope, GridRoundingMode, double...)
+     * @see Modifier#subgrid(Envelope, double...)
      */
     GridGeometry(final GridGeometry other, final GridExtent extent, final MathTransform toOther)
throws TransformException {
         final int dimension = other.getDimension();
@@ -507,7 +509,7 @@ public class GridGeometry implements Serializable {
      * @param  dimensions  the dimensions to select, in strictly increasing order (this may
not be verified).
      * @throws FactoryException if an error occurred while separating the "grid to CRS" transform.
      *
-     * @see #reduce(int...)
+     * @see Modifier#reduce(int...)
      */
     private GridGeometry(final GridGeometry other, int[] dimensions) throws FactoryException
{
         extent = (other.extent != null) ? other.extent.reduce(dimensions) : null;
@@ -628,7 +630,7 @@ public class GridGeometry implements Serializable {
      * {@linkplain #getGridToCRS(PixelInCell) transformed} to the "real world" coordinate
system.
      * The initial envelope encompasses all cell surfaces, from the left border of leftmost
cell
      * to the right border of the rightmost cell and similarly along other axes.
-     * If this grid geometry is a {@linkplain #subgrid(Envelope, GridRoundingMode, double...)
subgrid}, then the envelope is also
+     * If this grid geometry is a {@linkplain Modifier#subgrid(Envelope, double...) subgrid},
then the envelope is also
      * {@linkplain GeneralEnvelope#intersect(Envelope) clipped} to the envelope of the original
(non subsampled) grid geometry.
      *
      * @return the bounding box in "real world" coordinates (never {@code null}).
@@ -663,70 +665,6 @@ public class GridGeometry implements Serializable {
     }
 
     /**
-     * Returns the coordinate range of the grid intersecting the given spatiotemporal region.
-     * The given envelope does not need to be expressed in the same coordinate reference
system
-     * (CRS) than {@linkplain #getCoordinateReferenceSystem() the one of this grid geometry};
-     * coordinate conversions or transformations will be applied as needed.
-     * 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 {@code GridGeometry} 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>
-     *
-     * <p>This method does not reduce the number of dimensions of this grid geometry.
-     * For dimensionality reduction, see {@link #reduce(int...)}.</p>
-     *
-     * @param  areaOfInterest  the desired spatiotemporal region in any CRS (transformations
will be applied as needed).
-     * @param  roundingMode  the grid extent envelope rounding mode.
-     * @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 IllegalGridGeometryException if an error occurred while converting the envelope
coordinates to grid coordinates.
-     *
-     * @see #subgrid(Envelope, GridRoundingMode, double...)
-     */
-    public GridExtent subExtent(final Envelope areaOfInterest, final GridRoundingMode roundingMode)
{
-        ArgumentChecks.ensureNonNull("areaOfInterest", areaOfInterest);
-        requireGridToCRS();
-        try {
-            return new SubgridCalculator(this, cornerToCRS, areaOfInterest, null, roundingMode).extent;
-        } catch (FactoryException | TransformException e) {
-            throw new IllegalGridGeometryException(e, "areaOfInterest");
-        }
-    }
-
-    /**
-     * Returns the coordinate range of a slice of this grid geometry at the given point.
-     * The given position can be expressed in any coordinate reference system (CRS).
-     * The position should not define a coordinate for all dimensions, otherwise the slice
would degenerate
-     * to a single point. Dimensions can be left unspecified either by assigning to {@code
slicePoint} a CRS
-     * without those dimensions, or by assigning the NaN value to some coordinates.
-     * See {@link #slice(DirectPosition)} for examples.
-     *
-     * <p>This method does not reduce the number of dimensions of this grid geometry.
-     * For dimensionality reduction, see {@link #reduce(int...)}.</p>
-     *
-     * @param  slicePoint   the coordinates where to get a slice.
-     * @return a slice of the grid extent at the given slice point.
-     * @throws IncompleteGridGeometryException if this grid geometry has no extent or no
"grid to CRS" transform.
-     * @throws PointOutsideCoverageException if the given point is outside the grid extent.
-     * @throws IllegalGridGeometryException if an error occurred while converting the point
coordinates to grid coordinates.
-     *
-     * @see #slice(DirectPosition)
-     * @see GridCoverage#render(GridExtent)
-     */
-    public GridExtent subExtent(final DirectPosition slicePoint) {
-        ArgumentChecks.ensureNonNull("slicePoint", slicePoint);
-        requireGridToCRS();
-        try {
-            return new SubgridCalculator(this, cornerToCRS, slicePoint).extent;
-        } catch (TransformException e) {
-            throw new IllegalGridGeometryException(e, "slicePoint");
-        }
-    }
-
-    /**
      * Returns the conversion from grid coordinates to "real world" coordinates.
      * The conversion is often an affine transform, but not necessarily.
      * Conversions from cell indices to geospatial coordinates can be performed for example
as below:
@@ -1038,113 +976,237 @@ public class GridGeometry implements Serializable {
     }
 
     /**
-     * Returns a grid geometry over a sub-region of this grid geometry and optionally with
subsampling.
-     * The given envelope does not need to be expressed in the same coordinate reference
system (CRS)
-     * than {@linkplain #getCoordinateReferenceSystem() the CRS of this grid geometry};
-     * coordinate conversions or transformations will be applied as needed.
-     * 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.
-     * The target resolution, if provided, shall be in same units and same order than the
given envelope axes.
-     * If the length of {@code targetResolution} array is less than the number of dimensions
of {@code areaOfInterest},
-     * then no subsampling will be applied on the missing dimensions.
+     * Returns an object that can be used for creating a new grid geometry derived from this
grid geometry.
+     * Despite its name, {@code Modifier} does not change the state of this {@code GridGeometry}
but instead
+     * creates new instances as needed. Examples of modifications include clipping to a sub-area,
applying a
+     * sub-sampling, or selecting some grid dimensions.
      *
-     * <p>This method does not reduce the number of dimensions of this grid geometry.
-     * For dimensionality reduction, see {@link #reduce(int...)}.</p>
+     * <div class="note"><b>Example:</b>
+     * for clipping this grid geometry to a sub-area, one can use:
+     *
+     * {@preformat java
+     *     GridGeometry gg = ...;
+     *     Envelope areaOfInterest = ...;
+     *     gg = gg.modify().rounding(GridRoundingMode.ENCLOSING)
+     *                     .subgrid(areaOfInterest).apply();
+     * }
+     * </div>
      *
-     * @param  areaOfInterest    the desired spatiotemporal region in any CRS (transformations
will be applied as needed),
-     *                           or {@code null} for not restricting the sub-grid to a sub-area.
-     * @param  roundingMode  the grid extent envelope rounding mode.
-     * @param  targetResolution  the desired resolution in the same units and order than
the axes of the given envelope,
-     *                           or {@code null} or an empty array if no subsampling is
desired.
-     * @return a grid geometry over the specified sub-region of this grid geometry with the
specified resolution.
-     * @throws IncompleteGridGeometryException if this grid geometry has no extent or no
"grid to CRS" transform.
-     * @throws IllegalGridGeometryException if an error occurred while converting the envelope
coordinates to grid coordinates.
+     * Each {@code Modifier} instance can be used only once.
      *
-     * @see #subExtent(Envelope, GridRoundingMode)
-     * @see GridExtent#subsample(int[])
+     * @return an object for deriving a grid geometry from {@code this}.
      */
-    public GridGeometry subgrid(final Envelope areaOfInterest, final GridRoundingMode roundingMode,
-            double... targetResolution) {
-        requireGridToCRS();
-        try {
-            final SubgridCalculator sub = new SubgridCalculator(this, cornerToCRS, areaOfInterest,
targetResolution, roundingMode);
-            if (sub.toSubsampled != null || sub.extent != extent) {
-                return new GridGeometry(this, sub.extent, sub.toSubsampled);
-            }
-        } catch (FactoryException | TransformException e) {
-            throw new IllegalGridGeometryException(e, "areaOfInterest");
-        }
-        return this;
+    public Modifier modify() {
+        return new Modifier();
     }
 
     /**
-     * Returns a grid geometry for a slice at the given point.
-     * The given position can be expressed in any coordinate reference system (CRS).
-     * The position should not define a coordinate for all dimensions, otherwise the slice
would degenerate
-     * to a single point. Dimensions can be left unspecified either by assigning to {@code
slicePoint} a CRS
-     * without those dimensions, or by assigning the NaN value to some coordinates.
-     *
-     * <div class="note"><b>Example:</b>
-     * if the {@linkplain #getCoordinateReferenceSystem() coordinate reference system} of
this grid geometry has
-     * (<var>longitude</var>, <var>latitude</var>, <var>time</var>)
axes, then a (<var>longitude</var>, <var>latitude</var>)
-     * slice at time <var>t</var> can be created with one of the following two
positions:
-     * <ul>
-     *   <li>A three-dimensional position with ({@link Double#NaN}, {@link Double#NaN},
<var>t</var>) coordinates.</li>
-     *   <li>A one-dimensional position with (<var>t</var>) coordinate
and the coordinate reference system set to
-     *       {@linkplain org.apache.sis.referencing.CRS#getTemporalComponent(CoordinateReferenceSystem)
the temporal component}
-     *       of the grid geometry CRS.</li>
-     * </ul></div>
+     * Creates a new grid geometry derived from the enclosing grid geometry with different
extent or resolution.
+     * {@code Modifier} are created by calls to {@link GridGeometry#modify()}. Properties
of the desired grid
+     * geometry can be specified by {@link #rounding rounding}, {@link #subgrid subgrid},
{@link #slice slice}
+     * or {@link #reduce reduce} methods, and the grid geometry is created by {@link #apply()}.
      *
-     * This method does not reduce the number of dimensions of this grid geometry.
-     * For dimensionality reduction, see {@link #reduce(int...)}.
+     * @author  Martin Desruisseaux (Geomatys)
+     * @version 1.0
      *
-     * @param  slicePoint   the coordinates where to get a slice.
-     * @return a slice of this grid geometry at the given slice point. May be {@code this}.
-     * @throws IllegalGridGeometryException if an error occurred while converting the point
coordinates to grid coordinates.
-     * @throws PointOutsideCoverageException if the given point is outside the grid extent.
+     * @see GridGeometry#modify()
      *
-     * @see #subExtent(DirectPosition)
+     * @since 1.0
+     * @module
      */
-    public GridGeometry slice(final DirectPosition slicePoint) {
-        ArgumentChecks.ensureNonNull("slicePoint", slicePoint);
-        requireGridToCRS();
-        try {
-            final GridExtent slice = new SubgridCalculator(this, cornerToCRS, slicePoint).extent;
-            if (slice != extent) {
-                return new GridGeometry(this, slice, null);
+    public class Modifier {
+        /**
+         * Builder of grid geometry based on a slice of enclosing grid geometry, or {@code
null} if none.
+         */
+        private SubgridCalculator subgrid;
+
+        /**
+         * The grid dimension to keep, or {@code null} if no filtering is applied.
+         */
+        private int[] dimensions;
+
+        /**
+         * Controls behavior of rounding from floating point values to integers.
+         */
+        private GridRoundingMode rounding;
+
+        /**
+         * Creates a new modifier based on the enclosing grid geometry.
+         * This constructor is for subclasses only.
+         *
+         * @see GridGeometry#modify()
+         */
+        protected Modifier() {
+            rounding = GridRoundingMode.NEAREST;
+        }
+
+        /**
+         * Verifies that a sub-grid has not yet been defined.
+         */
+        private void ensureBeforeSubgrid() {
+            if (subgrid != null) {
+                throw new IllegalStateException();
             }
-        } catch (TransformException e) {
-            throw new IllegalGridGeometryException(e, "slicePoint");
         }
-        return this;
-    }
 
-    /**
-     * Returns a grid geometry that encompass only some dimensions of the grid extent.
-     * This method copies the specified dimensions into a new grid geometry.
-     * The selection is applied on {@linkplain #getExtent() grid extent} dimensions;
-     * they are not necessarily the same than the {@linkplain #getEnvelope() envelope} dimensions.
-     * The given dimensions must be in strictly ascending order without duplicated values.
-     * The number of dimensions of the sub grid geometry will be {@code dimensions.length}.
-     *
-     * <p>This method performs a <cite>dimensionality reduction</cite>.
-     * This method can not be used for changing dimension order.</p>
-     *
-     * @param  dimensions  the grid (not CRS) dimensions to select, in strictly increasing
order.
-     * @return the sub grid geometry, or {@code this} if the given array contains all dimensions
of this grid grid geometry.
-     * @throws IndexOutOfBoundsException if an index is out of bounds.
-     *
-     * @see GridExtent#getSubspaceDimensions(int)
-     * @see GridExtent#reduce(int...)
-     * @see org.apache.sis.referencing.CRS#reduce(CoordinateReferenceSystem, int...)
-     */
-    public GridGeometry reduce(final int... dimensions) {
-        if (GridExtent.verifyDimensions(dimensions, getDimension())) {
+        /**
+         * Controls behavior of rounding from floating point values to integers.
+         * This method can be invoked before any method expecting an {@link Envelope} argument.
+         * If this method is never invoked, the default value is {@link GridRoundingMode#NEAREST}.
+         * If this method is invoked too late, an {@link IllegalStateException} is thrown.
+         *
+         * @param  mode  the new rounding mode.
+         * @return {@code this} for method call chaining.
+         * @throws IllegalStateException if {@link #subgrid(Envelope, double...)} has already
been invoked.
+         */
+        public Modifier rounding(final GridRoundingMode mode) {
+            ArgumentChecks.ensureNonNull("mode", mode);
+            ensureBeforeSubgrid();
+            rounding = mode;
+            return this;
+        }
+
+        /**
+         * Returns a grid geometry over a sub-region of this grid geometry and optionally
with subsampling.
+         * The given envelope does not need to be expressed in the same coordinate reference
system (CRS)
+         * than {@linkplain #getCoordinateReferenceSystem() the CRS of this grid geometry};
+         * coordinate conversions or transformations will be applied as needed.
+         * 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.
+         * The target resolution, if provided, shall be in same units and same order than
the given envelope axes.
+         * If the length of {@code targetResolution} array is less than the number of dimensions
of {@code areaOfInterest},
+         * then no subsampling will be applied on the missing dimensions.
+         *
+         * <p>This method does not reduce the number of dimensions of this grid geometry.
+         * For dimensionality reduction, see {@link #reduce(int...)}.</p>
+         *
+         * @param  areaOfInterest    the desired spatiotemporal region in any CRS (transformations
will be applied as needed),
+         *                           or {@code null} for not restricting the sub-grid to
a sub-area.
+         * @param  targetResolution  the desired resolution in the same units and order than
the axes of the given envelope,
+         *                           or {@code null} or an empty array if no subsampling
is desired.
+         * @return {@code this} for method call chaining.
+         * @throws IncompleteGridGeometryException if this grid geometry has no extent or
no "grid to CRS" transform.
+         * @throws IllegalGridGeometryException if an error occurred while converting the
envelope coordinates to grid coordinates.
+         *
+         * @see GridExtent#subsample(int[])
+         */
+        public Modifier subgrid(final Envelope areaOfInterest, double... targetResolution)
{
+            ensureBeforeSubgrid();
+            requireGridToCRS();
+            try {
+                subgrid = new SubgridCalculator(GridGeometry.this, cornerToCRS, areaOfInterest,
targetResolution, rounding);
+            } catch (FactoryException | TransformException e) {
+                throw new IllegalGridGeometryException(e, "areaOfInterest");
+            }
+            return this;
+        }
+
+        /**
+         * Requests a grid geometry for a slice at the given point.
+         * The given position can be expressed in any coordinate reference system (CRS).
+         * The position should not define a coordinate for all dimensions, otherwise the
slice would degenerate
+         * to a single point. Dimensions can be left unspecified either by assigning to {@code
slicePoint} a CRS
+         * without those dimensions, or by assigning the NaN value to some coordinates.
+         *
+         * <div class="note"><b>Example:</b>
+         * if the {@linkplain #getCoordinateReferenceSystem() coordinate reference system}
of current grid geometry has
+         * (<var>longitude</var>, <var>latitude</var>, <var>time</var>)
axes, then a (<var>longitude</var>, <var>latitude</var>)
+         * slice at time <var>t</var> can be created with one of the following
two positions:
+         * <ul>
+         *   <li>A three-dimensional position with ({@link Double#NaN}, {@link Double#NaN},
<var>t</var>) coordinates.</li>
+         *   <li>A one-dimensional position with (<var>t</var>) coordinate
and the coordinate reference system set to
+         *       {@linkplain org.apache.sis.referencing.CRS#getTemporalComponent(CoordinateReferenceSystem)
the temporal component}
+         *       of the grid geometry CRS.</li>
+         * </ul></div>
+         *
+         * This method does not reduce the number of dimensions of this grid geometry.
+         * For dimensionality reduction, see {@link #reduce(int...)}.
+         *
+         * @param  slicePoint   the coordinates where to get a slice.
+         * @return {@code this} for method call chaining.
+         * @throws IncompleteGridGeometryException if this grid geometry has no extent or
no "grid to CRS" transform.
+         * @throws IllegalGridGeometryException if an error occurred while converting the
point coordinates to grid coordinates.
+         * @throws PointOutsideCoverageException if the given point is outside the grid extent.
+         */
+        public Modifier slice(final DirectPosition slicePoint) {
+            ArgumentChecks.ensureNonNull("slicePoint", slicePoint);
+            ensureBeforeSubgrid();
+            requireGridToCRS();
+            try {
+                subgrid = new SubgridCalculator(GridGeometry.this, cornerToCRS, slicePoint);
+            } catch (TransformException e) {
+                throw new IllegalGridGeometryException(e, "slicePoint");
+            }
             return this;
-        } else try {
-            return new GridGeometry(this, dimensions);
-        } catch (FactoryException e) {
-            throw new IllegalGridGeometryException(e, "dimensions");
+        }
+
+        /**
+         * Requests a grid geometry that encompass only some dimensions of the grid extent.
+         * The specified dimensions will be copied into a new grid geometry.
+         * The selection is applied on {@linkplain #getExtent() grid extent} dimensions;
+         * they are not necessarily the same than the {@linkplain #getEnvelope() envelope}
dimensions.
+         * The given dimensions must be in strictly ascending order without duplicated values.
+         * The number of dimensions of the sub grid geometry will be {@code dimensions.length}.
+         *
+         * <p>This method performs a <cite>dimensionality reduction</cite>.
+         * This method can not be used for changing dimension order.</p>
+         *
+         * @param  dimensions  the grid (not CRS) dimensions to select, in strictly increasing
order.
+         * @return {@code this} for method call chaining.
+         * @throws IndexOutOfBoundsException if an index is out of bounds.
+         *
+         * @see GridExtent#getSubspaceDimensions(int)
+         * @see GridExtent#reduce(int...)
+         * @see org.apache.sis.referencing.CRS#reduce(CoordinateReferenceSystem, int...)
+         */
+        public Modifier reduce(int... dimensions) {
+            if (GridExtent.verifyDimensions(dimensions, getDimension())) {
+                this.dimensions = null;
+            } else {
+                this.dimensions = dimensions.clone();
+            }
+            return this;
+        }
+
+        /**
+         * Returns the extent of the modified grid geometry. This method is more efficient
than
+         * {@link #apply()} if only the grid extent is desired instead than the full grid
geometry.
+         *
+         * @return the modified grid geometry extent.
+         */
+        public GridExtent extent() {
+            GridExtent e = (subgrid != null) ? subgrid.extent : getExtent();
+            if (dimensions != null) {
+                e = e.reduce(dimensions);
+            }
+            return e;
+        }
+
+        /**
+         * Builds a grid geometry with the configuration specified by the other methods in
this {@code Modifier} class.
+         *
+         * @return the modified grid geometry. May be the enclosing geometry if no change
apply.
+         */
+        public GridGeometry apply() {
+            GridGeometry gg = GridGeometry.this;
+            String cause = null;
+            try {
+                cause = "subgrid";
+                if (subgrid != null) {
+                    final GridExtent    slice        = subgrid.extent;
+                    final MathTransform toSubsampled = subgrid.toSubsampled;
+                    if (toSubsampled != null || slice != extent) {
+                        gg = new GridGeometry(gg, slice, toSubsampled);
+                    }
+                }
+                cause = "dimensions";
+                if (dimensions != null) {
+                    gg = new GridGeometry(gg, dimensions);
+                }
+            } catch (FactoryException | TransformException e) {
+                throw new IllegalGridGeometryException(e, cause);
+            }
+            return gg;
         }
     }
 
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/SubgridCalculator.java
b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/SubgridCalculator.java
index 4db895e..4d21eff 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/SubgridCalculator.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/SubgridCalculator.java
@@ -113,12 +113,11 @@ final class SubgridCalculator {
      * @param  areaOfInterest  the desired spatiotemporal region in any CRS, or {@code null}
for the whole area.
      * @param  resolution      the desired resolution in the same units and order than the
axes of the AOI envelope,
      *                         or {@code null} or an empty array if no subsampling is desired.
-     * @param  roundingMode    the grid extent envelope rounding mode.
+     * @param  rounding        controls behavior of rounding from floating point values to
integers.
      * @throws TransformException if an error occurred while converting the envelope coordinates
to grid coordinates.
      */
     SubgridCalculator(final GridGeometry grid, MathTransform cornerToCRS, final Envelope
areaOfInterest,
-            double[] resolution, final GridRoundingMode roundingMode)
-            throws TransformException, FactoryException
+            double[] resolution, final GridRoundingMode rounding) throws TransformException,
FactoryException
     {
         /*
          * If the envelope CRS is different than the expected CRS, concatenate the envelope
transformation
@@ -146,7 +145,7 @@ final class SubgridCalculator {
         GeneralEnvelope indices = null;
         if (areaOfInterest != null) {
             indices = Envelopes.transform(cornerToCRS.inverse(), areaOfInterest);
-            setExtent(indices, extent, roundingMode);
+            setExtent(indices, extent, rounding);
         }
         if (indices == null || indices.getDimension() != dimension) {
             indices = new GeneralEnvelope(dimension);
@@ -193,7 +192,7 @@ final class SubgridCalculator {
              */
             if (modified) {
                 final GridExtent unscaled = extent;
-                setExtent(indices, null, roundingMode);
+                setExtent(indices, null, rounding);
                 m = Matrices.createIdentity(dimension + 1);
                 for (int k=0; k<resolution.length; k++) {
                     final double s = resolution[k];
@@ -236,11 +235,10 @@ final class SubgridCalculator {
      *
      * @param  indices    the envelope to use for setting the grid extent.
      * @param  enclosing  the enclosing grid extent if a subsampling is not yet applied,
{@code null} otherwise.
-     * @param  roundingMode  the grid extent envelope rounding mode.
+     * @param  rounding   controls behavior of rounding from floating point values to integers.
      */
-    private void setExtent(final GeneralEnvelope indices, final GridExtent enclosing,
-            final GridRoundingMode roundingMode) {
-        final GridExtent sub = new GridExtent(indices, roundingMode, null, enclosing, modifiedDimensions);
+    private void setExtent(final GeneralEnvelope indices, final GridExtent enclosing, final
GridRoundingMode rounding) {
+        final GridExtent sub = new GridExtent(indices, rounding, null, enclosing, modifiedDimensions);
         if (!sub.equals(extent)) {
             extent = sub;
         }
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 9fb9e86..709f86a 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
@@ -239,7 +239,7 @@ public final strictfp class GridGeometryTest extends TestCase {
     }
 
     /**
-     * Tests {@link GridGeometry#subExtent(Envelope, GridRoundingMode)}.
+     * Tests {@link GridGeometry.Modifier#subgrid(Envelope, double...)}.
      */
     @Test
     @DependsOnMethod("testFromGeospatialEnvelope")
@@ -265,11 +265,11 @@ public final strictfp class GridGeometryTest extends TestCase {
         envelope.setRange(0, -70.001, +80.002);
         envelope.setRange(1,   4.997,  15.003);
         assertExtentEquals(new long[] {370,  40,  4},
-                           new long[] {389, 339, 10}, grid.subExtent(envelope, GridRoundingMode.NEAREST));
+                           new long[] {389, 339, 10}, grid.modify().subgrid(envelope).extent());
     }
 
     /**
-     * Tests {@link GridGeometry#subExtent(Envelope, GridRoundingMode)} with a non-linear
"grid to CRS" transform.
+     * Tests {@link GridGeometry.Modifier#subgrid(Envelope, double...)} with a non-linear
"grid to CRS" transform.
      */
     @Test
     @DependsOnMethod({"testNonLinear", "testSubExtent"})
@@ -299,14 +299,14 @@ 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 GridExtent actual = grid.subExtent(envelope, GridRoundingMode.NEAREST);
+        final GridExtent actual = grid.modify().subgrid(envelope).extent();
         assertEquals(extent.getAxisType(0), actual.getAxisType(0));
         assertExtentEquals(new long[] { 56, 69, 2},
                            new long[] {130, 73, 4}, actual);
     }
 
     /**
-     * Tests {@link GridGeometry#subgrid(Envelope, GridRoundingMode, double...)}.
+     * Tests {@link GridGeometry.Modifier#subgrid(Envelope, double...)}.
      *
      * @throws TransformException if an error occurred during computation.
      */
@@ -329,7 +329,7 @@ public final strictfp class GridGeometryTest extends TestCase {
          */
         envelope.setRange(0, -50, +30);
         envelope.setRange(1,   8,  12);
-        grid = grid.subgrid(envelope, GridRoundingMode.NEAREST, 1, 2);
+        grid = grid.modify().subgrid(envelope, 1, 2).apply();
         assertExtentEquals(new long[] {94, 40}, new long[] {95, 119}, grid.getExtent());
         assertEnvelopeEquals(envelope, grid.getEnvelope(), STRICT);
         assertMatrixEquals("gridToCRS", new Matrix3(
@@ -341,7 +341,7 @@ public final strictfp class GridGeometryTest extends TestCase {
          * It will force GridGeometry to adjust the translation term to compensate. We verify
that the adustment
          * is correct by verifying that we still get the same envelope.
          */
-        grid = grid.subgrid(envelope, GridRoundingMode.NEAREST, 3, 2);
+        grid = grid.modify().subgrid(envelope, 3, 2).apply();
         assertExtentEquals(new long[] {94, 13}, new long[] {95, 39}, grid.getExtent());
         assertEnvelopeEquals(envelope, grid.getEnvelope(), STRICT);
         MathTransform cornerToCRS = grid.getGridToCRS(PixelInCell.CELL_CORNER);
@@ -358,7 +358,7 @@ public final strictfp class GridGeometryTest extends TestCase {
     }
 
     /**
-     * Tests {@link GridGeometry#slice(DirectPosition)}.
+     * Tests {@link GridGeometry.Modifier#slice(DirectPosition)}.
      */
     @Test
     public void testSlice() {
@@ -372,7 +372,7 @@ public final strictfp class GridGeometryTest extends TestCase {
         /*
          * There is two ways to ask for a slice. The first way is to set some coordinates
to NaN.
          */
-        GridGeometry slice = grid.slice(new GeneralDirectPosition(Double.NaN, Double.NaN,
15));
+        GridGeometry slice = grid.modify().slice(new GeneralDirectPosition(Double.NaN, Double.NaN,
15)).apply();
         assertNotSame(grid, slice);
         assertSame("gridToCRS", grid.gridToCRS, slice.gridToCRS);
         final long[] expectedLow  = {336,  20, 6};
@@ -384,14 +384,14 @@ public final strictfp class GridGeometryTest extends TestCase {
          */
         GeneralDirectPosition p = new GeneralDirectPosition(HardCodedCRS.ELLIPSOIDAL_HEIGHT_cm);
         p.setOrdinate(0, 1500);
-        slice = grid.slice(p);
+        slice = grid.modify().slice(p).apply();
         assertNotSame(grid, slice);
         assertSame("gridToCRS", grid.gridToCRS, slice.gridToCRS);
         assertExtentEquals(expectedLow, expectedHigh, slice.getExtent());
     }
 
     /**
-     * Tests {@link GridGeometry#reduce(int...)}.
+     * Tests {@link GridGeometry.Modifier#reduce(int...)}.
      */
     @Test
     public void testReduce() {
@@ -405,7 +405,7 @@ public final strictfp class GridGeometryTest extends TestCase {
         /*
          * Tests on the two first dimensions.
          */
-        GridGeometry reduced = grid.reduce(0, 1);
+        GridGeometry reduced = grid.modify().reduce(0, 1).apply();
         assertNotSame(grid, reduced);
         assertExtentEquals(new long[] {336, 20}, new long[] {401, 419}, reduced.getExtent());
         assertSame("CRS", HardCodedCRS.WGS84, reduced.getCoordinateReferenceSystem());
@@ -417,7 +417,7 @@ public final strictfp class GridGeometryTest extends TestCase {
         /*
          * Tests on the last dimension.
          */
-        reduced = grid.reduce(2);
+        reduced = grid.modify().reduce(2).apply();
         assertNotSame(grid, reduced);
         assertExtentEquals(new long[] {4}, new long[] {10}, reduced.getExtent());
         assertSame("CRS", HardCodedCRS.GRAVITY_RELATED_HEIGHT, reduced.getCoordinateReferenceSystem());


Mime
View raw message