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: Try to enable wraparound handling in JavaFX application and prints more extensive information about `CoverageCanvas` operations. The intent is to debug why the image is not visible when a `GridCoverage` crossing the anti-meridian is resampled.
Date Tue, 06 Oct 2020 18:18:52 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 56e596f  Try to enable wraparound handling in JavaFX application and prints more
extensive information about `CoverageCanvas` operations. The intent is to debug why the image
is not visible when a `GridCoverage` crossing the anti-meridian is resampled.
56e596f is described below

commit 56e596f51cdd6dac62ae78e445dee0d88eb61bbd
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Tue Oct 6 20:15:48 2020 +0200

    Try to enable wraparound handling in JavaFX application and prints more extensive information
about `CoverageCanvas` operations.
    The intent is to debug why the image is not visible when a `GridCoverage` crossing the
anti-meridian is resampled.
---
 .../apache/sis/gui/coverage/CoverageCanvas.java    |  82 +++++++++++++---
 .../org/apache/sis/gui/coverage/RenderingData.java | 106 ++++++++++++++++++++-
 .../java/org/apache/sis/gui/map/StatusBar.java     |   4 +-
 .../apache/sis/coverage/grid/GridCoverage2D.java   |   2 +-
 .../org/apache/sis/coverage/grid/GridExtent.java   |  28 +++++-
 .../coverage/grid/ResampledGridCoverageTest.java   |   8 +-
 .../main/java/org/apache/sis/portrayal/Canvas.java |  42 +++++---
 .../org/apache/sis/geometry/AbstractEnvelope.java  |  16 ++++
 .../java/org/apache/sis/geometry/Envelope2D.java   |  28 +++++-
 9 files changed, 272 insertions(+), 44 deletions(-)

diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
index 802163c..bbbccf3 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
@@ -21,6 +21,7 @@ import java.util.EnumMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.function.Function;
+import java.io.PrintStream;
 import java.awt.Graphics2D;
 import java.awt.Rectangle;
 import java.awt.RenderingHints;
@@ -91,12 +92,12 @@ public class CoverageCanvas extends MapCanvasAWT {
     private static final int OVERFLOW_SAFETY_MARGIN = 10_000_000;
 
     /**
-     * Whether to print debug information to {@link System#out}. We use {@code stdout} instead
than logging
+     * Where to print debug information. If non-null, we use {@link System#out} instead than
logging
      * because the log messages are intercepted and rerouted to the "logging" tab in the
explorer widget.
-     * This field should always be {@code false} except during debugging.
+     * This field should always be {@code null} except during debugging.
      */
     @Debug
-    private static final boolean TRACE = false;
+    private static final PrintStream TRACE = null;
 
     /**
      * The data shown in this canvas. Note that setting this property to a non-null value
may not
@@ -176,6 +177,12 @@ public class CoverageCanvas extends MapCanvasAWT {
     private Reference<Resource> originator;
 
     /**
+     * Number of times that a coverage has been rendered.
+     * Used only for debugging purposes.
+     */
+    private int renderingCount;
+
+    /**
      * Creates a new two-dimensional canvas for {@link RenderedImage}.
      */
     public CoverageCanvas() {
@@ -321,6 +328,9 @@ public class CoverageCanvas extends MapCanvasAWT {
      * @param  colors  colors to use for arbitrary categories of sample values, or {@code
null} for default.
      */
     final void setCategoryColors(final Function<Category, java.awt.Color[]> colors)
{
+        if (TRACE != null) {
+            TRACE.format("CoverageCanvas.setCategoryColors(…).%n");
+        }
         data.processor.setCategoryColors(colors);
         resampledImage = null;
         requestRepaint();
@@ -340,7 +350,7 @@ public class CoverageCanvas extends MapCanvasAWT {
      *
      * @param  newValue  the new Coordinate Reference System in which to resample the coverage
before displaying.
      * @param  anchor    the point to keep at fixed display coordinates, expressed in any
compatible CRS.
-     *                   If {@code null}, defaults to {@linkplain #getPointOfInterest() point
of interest}.
+     *                   If {@code null}, defaults to {@linkplain #getPointOfInterest(boolean)
point of interest}.
      *                   If non-null, the anchor must be associated to a CRS.
      * @throws RenderException if the objective CRS can not be set to the given value.
      *
@@ -437,8 +447,10 @@ public class CoverageCanvas extends MapCanvasAWT {
      *
      * <p>All arguments can be {@code null} for clearing the canvas.</p>
      */
-    @SuppressWarnings("UseOfSystemOutOrSystemErr")
     private void setRawImage(final RenderedImage image, final GridGeometry domain, final
List<SampleDimension> ranges) {
+        if (TRACE != null) {
+            TRACE.format("CoverageCanvas.setRawImage(%s).%n", image);
+        }
         resampledImage = null;
         derivedImages.clear();
         data.setImage(image, domain, ranges);
@@ -448,7 +460,6 @@ public class CoverageCanvas extends MapCanvasAWT {
         }
         setObjectiveBounds(bounds);
         requestRepaint();                       // Cause `Worker` class to be executed.
-        if (TRACE) System.out.format("setRawImage: %s%n", image);
     }
 
     /**
@@ -457,6 +468,9 @@ public class CoverageCanvas extends MapCanvasAWT {
      * @see #setInterpolation(Interpolation)
      */
     private void onInterpolationSpecified(final Interpolation newValue) {
+        if (TRACE != null) {
+            TRACE.format("CoverageCanvas.onInterpolationSpecified(%s).%n", newValue);
+        }
         data.processor.setInterpolation(newValue);
         resampledImage = null;
         requestRepaint();
@@ -512,6 +526,12 @@ public class CoverageCanvas extends MapCanvasAWT {
         private final Envelope2D displayBounds;
 
         /**
+         * The coordinates of the point to show typically (but not necessarily) in the center
of display area.
+         * The coordinate is expressed in objective CRS.
+         */
+        private final DirectPosition objectivePOI;
+
+        /**
          * The {@link #data} image after color ramp stretching, before resampling is applied.
          * May be {@code null} if not yet computed, in which case it will be computed by
{@link #render()}.
          */
@@ -555,6 +575,7 @@ public class CoverageCanvas extends MapCanvasAWT {
             objectiveCRS       = canvas.getObjectiveCRS();
             objectiveToDisplay = canvas.getObjectiveToDisplay();
             displayBounds      = canvas.getDisplayBounds();
+            objectivePOI       = canvas.getPointOfInterest(true);
             recoloredImage     = canvas.derivedImages.get(data.selectedDerivative);
             if (data.validateCRS(objectiveCRS)) {
                 resampledImage = canvas.resampledImage;
@@ -582,7 +603,7 @@ public class CoverageCanvas extends MapCanvasAWT {
          * to skip some steps for example if the required source image is already resampled.
          */
         @Override
-        @SuppressWarnings({"PointlessBitwiseExpression", "UseOfSystemOutOrSystemErr"})
+        @SuppressWarnings("PointlessBitwiseExpression")
         protected void render() throws TransformException {
             final Long id = LogHandler.loadingStart(originator);
             try {
@@ -605,19 +626,23 @@ public class CoverageCanvas extends MapCanvasAWT {
                         isValid = Math.max(Math.abs(resampledToDisplay.getTranslateX()),
                                            Math.abs(resampledToDisplay.getTranslateY()))
                                   < Integer.MAX_VALUE - OVERFLOW_SAFETY_MARGIN;
-                        if (TRACE && !isValid) {
-                            System.out.println("New resample for avoiding overflow caused
by translation.");
+                        if (TRACE != null && !isValid) {
+                            TRACE.println("CoverageCanvas: New resample for avoiding overflow
caused by translation.");
                         }
                     }
                 }
                 if (!isValid) {
                     if (recoloredImage == null) {
                         recoloredImage = data.recolor();
-                        if (TRACE) System.out.format("Recolor by application of %s.%n", data.selectedDerivative.name());
+                        if (TRACE != null) {
+                            TRACE.format("CoverageCanvas: Recolor by application of %s.%n",
data.selectedDerivative.name());
+                        }
                     }
-                    resampledImage = data.resampleAndConvert(recoloredImage, objectiveCRS,
objectiveToDisplay);
+                    resampledImage = data.resampleAndConvert(recoloredImage, objectiveCRS,
objectiveToDisplay, objectivePOI);
                     resampledToDisplay = data.getTransform(objectiveToDisplay);
-                    if (TRACE) System.out.format("Resampled image: %s%n", resampledImage);
+                    if (TRACE != null) {
+                        TRACE.format("CoverageCanvas: Resampled image: %s.%n", resampledImage);
+                    }
                 }
                 prefetchedImage = data.prefetch(resampledImage, resampledToDisplay, displayBounds);
             } finally {
@@ -651,19 +676,24 @@ public class CoverageCanvas extends MapCanvasAWT {
      * Invoked after a paint event for caching rendering data.
      * If the resampled image changed, all previously cached images are discarded.
      */
-    @SuppressWarnings("UseOfSystemOutOrSystemErr")
     private void cacheRenderingData(final Worker worker) {
         data = worker.data;
         derivedImages.put(data.selectedDerivative, worker.recoloredImage);
         resampledImage = worker.resampledImage;
+        renderingCount++;
+        if (TRACE != null) {
+            TRACE.println(this);
+        }
         /*
          * Notify the "Image properties" tab that the image changed. The `imageProperty`
field is non-null
          * only if the "Properties" section in `CoverageControls` has been shown at least
once.
          */
         if (imageProperty != null) {
             imageProperty.setImage(resampledImage, worker.getVisibleImageBounds());
-            if (TRACE) System.out.format("Update image property view with visible area %s.%n",
-                                         imageProperty.getVisibleImageBounds(resampledImage));
+            if (TRACE != null) {
+                TRACE.format("CoverageCanvas: Update image property view with visible area
%s.%n",
+                             imageProperty.getVisibleImageBounds(resampledImage));
+            }
         }
         if (statusBar != null) {
             final Object value = resampledImage.getProperty(PlanarImage.POSITIONAL_ACCURACY_KEY);
@@ -705,6 +735,9 @@ public class CoverageCanvas extends MapCanvasAWT {
      * The sample values are assumed the same; only the image appearance is modified.
      */
     final void setStyling(final Stretching selection) {
+        if (TRACE != null) {
+            TRACE.format("CoverageCanvas.setStyling(%s).%n", selection);
+        }
         if (data.selectedDerivative != selection) {
             data.selectedDerivative = selection;
             resampledImage = null;
@@ -724,7 +757,26 @@ public class CoverageCanvas extends MapCanvasAWT {
      */
     @Override
     protected void clear() {
+        if (TRACE != null) {
+            TRACE.format("CoverageCanvas.clear().%n");
+        }
         setRawImage(null, null, null);
         super.clear();
     }
+
+    /**
+     * Returns a string representation for debugging purposes.
+     * The string content may change in any future version.
+     * Current implementation requires a wide screen.
+     */
+    @Override
+    public String toString() {
+        if (Platform.isFxApplicationThread()) {
+            final StringBuilder buffer = new StringBuilder(12000);
+            buffer.append("Rendering #").append(renderingCount).append(':').append(System.lineSeparator());
+            return data.toString(buffer, this);
+        } else {
+            return super.toString();
+        }
+    }
 }
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java
index 2738e2d..9245b54 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java
@@ -19,14 +19,15 @@ package org.apache.sis.gui.coverage;
 import java.util.Map;
 import java.util.HashMap;
 import java.util.List;
+import java.io.IOException;
 import java.awt.Graphics2D;
 import java.awt.Rectangle;
 import java.awt.image.BufferedImage;
 import java.awt.image.RenderedImage;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.NoninvertibleTransformException;
-import org.apache.sis.coverage.SampleDimension;
 import org.opengis.util.FactoryException;
+import org.opengis.geometry.DirectPosition;
 import org.opengis.referencing.datum.PixelInCell;
 import org.opengis.referencing.operation.CoordinateOperation;
 import org.opengis.referencing.operation.MathTransform;
@@ -35,12 +36,17 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.GridExtent;
+import org.apache.sis.coverage.SampleDimension;
+import org.apache.sis.geometry.AbstractEnvelope;
 import org.apache.sis.geometry.Envelope2D;
 import org.apache.sis.geometry.Shapes2D;
 import org.apache.sis.image.ImageProcessor;
+import org.apache.sis.portrayal.RenderException;
 import org.apache.sis.internal.coverage.j2d.ColorModelType;
 import org.apache.sis.internal.coverage.j2d.ImageUtilities;
+import org.apache.sis.internal.referencing.WraparoundApplicator;
 import org.apache.sis.internal.system.Modules;
+import org.apache.sis.io.TableAppender;
 import org.apache.sis.math.Statistics;
 import org.apache.sis.measure.Quantities;
 import org.apache.sis.measure.Units;
@@ -243,6 +249,16 @@ final class RenderingData implements Cloneable {
     }
 
     /**
+     * Returns the position at the center of source data, or {@code null} if none.
+     */
+    private DirectPosition getSourceMedian() {
+        if (dataGeometry.isDefined(GridGeometry.ENVELOPE)) {
+            return AbstractEnvelope.castOrCopy(dataGeometry.getEnvelope()).getMedian();
+        }
+        return null;
+    }
+
+    /**
      * Stretch the color ramp of source image according the current value of {@link #selectedDerivative}.
      * This method uses the original image as the source of statistics. It saves computation
time
      * (no need to recompute the statistics when the projection is changed) and provides
more stable
@@ -274,10 +290,13 @@ final class RenderingData implements Cloneable {
      * @param  recoloredImage      the image computed by {@link #recolor()}.
      * @param  objectiveCRS        value of {@link CoverageCanvas#getObjectiveCRS()}.
      * @param  objectiveToDisplay  value of {@link CoverageCanvas#getObjectiveToDisplay()}.
+     * @param  objectivePOI        value of {@link CoverageCanvas#getPointOfInterest(boolean)}
in objective CRS.
      * @return image with operation applied and color ramp stretched.
      */
-    final RenderedImage resampleAndConvert(final RenderedImage recoloredImage,
-            final CoordinateReferenceSystem objectiveCRS, final LinearTransform objectiveToDisplay)
+    final RenderedImage resampleAndConvert(final RenderedImage             recoloredImage,
+                                           final CoordinateReferenceSystem objectiveCRS,
+                                           final LinearTransform           objectiveToDisplay,
+                                           final DirectPosition            objectivePOI)
             throws TransformException
     {
         if (changeOfCRS == null && objectiveCRS != null && dataGeometry.isDefined(GridGeometry.CRS))
{
@@ -305,8 +324,15 @@ final class RenderingData implements Cloneable {
             cornerToObjective = dataGeometry.getGridToCRS(PixelInCell.CELL_CORNER);
             objectiveToCenter = dataGeometry.getGridToCRS(PixelInCell.CELL_CENTER).inverse();
             if (changeOfCRS != null) {
+                DirectPosition median = getSourceMedian();
                 MathTransform forward = changeOfCRS.getMathTransform();
                 MathTransform inverse = forward.inverse();
+                try {
+                    forward = applyWraparound(forward, median, objectivePOI, changeOfCRS.getTargetCRS());
+                    inverse = applyWraparound(inverse, objectivePOI, median, changeOfCRS.getSourceCRS());
+                } catch (TransformException e) {
+                    recoverableException(e);
+                }
                 cornerToObjective = MathTransforms.concatenate(cornerToObjective, forward);
                 objectiveToCenter = MathTransforms.concatenate(inverse, objectiveToCenter);
             }
@@ -345,6 +371,19 @@ final class RenderingData implements Cloneable {
     }
 
     /**
+     * Conversion or transformation from {@linkplain CoverageCanvas#getObjectiveCRS() objective
CRS} to
+     * {@linkplain #data} CRS. This transform will include {@code WraparoundTransform} steps
if needed.
+     */
+    private static MathTransform applyWraparound(final MathTransform transform, final DirectPosition
sourceMedian,
+            final DirectPosition targetMedian, final CoordinateReferenceSystem targetCRS)
throws TransformException
+    {
+        if (targetMedian == null) {
+            return transform;
+        }
+        return new WraparoundApplicator(sourceMedian, targetMedian, targetCRS.getCoordinateSystem()).forDomainOfUse(transform);
+    }
+
+    /**
      * Computes immediately, possibly using many threads, the tiles that are going to be
displayed.
      * The returned instance should be used only for current rendering event; it should not
be cached.
      *
@@ -404,4 +443,65 @@ final class RenderingData implements Cloneable {
             throw new AssertionError(e);
         }
     }
+
+    /**
+     * Returns a string representation for debugging purposes.
+     * The string content may change in any future version.
+     * Current implementation requires a wide screen.
+     *
+     * @see CoverageCanvas#toString()
+     */
+    @Override
+    public String toString() {
+        return toString(new StringBuilder(6000), null);
+    }
+
+    /**
+     * Returns a string representation which combines information of
+     * this {@code RenderingData} with information of the given canvas.
+     */
+    final String toString(final StringBuilder buffer, final CoverageCanvas canvas) {
+        final String lineSeparator = System.lineSeparator();
+        final TableAppender table  = new TableAppender(buffer);
+        table.setMultiLinesCells(true);
+        try {
+            table.nextLine('═');
+            table.append("Geometry of source coverage:").append(lineSeparator)
+                 .append(String.valueOf(dataGeometry));
+            if (canvas != null) {
+                table.nextColumn();
+                table.append("Geometry of display canvas:").append(lineSeparator)
+                     .append(String.valueOf(canvas.getGridGeometry()));
+            }
+            table.appendHorizontalSeparator();
+            table.append("Median in data CRS:").append(lineSeparator)
+                 .append(String.valueOf(getSourceMedian()));
+            if (canvas != null) {
+                table.nextColumn();
+                table.append("Median in objective CRS:").append(lineSeparator)
+                     .append(String.valueOf(canvas.getPointOfInterest(true)));
+            }
+            table.appendHorizontalSeparator();
+            table.append("Objective CRS to source pixel centers:").append(lineSeparator)
+                 .append(String.valueOf(objectiveToCenter));
+            if (canvas != null) {
+                final Envelope2D bounds = canvas.getDisplayBounds();
+                final Rectangle db = (Rectangle) Shapes2D.transform(
+                        MathTransforms.bidimensional(objectiveToCenter),
+                        AffineTransforms2D.transform(displayToObjective, bounds, bounds),
new Rectangle());
+                table.nextColumn();
+                table.append("Display bounds in objective CRS:").append(lineSeparator)
+                     .append(String.valueOf(bounds)).append(lineSeparator)
+                     .append(lineSeparator)
+                     .append("Display bounds in source coverage pixels:").append(lineSeparator)
+                     .append(String.valueOf(new GridExtent(db))).append(lineSeparator);
+            }
+            table.nextLine();
+            table.nextLine('═');
+            table.flush();
+        } catch (RenderException | TransformException | IOException e) {
+            buffer.append(e).append(lineSeparator);
+        }
+        return buffer.toString();
+    }
 }
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 88a821c..876db5d 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
@@ -1057,7 +1057,9 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
                 values = null;
             }
             position.setText(text);
-            sampleValues.setText(values);
+            if (isSampleValuesVisible) {
+                sampleValues.setText(values);
+            }
             /*
              * Make sure that there is enough space for keeping the coordinates always visible.
              * This is the needed if there is an error message on the left which may be long.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
index f846d48..d2a2c2b 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
@@ -289,7 +289,7 @@ public class GridCoverage2D extends GridCoverage {
      */
     static GridGeometry addExtentIfAbsent(GridGeometry domain, final Rectangle bounds) {
         if (domain == null) {
-            GridExtent extent = new GridExtent(bounds.x, bounds.y, bounds.width, bounds.height);
+            GridExtent extent = new GridExtent(bounds);
             domain = new GridGeometry(extent, PixelInCell.CELL_CENTER, null, null);
         } else if (!domain.isDefined(GridGeometry.EXTENT)) {
             final int dimension = domain.getDimension();
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
index 135a68a..727ea00 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
@@ -24,6 +24,7 @@ import java.util.Locale;
 import java.io.Serializable;
 import java.io.IOException;
 import java.io.UncheckedIOException;
+import java.awt.Rectangle;
 import org.opengis.util.FactoryException;
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
@@ -76,7 +77,7 @@ import org.opengis.coverage.PointOutsideCoverageException;
  * <div class="note"><b>Note:</b>
  * The inclusiveness of {@linkplain #getHigh() high} coordinates come from ISO 19123.
  * 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>
+ * that this is the opposite of Java2D usage where {@link Rectangle} maximal values are exclusive.</div>
  *
  * <p>{@code GridExtent} instances are immutable and thread-safe.
  * The same instance can be shared by different {@link GridGeometry} instances.</p>
@@ -231,6 +232,19 @@ public class GridExtent implements GridEnvelope, LenientComparable, Serializable
     }
 
     /**
+     * Creates a new grid extent for an image or matrix of the given bounds.
+     * The axis types are {@link DimensionNameType#COLUMN} and {@link DimensionNameType#ROW
ROW} in that order.
+     *
+     * @param  bounds  the bounds to copy in the new grid extent.
+     *
+     * @since 1.1
+     */
+    public GridExtent(final Rectangle bounds) {
+        this(bounds.width, bounds.height);
+        translate2D(bounds.x, bounds.y);
+    }
+
+    /**
      * Creates a new grid extent for an image or matrix of the given size.
      * The {@linkplain #getLow() low} grid coordinates are zeros and the axis types are
      * {@link DimensionNameType#COLUMN} and {@link DimensionNameType#ROW ROW} in that order.
@@ -249,9 +263,7 @@ public class GridExtent implements GridEnvelope, LenientComparable, Serializable
 
     /**
      * Creates a new grid extent for an image of the given size and location. This constructor
-     * is for {@link GridCoverage2D} internal usage: it does not check for overflow (arguments
-     * are assumed small enough, which is the case when they are converted from {@code int}s),
-     * and argument meanings differ from conventions in public constructors.
+     * is for internal usage: argument meanings differ from conventions in public constructors.
      *
      * @param  xmin    column index of the first cell.
      * @param  ymin    row index of the first cell.
@@ -260,6 +272,14 @@ public class GridExtent implements GridEnvelope, LenientComparable, Serializable
      */
     GridExtent(final int xmin, final int ymin, final int width, final int height) {
         this(width, height);
+        translate2D(xmin, ymin);
+    }
+
+    /**
+     * Completes a {@link GridExtent} construction with a final translation.
+     * Shall be invoked for two-dimensional extents only.
+     */
+    private void translate2D(final long xmin, final long ymin) {
         for (int i=coordinates.length; --i >= 0;) {
             coordinates[i] += ((i & 1) == 0) ? xmin : ymin;
         }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
index fb15991..53a6bbb 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
@@ -228,11 +228,11 @@ public final strictfp class ResampledGridCoverageTest extends TestCase
{
         final int tx = random.nextInt(3);
         final int ty = random.nextInt(3);
         final GridExtent sourceExtent = source.gridGeometry.getExtent();
-        final int newWidth   = (int) sourceExtent.getSize(0) - tx;
-        final int newHeight  = (int) sourceExtent.getSize(1) - ty;
+        final int newWidth   = StrictMath.toIntExact(sourceExtent.getSize(0) - tx);
+        final int newHeight  = StrictMath.toIntExact(sourceExtent.getSize(1) - ty);
         GridExtent subExtent = new GridExtent(
-                (int) sourceExtent.getLow(0) + tx,
-                (int) sourceExtent.getLow(1) + ty,
+                StrictMath.toIntExact(sourceExtent.getLow(0) + tx),
+                StrictMath.toIntExact(sourceExtent.getLow(1) + ty),
                 newWidth,
                 newHeight
         );
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/portrayal/Canvas.java b/core/sis-portrayal/src/main/java/org/apache/sis/portrayal/Canvas.java
index 1df30d3..e16b1fe 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/portrayal/Canvas.java
+++ b/core/sis-portrayal/src/main/java/org/apache/sis/portrayal/Canvas.java
@@ -189,7 +189,7 @@ public class Canvas extends Observable implements Localized {
      * in all dimensions beyond the ones shown by the device.
      * Associated values are instances of {@link DirectPosition}.
      *
-     * @see #getPointOfInterest()
+     * @see #getPointOfInterest(boolean)
      * @see #setPointOfInterest(DirectPosition)
      * @see #addPropertyChangeListener(String, PropertyChangeListener)
      */
@@ -266,12 +266,15 @@ public class Canvas extends Observable implements Localized {
     final GeneralEnvelope displayBounds;
 
     /**
-     * The point of interest to show typically (but not necessarily) in the center of display
area.
+     * The point to show in the center of display area when no zoom or translation is applied.
+     * This is typically (but not necessarily) the center of data bounding box.
+     * May become outside the viewing area after zooms or translations have been applied.
+     *
      * Also used for selecting a slice in all supplemental dimensions.
      * If {@code null}, then calculations that depend on a point of interest are skipped.
      *
      * @see #POINT_OF_INTEREST_PROPERTY
-     * @see #getPointOfInterest()
+     * @see #getPointOfInterest(boolean)
      * @see #setPointOfInterest(DirectPosition)
      */
     private GeneralDirectPosition pointOfInterest;
@@ -503,7 +506,7 @@ public class Canvas extends Observable implements Localized {
      *
      * @param  newValue  the new Coordinate Reference System in which to transform all data
before displaying.
      * @param  anchor    the point to keep at fixed display coordinates, expressed in any
compatible CRS.
-     *                   If {@code null}, defaults to {@linkplain #getPointOfInterest() point
of interest}.
+     *                   If {@code null}, defaults to {@linkplain #getPointOfInterest(boolean)
point of interest}.
      *                   If non-null, the anchor must be associated to a CRS.
      * @throws NullPointerException if the given CRS is null.
      * @throws MismatchedDimensionException if the given CRS does not have the number of
dimensions of the display device.
@@ -814,26 +817,33 @@ public class Canvas extends Observable implements Localized {
     }
 
     /**
-     * Returns the coordinates of the point to show typically (but not necessarily) in the
center of display area.
-     * This position may be expressed in any CRS, not necessarily the {@linkplain #getObjectiveCRS()
objective CRS}.
-     * The number of dimensions is equal or greater than the highest number of dimensions
found in data CRS.
-     * The coordinate values in dimensions beyond the {@linkplain #getObjectiveCRS() objective
CRS} dimensions
-     * specifies which slice to show (for example the depth of the horizontal plane to display,
or the date of
-     * the dynamic phenomenon to display). See {@linkplain Canvas class javadoc} for more
discussion.
+     * Returns the coordinates of the point to show in the center of display area in absence
of zoom
+     * or translation events. This is typically (but not necessarily) the center of data
bounding box.
+     * This position may become outside the viewing area after zooms or translations have
been applied.
+     *
+     * <p>The coordinates can be given in their original CRS or in the {@linkplain
#getObjectiveCRS() objective CRS}.
+     * If {@code objective} is {@code false}, then the returned position can be expressed
in any CRS convertible to
+     * data or objective CRS. If that CRS has more dimensions than the {@linkplain #getObjectiveCRS()
objective CRS},
+     * then the supplemental dimensions specify which slice to show
+     * (for example the depth of the horizontal plane to display, or the date of the dynamic
phenomenon to display.
+     * See {@linkplain Canvas class javadoc} for more discussion.)
+     * If {@code objective} is {@code true}, then the position is transformed to the objective
CRS.</p>
      *
      * <p>This value may be {@code null} on newly created {@code Canvas}, before data
are added and canvas
      * is configured. It should not be {@code null} anymore once a {@code Canvas} is ready
for displaying.</p>
      *
+     * @param  objective  whether to return a position transformed to {@linkplain #getObjectiveCRS()
objective CRS}.
      * @return coordinates of the point to show typically (but not necessarily) in the center
of display area.
      *
      * @see #POINT_OF_INTEREST_PROPERTY
      */
-    public DirectPosition getPointOfInterest() {
-        return (pointOfInterest != null) ? pointOfInterest.clone() : null;
+    public DirectPosition getPointOfInterest(final boolean objective) {
+        final DirectPosition poi = objective ? objectivePOI : pointOfInterest;
+        return (poi != null) ? new GeneralDirectPosition(poi) : null;
     }
 
     /**
-     * Sets the coordinates of the point to show typically (but not necessarily) in the center
of display area.
+     * Sets the coordinates of the point center of display area when there is no zoom or
translations events.
      * If the given value is different than the previous value, then a change event is sent
to all listeners
      * registered for the {@value #POINT_OF_INTEREST_PROPERTY} property.
      *
@@ -926,7 +936,8 @@ public class Canvas extends Observable implements Localized {
      *   </tr><tr>
      *     <td>{@link GridGeometry#getCoordinateReferenceSystem()}</td>
      *     <td>{@link #getObjectiveCRS()}.</td>
-     *     <td>Some of <code>{@linkplain #getPointOfInterest()}.getCoordinateReferenceSystem()</code></td>
+     *     <td>Some of <code>{@linkplain #getPointOfInterest(boolean)
+     *         getPointOfInterest}(false).getCoordinateReferenceSystem()</code></td>
      *   </tr><tr>
      *     <td>{@link GridGeometry#getExtent()}</td>
      *     <td>{@link #getDisplayBounds()} rounded to enclosing (floor and ceil) integers</td>
@@ -934,7 +945,7 @@ public class Canvas extends Observable implements Localized {
      *   </tr><tr>
      *     <td>{@link GridGeometry#getGridToCRS(PixelInCell)}</td>
      *     <td>Inverse of {@link #getObjectiveToDisplay()}</td>
-     *     <td>Some {@linkplain #getPointOfInterest() point of interest} coordinates
as translation terms</td>
+     *     <td>Some {@linkplain #getPointOfInterest(boolean) point of interest} coordinates
as translation terms</td>
      *   </tr>
      * </table>
      *
@@ -1089,6 +1100,7 @@ public class Canvas extends Observable implements Localized {
             displayBounds.setEnvelope(newBounds);
             updateObjectiveToDisplay(newObjectiveToDisplay);
             pointOfInterest       = newPOI;
+            objectivePOI          = newPOI;
             objectiveCRS          = newObjectiveCRS;
             multidimToObjective   = dimensionSelect;
             augmentedObjectiveCRS = null;               // Will be recomputed when first
needed.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
b/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
index 077acaa..ef8847a 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
@@ -291,6 +291,9 @@ public abstract class AbstractEnvelope extends FormattableObject implements
Enve
      * mostly to axes having {@code WRAPAROUND} range meaning.</div>
      *
      * @return a view over the lower corner, typically (but not necessarily) containing minimal
coordinate values.
+     *
+     * @see #getLower(int)
+     * @see #getMinimum(int)
      */
     @Override
     public DirectPosition getLowerCorner() {
@@ -316,6 +319,9 @@ public abstract class AbstractEnvelope extends FormattableObject implements
Enve
      * mostly to axes having {@code WRAPAROUND} range meaning.</div>
      *
      * @return a view over the upper corner, typically (but not necessarily) containing maximal
coordinate values.
+     *
+     * @see #getUpper(int)
+     * @see #getMaximum(int)
      */
     @Override
     public DirectPosition getUpperCorner() {
@@ -330,6 +336,8 @@ public abstract class AbstractEnvelope extends FormattableObject implements
Enve
      * so changes in this envelope will be immediately reflected in the returned direct position.
      *
      * @return the median coordinates.
+     *
+     * @see #getMedian(int)
      */
     public DirectPosition getMedian() {
         // We do not cache the object because it is very cheap to create and we
@@ -346,6 +354,9 @@ public abstract class AbstractEnvelope extends FormattableObject implements
Enve
      * @return the starting coordinate value at the given dimension.
      * @throws IndexOutOfBoundsException if the given index is negative or is equals or greater
      *         than the {@linkplain #getDimension() envelope dimension}.
+     *
+     * @see #getLowerCorner()
+     * @see #getMinimum(int)
      */
     public abstract double getLower(int dimension) throws IndexOutOfBoundsException;
 
@@ -358,6 +369,9 @@ public abstract class AbstractEnvelope extends FormattableObject implements
Enve
      * @return the starting coordinate value at the given dimension.
      * @throws IndexOutOfBoundsException if the given index is negative or is equals or greater
      *         than the {@linkplain #getDimension() envelope dimension}.
+     *
+     * @see #getUpperCorner()
+     * @see #getMaximum(int)
      */
     public abstract double getUpper(int dimension) throws IndexOutOfBoundsException;
 
@@ -427,6 +441,8 @@ public abstract class AbstractEnvelope extends FormattableObject implements
Enve
      * @return the median coordinate at the given dimension, or {@link Double#NaN}.
      * @throws IndexOutOfBoundsException if the given index is negative or is equals or greater
      *         than the {@linkplain #getDimension() envelope dimension}.
+     *
+     * @see #getMedian()
      */
     @Override
     public double getMedian(final int dimension) throws IndexOutOfBoundsException {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
index 745fb32..a4339ef 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
@@ -97,7 +97,7 @@ import static org.apache.sis.geometry.AbstractEnvelope.isNegativeUnsafe;
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Johann Sorel (Geomatys)
- * @version 0.8
+ * @version 1.1
  *
  * @see GeneralEnvelope
  * @see org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox
@@ -333,6 +333,10 @@ public class Envelope2D extends Rectangle2D.Double implements Envelope,
Emptiabl
      * mostly to axes having {@code WRAPAROUND} range meaning.</div>
      *
      * @return a copy of the lower corner, typically (but not necessarily) containing minimal
coordinate values.
+     *
+     * @see #getMinX()
+     * @see #getMinY()
+     * @see #getMinimum(int)
      */
     @Override
     public DirectPosition2D getLowerCorner() {
@@ -356,6 +360,10 @@ public class Envelope2D extends Rectangle2D.Double implements Envelope,
Emptiabl
      * mostly to axes having {@code WRAPAROUND} range meaning.</div>
      *
      * @return a copy of the upper corner, typically (but not necessarily) containing maximal
coordinate values.
+     *
+     * @see #getMaxX()
+     * @see #getMaxY()
+     * @see #getMaximum(int)
      */
     @Override
     public DirectPosition2D getUpperCorner() {
@@ -363,6 +371,22 @@ public class Envelope2D extends Rectangle2D.Double implements Envelope,
Emptiabl
     }
 
     /**
+     * A coordinate position consisting of all the median coordinate values.
+     *
+     * <p>The object returned by this method is a copy. Change in the returned position
+     * will not affect this envelope, and conversely.</p>
+     *
+     * @return a copy of the median coordinates.
+     *
+     * @see #getMedian(int)
+     *
+     * @since 1.1
+     */
+    public DirectPosition2D getMedian() {
+        return new DirectPosition2D(crs, getMedian(0), getMedian(1));
+    }
+
+    /**
      * Creates an exception for an index out of bounds.
      */
     private static IndexOutOfBoundsException indexOutOfBounds(final int dimension) {
@@ -422,6 +446,8 @@ public class Envelope2D extends Rectangle2D.Double implements Envelope,
Emptiabl
      * @param  dimension  the dimension to query.
      * @return the mid coordinate value along the given dimension.
      * @throws IndexOutOfBoundsException if the given index is out of bounds.
+     *
+     * @see #getMedian()
      */
     @Override
     public double getMedian(final int dimension) throws IndexOutOfBoundsException {


Mime
View raw message