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 62afc80973693ebf9c36a73f43f64e5ec72dfe6c
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Wed Jan 22 10:58:14 2025 +0100

    Add methods in `GridDerivation` for providing more information on the 
subsampling.
---
 .../apache/sis/coverage/grid/GridDerivation.java   | 79 ++++++++++++++++++----
 .../apache/sis/coverage/grid/PixelTranslation.java | 28 ++++++--
 .../sis/coverage/grid/GridDerivationTest.java      | 10 ++-
 3 files changed, 99 insertions(+), 18 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java
index 44ccc2be1f..3bb5d54224 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java
@@ -215,7 +215,7 @@ public class GridDerivation {
 
     /**
      * Intersection between the grid envelope and the area of interest, 
computed when only envelopes are available.
-     * Normally we do not compute this envelope directly; instead we compute 
the grid extent and the "grid to CRS"
+     * Normally we do not compute this envelope directly. Instead, we compute 
the grid extent and the "grid to CRS"
      * transform. This envelope is computed only if it cannot be computed from 
other grid geometry properties.
      *
      * @see #subgrid(Envelope, double...)
@@ -474,8 +474,9 @@ public class GridDerivation {
 
     /**
      * Adapts the base grid for the geographic area and resolution of the 
given grid geometry.
-     * The new grid geometry will cover the intersection of the {@linkplain 
#base} grid geometry
-     * and the spatiotemporal region given by {@code areaOfInterest} envelope.
+     * By default, the new grid geometry will cover the intersection of the 
{@linkplain #base}
+     * grid geometry and the spatiotemporal region given by {@code 
areaOfInterest} envelope.
+     * Whether that intersection is clipped can be {@linkplain 
#clipping(GridClippingMode) configured}.
      * Coordinate operations are applied as needed if the Coordinate Reference 
Systems are not the same.
      * The new grid geometry resolution will be integer multiples of the 
{@linkplain #base} grid geometry resolution.
      *
@@ -555,14 +556,14 @@ public class GridDerivation {
             }
             scales = areaOfInterest.resolution;
             /*
-             * In principle `resolution` is always null here because it is 
computed from `gridToCRS`,
-             * which is null (otherwise `isExtentOnly()` would have been 
false). However, an exception
-             * to this rule happens if `areaOfInterest` has been computed by 
another `GridDerivation`,
-             * in which case the resolution requested by user is saved even 
when `gridToCRS` is null.
-             * In that case the resolution is relative to the base grid of the 
other `GridDerivation`.
-             * Note however that the `resolution` field is only an 
approximation (the exact transform
-             * would have been stored in `gridToCRS` if it was non-null) and 
the subsampling offsets
-             * are lost (they would also have been stored in `gridToCRS`).
+             * In principle, the above `resolution` should be null because it 
could not be derived from `gridToCRS`,
+             * because the latter is null (otherwise `isExtentOnly()` would 
have been false). However, an exception
+             * to this rule happens if the given `areaOfInterest` has been 
computed by another `GridDerivation`,
+             * in which case the resolution requested by the user was saved 
even if no `gridToCRS` was available.
+             * That resolution is relative to the base grid of the other 
`GridDerivation` instead of this one,
+             * but we ignore that details because the `resolution` field is 
only indicative, especially since
+             * the subsampling offsets are lost. If the exact resolution and 
subsampling offsets where known,
+             * they would have been stored in `gridToCRS`.
              */
         } else try {
             final MathTransform nowraparound;
@@ -574,6 +575,13 @@ public class GridDerivation {
                 finder.nowraparound();
                 nowraparound = finder.gridToGrid();
             } else {
+                /*
+                 * Get the transform from the base grid to the grid of 
`areaOfInterest`.
+                 * There is two variants, depending on whether the user wants 
tight box:
+                 *
+                 * - Default:   map pixel corners with exclusive upper grid 
coordinate value.
+                 * - Tight box: map pixel centers with inclusive upper grid 
coordinate value.
+                 */
                 final boolean tight = (pointsToInclude == 
PixelInCell.CELL_CENTER);
                 if (tight) {
                     finder.setAnchor(PixelInCell.CELL_CENTER);
@@ -905,7 +913,7 @@ public class GridDerivation {
 
     /**
      * Requests a grid geometry over a sub-region of the base grid geometry 
and optionally with subsampling.
-     * The given grid geometry must have the same number of dimension than the 
base grid geometry.
+     * The given grid geometry must have the same number of dimensions than 
the base grid geometry.
      * If the length of {@code subsampling} array is less than the number of 
dimensions,
      * then no subsampling will be applied on the missing dimensions.
      *
@@ -921,7 +929,7 @@ public class GridDerivation {
      *       For dimensionality reduction, see {@link 
GridGeometry#selectDimensions(int[])}.</li>
      * </ul>
      *
-     * @param  areaOfInterest  the desired grid extent in unit of base grid 
cell (i.e. ignoring subsampling),
+     * @param  areaOfInterest  the desired grid extent in units of base grid 
cells (i.e. ignoring subsampling),
      *                         or {@code null} for not restricting the 
sub-grid to a sub-area.
      * @param  subsampling     the subsampling to apply on each grid 
dimension, or {@code null} if none.
      *                         All values shall be greater than zero. If the 
array length is shorter than
@@ -1292,6 +1300,49 @@ public class GridDerivation {
         return getBaseExtentExpanded(true);
     }
 
+    /**
+     * Returns the transform from derived <em>grid</em> coordinates to base 
<em>grid</em> coordinates.
+     * In the {@linkplain LinearTransform#getMatrix() matrix representation} 
of this transform,
+     * the scale factors on the diagonal are the {@linkplain #getSubsampling() 
subsampling values} and the
+     * translation terms in the last column are the {@linkplain 
#getSubsamplingOffsets() subsampling offsets}.
+     *
+     * @return transform of <em>grid</em> coordinates from the derived grid to 
the {@linkplain #base} grid.
+     *
+     * @see #getSubsampling()
+     * @see #getSubsamplingOffsets()
+     *
+     * @since 1.5
+     */
+    public LinearTransform getGridTransform(final PixelInCell anchor) {
+        LinearTransform tr = toBase;
+        if (tr == null) {
+            return MathTransforms.identity(base.getDimension());
+        }
+        // Result of concatenating linear transforms should always be a linear 
transform.
+        return (LinearTransform) PixelTranslation.translateGridToGrid(tr, 
PixelInCell.CELL_CORNER, anchor);
+    }
+
+    /**
+     * Returns whether the conversion from the base grid to the derived grid 
contains a scale factor.
+     * A return value of {@code false} means that <em>all</em> the following 
are true:
+     *
+     * <ul>
+     *   <li>{@link #getSubsampling()} returns an array filled with the value 
1.</li>
+     *   <li>{@link #getSubsamplingOffsets()} returns an array filled with the 
value 0.</li>
+     *   <li>{@link #getGridTransform(PixelInCell)} returns an identity 
transform (regardless the argument).</li>
+     * </ul>
+     *
+     * Conversely, a return value of {@code true} means that the above are 
false,
+     * except the subsampling offsets which may still be zero.
+     *
+     * @return whether the conversion from the base grid to the derived grid 
contains a scale factor.
+     *
+     * @since 1.5
+     */
+    public boolean hasSubsampling() {
+        return (toBase != null) && !toBase.isIdentity();
+    }
+
     /**
      * Returns the strides for accessing cells along each axis of the base 
grid.
      * Those values define part of the conversion from <em>derived</em> grid 
coordinates
@@ -1321,6 +1372,7 @@ public class GridDerivation {
      *         grid has been constructed by a call to {@link 
#subgrid(Envelope, double...)}.
      *
      * @see #getSubsamplingOffsets()
+     * @see #getGridTransform(PixelInCell)
      * @see #subgrid(GridGeometry)
      * @see #subgrid(GridExtent, long...)
      *
@@ -1444,6 +1496,7 @@ public class GridDerivation {
      *         derived grid has been constructed by a call to {@link 
#subgrid(Envelope, double...)}.
      *
      * @see #getSubsampling()
+     * @see #getGridTransform(PixelInCell)
      * @see #subgrid(GridGeometry)
      * @see #subgrid(GridExtent, long...)
      *
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/PixelTranslation.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/PixelTranslation.java
index ed7bbaf7d0..e98fa07134 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/PixelTranslation.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/PixelTranslation.java
@@ -194,6 +194,26 @@ public final class PixelTranslation extends Static 
implements Serializable {
         return offset;
     }
 
+    /**
+     * Converts a math transform from a "pixel in cell" convention to another 
"pixel in cell" convention.
+     * The given transform is from the grid coordinates of a {@link 
GridGeometry} to the grid coordinates
+     * of another {@code GridGeometry}.
+     *
+     * @param  gridToGrid  the transform from a grid geometry to another grid 
geometry.
+     * @param  current     the pixel in cell convention of the given {@code 
gridToCRS} transform.
+     * @param  desired     the pixel in cell convention of the desired 
transform.
+     * @return the "grid to grid" transform using the new convention, or 
{@code null} if {@code gridToGrid} was null.
+     */
+    static MathTransform translateGridToGrid(MathTransform gridToGrid, final 
PixelInCell current, final PixelInCell desired) {
+        final double offset = desired.translationFromCentre - 
current.translationFromCentre;
+        if (offset != 0) {
+            gridToGrid = MathTransforms.concatenate(
+                    
MathTransforms.uniformTranslation(gridToGrid.getSourceDimensions(),  offset), 
gridToGrid,
+                    
MathTransforms.uniformTranslation(gridToGrid.getTargetDimensions(), -offset));
+        }
+        return gridToGrid;
+    }
+
     /**
      * Converts a math transform from a "pixel in cell" convention to another 
"pixel in cell" convention.
      * This method concatenates −½, 0 or +½ translations on <em>all</em> 
dimensions before the given transform.
@@ -212,9 +232,9 @@ public final class PixelTranslation extends Static 
implements Serializable {
      * by +½, because the grid coordinates (0,0) relative to cell center is 
(½,½) relative to cell corner.
      *
      * @param  gridToCRS  a math transform from pixel coordinates to any CRS, 
or {@code null}.
-     * @param  current    the pixel orientation of the given {@code gridToCRS} 
transform.
-     * @param  desired    the pixel orientation of the desired transform.
-     * @return the translation from {@code current} to {@code desired}, or 
{@code null} if {@code gridToCRS} was null.
+     * @param  current    the pixel in cell convention of the given {@code 
gridToCRS} transform.
+     * @param  desired    the pixel in cell convention of the desired 
transform.
+     * @return the "grid to CRS" transform using the new convention, or {@code 
null} if {@code gridToCRS} was null.
      * @throws IllegalArgumentException if {@code current} or {@code desired} 
is not a known code list value.
      */
     public static MathTransform translate(final MathTransform gridToCRS, final 
PixelInCell current, final PixelInCell desired) {
@@ -263,7 +283,7 @@ public final class PixelTranslation extends Static 
implements Serializable {
      * @param  desired     the pixel orientation of the desired transform.
      * @param  xDimension  the dimension of <var>x</var> coordinates (pixel 
columns). Often 0.
      * @param  yDimension  the dimension of <var>y</var> coordinates (pixel 
rows). Often 1.
-     * @return the translation from {@code current} to {@code desired}, or 
{@code null} if {@code gridToCRS} was null.
+     * @return the "grid to CRS" transform using the new convention, or {@code 
null} if {@code gridToCRS} was null.
      * @throws IllegalArgumentException if {@code current} or {@code desired} 
is not a known code list value.
      */
     public static MathTransform translate(final MathTransform gridToCRS,
diff --git 
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridDerivationTest.java
 
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridDerivationTest.java
index 38558223e2..d6055afc6f 100644
--- 
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridDerivationTest.java
+++ 
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridDerivationTest.java
@@ -93,8 +93,11 @@ public final class GridDerivationTest extends TestCase {
         envelope = new GeneralEnvelope(HardCodedCRS.WGS84);
         envelope.setRange(0, -70.001, +80.002);
         envelope.setRange(1,   4.997,  15.003);
+        GridDerivation change = grid.derive().subgrid(envelope);
+        assertFalse(change.hasSubsampling());
+        
assertTrue(change.getGridTransform(PixelInCell.CELL_CENTER).isIdentity());
         assertExtentEquals(new long[] {370,  40,  4},
-                           new long[] {389, 339, 10}, 
grid.derive().subgrid(envelope).getIntersection());
+                           new long[] {389, 339, 10}, 
change.getIntersection());
     }
 
     /**
@@ -142,6 +145,8 @@ public final class GridDerivationTest extends TestCase {
         GridExtentTest.assertExtentEquals(extent, 1, -1000, 8000);
         assertArrayEquals(new long[] {50, 300}, change.getSubsampling());      
         // s = scaleQuery / scaleBase
         assertArrayEquals(new long[] {0, -100}, 
change.getSubsamplingOffsets());
+        
assertFalse(change.getGridTransform(PixelInCell.CELL_CENTER).isIdentity());
+        assertTrue(change.hasSubsampling());
         /*
          * Above (50, 300) subsampling shall be applied and the `gridToCRS` 
transform adjusted consequently.
          */
@@ -460,6 +465,7 @@ public final class GridDerivationTest extends TestCase {
         GridExtentTest.assertExtentEquals(extent, 1,   low, 8000);
         assertArrayEquals(new long[] {39, 294},    change.getSubsampling());
         assertArrayEquals(new long[] { 0, offset}, 
change.getSubsamplingOffsets());
+        assertTrue(change.hasSubsampling());
 
         final GridGeometry tg = change.build();
         extent = tg.getExtent();
@@ -491,6 +497,8 @@ public final class GridDerivationTest extends TestCase {
          * Subsampling values checked below shall be equal or smaller
          * than the values given to `maximumSubsampling(…)`.
          */
+        assertTrue(change.hasSubsampling());
+        
assertFalse(change.getGridTransform(PixelInCell.CELL_CORNER).isIdentity());
         assertArrayEquals(new long[] {15,  84}, change.getSubsampling());
         assertArrayEquals(new long[] { 0, -70}, 
change.getSubsamplingOffsets());
         assertMatrixEquals(new Matrix3(30,   0, 200,

Reply via email to