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 e26c2825bf5cc4a196873bf38c9827f5a9fbc8e0
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri May 27 01:00:44 2022 +0200

    Change of slider position now cause the rendering of corresponding slice of 
data.
    It works for `GridView` only at this stage, not yet for `CoverageCanvas`.
---
 .../org/apache/sis/gui/coverage/ImageRequest.java  | 90 +++++++++++++---------
 .../apache/sis/gui/coverage/ViewAndControls.java   |  6 ++
 .../java/org/apache/sis/gui/map/StatusBar.java     | 32 +++++++-
 .../org/apache/sis/coverage/grid/GridGeometry.java | 33 +++++++-
 .../apache/sis/coverage/grid/GridGeometryTest.java | 31 +++++++-
 5 files changed, 151 insertions(+), 41 deletions(-)

diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImageRequest.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImageRequest.java
index 30b2f589b8..1f09e4c607 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImageRequest.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImageRequest.java
@@ -20,6 +20,7 @@ import java.util.Optional;
 import java.util.concurrent.FutureTask;
 import java.awt.image.RenderedImage;
 import javafx.scene.Node;
+import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.storage.GridCoverageResource;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.coverage.grid.GridGeometry;
@@ -28,7 +29,6 @@ import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.gui.map.StatusBar;
 import org.apache.sis.internal.gui.LogHandler;
 import org.apache.sis.internal.gui.ExceptionReporter;
-import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.event.StoreListeners;
 
@@ -81,13 +81,13 @@ public class ImageRequest {
 
     /**
      * A subspace of the grid coverage extent to render, or {@code null} for 
the whole extent.
-     * If the extent has more than two dimensions, then the image will be 
rendered along the
-     * two first dimensions having a size greater than 1 cell.
+     * It can be used for specifying a slice in a <var>n</var>-dimensional 
data cube.
+     * If not specified by the user, will be updated to the extent actually 
rendered.
      *
      * @see #getSliceExtent()
      * @see #setSliceExtent(GridExtent)
      */
-    private GridExtent sliceExtent;
+    private volatile GridExtent sliceExtent;
 
     /**
      * Creates a new request with both a resource and a coverage. At least one 
argument shall be non-null.
@@ -209,18 +209,18 @@ public class ImageRequest {
 
     /**
      * Returns the subspace of the grid coverage extent to render.
-     * This is the {@code sliceExtent} argument specified to the following 
constructor:
+     * This method returns the first non-empty value in the following choices:
      *
-     * <blockquote>{@link #ImageRequest(GridCoverage, GridExtent)}</blockquote>
-     *
-     * This argument will be forwarded verbatim to the following method
-     * (see its javadoc for more explanation):
-     *
-     * <blockquote>{@link GridCoverage#render(GridExtent)}</blockquote>
-     *
-     * If non-empty, then all dimensions except two should have a size of 1 
cell.
+     * <ol>
+     *   <li>The last value specified to {@link 
#setSliceExtent(GridExtent)}.</li>
+     *   <li>The value specified to the {@link #ImageRequest(GridCoverage, 
GridExtent)} constructor.</li>
+     *   <li>The extent of the default slice selected by this {@code 
ImageRequest}
+     *       after completion of the reading task.</li>
+     * </ol>
      *
      * @return subspace of the grid coverage extent to render.
+     *
+     * @see GridCoverage#render(GridExtent)
      */
     public final Optional<GridExtent> getSliceExtent() {
         return Optional.ofNullable(sliceExtent);
@@ -228,15 +228,22 @@ public class ImageRequest {
 
     /**
      * Sets a new subspace of the grid coverage extent to render.
+     * This method can be used for specifying a two-dimensional slice in a 
<var>n</var>-dimensional data cube,
+     * as specified in {@link GridCoverage#render(GridExtent)} documentation.
      *
      * <div class="note"><b>API design note:</b>
      * this {@code sliceExtent} argument is not specified
      * to the {@link #ImageRequest(GridCoverageResource, GridGeometry, int[])} 
constructor because when reading data
      * from a {@link GridCoverageResource}, a slicing can already be done by 
the {@link GridGeometry} {@code domain}
      * argument. This method is provided for the rare cases where it may be 
useful to specify both the {@code domain}
-     * and the {@code sliceExtent}.</div>
+     * and the {@code sliceExtent}. The difference between the two ways to 
specify a slice is that the {@code domain}
+     * argument is used at reading time for reducing the amount of data to 
load, while this {@link sliceExtent}
+     * property is typically used after data has been read.</div>
      *
      * @param  sliceExtent  subspace of the grid coverage extent to render, or 
{@code null} for the whole extent.
+     *         All dimensions except two shall have a size of 1 cell.
+     *
+     * @see GridCoverage#render(GridExtent)
      */
     public final void setSliceExtent(final GridExtent sliceExtent) {
         this.sliceExtent = sliceExtent;
@@ -269,16 +276,20 @@ public class ImageRequest {
                 cv = MultiResolutionImageLoader.getInstance(resource, 
null).getOrLoad(domain, range);
             }
             coverage = cv = cv.forConvertedValues(true);
-            if (task.isCancelled()) {
-                return null;
-            }
             GridExtent ex = sliceExtent;
             if (ex == null) {
                 final GridGeometry gg = cv.getGridGeometry();
-                if (gg.getDimension() > 
MultiResolutionImageLoader.BIDIMENSIONAL) {
-                    ex = MultiResolutionImageLoader.slice(gg.derive(), 
gg.getExtent()).getIntersection();
+                if (gg.isDefined(GridGeometry.EXTENT)) {
+                    ex = gg.getExtent();
+                    if (gg.getDimension() > 
MultiResolutionImageLoader.BIDIMENSIONAL) {
+                        ex = MultiResolutionImageLoader.slice(gg.derive(), 
ex).getIntersection();
+                    }
+                    sliceExtent = ex;
                 }
             }
+            if (task.isCancelled()) {
+                return null;
+            }
             return cv.render(ex);
         } finally {
             LogHandler.loadingStop(id);
@@ -287,30 +298,35 @@ public class ImageRequest {
 
     /**
      * Configures the given status bar with the geometry of the grid coverage 
we have just read.
-     * This method is invoked in JavaFX thread after {@link 
GridView#setImage(ImageRequest)}
-     * loaded in background thread a new image, successfully or not.
+     * This method is invoked in JavaFX thread after above {@link 
#load(FutureTask)} background
+     * task completed, regardless if successful or not.
+     * The two method calls are done (indirectly) by {@link 
GridView#setImage(ImageRequest)}.
      */
     final void configure(final StatusBar bar) {
         final Long id = LogHandler.loadingStart(resource);
         try {
-            final GridCoverage cv = coverage;
-            final GridExtent ex = sliceExtent;
-            bar.applyCanvasGeometry(cv != null ? cv.getGridGeometry() : null);
-            /*
-             * By `GridCoverage.render(GridExtent)` contract, the 
`RenderedImage` pixel coordinates are relative
-             * to the requested `GridExtent`. Consequently we need to 
translate the image coordinates so that it
-             * become the coordinates of the original `GridGeometry` before to 
apply `gridToCRS`.  It is okay to
-             * modify `StatusBar.localToObjectiveCRS` because we do not 
associate it to a `MapCanvas`, so it will
-             * not be overwritten by gesture events (zoom, pan, etc).
-             */
-            if (ex != null) {
-                final double[] origin = new double[ex.getDimension()];
-                for (int i=0; i<origin.length; i++) {
-                    origin[i] = ex.getLow(i);
+            GridExtent ex = sliceExtent;
+            GridCoverage cv = coverage;
+            GridGeometry gg = (cv != null) ? cv.getGridGeometry() : null;
+            if (gg != null && ex != null) {
+                /*
+                 * By `GridCoverage.render(GridExtent)` contract, the 
`RenderedImage` pixel coordinates are relative
+                 * to the requested `GridExtent`. Consequently we need to 
translate the grid coordinates so that the
+                 * request coordinates start at zero.
+                 */
+                final long[] offset = new long[ex.getDimension()];
+                for (final int i : bar.getXYDimensions()) {
+                    offset[i] = Math.negateExact(ex.getLow(i));
+                }
+                ex = ex.translate(offset);
+                gg = gg.translate(offset);          // Does not change the 
"real world" envelope.
+                try {
+                    gg = gg.relocate(ex);           // Changes the "real 
world" envelope.
+                } catch (TransformException e) {
+                    bar.setErrorMessage(null, e);
                 }
-                bar.localToObjectiveCRS.set(MathTransforms.concatenate(
-                        MathTransforms.translation(origin), 
bar.localToObjectiveCRS.get()));
             }
+            bar.applyCanvasGeometry(gg);
         } finally {
             LogHandler.loadingStop(id);
         }
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ViewAndControls.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ViewAndControls.java
index 3e34ad71d3..ff6e9bd8a5 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ViewAndControls.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ViewAndControls.java
@@ -122,6 +122,12 @@ abstract class ViewAndControls {
         this.owner = owner;
         sliceSelector = new GridSliceSelector(owner.getLocale());
         viewAndNavigation = new VBox();
+        sliceSelector.selectedExtentProperty().addListener((p,o,n) -> {
+            final GridCoverage coverage = 
ViewAndControls.this.owner.getCoverage();
+            if (coverage != null) {
+                load(new ImageRequest(coverage, n));    // Show a new slice of 
data.
+            }
+        });
     }
 
     /**
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
index 672b10ba7c..cce34c15a0 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.gui.map;
 
+import java.util.Arrays;
 import java.util.Locale;
 import java.util.Optional;
 import java.util.function.Predicate;
@@ -71,6 +72,7 @@ import org.apache.sis.internal.util.Strings;
 import org.apache.sis.measure.Quantities;
 import org.apache.sis.measure.Units;
 import org.apache.sis.util.Classes;
+import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.ArgumentChecks;
@@ -117,6 +119,8 @@ import org.apache.sis.referencing.IdentifiedObjects;
 public class StatusBar extends Widget implements EventHandler<MouseEvent> {
     /**
      * The {@value} value, for identifying code that assume two-dimensional 
objects.
+     *
+     * @see #getXYDimensions()
      */
     private static final int BIDIMENSIONAL = 2;
 
@@ -600,6 +604,7 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
          */
         MathTransform localToCRS = null;
         CoordinateReferenceSystem crs = null;
+        sourceCoordinates = ArraysExt.EMPTY_DOUBLE;
         double resolution = 1;
         double[] inflate = null;
         Unit<?> unit = Units.PIXEL;
@@ -640,6 +645,7 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
                 for (int i=0; i<n; i++) {
                     inflate[i] = (0.5 / extent.getSize(i)) + 1;
                 }
+                sourceCoordinates = 
extent.getPointOfInterest(PixelInCell.CELL_CENTER);
             }
         }
         final boolean sameCRS = Utilities.equalsIgnoreMetadata(objectiveCRS, 
crs);
@@ -658,8 +664,8 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
          * Instead we will wait for the next mouse event to provide new local 
coordinates.
          */
         ((LocalToObjective) localToObjectiveCRS).setNoCheck(localToCRS);
+        sourceCoordinates   = Arrays.copyOf(sourceCoordinates, srcDim);
         targetCoordinates   = new GeneralDirectPosition(tgtDim);
-        sourceCoordinates   = new double[srcDim];
         objectiveCRS        = crs;
         localToPositionCRS  = localToCRS;                           // May be 
updated again below.
         inflatePrecisions   = inflate;
@@ -693,8 +699,9 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
      * Other properties, in particular {@link #objectiveToPositionCRS}, must 
be valid.
      */
     private void updateLocalToPositionCRS() {
+        localToPositionCRS = localToObjectiveCRS.get();
         if (objectiveToPositionCRS != null) {
-            localToPositionCRS = 
MathTransforms.concatenate(localToObjectiveCRS.get(), objectiveToPositionCRS);
+            localToPositionCRS = 
MathTransforms.concatenate(localToPositionCRS, objectiveToPositionCRS);
         }
         setTargetCRS(format.getDefaultCRS());
     }
@@ -978,6 +985,21 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
         }
     }
 
+    /**
+     * Returns the indices of <var>x</var> and <var>y</var> coordinate values 
in a grid coordinate tuple.
+     * They are the indices where to assign the values of the <var>x</var> and 
<var>y</var> arguments in
+     * calls to <code>{@linkplain #setLocalCoordinates(double, double) 
setLocalCoordinates}(x,y)</code>.
+     * The default value is {0,1}, i.e. the 2 first dimensions in a coordinate 
tuple.
+     *
+     * @return indices of <var>x</var> and <var>y</var> coordinate values in a 
grid coordinate tuple.
+     *
+     * @since 1.3
+     */
+    public final int[] getXYDimensions() {
+        // Fixed for now, future version may allow configuration.
+        return ArraysExt.range(0, BIDIMENSIONAL);
+    }
+
     /**
      * Returns the lowest value appended as "± <var>accuracy</var>" after the 
coordinate values.
      * This is the last value specified to {@link 
#setLowestAccuracy(Quantity)}.
@@ -1024,6 +1046,12 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
      * Converts and formats the given pixel coordinates. Those coordinates 
will be automatically
      * converted to geographic or projected coordinates if a "local to CRS" 
conversion is available.
      *
+     * <h4>Supplemental dimensions</h4>
+     * If local coordinates have more than 2 dimensions, then the given (x,y) 
values will be assigned
+     * to the dimensions specified by {@link #getXYDimensions()}. Coordinates 
in all other dimensions
+     * will have the values given by {@link 
GridExtent#getPointOfInterest(PixelInCell)} from the extent
+     * of the grid geometry given to {@link 
#applyCanvasGeometry(GridGeometry)}.
+     *
      * @param  x  the <var>x</var> coordinate local to the view.
      * @param  y  the <var>y</var> coordinate local to the view.
      *
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
index d7108e3167..7d7c14fe8c 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
@@ -457,7 +457,7 @@ public class GridGeometry implements LenientComparable, 
Serializable {
      * The {@link #extent}, {@link #gridToCRS} and {@link #cornerToCRS} fields 
must be set before this method is invoked.
      *
      * @param  specified  the transform specified by the user. This is not 
necessarily {@link #gridToCRS}.
-     * @param  crs        the coordinate reference system to declare in the 
envelope.
+     * @param  crs        the coordinate reference system to declare in the 
envelope. May be {@code null}.
      * @param  limits     if non-null, intersect with that envelope. The CRS 
must be the same than {@code crs}.
      */
     private ImmutableEnvelope computeEnvelope(final MathTransform specified, 
final CoordinateReferenceSystem crs,
@@ -1382,6 +1382,37 @@ public class GridGeometry implements LenientComparable, 
Serializable {
         return new GridGeometry(te, t1, t2, envelope, resolution, nonLinears);
     }
 
+    /**
+     * Returns a grid geometry with the given grid extent, which implies a new 
"real world" computation.
+     * The "grid to CRS" transforms and the resolution stay the same than this 
{@code GridGeometry}.
+     * The "real world" envelope is recomputed for the new grid extent using 
the "grid to CRS" transforms.
+     *
+     * <p>The given extent is taken verbatim; this method does no clipping.
+     * The given extent does not need to intersect the extent of this grid 
geometry.</p>
+     *
+     * @param  extent  extent of the grid geometry to return.
+     * @return grid geometry with the given extent. May be {@code this} if 
there is no change.
+     * @throws TransformException if the geospatial envelope can not be 
recomputed with the new grid extent.
+     *
+     * @since 1.3
+     */
+    public GridGeometry relocate(final GridExtent extent) throws 
TransformException {
+        ArgumentChecks.ensureNonNull("size", extent);
+        if (extent.equals(this.extent)) {
+            return this;
+        }
+        ensureDimensionMatches(getDimension(), extent);
+        final ImmutableEnvelope relocated;
+        if (cornerToCRS != null) {
+            final GeneralEnvelope env = extent.toCRS(cornerToCRS, gridToCRS, 
null);
+            
env.setCoordinateReferenceSystem(getCoordinateReferenceSystem(envelope));
+            relocated = new ImmutableEnvelope(env);
+        } else {
+            relocated = envelope;           // Either null or contains only 
the CRS.
+        }
+        return new GridGeometry(extent, gridToCRS, cornerToCRS, relocated, 
resolution, nonLinears);
+    }
+
     /**
      * Returns a grid geometry that encompass only some dimensions of this 
grid geometry.
      * The specified dimensions will be copied into a new grid geometry if 
necessary.
diff --git 
a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
 
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
index c13bac0d13..be70a646f1 100644
--- 
a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
+++ 
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
@@ -43,7 +43,7 @@ import static org.apache.sis.test.ReferencingAssert.*;
  * Tests the {@link GridGeometry} implementation.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.2
+ * @version 1.3
  * @since   1.0
  * @module
  */
@@ -488,6 +488,35 @@ public final strictfp class GridGeometryTest extends 
TestCase {
         assertEquals(envelope, grid.getEnvelope());
     }
 
+    /**
+     * Tests {@link GridGeometry#relocate(GridExtent)}.
+     *
+     * @throws TransformException if the relocated envelope can not be 
computed.
+     */
+    @Test
+    public void testRelocate() throws TransformException {
+        final GridGeometry grid = new GridGeometry(
+                new GridExtent(10, 10),
+                PixelInCell.CELL_CORNER,
+                MathTransforms.linear(new Matrix3(
+                    2,  0,  10,
+                    0,  3,  20,
+                    0,  0,   1)),
+                HardCodedCRS.WGS84);
+
+        assertSame(grid, grid.relocate(new GridExtent(10, 10)));
+        final GridGeometry relocated = grid.relocate(new GridExtent(20, 20));
+        assertSame(grid.gridToCRS,   relocated.gridToCRS);
+        assertSame(grid.cornerToCRS, relocated.cornerToCRS);
+        assertSame(grid.resolution,  relocated.resolution);
+        assertEnvelopeEquals(new GeneralEnvelope(
+                new double[] {10, 20},
+                new double[] {30, 50}), grid.envelope, STRICT);
+        assertEnvelopeEquals(new GeneralEnvelope(
+                new double[] {10, 20},
+                new double[] {50, 80}), relocated.envelope, STRICT);
+    }
+
     /**
      * Tests {@link GridGeometry#reduce(int...)}.
      */

Reply via email to