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 <[email protected]>
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);
+ }
+ }
+}