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: The AffineTransform used for rendering coverage needs to be the concatenation of "grid to CRS" + "objective to display", assuming that "grid CRS" = "objective CRS". This will be needed later for zoom, translations, etc.
Date Tue, 03 Mar 2020 23:37:24 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 8fd1a2f  The AffineTransform used for rendering coverage needs to be the concatenation
of "grid to CRS" + "objective to display", assuming that "grid CRS" = "objective CRS". This
will be needed later for zoom, translations, etc.
8fd1a2f is described below

commit 8fd1a2fd91cbe55a704e970db77cbddf6215eb14
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Wed Mar 4 00:35:04 2020 +0100

    The AffineTransform used for rendering coverage needs to be the concatenation of "grid
to CRS" + "objective to display", assuming that "grid CRS" = "objective CRS".
    This will be needed later for zoom, translations, etc.
---
 .../org/apache/sis/gui/coverage/CoverageView.java  | 68 ++++++++++++++-----
 .../java/org/apache/sis/gui/coverage/GridView.java |  3 +-
 .../org/apache/sis/gui/coverage/StatusBar.java     | 78 +++++++++++-----------
 .../java/org/apache/sis/gui/map/MapCanvas.java     | 44 ++++++++++--
 .../java/org/apache/sis/internal/map/Canvas.java   | 25 +++++--
 .../org/apache/sis/internal/map/Observable.java    | 10 +++
 .../org/apache/sis/internal/map/PlanarCanvas.java  | 60 ++++++++++++-----
 .../operation/matrix/AffineTransforms2D.java       | 19 ++++++
 8 files changed, 225 insertions(+), 82 deletions(-)

diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageView.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageView.java
index 552119b..746c5c2 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageView.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageView.java
@@ -40,6 +40,7 @@ import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.internal.gui.ImageRenderings;
 import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.gui.map.MapCanvas;
+import org.apache.sis.referencing.operation.matrix.AffineTransforms2D;
 
 
 /**
@@ -96,17 +97,24 @@ final class CoverageView extends MapCanvas {
     private RenderedImage data;
 
     /**
-     * The image together with the status bar.
+     * The {@link GridGeometry#getGridToCRS(PixelInCell)} conversion of rendered {@linkplain
#data}
+     * as an affine transform. This is often an immutable instance.
      */
-    private final BorderPane imageAndStatus;
+    private AffineTransform gridToCRS;
+
+    /*
+     * The transform from {@link #data} pixel coordinates to pixel coordinates in the widget.
+     * This is the concatenation of {@link #gridToCRS} followed by {@link #objectiveToDisplay}.
+     * This transform is replaced when the zoom changes or when the viewed area is translated.
+     * We create a new transform each time (we do not modify the existing instance) because
it
+     * may be used by a background thread.
+     */
+    private AffineTransform gridToDisplay;
 
     /**
-     * The transform from {@link #data} pixel coordinates to {@link #buffer} (and {@link
#image})
-     * pixel coordinates. This is the concatenation of {@link GridGeometry#getGridToCRS(PixelInCell)}
-     * followed by {@link #getObjectiveToDisplay()}. This transform is updated when the zoom
changes
-     * or when the viewed area is translated.
+     * The image together with the status bar.
      */
-    private final AffineTransform dataToImage;
+    private final BorderPane imageAndStatus;
 
     /**
      * The bar where to format the coordinates below mouse cursor.
@@ -121,7 +129,6 @@ final class CoverageView extends MapCanvas {
         coverageProperty       = new SimpleObjectProperty<>(this, "coverage");
         sliceExtentProperty    = new SimpleObjectProperty<>(this, "sliceExtent");
         dataAlternatives       = new EnumMap<>(RangeType.class);
-        dataToImage            = new AffineTransform();
         currentDataAlternative = RangeType.DECLARED;
         statusBar              = new StatusBar(this::toImageCoordinates);
         imageAndStatus         = new BorderPane(view);
@@ -231,7 +238,6 @@ final class CoverageView extends MapCanvas {
             clear();
         } else {
             final GridExtent sliceExtent = getSliceExtent();
-            statusBar.setCoordinateConversion(coverage.getGridGeometry(), sliceExtent);
             execute(new Task<RenderedImage>() {
                 /** Invoked in background thread for fetching the image. */
                 @Override protected RenderedImage call() {
@@ -242,8 +248,7 @@ final class CoverageView extends MapCanvas {
                 @Override protected void succeeded() {
                     super.succeeded();
                     if (coverage.equals(getCoverage()) && Objects.equals(sliceExtent,
getSliceExtent())) {
-                        setImage(RangeType.DECLARED, getValue());
-                        setRangeType(currentDataAlternative);
+                        setImage(getValue(), coverage.getGridGeometry(), sliceExtent);
                     }
                 }
             });
@@ -307,6 +312,28 @@ final class CoverageView extends MapCanvas {
     }
 
     /**
+     * Invoked when a new image has been successfully loaded.
+     *
+     * @param  image        the image to load.
+     * @param  geometry     the grid geometry of the coverage that produced the image.
+     * @param  sliceExtent  the extent that were requested.
+     */
+    private void setImage(final RenderedImage image, final GridGeometry geometry, final GridExtent
sliceExtent) {
+        setImage(RangeType.DECLARED, image);
+        setRangeType(currentDataAlternative);
+        statusBar.setCoordinateConversion(geometry, sliceExtent);
+        try {
+            gridToCRS = AffineTransforms2D.castOrCopy(geometry.getGridToCRS(PixelInCell.CELL_CENTER));
+
+            // TODO: scale according the display bounds.
+            setObjectiveToDisplay(new org.apache.sis.internal.referencing.j2d.AffineTransform2D(gridToCRS.createInverse()));
+        } catch (Exception e) {
+            gridToCRS = null;
+            errorOccurred(e);               // Conversion not defined or not affine.
+        }
+    }
+
+    /**
      * Sets the background, as a color for now but more patterns my be allowed in a future
version.
      */
     final void setBackground(final Color color) {
@@ -322,10 +349,14 @@ final class CoverageView extends MapCanvas {
         if (data == null) {
             return null;
         }
-        final AffineTransform dataToImage = new AffineTransform(this.dataToImage);
+        final AffineTransform tr = new AffineTransform(objectiveToDisplay);
+        if (gridToCRS != null) {
+            tr.concatenate(gridToCRS);
+        }
+        gridToDisplay = tr;
         return new Renderer() {
             @Override protected void paint(final Graphics2D gr) {
-                gr.drawRenderedImage(data, dataToImage);
+                gr.drawRenderedImage(data, tr);
             }
         };
     }
@@ -337,7 +368,8 @@ final class CoverageView extends MapCanvas {
      *
      * @todo Should provide a button for getting more details.
      */
-    private void errorOccurred(final Throwable ex) {
+    @Override
+    protected void errorOccurred(final Throwable ex) {
         String message = ex.getMessage();
         if (message == null) {
             message = ex.toString();
@@ -356,11 +388,15 @@ final class CoverageView extends MapCanvas {
     /**
      * Converts pixel indices in the window to pixel indices in the image.
      */
-    private void toImageCoordinates(final double[] indices) {
+    private boolean toImageCoordinates(final double[] indices) {
+        if (gridToDisplay == null) {
+            return false;
+        }
         try {
-            dataToImage.inverseTransform(indices, 0, indices, 0, 1);
+            gridToDisplay.inverseTransform(indices, 0, indices, 0, 1);
         } catch (NoninvertibleTransformException e) {
             throw new BackingStoreException(e);         // Will be unwrapped by the caller
         }
+        return true;
     }
 }
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java
index 7d7f97e..18e9038 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java
@@ -519,9 +519,10 @@ public class GridView extends Control {
      * {@link RenderedImage} uses a coordinate system where the coordinates of the upper-left
corner
      * is not (0,0).
      */
-    final void toImageCoordinates(final double[] indices) {
+    final boolean toImageCoordinates(final double[] indices) {
         indices[0] += minX;
         indices[1] += minY;
+        return true;
     }
 
     /**
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/StatusBar.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/StatusBar.java
index 9de29ed..f3fe06b 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/StatusBar.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/StatusBar.java
@@ -17,7 +17,7 @@
 package org.apache.sis.gui.coverage;
 
 import java.util.Locale;
-import java.util.function.Consumer;
+import java.util.function.Predicate;
 import javax.measure.Unit;
 import javafx.geometry.Insets;
 import javafx.scene.paint.Color;
@@ -80,10 +80,11 @@ final class StatusBar extends HBox implements EventHandler<MouseEvent>
{
     /**
      * A function which take cell indices in input and gives pixel coordinates in output.
      * The input and output coordinates are in the given array, which is updated in-place.
+     * This method returns {@code false} if it can not compute the coordinates.
      *
      * @see GridView#toImageCoordinates(double[])
      */
-    private final Consumer<double[]> toImageCoordinates;
+    private final Predicate<double[]> toImageCoordinates;
 
     /**
      * Zero-based cell coordinates currently formatted in the {@link #coordinates} field.
@@ -128,7 +129,7 @@ final class StatusBar extends HBox implements EventHandler<MouseEvent>
{
     /**
      * Creates a new status bar.
      */
-    StatusBar(final Consumer<double[]> toImageCoordinates) {
+    StatusBar(final Predicate<double[]> toImageCoordinates) {
         this.toImageCoordinates = toImageCoordinates;
         format      = new CoordinateFormat();
         coordinates = new Label();
@@ -230,46 +231,47 @@ final class StatusBar extends HBox implements EventHandler<MouseEvent>
{
         if (x != column || y != row) {
             sourceCoordinates[0] = column = x;
             sourceCoordinates[1] = row    = y;
-            String text;
+            String text = null;
             try {
-                toImageCoordinates.accept(sourceCoordinates);
-                Matrix derivative;
-                try {
-                    derivative = MathTransforms.derivativeAndTransform(gridToCRS,
-                            sourceCoordinates, 0, targetCoordinates.coordinates, 0);
-                } catch (TransformException ignore) {
-                    /*
-                     * If above operation failed, it may be because the MathTransform does
not support
-                     * derivative calculation. Try again without derivative (the precision
will be set
-                     * to the default resolution computed in `setCoordinateConversion(…)`).
-                     */
-                    gridToCRS.transform(sourceCoordinates, 0, targetCoordinates.coordinates,
0, 1);
-                    derivative = null;
-                }
-                if (derivative == null) {
-                    precisions = null;
-                } else {
-                    if (precisions == null) {
-                        precisions = new double[targetCoordinates.getDimension()];
+                if (toImageCoordinates.test(sourceCoordinates)) {
+                    Matrix derivative;
+                    try {
+                        derivative = MathTransforms.derivativeAndTransform(gridToCRS,
+                                sourceCoordinates, 0, targetCoordinates.coordinates, 0);
+                    } catch (TransformException ignore) {
+                        /*
+                         * If above operation failed, it may be because the MathTransform
does not support
+                         * derivative calculation. Try again without derivative (the precision
will be set
+                         * to the default resolution computed in `setCoordinateConversion(…)`).
+                         */
+                        gridToCRS.transform(sourceCoordinates, 0, targetCoordinates.coordinates,
0, 1);
+                        derivative = null;
                     }
-                    /*
-                     * Estimate the precision by looking at the maximal displacement in the
CRS caused by
-                     * a displacement of one cell (i.e. when moving by row or one column).
 We search for
-                     * maximal displacement instead than minimal because we expect the displacement
to be
-                     * zero along some axes (e.g. one row down does not change longitude
value in a Plate
-                     * Carrée projection).
-                     */
-                    for (int j=derivative.getNumRow(); --j >= 0;) {
-                        double p = 0;
-                        for (int i=derivative.getNumCol(); --i >= 0;) {
-                            final double e = Math.abs(derivative.getElement(j, i));
-                            if (e > p) p = e;
+                    if (derivative == null) {
+                        precisions = null;
+                    } else {
+                        if (precisions == null) {
+                            precisions = new double[targetCoordinates.getDimension()];
+                        }
+                        /*
+                         * Estimate the precision by looking at the maximal displacement
in the CRS caused by
+                         * a displacement of one cell (i.e. when moving by row or one column).
 We search for
+                         * maximal displacement instead than minimal because we expect the
displacement to be
+                         * zero along some axes (e.g. one row down does not change longitude
value in a Plate
+                         * Carrée projection).
+                         */
+                        for (int j=derivative.getNumRow(); --j >= 0;) {
+                            double p = 0;
+                            for (int i=derivative.getNumCol(); --i >= 0;) {
+                                final double e = Math.abs(derivative.getElement(j, i));
+                                if (e > p) p = e;
+                            }
+                            precisions[j] = p;
                         }
-                        precisions[j] = p;
                     }
+                    format.setPrecisions(precisions);
+                    text = format.format(targetCoordinates);
                 }
-                format.setPrecisions(precisions);
-                text = format.format(targetCoordinates);
             } catch (TransformException | RuntimeException e) {
                 /*
                  * If even the fallback without derivative failed, show the error message.
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 bcb7733..b53cd8f 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
@@ -33,9 +33,12 @@ import javafx.scene.layout.Pane;
 import javafx.beans.Observable;
 import javafx.concurrent.Task;
 import javafx.util.Callback;
+import org.apache.sis.geometry.Envelope2D;
 import org.apache.sis.internal.coverage.j2d.ColorModelFactory;
 import org.apache.sis.internal.gui.BackgroundThreads;
+import org.apache.sis.internal.gui.ExceptionReporter;
 import org.apache.sis.internal.map.PlanarCanvas;
+import org.apache.sis.internal.map.RenderException;
 import org.apache.sis.internal.util.Numerics;
 
 
@@ -58,6 +61,9 @@ public abstract class MapCanvas extends PlanarCanvas {
      * {@link org.apache.sis.coverage.grid.GridCoverage} which may have any color model.
      * This buffered image will contain only the visible region of the map;
      * it may be a zoom over a small region.
+     *
+     * <p>This buffered image contains the same data than the {@linkplain #image} of
this canvas.
+     * Those two images will share the same data array (no copy) and the same coordinate
system.</p>
      */
     private BufferedImage buffer;
 
@@ -83,15 +89,14 @@ public abstract class MapCanvas extends PlanarCanvas {
     private PixelBuffer<IntBuffer> bufferWrapper;
 
     /**
-     * The node where the image will be shown. The image of this canvas contains the same
data than
-     * {@link #buffer}. They will share the same data array (no copy) and the same coordinate
system.
+     * The node where the rendered map will be shown.
      */
-    private final ImageView image;
+    protected final ImageView image;
 
     /**
      * The pane where to put children. This pane uses absolute layout. It contains at least
the
-     * JavaFX image of the map, but can also contain additional nodes for geometric shapes,
texts,
-     * <i>etc</i>.
+     * JavaFX {@linkplain #image} of the map, but can also contain additional nodes for geometric
+     * shapes, texts, <i>etc</i>.
      */
     protected final Pane view;
 
@@ -120,6 +125,11 @@ public abstract class MapCanvas extends PlanarCanvas {
     private boolean isRendering;
 
     /**
+     * Whether the size of this canvas changed.
+     */
+    private boolean sizeChanged;
+
+    /**
      * Creates a new canvas for JavaFX application.
      *
      * @param  locale  the locale to use for labels and some messages, or {@code null} for
default.
@@ -153,6 +163,7 @@ public abstract class MapCanvas extends PlanarCanvas {
      */
     private void onSizeChanged(final Observable property) {
         contentChangeCount++;
+        sizeChanged = true;
         repaint();
     }
 
@@ -301,6 +312,19 @@ public abstract class MapCanvas extends PlanarCanvas {
             return;
         }
         renderedContentStamp = contentChangeCount;
+        /*
+         * If a new canvas size is known, inform the parent `PlanarCanvas` about that.
+         * It may cause a recomputation of the "objective to display" transform.
+         */
+        if (sizeChanged) try {
+            sizeChanged = false;
+            Envelope2D bounds = new Envelope2D(null, view.getLayoutX(), view.getLayoutY(),
view.getWidth(), view.getHeight());
+            if (bounds.isEmpty()) return;
+            setDisplayBounds(bounds);
+        } catch (RenderException ex) {
+            errorOccurred(ex);
+            return;
+        }
         final Renderer context = createRenderer();
         if (context == null || !context.initialize(view)) {
             return;
@@ -486,4 +510,14 @@ public abstract class MapCanvas extends PlanarCanvas {
         doubleBuffer        = null;
         bufferConfiguration = null;
     }
+
+    /**
+     * Invoked when an error occurred. The default implementation popups a dialog box.
+     * Subclasses may override. For example the error messages could be written in a status
bar instead.
+     *
+     * @param  ex  the exception that occurred.
+     */
+    protected void errorOccurred(final Throwable ex) {
+        ExceptionReporter.show(null, null, ex);
+    }
 }
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Canvas.java b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Canvas.java
index 1fa4edc..56126de 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Canvas.java
+++ b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Canvas.java
@@ -106,10 +106,8 @@ import org.apache.sis.coverage.grid.GridExtent;
  * <h2>Location of data to display</h2>
  * In addition of above-cited Coordinate Reference Systems, a {@code Canvas} contains also
a point of interest.
  * The point of interest is often, but not necessarily, at the center of display area.
- * It defines the position where {@linkplain #getSpatialResolution() resolutions} will be
computed, and where
- * {@linkplain PlanarCanvas#scale(double, double) scales},
- * {@linkplain PlanarCanvas#translate(double, double) translations} and
- * {@linkplain PlanarCanvas#rotate(double) rotations} will be applied.
+ * It defines the position where {@linkplain #getSpatialResolution() resolutions} will be
computed,
+ * and the position to keep fixed when scales and rotations are applied.
  *
  * <p>The point of interest can be expressed in any CRS;
  * it does not need to be the objective CRS or the CRS of any data.
@@ -593,6 +591,7 @@ public class Canvas extends Observable implements Localized {
      * @return snapshot of objective to display conversion, never null.
      *
      * @see #updateObjectiveToDisplay(LinearTransform)
+     * @see #invalidateObjectiveToDisplay(LinearTransform)
      */
     LinearTransform updateObjectiveToDisplay() {
         return MathTransforms.identity(getDisplayDimensions());
@@ -648,6 +647,24 @@ public class Canvas extends Observable implements Localized {
     }
 
     /**
+     * Declares that the {@link #objectiveToDisplay} transform became invalid and will need
to be recomputed.
+     * It is subclasses responsibility to recompute the transform in their {@link #updateObjectiveToDisplay()}
+     * method.
+     *
+     * @param  oldValue  the old value, or {@code null} for not firing change event.
+     *
+     * @see #updateObjectiveToDisplay()
+     */
+    final void invalidateObjectiveToDisplay(final LinearTransform oldValue) {
+        objectiveToDisplay = null;
+        gridGeometry       = null;
+        operationContext.clear();
+        if (oldValue != null) {
+            firePropertyChange(OBJECTIVE_TO_DISPLAY_PROPERTY, oldValue, getObjectiveToDisplay());
+        }
+    }
+
+    /**
      * Returns the size and location of the display device.
      * The unit of measurement is typically (but not necessarily) pixels.
      * The coordinate values are often integers, but this is not mandatory.
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Observable.java
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Observable.java
index 99d7baa..22e6d48 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Observable.java
+++ b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Observable.java
@@ -147,4 +147,14 @@ abstract class Observable {
             }
         }
     }
+
+    /**
+     * Returns {@code true} if the given property has at least one listener.
+     *
+     * @param  propertyName  name of the property.
+     * @return {@code true} if the given property has at least one listener.
+     */
+    final boolean hasListener(final String propertyName) {
+        return (listeners != null) && listeners.containsKey(propertyName);
+    }
 }
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/PlanarCanvas.java
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/PlanarCanvas.java
index ec0eed8..b0c8792 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/PlanarCanvas.java
+++ b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/PlanarCanvas.java
@@ -68,16 +68,16 @@ public abstract class PlanarCanvas extends Canvas {
     }
 
     /**
-     * The conversion from {@linkplain #getObjectiveCRS() objective CRS} to the display coordinate
system.
-     * This transform will be modified in-place when user applies zoom, translation or rotation
on the view area.
-     * The {@link #objectiveToDisplay} transform inherited from parent class is used as an
immutable snapshot of
-     * this {@link #toDisplayAsAffine} transform. That snapshot is created when needed and
reset to {@code null}
-     * when {@link #toDisplayAsAffine} is modified.
+     * The conversion from {@linkplain #getObjectiveCRS() objective CRS} to the display coordinate
system as a
+     * Java2D affine transform. This transform will be modified in-place when user applies
zoom, translation or
+     * rotation on the view area. Subclasses should generally not modify this affine transform
directly; invoke
+     * one of the <code>transform<var>Foo</var>Coordinates(AffineTransform)</code>
methods instead.
      *
      * @see #getObjectiveToDisplay()
-     * @see #objectiveToDisplay
+     * @see #transformObjectiveCoordinates(AffineTransform)
+     * @see #transformDisplayCoordinates(AffineTransform)
      */
-    private final AffineTransform toDisplayAsAffine;
+    protected final AffineTransform objectiveToDisplay;
 
     /**
      * Creates a new two-dimensional canvas.
@@ -86,7 +86,7 @@ public abstract class PlanarCanvas extends Canvas {
      */
     protected PlanarCanvas(final Locale locale) {
         super(DISPLAY_CRS, locale);
-        toDisplayAsAffine = new AffineTransform();
+        objectiveToDisplay = new AffineTransform();
     }
 
     /**
@@ -141,12 +141,18 @@ public abstract class PlanarCanvas extends Canvas {
      * subsequent changes in the <cite>objective to display</cite> conversion
are not reflected in
      * the returned transform.
      *
+     * <p>The {@link Canvas#objectiveToDisplay} transform in parent class is used as
an immutable snapshot of
+     * the {@link #objectiveToDisplay} transform in this class. That snapshot is created
when needed and reset
+     * to {@code null} when {@link #objectiveToDisplay} is modified.</p>
+     *
      * @return snapshot of the affine conversion from objective CRS
      *         to display coordinate system (never {@code null}).
+     *
+     * @see Canvas#objectiveToDisplay
      */
     @Override
     final LinearTransform updateObjectiveToDisplay() {
-        return new AffineTransform2D(toDisplayAsAffine);
+        return new AffineTransform2D(objectiveToDisplay);
     }
 
     /**
@@ -161,19 +167,37 @@ public abstract class PlanarCanvas extends Canvas {
      */
     @Override
     final void updateObjectiveToDisplay(final LinearTransform newValue) {
-        toDisplayAsAffine.setTransform(AffineTransforms2D.castOrCopy(newValue.getMatrix()));
+        objectiveToDisplay.setTransform(AffineTransforms2D.castOrCopy(newValue.getMatrix()));
         super.updateObjectiveToDisplay(newValue);
     }
 
-    public void scale(final double sx, final double sy) {
-        // TODO
-    }
-
-    public void translate(final double tx, final double ty) {
-        // TODO
+    /**
+     * Updates the <cite>objective to display</cite> transform as if the given
transform was applied <em>before</em>
+     * the current transform. For example if the given {@code before} transform is a translation,
then the translation
+     * vector is in units of the {@linkplain #getObjectiveCRS() objective CRS} (typically
metres on the map).
+     *
+     * @param  before  coordinate conversion to apply before the current <cite>objective
to display</cite> transform.
+     */
+    public void transformObjectiveCoordinates(final AffineTransform before) {
+        if (!before.isIdentity()) {
+            final LinearTransform old = hasListener(OBJECTIVE_TO_DISPLAY_PROPERTY) ? getObjectiveToDisplay()
: null;
+            objectiveToDisplay.concatenate(before);
+            invalidateObjectiveToDisplay(old);
+        }
     }
 
-    public void rotate(final double angle) {
-        // TODO
+    /**
+     * Updates the <cite>objective to display</cite> transform as if the given
transform was applied <em>after</em>
+     * the current transform. For example if the given {@code after} transform is a translation,
then the translation
+     * vector is in pixel units.
+     *
+     * @param  after  coordinate conversion to apply after the current <cite>objective
to display</cite> transform.
+     */
+    public void transformDisplayCoordinates(final AffineTransform after) {
+        if (!after.isIdentity()) {
+            final LinearTransform old = hasListener(OBJECTIVE_TO_DISPLAY_PROPERTY) ? getObjectiveToDisplay()
: null;
+            objectiveToDisplay.preConcatenate(after);
+            invalidateObjectiveToDisplay(old);
+        }
     }
 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/AffineTransforms2D.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/AffineTransforms2D.java
index 3467be5..781c76b 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/AffineTransforms2D.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/AffineTransforms2D.java
@@ -25,6 +25,8 @@ import java.awt.geom.RectangularShape;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.NoninvertibleTransformException;
 import org.opengis.referencing.operation.Matrix;
+import org.opengis.referencing.operation.MathTransform;
+import org.apache.sis.referencing.operation.transform.LinearTransform;
 import org.apache.sis.internal.referencing.Resources;
 import org.apache.sis.util.Static;
 import org.apache.sis.util.ArgumentChecks;
@@ -50,6 +52,23 @@ public final class AffineTransforms2D extends Static {
     }
 
     /**
+     * Returns the given transform as a Java2D affine transform.
+     *
+     * @param  transform  the transform to convert, or {@code null}.
+     * @return the transform argument if it can be safely casted (including {@code null}
argument) or converted.
+     * @throws IllegalArgumentException if the given transform can not be caster or converted.
+     */
+    public static AffineTransform castOrCopy(final MathTransform transform) throws IllegalArgumentException
{
+        if (transform == null || transform instanceof AffineTransform) {
+            return (AffineTransform) transform;
+        }
+        if (transform instanceof LinearTransform) {
+            return castOrCopy(((LinearTransform) transform).getMatrix());
+        }
+        throw new IllegalArgumentException(Resources.format(Resources.Keys.NotAnAffineTransform));
+    }
+
+    /**
      * Returns the given matrix as a Java2D affine transform.
      * If the given matrix is already an instance of {@link AffineTransform}, then it is
returned directly.
      * Otherwise the values are copied in a new {@code AffineTransform} instance.


Mime
View raw message