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,