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
