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: Resolve numerous problems with the display of geographic/projected coordinates under change of CRS.
Date Thu, 23 Apr 2020 17:55:03 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 3ff7c26  Resolve numerous problems with the display of geographic/projected coordinates
under change of CRS.
3ff7c26 is described below

commit 3ff7c264a1066b8c6a0455f960b96bc296f121a0
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Thu Apr 23 19:54:23 2020 +0200

    Resolve numerous problems with the display of geographic/projected coordinates under change
of CRS.
---
 .../apache/sis/gui/coverage/CoverageExplorer.java  |   2 +-
 .../java/org/apache/sis/gui/map/StatusBar.java     | 353 +++++++++++++++------
 .../org/apache/sis/internal/gui/Resources.java     |   5 +
 .../apache/sis/internal/gui/Resources.properties   |   1 +
 .../sis/internal/gui/Resources_fr.properties       |   1 +
 .../org/apache/sis/geometry/CoordinateFormat.java  |   1 +
 .../referencing/provider/PolarStereographicA.java  |   2 +-
 7 files changed, 259 insertions(+), 106 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 23686e8..8712a48 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
@@ -152,7 +152,7 @@ public class CoverageExplorer extends Widget {
         viewTypeProperty.addListener(this::onViewTypeSpecified);
         referenceSystems = new RecentReferenceSystems();
         referenceSystems.addUserPreferences();
-        referenceSystems.addAlternatives("EPSG:3395");           // WGS 84 / World Mercator
+        referenceSystems.addAlternatives("EPSG:4326", "EPSG:3395");         // WGS 84 / World
Mercator
         /*
          * Prepare buttons to add on the toolbar. Those buttons are not managed by this class;
          * they are managed by org.apache.sis.gui.dataset.DataWindow. We only declare here
the
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
index 5a16356..2de5596 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
@@ -19,6 +19,7 @@ package org.apache.sis.gui.map;
 import java.util.Locale;
 import java.util.Optional;
 import javax.measure.Unit;
+import javafx.geometry.Pos;
 import javafx.geometry.Insets;
 import javafx.geometry.Point2D;
 import javafx.scene.paint.Color;
@@ -27,9 +28,9 @@ import javafx.scene.layout.Region;
 import javafx.scene.layout.Priority;
 import javafx.scene.control.Label;
 import javafx.scene.control.Button;
-import javafx.scene.control.ProgressBar;
 import javafx.scene.control.ContextMenu;
 import javafx.scene.input.MouseEvent;
+import javafx.scene.text.TextAlignment;
 import javafx.event.EventHandler;
 import javafx.event.EventType;
 import javafx.beans.value.ObservableValue;
@@ -59,24 +60,27 @@ import org.apache.sis.internal.util.Strings;
 import org.apache.sis.measure.Quantities;
 import org.apache.sis.measure.Units;
 import org.apache.sis.util.Classes;
+import org.apache.sis.util.Utilities;
 import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.gui.Widget;
 import org.apache.sis.gui.referencing.RecentReferenceSystems;
+import org.apache.sis.internal.referencing.ReferencingUtilities;
 import org.apache.sis.internal.gui.BackgroundThreads;
 import org.apache.sis.internal.gui.ExceptionReporter;
 import org.apache.sis.internal.gui.Resources;
 import org.apache.sis.internal.gui.Styles;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.IdentifiedObjects;
 
 
 /**
  * A status bar showing geographic or projected coordinates under mouse cursor.
  * The number of fraction digits is adjusted according pixel resolution for each coordinate
to format.
- * Other components such as progress bar or error message may also be shown.
+ * Other components such as error message may also be shown.
  *
  * <p>Since the main {@code StatusBar} job is to listen to mouse events for updating
coordinates,
  * this class implements {@link EventHandler} directly. {@code StatusBar} can be registered
as a listener
@@ -108,7 +112,7 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
     /**
      * Some spaces to add around the status bar.
      */
-    private static final Insets PADDING = new Insets(5, Styles.SCROLLBAR_WIDTH, 6, 0);
+    private static final Insets PADDING = new Insets(5, Styles.SCROLLBAR_WIDTH, 6, 9);
 
     /**
      * The container of controls making the status bar.
@@ -116,28 +120,23 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
     private final HBox view;
 
     /**
-     * The progress bar, hidden by default. This bar is initialized to undetermined state.
-     */
-    private final ProgressBar progress;
-
-    /**
      * Message to write in the middle of the status bar.
      * This component usually has nothing to show; it is used mostly for error messages.
-     * It takes all the space between {@link #progress} and {@link #coordinates}.
+     * It takes all the space before {@link #position}.
      */
     private final Label message;
 
     /**
-     * Local coordinates currently formatted in the {@link #coordinates} field.
+     * Local coordinates currently formatted in the {@link #position} field.
      * This is used for detecting if coordinate values changed since last formatting.
      * Those coordinates are often integer values.
      */
     private double lastX, lastY;
 
     /**
-     * The area of interest, or {@code null} if none. This is a reference to the
-     * {@link RecentReferenceSystems#areaOfInterest} property. We do not make this
-     * property public because it does not belong to this object.
+     * The area of interest, or {@code null} if none. Used for computing {@link #objectiveToFormatCRS}.
+     * This field is a reference to the {@link RecentReferenceSystems#areaOfInterest} property.
+     * We do not make this property public because it does not belong to this object.
      */
     private final ObjectProperty<Envelope> areaOfInterest;
 
@@ -147,11 +146,22 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
      * data CRS. It may not be the same than the CRS of coordinates actually shown in the
status bar.
      *
      * @see #getObjectiveCRS()
+     * @see #getFormatReferenceSystem()
      * @see MapCanvas#getObjectiveCRS()
      */
     private CoordinateReferenceSystem objectiveCRS;
 
     /**
+     * Hold the transform from <cite>objective CRS</cite> to the CRS of coordinates
shown in this status bar.
+     * The {@linkplain CoordinateOperation#getSourceCRS() source CRS} is {@link #objectiveCRS}
and
+     * the {@linkplain CoordinateOperation#getTargetCRS() target CRS} is {@link CoordinateFormat#getDefaultCRS()}.
+     * This coordinate operation may be null if there is no CRS change to apply
+     * (in which case {@link #localToFormatCRS} is the same instance than {@link #localToObjectiveCRS})
+     * or if the target is not a CRS (for example it may be a Military Grid Reference System
(MGRS) code).
+     */
+    private CoordinateOperation objectiveToFormatCRS;
+
+    /**
      * Conversion from local coordinates to geographic or projected coordinates of rendered
data.
      * This is not necessarily the conversion to the coordinates shown in this status bar.
      * This conversion shall never be null but may be the identity transform.
@@ -165,19 +175,24 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
 
     /**
      * Conversion from local coordinates to geographic or projected coordinates shown in
this status bar.
-     * This is the concatenation of {@link #localToObjectiveCRS} with the transform from
{@link #objectiveCRS}
-     * to the user-selected CRS for displaying in the status bar. This conversion shall never
be null but may be
-     * the identity transform. It is usually non-affine if the display CRS is not the same
than the objective CRS.
+     * This is the concatenation of {@link #localToObjectiveCRS} with {@link #objectiveToFormatCRS}
transform.
+     * The result is a transform to the user-selected CRS for coordinates shown in the status
bar.
+     * This conversion shall never be null but may be the identity transform.
+     * It is usually non-affine if the display CRS is not the same than the objective CRS.
      * This transform may have a {@linkplain CoordinateOperation#getCoordinateOperationAccuracy()
limited accuracy}.
      *
-     * <p>The target CRS can be obtained by {@link CoordinateFormat#getDefaultCRS()}.</p>
+     * <p>The target CRS can be obtained by {@link CoordinateOperation#getTargetCRS()}
on
+     * {@link #objectiveToFormatCRS} or by {@link CoordinateFormat#getDefaultCRS()}.</p>
      */
-    private MathTransform localToTargetCRS;
+    private MathTransform localToFormatCRS;
 
     /**
      * The source local indices before conversion to geospatial coordinates.
      * The number of dimensions is often {@value #BIDIMENSIONAL}.
      * Shall never be {@code null}.
+     *
+     * @see #targetCoordinates
+     * @see #position
      */
     private double[] sourceCoordinates;
 
@@ -185,12 +200,15 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
      * Coordinates after conversion to the CRS. The number of dimensions depends on
      * the target CRS. This object is reused during each coordinate transformation.
      * Shall never be {@code null}.
+     *
+     * @see #sourceCoordinates
+     * @see #position
      */
     private GeneralDirectPosition targetCoordinates;
 
     /**
      * The desired precisions for each dimension in the {@link #targetCoordinates} to format.
-     * It may vary for each position if the {@link #localToTargetCRS} transform is non-linear.
+     * It may vary for each position if the {@link #localToFormatCRS} transform is non-linear.
      * This array is initially {@code null} and created when first needed.
      */
     private double[] precisions;
@@ -212,9 +230,11 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
     private final CoordinateFormat format;
 
     /**
-     * The labels where to format the coordinates.
+     * The labels where to format the cursor position, either as coordinate values or other
representations.
+     * The text is usually the result of formatting {@link #targetCoordinates} as numerical
values,
+     * but may also be other representations such as Military Grid Reference System (MGRS)
codes.
      */
-    private final Label coordinates;
+    private final Label position;
 
     /**
      * The canvas that this status bar is tracking.
@@ -226,42 +246,52 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
     public final ObjectProperty<MapCanvas> canvasProperty;
 
     /**
-     * The listener registered on {@link MapCanvas#renderingProperty()}.
-     * This reference is stored for allowed removal.
+     * The listener registered on {@link MapCanvas#renderingProperty()}, or {@code null}
if the
+     * listener has not yet been registered. This listener is remembered for allowing removal.
      *
      * @see #setCanvas(MapCanvas)
+     * @see #onCanvasSpecified(ObservableValue, MapCanvas, MapCanvas)
      */
     private ChangeListener<Boolean> renderingListener;
 
     /**
-     * Creates a new status bar.
+     * Whether the mouse listeners have been registered. Those listeners are registered the
+     * first time that {@link #apply(GridGeometry)} is invoked on a newly initialized canvas.
+     */
+    private boolean isMouseListenerRegistered;
+
+    /**
+     * Creates a new status bar for showing coordinates of mouse cursor position in a canvas.
+     * If the {@code choices} argument is non-null, user will be able to select different
CRS
+     * using the contextual menu on the status bar.
      *
-     * @param  referenceSystems  the manager of reference systems chosen by the user, or
{@code null} if none.
+     * @param  choices  the manager of reference systems chosen by user, or {@code null}
if none.
      */
-    public StatusBar(final RecentReferenceSystems referenceSystems) {
+    public StatusBar(final RecentReferenceSystems choices) {
         localToObjectiveCRS = MathTransforms.identity(BIDIMENSIONAL);
-        localToTargetCRS    = localToObjectiveCRS;
+        localToFormatCRS    = localToObjectiveCRS;
         targetCoordinates   = new GeneralDirectPosition(BIDIMENSIONAL);
         sourceCoordinates   = targetCoordinates.coordinates;
         lastX = lastY       = Double.NaN;
         format              = new CoordinateFormat();
-        coordinates         = new Label();
+        position            = new Label();
         message             = new Label();
-        progress            = new ProgressBar();
-        progress.setVisible(false);
+        message.setVisible(false);                      // Waiting for getting a message
to display.
         message.setTextFill(Styles.ERROR_TEXT);
         message.setMaxWidth(Double.POSITIVE_INFINITY);
         HBox.setHgrow(message, Priority.ALWAYS);
-        coordinates.minWidthProperty().bind(coordinates.widthProperty());
-        view = new HBox(12, progress, message, coordinates);
+        view = new HBox(18, message, position);
         view.setPadding(PADDING);
+        view.setAlignment(Pos.CENTER_RIGHT);
+        position.setAlignment(Pos.CENTER_RIGHT);
+        position.setTextAlignment(TextAlignment.RIGHT);
         canvasProperty = new SimpleObjectProperty<>(this, "canvas");
         canvasProperty.addListener(this::onCanvasSpecified);
-        if (referenceSystems == null) {
+        if (choices == null) {
             areaOfInterest = null;
         } else {
-            areaOfInterest = referenceSystems.areaOfInterest;
-            final ContextMenu menu = new ContextMenu(referenceSystems.createMenuItems(this::onSelectCRS));
+            areaOfInterest = choices.areaOfInterest;
+            final ContextMenu menu = new ContextMenu(choices.createMenuItems(this::onSelectCRS));
             view.setOnMousePressed((MouseEvent event) -> {
                 if (event.isSecondaryButtonDown()) {
                     menu.show((HBox) event.getSource(), event.getScreenX(), event.getScreenY());
@@ -292,9 +322,9 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
     }
 
     /**
-     * Sets the canvas that this status bar is tracking.
-     * This method registers all necessary listeners.
-     * A value of {@code null} unregisters all listeners.
+     * Sets the canvas that this status bar is tracking. After this method has been invoked,
+     * this {@code StatusBar} will show coordinates (usually geographic or projected) below
+     * mouse cursor when the mouse is over that canvas.
      *
      * @param  canvas  the canvas to track, or {@code null} if none.
      *
@@ -305,7 +335,10 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
     }
 
     /**
-     * Invoked when a new value is set on {@link #canvasProperty}.
+     * Invoked when a new value is set on {@link #canvasProperty}. Previous listeners (if
any) are removed
+     * but new mouse listeners may not be added immediately. Instead if the canvas seems
uninitialized, we
+     * will wait for the first call to {@link #apply(GridGeometry)} before to add the listener.
We do that
+     * for avoiding to show irrelevant coordinate values.
      */
     private void onCanvasSpecified(final ObservableValue<? extends MapCanvas> property,
                                    final MapCanvas previous, final MapCanvas value)
@@ -316,26 +349,55 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
             previous.floatingPane.removeEventHandler(MouseEvent.MOUSE_MOVED,   this);
             previous.renderingProperty().removeListener(renderingListener);
             renderingListener = null;
+            isMouseListenerRegistered = false;
         }
         if (value != null) {
+            value.renderingProperty().addListener(renderingListener = new RenderingListener());
+        }
+        position.setVisible(false);
+        registerMouseListeners(value);
+        try {
+            apply(value != null ? value.getGridGeometry() : null);
+        } catch (RenderException e) {
+            setErrorMessage(null, e);
+        }
+    }
+
+    /**
+     * Registers mouse listeners for the given canvas if not null. It is caller responsibility
to invoke
+     * this method only if {@link #isMouseListenerRegistered} is {@code false} (this method
does not verify).
+     *
+     * @see #apply(GridGeometry)
+     */
+    private void registerMouseListeners(final MapCanvas value) {
+        /*
+         * The canvas "objective to CRS" is null only for unitialized canvas.
+         * After the canvas has been initialized, it can not be null anymore.
+         * We use that for deciding if listener registration should be delayed.
+         */
+        if (value != null && value.getObjectiveCRS() != null) {
+            // Set first for avoiding duplicated registrations if an exception happen.
+            isMouseListenerRegistered = true;
             value.floatingPane.addEventHandler(MouseEvent.MOUSE_ENTERED, this);
             value.floatingPane.addEventHandler(MouseEvent.MOUSE_EXITED,  this);
             value.floatingPane.addEventHandler(MouseEvent.MOUSE_MOVED,   this);
-            value.renderingProperty().addListener(renderingListener = new RenderingListener());
         }
     }
 
     /**
      * Listener notified when {@link MapCanvas} completed its rendering. This listener sets
      * {@link StatusBar#localToObjectiveCRS} to the inverse of {@link MapCanvas#objectiveToDisplay}.
+     * It assumes that even if the JavaFX local coordinates and {@link #localToFormatCRS}
transform
+     * changed, the "real world" coordinates under the mouse cursor is still the same. This
assumption
+     * should be true if this listener is notified as a result of zoom, translation or rotation
events.
      */
     private final class RenderingListener implements ChangeListener<Boolean> {
         @Override public void changed(final ObservableValue<? extends Boolean> property,
                                       final Boolean previous, final Boolean value)
         {
-            progress.setVisible(value);
             if (!value) try {
-                applyCanvasGeometry(getCanvas().getGridGeometry());
+                apply(getCanvas().getGridGeometry());
+                reformat();
             } catch (RenderException e) {
                 setErrorMessage(null, e);
             }
@@ -343,13 +405,13 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
     }
 
     /**
-     * Configures this status bar for showing coordinates in the CRS and resolution given
by the specified
-     * grid geometry. The geometry properties are applied as below:
+     * Configures this status bar for showing coordinates in the CRS and with the resolution
given
+     * by the specified grid geometry. The geometry properties are applied as below:
      *
      * <ul>
      *   <li>{@link GridGeometry#getCoordinateReferenceSystem()} defines the CRS of
the coordinates to format.</li>
      *   <li>{@link GridGeometry#getGridToCRS(PixelInCell) GridGeometry.getGridToCRS(PixelInCell.CELL_CENTER)}
-     *       defines the conversion from coordinate values locale to the canvas to coordinate
values in the CRS
+     *       defines the conversion from coordinate values local to the canvas to coordinate
values in the CRS
      *       (the {@linkplain #getLocalToObjectiveCRS() local to objective CRS} conversion).</li>
      *   <li>{@link GridGeometry#getExtent()} provides the view size in pixels, used
for estimating a resolution.</li>
      *   <li>{@link GridGeometry#getResolution(boolean)} is also used for estimating
a resolution.</li>
@@ -359,8 +421,25 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
      * after this method call with {@link #setLocalToObjectiveCRS(MathTransform)}.
      *
      * @param  geometry  geometry of the coverage shown in {@link MapCanvas}, or {@code null}.
+     *
+     * @see MapCanvas#getGridGeometry()
      */
     public void applyCanvasGeometry(final GridGeometry geometry) {
+        position.setVisible(false);
+        apply(geometry);
+    }
+
+    /**
+     * Implementation of {@link #applyCanvasGeometry(GridGeometry)} without changing {@link
#position} visibility state.
+     * Invoking this method usually invalidate the coordinates shown in this status bar.
The new coordinates can not be
+     * easily recomputed because the {@link #lastX} and {@link #lastY} values may not be
valid anymore, as a result of
+     * possible changes in JavaFX local coordinate system. Consequently the coordinates should
be temporarily hidden
+     * until a new {@link MouseEvent} gives us the new local coordinates, unless this method
is invoked in a context
+     * where we know that the "real world" coordinates should be the same even if local coordinates
changed.
+     *
+     * @param  geometry  geometry of the coverage shown in {@link MapCanvas}, or {@code null}.
+     */
+    private void apply(final GridGeometry geometry) {
         /*
          * Compute values in local variables without modifying `StatusBar` fields for now.
          * The fields will be updated only after we know that this operation is successful.
@@ -409,10 +488,15 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
                 }
             }
         }
+        final boolean sameCRS = Utilities.equalsIgnoreMetadata(objectiveCRS, crs);
         /*
-         * Remaining code should not fail, so we can modify `StatusBar` fields.
-         * Prepare objects to be reused for each coordinate transformation.
-         * Configure the `CoordinateFormat` with the CRS.
+         * Remaining code should not fail, so we can start modifying the `StatusBar` fields.
+         * The buffers for source and target coordinates are recreated because the number
of
+         * dimensions may have changed. The `lastX` and `lastY` coordinates are local to
the
+         * JavaFX view and considered invalid  because they depend on the transforms applied
+         * on JavaFX node, which may have changed together with `localToObjectiveCRS` change.
+         * So we can not use those values for updating the coordinates shown in status bar.
+         * Instead we will wait for the next mouse event to provide new local coordinates.
          */
         if (localToCRS != null) {
             sourceCoordinates = new double[Math.max(localToCRS.getSourceDimensions(), BIDIMENSIONAL)];
@@ -423,40 +507,52 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
             sourceCoordinates = targetCoordinates.coordinates;      // Okay to share array
if same dimension.
         }
         objectiveCRS        = crs;
-        localToObjectiveCRS = localToTargetCRS = localToCRS;
+        localToObjectiveCRS = localToCRS;
+        localToFormatCRS    = localToCRS;                           // May be updated again
below.
         inflatePrecisions   = inflate;
         precisions          = null;
-        format.setDefaultCRS(crs);
+        lastX = lastY       = Double.NaN;                           // Not valid anymove
— see above block comment.
+        CoordinateReferenceSystem restore = null;
+        if (sameCRS) {
+            if (objectiveToFormatCRS != null) {
+                localToFormatCRS = MathTransforms.concatenate(localToCRS, objectiveToFormatCRS.getMathTransform());
+            }
+            // Keep the format CRS unchanged since we made `localToFormatCRS` consistent
with its value.
+        } else {
+            objectiveToFormatCRS = null;
+            restore = format.getDefaultCRS();           // CRS to restore in a background
thread.
+            format.setDefaultCRS(crs);                  // Should be invoked before to set
precision.
+        }
         format.setGroundPrecision(Quantities.create(resolution, unit));
-        refresh();
-    }
-
-    /**
-     * Invoked when the user selects a new reference system for the coordinates to show in
status bar.
-     *
-     * @param  property  the {@link org.apache.sis.gui.referencing.MenuSync} property.
-     * @param  oldValue  the old reference system, or {@code null} if none.
-     * @param  newValue  the CRS to use for formatting coordinates in this status bar.
-     */
-    private void onSelectCRS(ObservableValue<? extends ReferenceSystem> property,
-                             ReferenceSystem oldValue, ReferenceSystem newValue)
-    {
-        setTargetCRS(newValue instanceof CoordinateReferenceSystem ? (CoordinateReferenceSystem)
newValue : null);
+        if (ReferencingUtilities.getDimension(restore) == localToFormatCRS.getTargetDimensions())
{
+            setFormatCRS(restore);
+        }
+        /*
+         * If this is the first time that this method is invoked after `setCanvas(MapCanvas)`,
+         * the listeners are not yet registered and should be added now. Listeners registration
+         * was delayed because if they were added on uninitialized canvas, they would have
show
+         * irrelevant coordinates.
+         */
+        if (geometry != null && !isMouseListenerRegistered) {
+            registerMouseListeners(canvasProperty.getValue());
+        }
     }
 
     /**
      * Sets the coordinate reference system of the coordinates shown in this status bar.
      * The change may not appear immediately after method return; this method may use a
-     * background thread for computing the coordinate operation.
+     * background thread for computing the coordinate operation.  That task may be long
+     * the first time that it is executed, but should be fast on subsequent invocations.
+     *
+     * @param  crs  the new CRS, or {@code null} for {@link #objectiveCRS}.
      */
-    private void setTargetCRS(final CoordinateReferenceSystem crs) {
-        if (objectiveCRS != null && objectiveCRS != crs) {
-            coordinates.setTextFill(Styles.OUTDATED_TEXT);
+    private void setFormatCRS(final CoordinateReferenceSystem crs) {
+        if (crs != null && objectiveCRS != null && objectiveCRS != crs) {
+            position.setTextFill(Styles.OUTDATED_TEXT);
             final Envelope aoi = (areaOfInterest != null) ? areaOfInterest.get() : null;
             BackgroundThreads.execute(new Task<MathTransform>() {
                 /**
-                 * The operation used for computing the transform to target CRS.
-                 * This is used for configuring format with positional accuracy.
+                 * The new {@link StatusBar#objectiveToFormatCRS} value if successful.
                  */
                 private CoordinateOperation operation;
 
@@ -473,48 +569,88 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
                     } catch (TransformException e) {
                         bbox = null;
                         Logging.recoverableException(Logging.getLogger(Modules.APPLICATION),
-                                                     StatusBar.class, "setTargetCRS", e);
+                                                     StatusBar.class, "setFormatCRS", e);
                     }
                     operation = CRS.findOperation(objectiveCRS, crs, bbox);
                     return MathTransforms.concatenate(localToObjectiveCRS, operation.getMathTransform());
                 }
 
                 /**
-                 * Invoked in JavaFX thread on success. The {@link StatusBar#localToTargetCRS}
transform
+                 * Invoked in JavaFX thread on success. The {@link StatusBar#localToFormatCRS}
transform
                  * is set to the transform that we computed in background and the {@link
CoordinateFormat}
                  * is configured with auxiliary information such as positional accuracy.
                  */
                 @Override protected void succeeded() {
                     final CoordinateReferenceSystem targetCRS = operation.getTargetCRS();
-                    format.setDefaultCRS(targetCRS != null ? targetCRS : crs);
-                    localToTargetCRS = getValue();
-//                  TODO: CRS.getLinearAccuracy(op);
-                    coordinates.setTextFill(Styles.NORMAL_TEXT);
-                    refresh();
+                    applyFormatCRS(targetCRS != null ? targetCRS : crs, operation, getValue());
                 }
 
                 /**
-                 * Invoked in JavaFX thread on failure. The previous CRS is keep unchanged
but
+                 * Invoked in JavaFX thread on failure. The previous CRS is kept unchanged
but
                  * the coordinates will appear in red for telling user that there is a problem.
                  */
                 @Override protected void failed() {
-                    setErrorMessage(null, getException());
-                    resetTargetCRS(Styles.ERROR_TEXT);
+                    final Locale locale = format.getLocale(Locale.Category.DISPLAY);
+                    setErrorMessage(Resources.forLocale(locale).getString(Resources.Keys.CanNotUseRefSys_1,
+                                    IdentifiedObjects.getDisplayName(crs, locale)), getException());
+                    resetFormatCRS(Styles.ERROR_TEXT);
                 }
             });
         } else {
-            resetTargetCRS(Styles.NORMAL_TEXT);
+            position.setMinWidth(0);
+            resetFormatCRS(Styles.NORMAL_TEXT);
+        }
+    }
+
+    /**
+     * Invoked after the background thread computed the new coordinate operation.
+     * This method rewrites the coordinates on the assumption that {@link #lastX}
+     * and {@link #lastY} are still valid. This assumption should be correct when
+     * only the format CRS has been updated and not {@link #localToObjectiveCRS}.
+     *
+     * @param  crs        the new CRS. Should not be {@code null}.
+     * @param  operation  the new value to assign to {@link #objectiveToFormatCRS}
+     * @param  complete   the concatenation of {@link #localToObjectiveCRS} with {@code operation}.
+     */
+    private void applyFormatCRS(final CoordinateReferenceSystem crs,
+            final CoordinateOperation operation, final MathTransform complete)
+    {
+        format.setDefaultCRS(crs);
+        objectiveToFormatCRS = operation;
+        localToFormatCRS = complete;
+//      TODO: CRS.getLinearAccuracy(op);
+        position.setTextFill(Styles.NORMAL_TEXT);
+        position.setMinWidth(0);
+        setErrorMessage(null, null);
+        reformat();
+    }
+
+    /**
+     * Reformats the coordinates shown in {@link #position} using current {@link #lastX}
and {@link #lastY} values.
+     * This method should be invoked only when the caller knows that those values are still
valid. Note that those
+     * values may be invalid if {@link javafx.scene.Node#getTransforms()} changed even if
{@link #objectiveCRS} is
+     * the same.
+     */
+    private void reformat() {
+        if (position.isVisible()) {
+            final double x = lastX;
+            final double y = lastY;
+            lastX = lastY = Double.NaN;
+            if (!Double.isNaN(x) && !Double.isNaN(y)) {
+                setLocalCoordinates(x, y);
+            }
         }
     }
 
     /**
-     * Resets {@link #localToTargetCRS} to its default value. This is invoked either when
the specified
+     * Resets {@link #localToFormatCRS} to its default value. This is invoked either when
the
      * target CRS is {@link #objectiveCRS}, or when an attempt to use another CRS failed.
      */
-    private void resetTargetCRS(final Color textFill) {
-        localToTargetCRS = localToObjectiveCRS;
+    private void resetFormatCRS(final Color textFill) {
+        objectiveToFormatCRS = null;
+        localToFormatCRS = localToObjectiveCRS;
         format.setDefaultCRS(objectiveCRS);
-        coordinates.setTextFill(textFill);
+        position.setTextFill(textFill);
     }
 
     /**
@@ -578,7 +714,7 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
             actual   = conversion.getTargetDimensions();
             if (expected == actual) {
                 localToObjectiveCRS = conversion;
-                setTargetCRS(format.getDefaultCRS());                           // Recompute
`localToTargetCRS`.
+                setFormatCRS(format.getDefaultCRS());                           // Recompute
`localToFormatCRS`.
                 return;
             }
         }
@@ -593,25 +729,13 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
      * @return the local coordinates currently shown in the status bar.
      */
     public Optional<Point2D> getLocalCoordinates() {
-        if (coordinates.isVisible() && !Double.isNaN(lastX) && !Double.isNaN(lastY))
{
+        if (position.isVisible() && !Double.isNaN(lastX) && !Double.isNaN(lastY))
{
             return Optional.of(new Point2D(lastX, lastY));
         }
         return Optional.empty();
     }
 
     /**
-     * Rewrites the coordinates. This method is invoked after a change of coordinate reference
system.
-     */
-    private void refresh() {
-        final double x = lastX;
-        final double y = lastY;
-        lastX = lastY = Double.NaN;
-        if (!Double.isNaN(x) && !Double.isNaN(y)) {
-            setLocalCoordinates(x, y);
-        }
-    }
-
-    /**
      * Converts and formats the given pixel coordinates. Those coordinates will be automatically
      * converted to geographic or projected coordinates if a "local to CRS" conversion is
available.
      *
@@ -628,7 +752,7 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
             try {
                 Matrix derivative;
                 try {
-                    derivative = MathTransforms.derivativeAndTransform(localToTargetCRS,
+                    derivative = MathTransforms.derivativeAndTransform(localToFormatCRS,
                             sourceCoordinates, 0, targetCoordinates.coordinates, 0);
                 } catch (TransformException ignore) {
                     /*
@@ -636,7 +760,7 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
                      * derivative calculation. Try again without derivative (the precision
will be set
                      * to the default resolution computed in `setCanvasGeometry(…)`).
                      */
-                    localToTargetCRS.transform(sourceCoordinates, 0, targetCoordinates.coordinates,
0, 1);
+                    localToFormatCRS.transform(sourceCoordinates, 0, targetCoordinates.coordinates,
0, 1);
                     derivative = null;
                 }
                 if (derivative == null) {
@@ -676,8 +800,16 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
                     text = Classes.getShortClassName(cause);
                 }
             }
-            coordinates.setText(text);
-            coordinates.setVisible(true);
+            position.setText(text);
+            position.setVisible(true);
+            /*
+             * Make sure that there is enough space for keeping the coordinates always visible.
+             * This is the needed if there is an error message on the left which may be long.
+             */
+            final double width = Math.min(view.getWidth() / 2, Math.ceil(position.prefWidth(position.getHeight())));
+            if (width > position.getMinWidth()) {
+                position.setMinWidth(width);
+            }
         }
     }
 
@@ -706,7 +838,20 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
                 return;
             }
         }
-        coordinates.setVisible(false);
+        position.setVisible(false);
+    }
+
+    /**
+     * Invoked when the user selects a new reference system for the coordinates to show in
status bar.
+     *
+     * @param  property  the {@link org.apache.sis.gui.referencing.MenuSync} property.
+     * @param  oldValue  the old reference system, or {@code null} if none.
+     * @param  newValue  the CRS to use for formatting coordinates in this status bar.
+     */
+    private void onSelectCRS(ObservableValue<? extends ReferenceSystem> property,
+                             ReferenceSystem oldValue, ReferenceSystem newValue)
+    {
+        setFormatCRS(newValue instanceof CoordinateReferenceSystem ? (CoordinateReferenceSystem)
newValue : null);
     }
 
     /**
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
index bb0c425..ec356e9 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
@@ -101,6 +101,11 @@ public final class Resources extends IndexedResourceBundle {
         public static final short CanNotReadResource = 55;
 
         /**
+         * Can not use the “{0}” reference system.
+         */
+        public static final short CanNotUseRefSys_1 = 58;
+
+        /**
          * Cell geometry
          */
         public static final short CellGeometry = 15;
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
index 4ead37e..9761bf2 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
@@ -28,6 +28,7 @@ CanNotClose_1          = Can not close \u201c{0}\u201d. Data may be lost.
 CanNotCreateCRS_1      = Can not create reference system \u201c{0}\u201d.
 CanNotCreateXML        = Can not create XML document.
 CanNotReadResource     = A resource contained in the file can not be read. The cause is given
below.
+CanNotUseRefSys_1      = Can not use the \u201c{0}\u201d reference system.
 CellGeometry           = Cell geometry
 Close                  = Close
 Copy                   = Copy
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
index 9659427..f73612f 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
@@ -33,6 +33,7 @@ CanNotClose_1          = Ne peut pas fermer \u00ab\u202f{0}\u202f\u00bb.
Il pour
 CanNotCreateCRS_1      = Ne peut pas cr\u00e9er le syst\u00e8me de r\u00e9f\u00e9rence \u00ab\u202f{0}\u202f\u00bb.
 CanNotCreateXML        = Ne peut pas cr\u00e9er le document XML.
 CanNotReadResource     = Une ressource contenue dans le fichier ne peut pas \u00eatre lue.
La cause est donn\u00e9e ci-dessous.
+CanNotUseRefSys_1      = Ne peut pas utiliser le syst\u00e8me de r\u00e9f\u00e9rence \u00ab\u202f{0}\u202f\u00bb.
 CellGeometry           = G\u00e9om\u00e9trie des cellules
 Close                  = Fermer
 Copy                   = Copier
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/CoordinateFormat.java
b/core/sis-referencing/src/main/java/org/apache/sis/geometry/CoordinateFormat.java
index db94513..e3ae13d 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/CoordinateFormat.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/CoordinateFormat.java
@@ -681,6 +681,7 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition>
{
      *
      * @see DecimalFormat#setMaximumFractionDigits(int)
      * @see AngleFormat#setPrecision(double, boolean)
+     * @see Quantities#create(double, Unit)
      *
      * @since 1.1
      */
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicA.java
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicA.java
index 15f023c..6a65c50 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicA.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicA.java
@@ -149,7 +149,7 @@ public final class PolarStereographicA extends AbstractStereographic {
 
     /**
      * False Easting and false Northing value used in Universal Polar Stereographic (UPS)
projections.
-     * Represented as an integer for the convenience of Military Reference Grid System (MGRS)
or other
+     * Represented as an integer for the convenience of Military Grid Reference System (MGRS)
or other
      * grid systems.
      */
     public static final int UPS_SHIFT = 2000000;


Mime
View raw message