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 24938df77b4d1a0fbc195a871d0238a81907a127
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Sat Jun 11 21:19:55 2022 +0200

    Provide a way to set the initial "objective to display" transform of a 
`MapCanvas` in addition of the objective bounds.
---
 .../apache/sis/gui/coverage/CoverageExplorer.java  |   7 +-
 .../java/org/apache/sis/gui/map/MapCanvas.java     | 106 +++++++++++++++------
 .../gui/referencing/RecentReferenceSystems.java    |  23 ++++-
 .../org/apache/sis/coverage/grid/GridGeometry.java |  25 +++++
 4 files changed, 124 insertions(+), 37 deletions(-)

diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageExplorer.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageExplorer.java
index 8a86740bb0..43a9ff3752 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageExplorer.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageExplorer.java
@@ -33,7 +33,6 @@ import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.SimpleObjectProperty;
 import org.apache.sis.storage.GridCoverageResource;
 import org.apache.sis.coverage.grid.GridCoverage;
-import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.internal.gui.Resources;
 import org.apache.sis.internal.gui.ToolbarButton;
 import org.apache.sis.internal.gui.NonNullObjectProperty;
@@ -632,11 +631,7 @@ public class CoverageExplorer extends Widget {
      */
     final void notifyDataChanged(final GridCoverageResource resource, final 
GridCoverage coverage) {
         if (coverage != null) {
-            final GridGeometry gg = coverage.getGridGeometry();
-            
referenceSystems.areaOfInterest.set(gg.isDefined(GridGeometry.ENVELOPE) ? 
gg.getEnvelope() : null);
-            if (gg.isDefined(GridGeometry.CRS)) {
-                referenceSystems.setPreferred(true, 
gg.getCoordinateReferenceSystem());
-            }
+            referenceSystems.configure(coverage.getGridGeometry());
         }
         /*
          * Following calls will NOT forward the new values to the views 
because this `notifyDataChanged(…)`
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java
index 6aee443bbb..a9411566a3 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java
@@ -50,6 +50,7 @@ import javafx.scene.transform.Affine;
 import javafx.scene.transform.NonInvertibleTransformException;
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
+import org.opengis.geometry.MismatchedDimensionException;
 import org.opengis.referencing.ReferenceSystem;
 import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.datum.PixelInCell;
@@ -63,9 +64,9 @@ import org.apache.sis.referencing.cs.CoordinateSystems;
 import org.apache.sis.geometry.DirectPosition2D;
 import org.apache.sis.geometry.Envelope2D;
 import org.apache.sis.geometry.AbstractEnvelope;
-import org.apache.sis.geometry.ImmutableEnvelope;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.GridExtent;
+import org.apache.sis.coverage.grid.GridOrientation;
 import org.apache.sis.gui.referencing.PositionableProjection;
 import org.apache.sis.gui.referencing.RecentReferenceSystems;
 import org.apache.sis.util.ArraysExt;
@@ -192,12 +193,14 @@ public abstract class MapCanvas extends PlanarCanvas {
 
     /**
      * The data bounds to use for computing the initial value of {@link 
#objectiveToDisplay}.
-     * We differ this recomputation until all parameters are known.
+     * Optionally contains the initial "objective to display" CRS to use if a 
predetermined
+     * value is desired instead of an automatically computed one. The grid 
extent is ignored,
+     * except for fetching the grid center if a non-linear transform needs to 
be linearized.
      *
-     * @see #setObjectiveBounds(Envelope)
+     * @see #setInitialState(GridGeometry)
      * @see #invalidObjectiveToDisplay
      */
-    private Envelope objectiveBounds;
+    private GridGeometry initialState;
 
     /**
      * Incremented when the map needs to be rendered again.
@@ -244,7 +247,7 @@ public abstract class MapCanvas extends PlanarCanvas {
      * Whether {@link #objectiveToDisplay} needs to be recomputed.
      * We differ this recomputation until all parameters are known.
      *
-     * @see #objectiveBounds
+     * @see #initialState
      */
     private boolean invalidObjectiveToDisplay;
 
@@ -780,12 +783,42 @@ public abstract class MapCanvas extends PlanarCanvas {
      *
      * @param  visibleArea  bounding box in (new) objective CRS of the initial 
area to show,
      *         or {@code null} if unknown (in which case an identity transform 
will be set).
+     * @throws MismatchedDimensionException if the given envelope is not 
two-dimensional.
      *
      * @see #setObjectiveCRS(CoordinateReferenceSystem, DirectPosition)
      */
     protected void setObjectiveBounds(final Envelope visibleArea) {
         ArgumentChecks.ensureDimensionMatches("visibleArea", BIDIMENSIONAL, 
visibleArea);
-        objectiveBounds = ImmutableEnvelope.castOrCopy(visibleArea);
+        if (visibleArea == null) {
+            initialState = null;
+        } else {
+            setInitialState(new GridGeometry(null, visibleArea, 
GridOrientation.HOMOTHETY));
+        }
+    }
+
+    /**
+     * Sets the data bounds and initial "objective to display" transform to 
use. This method sets the same
+     * information than {@link #setObjectiveBounds(Envelope)} using the 
envelope of the given grid geometry.
+     * But if in addition a "grid to CRS" transform is specified, it will be 
used for defining the initial
+     * value of the "objective to display" transform.
+     *
+     * <p>Note that the objective bounds (defined by grid geometry envelope) 
are usually constant as long
+     * as the data do not change, while the other properties may change under 
user interactions.
+     * This is the reason why the given argument is an <em>initial</em> 
state.</p>
+     *
+     * <p>A typical use case for this method is to give in argument the {@link 
#getGridGeometry()} value
+     * of another canvas for initializing this canvas to the same geographic 
region and zoom level,
+     * assuming that the two canvas and rendering the same data.</p>
+     *
+     * @param  visibleArea  bounding box and initial zoom level to use for the 
canvas.
+     *
+     * @see #setGridGeometry(GridGeometry)
+     *
+     * @since 1.3
+     */
+    protected void setInitialState(final GridGeometry visibleArea) {
+        ArgumentChecks.ensureNonNull("visibleArea", visibleArea);
+        initialState = visibleArea;
         invalidObjectiveToDisplay = true;
     }
 
@@ -1040,31 +1073,44 @@ public abstract class MapCanvas extends PlanarCanvas {
                  * Otherwise the transform is initialized to an identity 
transform (should not happen often).
                  * If a CRS is present, it is used for deciding if we need to 
swap or flip axes.
                  */
-                CoordinateReferenceSystem objectiveCRS;
-                final LinearTransform crsToDisplay;
-                if (objectiveBounds != null) {
-                    objectiveCRS = 
objectiveBounds.getCoordinateReferenceSystem();
-                    final MatrixSIS m;
-                    if (objectiveCRS != null) {
-                        AxisDirection[] srcAxes = 
CoordinateSystems.getAxisDirections(objectiveCRS.getCoordinateSystem());
-                        m = Matrices.createTransform(objectiveBounds, srcAxes, 
target, toDisplayDirections(srcAxes));
-                    } else {
-                        m = Matrices.createTransform(objectiveBounds, target);
+                Envelope objectiveBounds = null;
+                CoordinateReferenceSystem objectiveCRS = null;
+                LinearTransform crsToDisplay = null;
+                if (initialState != null) {
+                    if (initialState.isDefined(GridGeometry.GRID_TO_CRS)) {
+                        crsToDisplay = 
initialState.getLinearGridToCRS(PixelInCell.CELL_CORNER).inverse();
+                    }
+                    if (initialState.isDefined(GridGeometry.ENVELOPE)) {
+                        objectiveBounds = initialState.getEnvelope();
+                    }
+                    if (initialState.isDefined(GridGeometry.CRS)) {
+                        objectiveCRS = 
initialState.getCoordinateReferenceSystem();
                     }
-                    Matrices.forceUniformScale(m, 0, new double[] 
{target.getCenterX(), target.getCenterY()});
-                    crsToDisplay = MathTransforms.linear(m);
-                    if (objectiveCRS == null) {
-                        objectiveCRS = 
extent.toEnvelope(crsToDisplay.inverse()).getCoordinateReferenceSystem();
-                        /*
-                         * Above code tried to provide a non-null CRS on a 
"best effort" basis. The objective CRS
-                         * may still be null, there is no obvious answer 
against that. It is not the display CRS
-                         * if the "display to objective" transform is not 
identity. A grid CRS is not appropriate
-                         * neither, otherwise `extent.toEnvelope(…)` would 
have found it.
-                         */
+                }
+                if (crsToDisplay == null) {
+                    if (objectiveBounds != null) {
+                        final MatrixSIS m;
+                        if (objectiveCRS != null) {
+                            AxisDirection[] srcAxes = 
CoordinateSystems.getAxisDirections(objectiveCRS.getCoordinateSystem());
+                            m = Matrices.createTransform(objectiveBounds, 
srcAxes, target, toDisplayDirections(srcAxes));
+                        } else {
+                            m = Matrices.createTransform(objectiveBounds, 
target);
+                        }
+                        Matrices.forceUniformScale(m, 0, new double[] 
{target.getCenterX(), target.getCenterY()});
+                        crsToDisplay = MathTransforms.linear(m);
+                    } else {
+                        objectiveCRS = getDisplayCRS();
+                        crsToDisplay = MathTransforms.identity(BIDIMENSIONAL);
                     }
-                } else {
-                    objectiveCRS = getDisplayCRS();
-                    crsToDisplay = MathTransforms.identity(BIDIMENSIONAL);
+                }
+                if (objectiveCRS == null) {
+                    objectiveCRS = 
extent.toEnvelope(crsToDisplay.inverse()).getCoordinateReferenceSystem();
+                    /*
+                     * Above code tried to provide a non-null CRS on a "best 
effort" basis. The objective CRS
+                     * may still be null, there is no obvious answer against 
that. It is not the display CRS
+                     * if the "display to objective" transform is not 
identity. A grid CRS is not appropriate
+                     * neither, otherwise `extent.toEnvelope(…)` would have 
found it.
+                     */
                 }
                 setGridGeometry(new GridGeometry(extent, 
PixelInCell.CELL_CORNER, crsToDisplay.inverse(), objectiveCRS));
                 transform.setToIdentity();
@@ -1405,7 +1451,7 @@ public abstract class MapCanvas extends PlanarCanvas {
         transform.setToIdentity();
         changeInProgress.setToIdentity();
         invalidObjectiveToDisplay = true;
-        objectiveBounds = null;
+        initialState = null;
         clearError();
         isDragging = false;
         isNavigationDisabled = false;
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java
index 48c1e56326..41124f6201 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java
@@ -42,6 +42,7 @@ import org.apache.sis.geometry.ImmutableEnvelope;
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
 import org.apache.sis.referencing.factory.IdentifiedObjectFinder;
+import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.Utilities;
@@ -73,7 +74,7 @@ import static java.util.logging.Logger.getLogger;
  * </ul>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
  * @since   1.1
  * @module
  */
@@ -248,6 +249,26 @@ public class RecentReferenceSystems {
         });
     }
 
+    /**
+     * Configures this instance for a grid coverage having the given geometry.
+     * This convenience method sets the {@link #areaOfInterest} and the
+     * {@linkplain #setPreferred(boolean, ReferenceSystem) preferred CRS}
+     * with the information found in the given grid geometry.
+     * The properties for which {@code gg} contains no information are left 
unchanged.
+     *
+     * @param  gg  the grid geometry, or {@code null} if none.
+     *
+     * @since 1.3
+     */
+    public void configure(final GridGeometry gg) {
+        if (gg != null) {
+            areaOfInterest.set(gg.isDefined(GridGeometry.ENVELOPE) ? 
gg.getEnvelope() : null);
+            if (gg.isDefined(GridGeometry.CRS)) {
+                setPreferred(true, gg.getCoordinateReferenceSystem());
+            }
+        }
+    }
+
     /**
      * Sets the native or preferred reference system. This is the system to 
always show as the first
      * choice and should typically be the native {@link 
CoordinateReferenceSystem} of visualized data.
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 42613d6ce3..dffde720ea 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
@@ -51,6 +51,7 @@ import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.operation.matrix.MatrixSIS;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.referencing.operation.transform.LinearTransform;
 import org.apache.sis.referencing.operation.transform.PassThroughTransform;
 import org.apache.sis.internal.referencing.DirectPositionView;
 import org.apache.sis.internal.referencing.TemporalAccessor;
@@ -840,6 +841,30 @@ public class GridGeometry implements LenientComparable, 
Serializable {
         throw incomplete(GRID_TO_CRS, Resources.Keys.UnspecifiedTransform);
     }
 
+    /**
+     * Returns a linear approximation of the conversion from grid coordinates 
to "real world" coordinates.
+     * If the value returned by {@link #getGridToCRS(PixelInCell)} is already 
an instance of {@link LinearTransform},
+     * then it is returned as is. Otherwise this method computes the tangent 
of the transform at the grid extent
+     * {@linkplain GridExtent#getPointOfInterest(PixelInCell) point of 
interest} (usually the center of the grid).
+     *
+     * @param  anchor  the cell part to map (center or corner).
+     * @return linear approximation of the conversion from grid coordinates to 
"real world" coordinates.
+     * @throws IllegalArgumentException if the given {@code anchor} is not a 
known code list value.
+     * @throws IncompleteGridGeometryException if this grid geometry has no 
transform,
+     *          of if the transform is non-linear but this grid geometry has 
no extent.
+     * @throws TransformException if an error occurred while computing the 
tangent.
+     *
+     * @since 1.3
+     */
+    public LinearTransform getLinearGridToCRS(final PixelInCell anchor) throws 
TransformException {
+        final MathTransform tr = getGridToCRS(anchor);
+        if (tr instanceof LinearTransform) {
+            return (LinearTransform) tr;
+        }
+        return MathTransforms.linear(MathTransforms.getMatrix(tr,
+                new 
DirectPositionView.Double(getExtent().getPointOfInterest(anchor))));
+    }
+
     /*
      * Do not provide a convenience `getGridToCRS()` method without 
PixelInCell or PixelOrientation argument.
      * Experience shows that 0.5 pixel offset in image localization is a 
recurrent problem. We really want to

Reply via email to