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: Display geographic or projected coordinates of selected cell.
Date Mon, 03 Feb 2020 14:40:21 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 37696ea  Display geographic or projected coordinates of selected cell.
37696ea is described below

commit 37696eaab23cd013aa516ca539e18da8fa1ee13b
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Feb 3 15:38:25 2020 +0100

    Display geographic or projected coordinates of selected cell.
---
 .../apache/sis/gui/coverage/CoverageExplorer.java  |   6 +-
 .../java/org/apache/sis/gui/coverage/GridView.java |  66 +++++---
 .../org/apache/sis/gui/coverage/GridViewSkin.java  |  75 +++++++--
 .../org/apache/sis/gui/coverage/ImageLoader.java   |  45 +++++-
 .../org/apache/sis/gui/coverage/ImageRequest.java  |  56 ++-----
 .../org/apache/sis/gui/coverage/StatusBar.java     | 179 +++++++++++++++++++++
 6 files changed, 332 insertions(+), 95 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 d2ecb09..b7ed500 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
@@ -223,7 +223,7 @@ public class CoverageExplorer {
         if (source == null) {
             setCoverage((GridCoverage) null);
         } else {
-            source.addListener(this);
+            source.listener = this;
             gridView.setImage(source);
         }
     }
@@ -242,7 +242,7 @@ public class CoverageExplorer {
         if (coverage != null) {
             gridView.setImage(new ImageRequest(coverage, null));        // Start a background
thread.
         }
-        onLoadStep(coverage);
+        onCoverageLoaded(coverage);
     }
 
     /**
@@ -253,7 +253,7 @@ public class CoverageExplorer {
      *
      * @param  coverage  the new coverage, or {@code null} if loading failed.
      */
-    final void onLoadStep(final GridCoverage coverage) {
+    final void onCoverageLoaded(final GridCoverage coverage) {
         final ObservableList<SampleDimension> items = sampleDimensions.getItems();
         if (coverage != null) {
             items.setAll(coverage.getSampleDimensions());
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 297a115..f343ad7 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
@@ -35,7 +35,9 @@ import javafx.scene.control.Control;
 import javafx.scene.control.Skin;
 import javafx.scene.paint.Color;
 import javafx.scene.paint.Paint;
+import org.opengis.geometry.DirectPosition;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.geometry.DirectPosition2D;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.internal.gui.BackgroundThreads;
 
@@ -74,7 +76,7 @@ public class GridView extends Control {
     /**
      * If a loading is in progress, the loading process. Otherwise {@code null}.
      *
-     * @see #coverageDefined(WorkerStateEvent)
+     * @see #onImageLoaded(WorkerStateEvent)
      */
     private ImageLoader loader;
 
@@ -250,7 +252,7 @@ public class GridView extends Control {
                 loader.cancel();
             }
             loader = new ImageLoader(source, true);
-            loader.setOnSucceeded(this::coverageDefined);
+            loader.setOnSucceeded(this::onImageLoaded);
             BackgroundThreads.execute(loader);
         }
     }
@@ -286,11 +288,14 @@ public class GridView extends Control {
 
     /**
      * Invoked in JavaFX thread after {@link #loader} completed its task successfully.
+     * This method updates the image shown in this {@link GridView} and configures the
+     * status bar.
      */
-    private void coverageDefined(final WorkerStateEvent event) {
-        final ImageLoader result = loader;
+    private void onImageLoaded(final WorkerStateEvent event) {
         loader = null;
+        final ImageLoader result = (ImageLoader) event.getSource();
         setImage(result.getValue());
+        result.request.configure(((GridViewSkin) getSkin()).statusBar);
     }
 
     /**
@@ -314,15 +319,15 @@ public class GridView extends Control {
         width    = 0;
         height   = 0;
         if (image != null) {
-            minX              = image.getMinX();
-            minY              = image.getMinY();
-            width             = image.getWidth();
-            height            = image.getHeight();
-            numXTiles         = image.getNumXTiles();
-            tileWidth         = Math.max(1, image.getTileWidth());
-            tileHeight        = Math.max(1, image.getTileHeight());
-            tileGridXOffset   = Math.subtractExact(image.getTileGridXOffset(), minX);
-            tileGridYOffset   = Math.subtractExact(image.getTileGridYOffset(), minY);
+            minX            = image.getMinX();
+            minY            = image.getMinY();
+            width           = image.getWidth();
+            height          = image.getHeight();
+            numXTiles       = image.getNumXTiles();
+            tileWidth       = Math.max(1, image.getTileWidth());
+            tileHeight      = Math.max(1, image.getTileHeight());
+            tileGridXOffset = Math.subtractExact(image.getTileGridXOffset(), minX);
+            tileGridYOffset = Math.subtractExact(image.getTileGridYOffset(), minY);
             cellFormat.dataTypeisInteger = false;           // To be kept consistent with
`cellFormat` pattern.
             final SampleModel sm = image.getSampleModel();
             if (sm != null) {                               // Should never be null, but
we are paranoiac.
@@ -372,12 +377,22 @@ public class GridView extends Control {
      */
     final double getContentWidth() {
         /*
-         * Add one more column for avoiding offsets caused by the rounding of scroll bar
position
-         * to integer multiple of column size. The 20 minimal value used below is arbitrary;
+         * Add one more column for avoiding offsets caused by the rounding of scroll bar
position to
+         * integer multiple of column size. The SCROLLBAR_WIDTH minimal value used below
is arbitrary;
          * we take a value close to the vertical scrollbar width as a safety.
          */
         final double w = getSizeValue(cellWidth);
-        return width * w + getSizeValue(headerWidth) + Math.max(w, 20);
+        return width * w + getSizeValue(headerWidth) + Math.max(w, GridViewSkin.SCROLLBAR_WIDTH);
+    }
+
+    /**
+     * Returns the value of the given property as a real number not smaller than {@value
#MIN_CELL_SIZE}.
+     * We use this method instead of {@link Math#max(double, double)} because we want {@link
Double#NaN}
+     * values to be replaced by {@value #MIN_CELL_SIZE}.
+     */
+    static double getSizeValue(final DoubleProperty property) {
+        final double value = property.get();
+        return (value >= MIN_CELL_SIZE) ? value : MIN_CELL_SIZE;
     }
 
     /**
@@ -482,6 +497,15 @@ public class GridView extends Control {
     }
 
     /**
+     * Converts cell indices to pixel indices. They are often the same indices, but may differ
if the
+     * {@link RenderedImage} uses a coordinate system where the coordinates of the upper-left
corner
+     * is not (0,0).
+     */
+    final DirectPosition toImageCoordinates(final int column, final int row) {
+        return new DirectPosition2D(column + (long) minX, row + (long) minY);
+    }
+
+    /**
      * Creates a new instance of the skin responsible for rendering this grid view.
      * From the perspective of this {@link Control}, the {@link Skin} is a black box.
      * It listens and responds to changes in state of this grid view. This method is
@@ -493,14 +517,4 @@ public class GridView extends Control {
     protected final Skin<GridView> createDefaultSkin() {
         return new GridViewSkin(this);
     }
-
-    /**
-     * Returns the value of the given property as a real number not smaller than {@value
#MIN_CELL_SIZE}.
-     * We use this method instead of {@link Math#max(double, double)} because we want {@link
Double#NaN}
-     * values to be replaced by {@value #MIN_CELL_SIZE}.
-     */
-    static double getSizeValue(final DoubleProperty property) {
-        final double value = property.get();
-        return (value >= MIN_CELL_SIZE) ? value : MIN_CELL_SIZE;
-    }
 }
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridViewSkin.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridViewSkin.java
index 23f7f30..b9b5dd6 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridViewSkin.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridViewSkin.java
@@ -27,6 +27,7 @@ import javafx.scene.control.ScrollBar;
 import javafx.scene.control.skin.VirtualFlow;
 import javafx.scene.control.skin.VirtualContainerBase;
 import javafx.scene.layout.HBox;
+import javafx.scene.paint.Color;
 import javafx.scene.shape.Rectangle;
 import javafx.scene.text.FontWeight;
 import javafx.scene.text.Font;
@@ -55,6 +56,11 @@ import org.apache.sis.internal.gui.Styles;
  */
 final class GridViewSkin extends VirtualContainerBase<GridView, GridRow> implements
EventHandler<MouseEvent> {
     /**
+     * Approximate size of vertical scroll bar.
+     */
+    static final int SCROLLBAR_WIDTH = 20;
+
+    /**
      * The cells that we put in the header row on the top of the view. The children list
is initially empty;
      * new elements are added or removed when first needed and when the view size changed.
      */
@@ -126,9 +132,14 @@ final class GridViewSkin extends VirtualContainerBase<GridView, GridRow>
impleme
     private boolean hasErrors;
 
     /**
-     * A rectangle around selected cells.
+     * A rectangle around selected cells in the content area or in the row/column header.
+     */
+    private final Rectangle selection, selectedRow, selectedColumn;
+
+    /**
+     * The status bar where to show coordinates of selected cell.
      */
-    private final Rectangle selection;
+    final StatusBar statusBar;
 
     /**
      * Creates a new skin for the specified view.
@@ -158,15 +169,25 @@ final class GridViewSkin extends VirtualContainerBase<GridView, GridRow>
impleme
          * Rectangle around the selected cell (for example the cell below mouse position).
          * Become visible only when the mouse enter in the widget area.
          */
-        selection = new Rectangle(view.cellWidth.getValue(), view.cellHeight.getValue());
-        selection.setFill(Styles.SELECTION_BACKGROUND);
-        selection.setVisible(false);
+        selection      = new Rectangle();
+        selectedRow    = new Rectangle();
+        selectedColumn = new Rectangle();
+        selection     .setFill(Styles.SELECTION_BACKGROUND);
+        selectedRow   .setFill(Color.SILVER);
+        selectedColumn.setFill(Color.SILVER);
+        selection     .setVisible(false);
+        selectedRow   .setVisible(false);
+        selectedColumn.setVisible(false);
         flow.setOnMouseExited(this::hideSelection);
         /*
+         * The status bar where to show coordinates of selected cell.
+         */
+        statusBar = new StatusBar(view);
+        /*
          * The list of children is initially empty. We need to
          * add the virtual flow, otherwise nothing will appear.
          */
-        getChildren().addAll(topBackground, leftBackground, headerRow, selection, flow);
+        getChildren().addAll(topBackground, leftBackground, selectedColumn, selectedRow,
headerRow, selection, statusBar, flow);
     }
 
     /**
@@ -179,24 +200,37 @@ final class GridViewSkin extends VirtualContainerBase<GridView, GridRow>
impleme
     @Override
     public final void handle(final MouseEvent event) {
         final double tx = leftBackground.getWidth();
-        final double x = event.getX() - tx;
+        double x = event.getX() - tx;
         boolean visible = (x >= 0);
         if (visible) {
             final int column = (int) (x / cellWidth);
             visible = (column >= firstVisibleColumn);
             if (visible) {
-                selection.setX((column - firstVisibleColumn) * cellWidth + tx);
-                selection.setY(((GridRow) event.getSource()).getLayoutY() + topBackground.getHeight());
+                final GridRow row = (GridRow) event.getSource();
+                double y = row.getLayoutY();
+                visible = y < getVirtualFlow().getHeight();
+                if (visible) {
+                    x  = (column - firstVisibleColumn) * cellWidth + tx;
+                    y += topBackground.getHeight();
+                    selection.relocate(x, y);
+                    selectedRow.setY(y);
+                    selectedColumn.setX(x);
+                    statusBar.setCoordinates(column, row.getIndex());
+                }
             }
         }
-        selection.setVisible(visible);
+        selection     .setVisible(visible);
+        selectedRow   .setVisible(visible);
+        selectedColumn.setVisible(visible);
     }
 
     /**
      * Hides the selection when the mouse moved outside the grid view area.
      */
     private void hideSelection(final MouseEvent event) {
-        selection.setVisible(false);
+        selection     .setVisible(false);
+        selectedRow   .setVisible(false);
+        selectedColumn.setVisible(false);
     }
 
     /**
@@ -401,10 +435,12 @@ final class GridViewSkin extends VirtualContainerBase<GridView, GridRow>
impleme
          * which may change the size calculations done after that. The flow is located below
the
          * header row, so we adjust y and height accordingly.
          */
-        final Flow    flow         = (Flow) getVirtualFlow();
-        final double  headerHeight = flow.getFixedCellSize() + 2*cellSpacing;
-        final double  dataY        = y + headerHeight;
-        final double  dataHeight   = height - headerHeight;
+        final Flow   flow         = (Flow) getVirtualFlow();
+        final double cellHeight   = flow.getFixedCellSize();
+        final double headerHeight = cellHeight + 2*cellSpacing;
+        final double statusHeight = statusBar.getHeight();
+        final double dataY        = y + headerHeight;
+        final double dataHeight   = height - headerHeight - statusHeight;
         layoutAll |= (flow.getWidth() != width) || (flow.getHeight() != dataHeight);
         flow.resizeRelocate(x, dataY, width, dataHeight);
         /*
@@ -431,6 +467,14 @@ final class GridViewSkin extends VirtualContainerBase<GridView, GridRow>
impleme
         leftBackground.setY(dataY);
         leftBackground.setWidth(headerWidth);
         leftBackground.setHeight(flow.getVisibleHeight());
+        selection     .setWidth (cellWidth);
+        selectedRow   .setWidth (headerWidth);
+        selectedColumn.setWidth (cellWidth);
+        selection     .setHeight(cellHeight);
+        selectedRow   .setHeight(cellHeight);
+        selectedColumn.setHeight(headerHeight);
+        selectedRow   .setX(x);
+        selectedColumn.setY(y);
         if (cellSpacing < headerWidth) {
             headerWidth  -= cellSpacing;
             leftPosition += cellSpacing;
@@ -442,6 +486,7 @@ final class GridViewSkin extends VirtualContainerBase<GridView, GridRow>
impleme
          */
         if (layoutAll || oldPos != leftPosition) {
             layoutInArea(headerRow, x, y, width, headerHeight, Node.BASELINE_OFFSET_SAME_AS_HEIGHT,
HPos.LEFT, VPos.TOP);
+            layoutInArea(statusBar, x, height - statusHeight, width, statusHeight, Node.BASELINE_OFFSET_SAME_AS_HEIGHT,
HPos.RIGHT, VPos.BOTTOM);
             final ObservableList<Node> children = headerRow.getChildren();
             final int count   = children.size();
             final int missing = (int) Math.ceil((width - headerWidth) / cellWidth) - count;
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImageLoader.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImageLoader.java
index eb3ff25..b62b52d 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImageLoader.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImageLoader.java
@@ -48,7 +48,13 @@ final class ImageLoader extends Task<RenderedImage> {
     /**
      * The image source together with optional parameters for reading only a subset.
      */
-    private final ImageRequest request;
+    final ImageRequest request;
+
+    /**
+     * The coverage explorer to inform when {@link ImageRequest#coverage} become available,
or {@code null} if none.
+     * We do not provide a more generic listeners API for now, but it could be done in the
future if there is a need.
+     */
+    private CoverageExplorer listener;
 
     /**
      * Whether the caller wants a grid coverage that contains real values or sample values.
@@ -63,8 +69,10 @@ final class ImageLoader extends Task<RenderedImage> {
      *                    or {@code false} for a coverage containing packed values.
      */
     ImageLoader(final ImageRequest request, final boolean converted) {
-        this.request   = request;
-        this.converted = converted;
+        this.request     = request;
+        this.converted   = converted;
+        this.listener    = request.listener;
+        request.listener = null;
     }
 
     /**
@@ -118,7 +126,7 @@ final class ImageLoader extends Task<RenderedImage> {
             cv = request.resource.read(domain, range);                      // May be long
to execute.
             cv = cv.forConvertedValues(converted);
             request.coverage = cv;
-            Platform.runLater(request::notifyLoaded);
+            Platform.runLater(this::fireCoverageLoaded);
         }
         if (isCancelled()) {
             return null;
@@ -140,7 +148,7 @@ final class ImageLoader extends Task<RenderedImage> {
     @Override
     protected void failed() {
         super.failed();
-        request.notifyListeners(null);
+        fireFinished(null);
         final GridCoverageResource resource = request.resource;
         if (resource instanceof StoreListeners) {
             ExceptionReporter.canNotReadFile(((StoreListeners) resource).getSourceName(),
getException());
@@ -155,6 +163,31 @@ final class ImageLoader extends Task<RenderedImage> {
     @Override
     protected void cancelled() {
         super.cancelled();
-        request.notifyListeners(null);
+        fireFinished(null);
+    }
+
+    /**
+     * Notifies listener that the given coverage has been read or failed to be read,
+     * then discards the listener. This method shall be invoked in JavaFX thread.
+     *
+     * <p>This method is also invoked with a null argument for notifying listener
+     * that the read operation failed (or has been cancelled).</p>
+     *
+     * @param  result  the result, or {@code null} on failure.
+     */
+    private void fireFinished(final GridCoverage result) {
+        final CoverageExplorer snapshot = listener;
+        if (snapshot != null) {
+            listener = null;                               // Clear now in case an error
happen.
+            snapshot.onCoverageLoaded(result);
+        }
+    }
+
+    /**
+     * Notifies all listeners that the coverage has been read, then discards the listeners.
+     * This method shall be invoked in JavaFX thread.
+     */
+    private void fireCoverageLoaded() {
+        fireFinished(request.coverage);
     }
 }
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImageRequest.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImageRequest.java
index 9d37913..e1fae05 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImageRequest.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImageRequest.java
@@ -16,9 +16,9 @@
  */
 package org.apache.sis.gui.coverage;
 
-import java.util.Arrays;
 import java.util.Optional;
 import java.util.OptionalInt;
+import javafx.concurrent.WorkerStateEvent;
 import org.apache.sis.storage.GridCoverageResource;
 import org.apache.sis.coverage.grid.GridDerivation;
 import org.apache.sis.coverage.grid.GridCoverage;
@@ -91,13 +91,11 @@ public class ImageRequest {
     private int overviewSize;
 
     /**
-     * The coverage explorers to inform when {@link #coverage} become available, or {@code
null} if none.
-     * We do not provide a more generic listeners API for now, but it could be done in the
future if there
-     * is a need for that.
-     *
-     * @see #addListener(CoverageExplorer)
+     * For transferring a listener to {@link ImageLoader#listener} before background execution
starts.
+     * We do not provide a more generic listeners API for now, but it could be done in the
future
+     * if there is a need for that.
      */
-    private CoverageExplorer[] listeners;
+    CoverageExplorer listener;
 
     /**
      * Creates a new request for loading an image from the specified resource.
@@ -250,44 +248,12 @@ public class ImageRequest {
     }
 
     /**
-     * Adds a listener to inform when {@link #coverage} become available. We do not provide
a more
-     * generic listeners API for now, but it could be done in the future if there is a need
for that.
-     *
-     * <p>All listeners are discarded after the reading process.</p>
-     */
-    final void addListener(final CoverageExplorer listener) {
-        final int n;
-        if (listeners == null) {                        // Usual case.
-            n = 0;
-            listeners = new CoverageExplorer[1];
-        } else {                                        // Should be very rare.
-            n = listeners.length;
-            listeners = Arrays.copyOf(listeners, n+1);
-        }
-        listeners[n] = listener;
-    }
-
-    /**
-     * Notifies all listeners that the given coverage has been read or failed to be read,
-     * then discards the listeners. This method shall be invoked in JavaFX thread.
-     *
-     * @param  result  the result, or {@code null} on failure.
-     */
-    final void notifyListeners(final GridCoverage result) {
-        final CoverageExplorer[] snapshot = listeners;
-        if (snapshot != null) {
-            listeners = null;                               // Clear now in case an error
happen.
-            for (final CoverageExplorer e : snapshot) {
-                e.onLoadStep(result);
-            }
-        }
-    }
-
-    /**
-     * Notifies all listeners that the coverage has been read, then discards the listeners.
-     * This method shall be invoked in JavaFX thread.
+     * Configures the given status bar with the geometry of the grid coverage we have just
read.
+     * This method is invoked by{@link GridView#onImageLoaded(WorkerStateEvent)} in JavaFX
thread
+     * after {@link ImageLoader} successfully loaded in background thread a new image.
      */
-    final void notifyLoaded() {
-        notifyListeners(coverage);
+    final void configure(final StatusBar bar) {
+        final GridCoverage cv = coverage;
+        bar.setCoordinateConversion(cv != null ? cv.getGridGeometry() : null, sliceExtent);
     }
 }
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
new file mode 100644
index 0000000..72f0b14
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/StatusBar.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.gui.coverage;
+
+import java.util.Locale;
+import java.util.TimeZone;
+import javax.measure.Unit;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.layout.HBox;
+import javafx.scene.control.Label;
+import javafx.scene.control.Tooltip;
+import org.opengis.geometry.DirectPosition;
+import org.opengis.referencing.datum.PixelInCell;
+import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.TransformException;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.geometry.CoordinateFormat;
+import org.apache.sis.coverage.grid.GridExtent;
+import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.measure.Units;
+import org.apache.sis.util.Classes;
+
+
+/**
+ * A status bar showing coordinates of a grid cell.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+final class StatusBar extends HBox {
+    /**
+     * The view for which we are showing geographic or projected coordinates of selected
cell.
+     */
+    private final GridView view;
+
+    /**
+     * Zero-based cell coordinates currently formatted in the {@link #coordinates} field.
+     * This is used for detecting if coordinate values changed since last formatting.
+     */
+    private int column, row;
+
+    /**
+     * Conversion from ({@linkplain #column},{@linkplain #row}) cell coordinates
+     * to geographic or projected coordinates.
+     */
+    private MathTransform gridToCRS;
+
+    /**
+     * Coordinates after conversion to the CRS. The number of dimensions depends on
+     * the target CRS. This object is reused during each coordinate transformation.
+     */
+    private DirectPosition position;
+
+    /**
+     * The object to use for formatting coordinate values.
+     */
+    private final CoordinateFormat format;
+
+    /**
+     * The labels where to format the coordinates.
+     */
+    private final Label coordinates;
+
+    /**
+     * Creates a new status bar.
+     */
+    StatusBar(final GridView view) {
+        this.view = view;
+        format = new CoordinateFormat(Locale.getDefault(Locale.Category.FORMAT), TimeZone.getDefault());
+        coordinates = new Label();
+        setAlignment(Pos.CENTER_RIGHT);
+        getChildren().setAll(coordinates);
+        setPadding(new Insets(5, GridViewSkin.SCROLLBAR_WIDTH, 6, 0));
+        setCoordinateConversion(null, null);
+    }
+
+    /**
+     * Sets the conversion from (column, row) cell indices to geographic or projected coordinates.
+     * The conversion is computed from the given grid geometry.
+     *
+     * @param  geometry  geometry of the grid coverage shown in {@link GridView}, or {@code
null}.
+     * @param  request   sub-region of the coverage which is shown, or {@code null} for the
full coverage.
+     */
+    final void setCoordinateConversion(final GridGeometry geometry, GridExtent request) {
+        gridToCRS = MathTransforms.identity(2);
+        CoordinateReferenceSystem crs = null;
+        double resolution = 1;
+        Unit<?> unit = Units.PIXEL;
+        if (geometry != null) {
+            if (geometry.isDefined(GridGeometry.GRID_TO_CRS)) {
+                gridToCRS = geometry.getGridToCRS(PixelInCell.CELL_CENTER);
+                if (geometry.isDefined(GridGeometry.CRS)) {
+                    crs = geometry.getCoordinateReferenceSystem();
+                }
+            }
+            if (request == null && geometry.isDefined(GridGeometry.EXTENT)) {
+                request = geometry.getExtent();
+            }
+            /*
+             * Computes the precision of coordinates to format. We use the finest resolution,
+             * looking only at axes having the same units of measurement than the first axis.
+             */
+            if (geometry.isDefined(GridGeometry.RESOLUTION)) {
+                double[] resolutions = geometry.getResolution(true);
+                if (crs != null && resolutions.length != 0) {
+                    final CoordinateSystem cs = crs.getCoordinateSystem();
+                    unit = cs.getAxis(0).getUnit();
+                    for (int i=0; i<resolutions.length; i++) {
+                        if (unit.equals(cs.getAxis(i).getUnit())) {
+                            final double r = resolutions[i];
+                            if (r < resolution) resolution = r;
+                        }
+                    }
+                }
+            }
+        }
+        /*
+         * By `GridCoverage.render(GridExtent)` contract, the `RenderedImage` pixel coordinates
are relative
+         * to the requested `GridExtent`. Consequently we need to translate the image coordinates
so that it
+         * become the coordinates of the original `GridGeometry` before to apply `gridToCRS`.
+         */
+        if (request != null) {
+            final double[] origin = new double[request.getDimension()];
+            for (int i=0; i<origin.length; i++) {
+                origin[i] = request.getLow(i);
+            }
+            gridToCRS = MathTransforms.concatenate(MathTransforms.translation(origin), gridToCRS);
+        }
+        format.setDefaultCRS(crs);
+        format.setPrecision(resolution, unit);
+        Tooltip tp = null;
+        if (crs != null) {
+            tp = new Tooltip(IdentifiedObjects.getDisplayName(crs, format.getLocale(Locale.Category.DISPLAY)));
+        }
+        coordinates.setTooltip(tp);
+    }
+
+    /**
+     * Sets the pixel coordinates to show. Those pixel coordinates will be automatically
+     * transformed to geographic coordinates if a "grid to CRS" conversion is available.
+     */
+    final void setCoordinates(final int x, final int y) {
+        if (x != this.column || y != this.row) {
+            this.column = x;
+            this.row = y;
+            String text;
+            try {
+                position = gridToCRS.transform(view.toImageCoordinates(x, y), position);
+                text = format.format(position);
+            } catch (TransformException e) {
+                text = e.getLocalizedMessage();
+                if (text == null) {
+                    text = Classes.getShortClassName(e);
+                }
+            }
+            coordinates.setText(text);
+        }
+    }
+}


Mime
View raw message