This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit baaa5771321edb6589f441a6e6125cfb2dd952aa
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Mon Jun 27 16:49:41 2022 +0200

    Allow the status bar to show coordinates using another system than 
`CoordinateReferenceSystem`.
    Current implementation uses `ReferenceSystemUsingIdentifiers` as a proof of 
work.
    The intent is to show coverage grid cell coordinates in a next commit.
---
 application/sis-javafx/pom.xml                     |   5 +
 .../apache/sis/gui/coverage/CoverageCanvas.java    |   2 +-
 .../apache/sis/gui/coverage/CoverageExplorer.java  |   2 +-
 .../main/java/org/apache/sis/gui/map/MapMenu.java  |   4 +-
 .../org/apache/sis/gui/map/OperationFinder.java    |   4 +-
 .../java/org/apache/sis/gui/map/StatusBar.java     | 615 +++++++++++++++------
 .../org/apache/sis/gui/referencing/MenuSync.java   | 131 +++--
 .../sis/gui/referencing/ObjectStringConverter.java |  14 +-
 .../gui/referencing/RecentReferenceSystems.java    | 246 ++++++---
 .../org/apache/sis/internal/gui/Resources.java     |  10 +
 .../apache/sis/internal/gui/Resources.properties   |   2 +
 .../sis/internal/gui/Resources_fr.properties       |   2 +
 12 files changed, 759 insertions(+), 278 deletions(-)

diff --git a/application/sis-javafx/pom.xml b/application/sis-javafx/pom.xml
index 463c965cb3..c5219fd2aa 100644
--- a/application/sis-javafx/pom.xml
+++ b/application/sis-javafx/pom.xml
@@ -135,6 +135,11 @@
       <artifactId>sis-portrayal</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.sis.core</groupId>
+      <artifactId>sis-referencing-by-identifiers</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     <dependency>
       <groupId>org.apache.sis.storage</groupId>
       <artifactId>sis-xmlstore</artifactId>
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
index 21e4bc55ba..daf385014a 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
@@ -1062,7 +1062,7 @@ public class CoverageCanvas extends MapCanvasAWT {
                     }
                 }
             }
-            controls.status.setLowestAccuracy(accuracy);
+            controls.status.lowestAccuracy.set(accuracy);
         }
         /*
          * If error(s) occurred during calls to `RenderedImage.getTile(tx, 
ty)`, reports those errors.
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 03a16c0e5b..b6bae9a127 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
@@ -252,7 +252,7 @@ public class CoverageExplorer extends Widget {
         coverageProperty = new SimpleObjectProperty<> (this, "coverage");
         referenceSystems = new RecentReferenceSystems();
         referenceSystems.addUserPreferences();
-        referenceSystems.addAlternatives("EPSG:4326", "EPSG:3395");         // 
WGS 84 / World Mercator
+        referenceSystems.addAlternatives("EPSG:4326", "EPSG:3395", "MGRS");    
 // WGS 84 / World Mercator
         viewTypeProperty.addListener((p,o,n) -> onViewTypeSet(n));
         resourceProperty.addListener((p,o,n) -> onPropertySet(n, null, 
coverageProperty));
         coverageProperty.addListener((p,o,n) -> onPropertySet(null, n, 
resourceProperty));
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapMenu.java 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapMenu.java
index 79e3045823..470f075fd6 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapMenu.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapMenu.java
@@ -132,7 +132,7 @@ public class MapMenu extends ContextMenu {
     public void addReferenceSystems(final RecentReferenceSystems preferences) {
         ArgumentChecks.ensureNonNull("preferences", preferences);
         final MapCanvas.MenuHandler handler = startNewMenuItems(CRS);
-        final Menu systemChoices = preferences.createMenuItems(handler);
+        final Menu systemChoices = preferences.createMenuItems(true, handler);
         handler.selectedCrsProperty = 
RecentReferenceSystems.getSelectedProperty(systemChoices);
         handler.positionables       = new ToggleGroup();
 
@@ -161,7 +161,7 @@ public class MapMenu extends ContextMenu {
         final Resources resources = Resources.forLocale(canvas.getLocale());
         final MenuItem coordinates = 
resources.menu(Resources.Keys.CopyCoordinates, (event) -> {
             try {
-                final String text = format.formatCoordinates(handler.x, 
handler.y);
+                final String text = 
format.formatTabSeparatedCoordinates(handler.x, handler.y);
                 final ClipboardContent content = new ClipboardContent();
                 content.putString(text);
                 Clipboard.getSystemClipboard().setContent(content);
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/OperationFinder.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/OperationFinder.java
index 0211c55141..dd9219f2c6 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/OperationFinder.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/OperationFinder.java
@@ -157,7 +157,7 @@ abstract class OperationFinder extends Task<MathTransform> {
     /**
      * If the given CRS is a grid CRS, replaces it by a geospatial CRS if 
possible.
      * If the given CRS is not geospatial, then this method tries to replace 
it by
-     * by the CRS of the coverage shown by the canvas (this is not necessarily 
the
+     * the CRS of the coverage shown by the canvas (this is not necessarily the
      * {@linkplain MapCanvas#getObjectiveCRS() objective CRS}).
      *
      * @param  crs     the CRS to eventually replace by a geospatial CRS.
@@ -191,7 +191,7 @@ abstract class OperationFinder extends Task<MathTransform> {
     }
 
     /**
-     * Returns the target CRS, giving precedence to {@link 
CoordinateOperation#getTargetCRS()} is suitable.
+     * Returns the target CRS, giving precedence to {@link 
CoordinateOperation#getTargetCRS()} if suitable.
      * That precedence is because the {@link CoordinateOperation} may provide 
a more complete CRS from EPSG
      * database.
      */
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 f8595e285b..5050a22ab1 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
@@ -18,7 +18,9 @@ package org.apache.sis.gui.map;
 
 import java.util.Arrays;
 import java.util.Locale;
+import java.util.Objects;
 import java.util.Optional;
+import java.util.logging.Logger;
 import java.util.function.Predicate;
 import java.awt.image.RenderedImage;
 import java.beans.PropertyChangeEvent;
@@ -50,9 +52,12 @@ import javafx.beans.property.ReadOnlyObjectPropertyBase;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.collections.ListChangeListener;
 import javafx.collections.ObservableList;
+import javafx.concurrent.Task;
 import javax.measure.Quantity;
 import javax.measure.quantity.Length;
+import javax.measure.IncommensurableException;
 import org.opengis.geometry.Envelope;
+import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
 import org.opengis.referencing.ReferenceSystem;
 import org.opengis.referencing.datum.PixelInCell;
@@ -65,6 +70,7 @@ import org.opengis.referencing.operation.CoordinateOperation;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.geometry.GeneralDirectPosition;
+import org.apache.sis.geometry.DirectPosition2D;
 import org.apache.sis.geometry.CoordinateFormat;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.coverage.grid.GridGeometry;
@@ -80,16 +86,18 @@ import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.logging.Logging;
 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.GUIUtilities;
 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;
+import org.apache.sis.referencing.gazetteer.ReferencingByIdentifiers;
 
 
 /**
@@ -139,6 +147,8 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
 
     /**
      * The container of controls making the status bar.
+     *
+     * @see #getView()
      */
     private final HBox view;
 
@@ -146,13 +156,18 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
      * 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 before {@link #position}.
+     *
+     * @see #getMessage()
      */
     private final Label message;
 
     /**
      * 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.
+     * If the mouse moved outside the canvas, then those coordinates are set 
to NaN.
+     * Otherwise those coordinates are usually integer values.
+     *
+     * @see #getLocalCoordinates()
      */
     private double lastX, lastY;
 
@@ -185,7 +200,7 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
     /**
      * The reference system used for rendering the data for which this status 
bar is providing cursor coordinates.
      * This is the "{@linkplain RecentReferenceSystems#setPreferred(boolean, 
ReferenceSystem) preferred}" or native
-     * data CRS. It may not be the same than the CRS of coordinates actually 
shown in the status bar.
+     * data CRS. It may be different than the CRS of coordinates actually 
shown in the status bar.
      *
      * @see MapCanvas#getObjectiveCRS()
      */
@@ -199,7 +214,8 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
      * (in which case {@link #localToPositionCRS} 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).
      *
-     * @see #updateLocalToPositionCRS()
+     * @see #localToObjectiveCRS
+     * @see #localToPositionCRS
      */
     private MathTransform objectiveToPositionCRS;
 
@@ -250,17 +266,16 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
     public final ReadOnlyObjectProperty<ReferenceSystem> 
positionReferenceSystem;
 
     /**
-     * Conversion from local coordinates to geographic or projected 
coordinates shown in this status bar.
+     * Transform from local coordinates to geographic or projected coordinates 
shown in this status bar.
      * This is the concatenation of {@link #localToObjectiveCRS} with {@link 
#objectiveToPositionCRS} 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.
+     * That transform target CRS shall correspond to {@link 
CoordinateFormat#getDefaultCRS()}.
+     * This transform 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 
CoordinateOperation#getTargetCRS()} on
-     * {@link #objectiveToPositionCRS} or by {@link 
CoordinateFormat#getDefaultCRS()}.</p>
-     *
-     * @see #updateLocalToPositionCRS()
+     * @see #localToObjectiveCRS
+     * @see #getPositionCRS()
      */
     private MathTransform localToPositionCRS;
 
@@ -290,7 +305,6 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
      *
      * @see #targetCoordinates
      * @see #position
-     * @see #setTargetCRS(CoordinateReferenceSystem)
      */
     private double[] sourceCoordinates;
 
@@ -308,6 +322,9 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
      * The desired precisions for each dimension in the {@link 
#targetCoordinates} to format.
      * It may vary for each position if the {@link #localToPositionCRS} 
transform is non-linear.
      * This array is initially {@code null} and created when first needed.
+     * It is the argument to be given to {@link 
CoordinateFormat#setPrecisions(double...)}.
+     *
+     * @see CoordinateFormat#setPrecisions(double...)
      */
     private double[] precisions;
 
@@ -323,18 +340,57 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
     private double[] inflatePrecisions;
 
     /**
-     * The declared accuracy on ground, or {@code null} if unspecified.
+     * The unit of measurement for {@link #precisions}.
+     * This is the unit of measurement of the first coordinate system axis.
+     */
+    private Unit<?> precisionUnit;
+
+    /**
+     * Number of elements in {@link #precisions} having the same unit of 
measurement than {@link #precisionUnit}.
+     * This value shall be between 1 and {@code precisions.length} inclusive, 
or 0 if {@link #precisionUnit} is null.
+     */
+    private int compatiblePrecisionCount;
+
+    /**
+     * Specifies a minimal uncertainty to append as "± <var>accuracy</var>" 
after the coordinate values.
+     * This uncertainty can be caused for example by a coordinate 
transformation applied on data before
+     * rendering in the canvas.
+     *
+     * <p>Note that {@code StatusBar} maintains also its own uncertainty, 
which can be caused by transformation
+     * from objective CRS to the {@linkplain #positionReferenceSystem 
reference system used in this status bar}.
+     * Such transformations happen when users select a CRS on the status bar 
(e.g. using the contextual menu)
+     * which is different than the canvas {@linkplain 
MapCanvas#getObjectiveCRS() objective CRS}.
+     * In such case we have two sources of stochastic errors: one internal to 
this status bar and one having
+     * causes external to this status bar. This {@code lowestAccuracy} 
property is for specifying the latter.</p>
+     *
+     * <p>The accuracy actually shown by {@code StatusBar} will be the 
greatest value between the accuracy
+     * specified in this property and the accuracy computed internally by 
{@code StatusBar}.
+     * Note that the "± <var>accuracy</var>" text may be shown or hidden 
depending on the zoom level.
+     * If pixels on screen are larger than the accuracy, then the accuracy 
text is hidden.</p>
+     *
+     * @see CoordinateFormat#setGroundAccuracy(Quantity)
      *
-     * @see #getLowestAccuracy()
-     * @see #setLowestAccuracy(Quantity)
+     * @since 1.3
      */
-    private Quantity<Length> lowestAccuracy;
+    public final ObjectProperty<Quantity<Length>> lowestAccuracy;
 
     /**
      * The object to use for formatting coordinate values.
+     * This reference shall not be null because it is the instance to use most 
of the time.
+     * In the rarer cases where {@link #formatAsIdentifiers} is non-null, the 
latter has precedence.
      */
     private final CoordinateFormat format;
 
+    /**
+     * The object to use for formatting coordinate values as identifiers 
(MGRS, GeoHash…).
+     * The null/non-null state tells whether to format coordinates as 
identifiers or not;
+     * a {@code null} values mean that coordinates shall be formatted using 
{@link #format} instead.
+     *
+     * <p>If non-null, then {@link #getPositionCRS()} should be the {@link 
#objectiveCRS}
+     * and {@link #objectiveToPositionCRS} should be null.</p>
+     */
+    private ReferencingByIdentifiers.Coder formatAsIdentifiers;
+
     /**
      * The label where to format the cursor position, either as coordinate 
values or other representations.
      * The text is usually the result of formatting coordinate values as 
numerical values,
@@ -392,6 +448,14 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
      */
     private boolean isSampleValuesVisible;
 
+    /**
+     * The background task under execution, or {@code null} if none. This is 
used for cancellation.
+     *
+     * @see #cancelWorker()
+     * @see #terminated(Task)
+     */
+    private Task<?> worker;
+
     /**
      * Creates a new status bar for showing coordinates of mouse cursor 
position in a canvas.
      * If {@link #track(Canvas)} is invoked, then this {@code StatusBar} will 
show coordinates
@@ -419,6 +483,7 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
         lastX = lastY           = Double.NaN;
         yDimension              = 1;
         format                  = new CoordinateFormat();
+        lowestAccuracy          = new SimpleObjectProperty<>(this, 
"lowestAccuracy");
 
         message = new Label();
         message.setVisible(false);                      // Waiting for getting 
a message to display.
@@ -454,8 +519,14 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
         if (systemChooser == null) {
             selectedSystem = null;
         } else {
-            final Menu choices = systemChooser.createMenuItems((property, 
oldValue, newValue) -> {
-                setPositionCRS(newValue instanceof CoordinateReferenceSystem ? 
(CoordinateReferenceSystem) newValue : null);
+            final Menu choices = systemChooser.createMenuItems(false, 
(property, oldValue, newValue) -> {
+                if (newValue instanceof CoordinateReferenceSystem) {
+                    setPositionCRS((CoordinateReferenceSystem) newValue);
+                } else if (newValue instanceof ReferencingByIdentifiers) {
+                    setPositionRID((ReferencingByIdentifiers) newValue);
+                } else {
+                    setPositionCRS(null);       // Default to `objectiveCRS`.
+                }
             });
             selectedSystem = 
RecentReferenceSystems.getSelectedProperty(choices);
             menu.getItems().add(choices);
@@ -465,14 +536,16 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
              * to `applyCanvasGeometry(GridGeometry)` has (λ,φ) axis order but 
the CRS offered to user have
              * (φ,λ) axis order (because we try to comply with definitions 
following geographers practice).
              * In such case we will replace (λ,φ) by (φ,λ). Since we use the 
list of choices as the source
-             * of desired CRS, we have to listen to new elements added to that 
list. This is necessary since
-             * the list of often empty at construction time and filled later 
after a background thread task.
+             * of desired CRS, we have to listen to new elements added to that 
list. This is necessary because
+             * the list is often empty at construction time and filled later 
after a background thread task.
              */
             systemChooser.getItems().addListener((ListChangeListener.Change<? 
extends ReferenceSystem> change) -> {
-                while (change.next()) {
-                    if (change.wasAdded() || change.wasReplaced()) {
-                        setReplaceablePositionCRS(format.getDefaultCRS());
-                        break;
+                if (formatAsIdentifiers == null) {
+                    while (change.next()) {
+                        if (change.wasAdded() || change.wasReplaced()) {
+                            setReplaceablePositionCRS(getPositionCRS());
+                            break;
+                        }
                     }
                 }
             });
@@ -487,7 +560,7 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
         sampleValuesProvider.addListener((p,o,n) -> {
             ValuesUnderCursor.update(this, o, n);
             if (o != null) items.remove(o.valueChoices);
-            if (n != null) items.add(0, n.valueChoices);
+            if (n != null) items.add(1, n.valueChoices);
             setSampleValuesVisible(n != null && !n.isEmpty());
         });
     }
@@ -507,7 +580,7 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
     }
 
     /**
-     * Registers listeners on the following canvas for track mouse movements.
+     * Registers listeners on the specified canvas for tracking mouse 
movements.
      * After this method call, this {@code StatusBar} will show coordinates 
(usually geographic or projected)
      * of mouse cursor position when the mouse is over that canvas. The {@link 
#localToObjectiveCRS} property
      * value may be overwritten at any time, for example after each gesture 
event such as pan, zoom or rotation.
@@ -680,10 +753,10 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
          */
         MathTransform localToCRS = null;
         CoordinateReferenceSystem crs = null;
-        sourceCoordinates = ArraysExt.EMPTY_DOUBLE;
-        double resolution = 1;
+        double[] pointOfInterest = ArraysExt.EMPTY_DOUBLE;
         double[] inflate = null;
-        Unit<?> unit = Units.PIXEL;
+        double   resolution = 1;
+        Unit<?>  unit = Units.PIXEL;
         if (geometry != null) {
             if (geometry.isDefined(GridGeometry.CRS)) {
                 crs = geometry.getCoordinateReferenceSystem();
@@ -721,13 +794,21 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
                 for (int i=0; i<n; i++) {
                     inflate[i] = (0.5 / extent.getSize(i)) + 1;
                 }
-                sourceCoordinates = 
extent.getPointOfInterest(PixelInCell.CELL_CENTER);
+                pointOfInterest = 
extent.getPointOfInterest(PixelInCell.CELL_CENTER);
             }
         }
-        final boolean sameCRS = Utilities.equalsIgnoreMetadata(objectiveCRS, 
crs);
+        /*
+         * If the objective CRS stay unchanged, then we will try to keep the 
same position CRS
+         * (which may be different), which implies keeping the same 
`objectiveToPositionCRS`.
+         */
+        final boolean clear = (fullOperationSearchRequired != null) && 
fullOperationSearchRequired.test(canvas);
+        final boolean sameCRS = !clear && 
Utilities.equalsIgnoreMetadata(objectiveCRS, crs);
         if (localToCRS == null) {
             localToCRS = MathTransforms.identity(BIDIMENSIONAL);
         }
+        if (sameCRS && objectiveToPositionCRS != null) {
+            localToCRS = MathTransforms.concatenate(localToCRS, 
objectiveToPositionCRS);
+        }
         final int srcDim = Math.max(localToCRS.getSourceDimensions(), 
BIDIMENSIONAL);
         final int tgtDim = localToCRS.getTargetDimensions();
         /*
@@ -740,24 +821,26 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
          * Instead we will wait for the next mouse event to provide new local 
coordinates.
          */
         ((LocalToObjective) localToObjectiveCRS).setNoCheck(localToCRS);
-        sourceCoordinates   = Arrays.copyOf(sourceCoordinates, srcDim);
+        sourceCoordinates   = Arrays.copyOf(pointOfInterest, srcDim);
         targetCoordinates   = new GeneralDirectPosition(tgtDim);
         objectiveCRS        = crs;
-        localToPositionCRS  = localToCRS;                           // May be 
updated again below.
+        localToPositionCRS  = localToCRS;
         inflatePrecisions   = inflate;
         precisions          = null;
-        lastX = lastY       = Double.NaN;                           // Not 
valid anymove — see above block comment.
+        lastX = lastY       = Double.NaN;           // Not valid anymove — see 
above block comment.
+        /*
+         * If the objective CRS is unchanged, keep the same position CRS (the 
CRS selected
+         * by user for formatting coordinates; it may be different than the 
objective CRS).
+         * Otherwise we reset the formatter to the CRS specified in the grid 
geometry.
+         */
         if (sameCRS) {
-            updateLocalToPositionCRS();
-            // Keep the format CRS unchanged since we made 
`localToPositionCRS` consistent with its value.
-            if (fullOperationSearchRequired != null && 
fullOperationSearchRequired.test(canvas)) {
-                setPositionCRS(format.getDefaultCRS());
-            }
+            crs = getPositionCRS();
+            targetCoordinates.setCoordinateReferenceSystem(crs);
         } else {
             objectiveToPositionCRS = null;
-            setFormatCRS(crs, null);                                // Should 
be invoked before to set precision.
+            setFormatCRS(crs, null);                            // Should be 
invoked before to set ground precision.
             crs = OperationFinder.toGeospatial(crs, canvas);
-            crs = setReplaceablePositionCRS(crs);                   // May 
invoke setFormatCRS(…) after background work.
+            crs = setReplaceablePositionCRS(crs);               // May invoke 
setFormatCRS(…) after background work.
         }
         format.setGroundPrecision(Quantities.create(resolution, unit));
         /*
@@ -770,31 +853,6 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
         }
     }
 
-    /**
-     * Computes {@link #localToPositionCRS} after a change of {@link 
#localToObjectiveCRS}.
-     * Other properties, in particular {@link #objectiveToPositionCRS}, must 
be valid.
-     */
-    private void updateLocalToPositionCRS() {
-        localToPositionCRS = localToObjectiveCRS.get();
-        if (objectiveToPositionCRS != null) {
-            localToPositionCRS = 
MathTransforms.concatenate(localToPositionCRS, objectiveToPositionCRS);
-        }
-        setTargetCRS(format.getDefaultCRS());
-    }
-
-    /**
-     * Sets the CRS of {@link #targetCoordinates}.
-     * This method creates a new position if the number of dimensions changed.
-     */
-    private void setTargetCRS(final CoordinateReferenceSystem crs) {
-        final int tgtDim = ReferencingUtilities.getDimension(crs);
-        if (tgtDim != 0 && tgtDim != targetCoordinates.getDimension()) {
-            precisions = null;
-            targetCoordinates = new GeneralDirectPosition(tgtDim);
-        }
-        targetCoordinates.setCoordinateReferenceSystem(crs);
-    }
-
     /**
      * Sets the CRS of the position shown in this status bar after replacement 
by one of the available CRS
      * if a match is found. This method compares the given CRS with the list 
of choices before to delegate
@@ -803,6 +861,9 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
      * axis order, and this method swapping axes to standard 
(<var>latitude</var>, <var>longitude</var>)
      * axis order for coordinates display purpose.
      *
+     * <h4>Prerequisite</h4>
+     * This method should be invoked only when {@link #formatAsIdentifiers} is 
null. This is not verified.
+     *
      * @param  crs  the new CRS (ignoring axis order), or {@code null} for 
{@link #objectiveCRS}.
      * @return the reference system actually used for formatting coordinates. 
It may have different axis order
      *         and units than the specified CRS. This is the CRS that {@link 
CoordinateFormat#getDefaultCRS()}
@@ -818,7 +879,7 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
                 }
             }
         }
-        if (crs != format.getDefaultCRS()) {
+        if (crs != getPositionCRS()) {
             setPositionCRS(crs);
         }
         return crs;
@@ -831,8 +892,12 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
      * 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}.
+     *
+     * @see #setPositionRID(ReferencingByIdentifiers)
+     * @see #getPositionCRS()
      */
     private void setPositionCRS(final CoordinateReferenceSystem crs) {
+        cancelWorker();
         if (crs != null && objectiveCRS != null && objectiveCRS != crs) {
             position.setTextFill(Styles.OUTDATED_TEXT);
             /*
@@ -841,10 +906,12 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
              * in the middle of changes at any time. All objects are assumed 
immutable.
              */
             final Envelope aoi = (systemChooser != null) ? 
systemChooser.areaOfInterest.get() : null;
-            BackgroundThreads.execute(new OperationFinder(canvas, aoi, 
objectiveCRS, crs) {
+            BackgroundThreads.execute(worker = new OperationFinder(canvas, 
aoi, objectiveCRS, crs) {
                 /**
                  * The accuracy to show on the status bar, or {@code null} if 
none.
                  * This is computed after {@link CoordinateOperation} has been 
determined.
+                 *
+                 * @see StatusBar#lowestAccuracy
                  */
                 private Quantity<Length> accuracy;
 
@@ -860,12 +927,14 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
                     }
                     return value;
                 }
+
                 /**
                  * Invoked in JavaFX thread on success. The {@link 
StatusBar#localToPositionCRS} 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() {
+                    terminated(this);
                     setPositionCRS(this, accuracy);
                 }
 
@@ -874,11 +943,8 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
                  * the coordinates will appear in red for telling user that 
there is a problem.
                  */
                 @Override protected void failed() {
-                    final Locale locale = getLocale();
-                    
setErrorMessage(Resources.forLocale(locale).getString(Resources.Keys.CanNotUseRefSys_1,
-                                    IdentifiedObjects.getDisplayName(crs, 
locale)), getException());
-                    selectedSystem.set(format.getDefaultCRS());
-                    resetPositionCRS(Styles.ERROR_TEXT);
+                    terminated(this);
+                    setReferenceSystemError(crs, getException());
                 }
 
                 /** For logging purpose if a non-fatal error occurs. */
@@ -913,13 +979,29 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
      *
      * @param  finder    the completed task with the new {@link 
#objectiveToPositionCRS}.
      * @param  accuracy  the accuracy to show on the status bar, or {@code 
null} if none.
+     *
+     * @see #setPositionRID(ReferencingByIdentifiers.Coder, String, 
DirectPosition)
      */
     private void setPositionCRS(final OperationFinder finder, final 
Quantity<Length> accuracy) {
+        worker = null;
         setErrorMessage(null, null);
-        setFormatCRS(finder.getTargetCRS(), accuracy);
-        objectiveToPositionCRS = finder.getValue();
         fullOperationSearchRequired = finder.fullOperationSearchRequired();
-        updateLocalToPositionCRS();
+        localToPositionCRS = localToObjectiveCRS.get();
+        objectiveToPositionCRS = finder.getValue();
+        if (objectiveToPositionCRS != null) {
+            localToPositionCRS = 
MathTransforms.concatenate(localToPositionCRS, objectiveToPositionCRS);
+        }
+        setFormatCRS(finder.getTargetCRS(), accuracy);
+        rewritePosition(null);
+    }
+
+    /**
+     * Invoked after a new reference system has been set. This method rewrites 
the coordinates
+     * on the assumption that {@link #lastX} and {@link #lastY} are still 
valid.
+     *
+     * @param  current  the local coordinates used for current text, or {@code 
null} if not valid.
+     */
+    private void rewritePosition(final DirectPosition current) {
         position.setTextFill(Styles.NORMAL_TEXT);
         position.setMinWidth(0);
         maximalPositionLength = 0;
@@ -928,64 +1010,92 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
             final double y = lastY;
             lastX = lastY = Double.NaN;
             if (!Double.isNaN(x) && !Double.isNaN(y)) {
-                setLocalCoordinates(x, y);
+                if (current == null || current.getOrdinate(0) != x || 
current.getOrdinate(1) != y) {
+                    setLocalCoordinates(x, y);
+                }
             }
         }
     }
 
+    /**
+     * Invoked in JavaFX thread when a background task finished its work, 
either successfully or on error.
+     */
+    private void terminated(final Task<?> caller) {
+        if (caller == worker) {
+            worker = null;
+        }
+    }
+
+    /**
+     * If a background task was in progress, cancels it. This is invoked 
before a new background task is launched.
+     */
+    private void cancelWorker() {
+        if (worker != null) {
+            worker.cancel();
+            worker = null;
+        }
+    }
+
     /**
      * Sets the {@link CoordinateFormat} default CRS together with the tool 
tip text.
      * Caller is responsible to setup transforms ({@link #localToPositionCRS}, 
<i>etc</i>).
-     * For the method that apply required changes on transforms before to set 
the format CRS,
+     * For method that applies required changes on transforms before to set 
the format CRS,
      * see {@link #setPositionCRS(CoordinateReferenceSystem)}.
      *
      * @param  crs       the new {@link #format} reference system.
-     * @param  accuracy  positional accuracy in the given CRS, or {@code null} 
if none.
+     * @param  accuracy  positional accuracy of the transformation from local 
coordinates to the given CRS,
+     *         or {@code null} if none. This is an accuracy computed by this 
{@code StatusBar} class,
+     *         as opposed to {@link #lowestAccuracy} which has causes external 
to {@code StatusBar}.
      *
      * @see #positionReferenceSystem
      */
     private void setFormatCRS(final CoordinateReferenceSystem crs, final 
Quantity<Length> accuracy) {
-        format.setDefaultCRS(crs);
-        format.setGroundAccuracy(Quantities.max(accuracy, lowestAccuracy));
-        String text = IdentifiedObjects.getDisplayName(crs, getLocale());
-        Tooltip tp = null;
-        if (text != null) {
-            tp = position.getTooltip();
-            if (tp == null) {
-                tp = new Tooltip(text);
-            } else {
-                tp.setText(text);
-            }
+        int dimension = localToPositionCRS.getTargetDimensions();
+        GeneralDirectPosition target = targetCoordinates;
+        if (dimension != target.getDimension()) {
+            target = new GeneralDirectPosition(dimension);
+            precisions = null;
         }
-        position.setTooltip(tp);
+        target.setCoordinateReferenceSystem(crs);
+        format.setDefaultCRS(crs);
+        targetCoordinates = target;         // Assign only after abpve succeed.
+        formatAsIdentifiers = null;
+        format.setGroundAccuracy(Quantities.max(accuracy, 
lowestAccuracy.get()));
+        setTooltip(crs);
         /*
          * Prepare the text to show when the mouse is outside the canvas area.
          * We will write axis abbreviations, for example "(φ, λ)".
+         * Also fetch the unit of measurement of first axes.
          */
-        text = null;
+        compatiblePrecisionCount = 0;
+        precisionUnit = null;
+        String text = null;
         if (crs != null) {
+            final StringBuilder b = new StringBuilder().append('(');
             final CoordinateSystem cs = crs.getCoordinateSystem();
-            if (cs != null) {                                               // 
Paranoiac check (should never be null).
-                final int dimension = cs.getDimension();
-                if (dimension > 0) {                                        // 
Paranoiac check (should never be zero).
-                    final StringBuilder b = new StringBuilder().append('(');
-                    for (int i=0; i<dimension; i++) {
-                        if (i != 0) b.append(", ");
-                        final CoordinateSystemAxis axis = cs.getAxis(i);
-                        if (axis != null) {                                 // 
Paranoiac check (should never be null).
-                            final String abbr = 
Strings.trimOrNull(axis.getAbbreviation());
-                            if (abbr != null) {
-                                b.append(abbr);
-                                continue;
-                            }
+            dimension = (cs != null) ? cs.getDimension() : 0;       // 
Paranoiac check (should never be null).
+            for (int i=0; i<dimension; i++) {
+                if (i != 0) b.append(", ");
+                final CoordinateSystemAxis axis = cs.getAxis(i);
+                if (axis != null) {                                 // 
Paranoiac check (should never be null).
+                    if (i == compatiblePrecisionCount) {            // Require 
consecutive axes for unit test.
+                        final Unit<?> unit = axis.getUnit();
+                        if (i == 0 || Objects.equals(precisionUnit, unit)) {
+                            compatiblePrecisionCount = i+1;
+                            precisionUnit = unit;
                         }
-                        b.append('?');
                     }
-                    b.append(')');
-                    format.getGroundAccuracyText().ifPresent(b::append);
-                    text = b.toString();
+                    final String abbr = 
Strings.trimOrNull(axis.getAbbreviation());
+                    if (abbr != null) {
+                        b.append(abbr);
+                        continue;
+                    }
                 }
+                b.append('?');
             }
+            b.append(')');
+            format.getGroundAccuracyText().ifPresent(b::append);
+            text = b.toString();
         }
         /*
          * If the mouse is already outside canvas area, update the `position` 
text now.
@@ -995,7 +1105,6 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
             position.setText(text);
         }
         outsideText = text;
-        setTargetCRS(crs);
         ((PositionSystem) positionReferenceSystem).fireValueChangedEvent();
     }
 
@@ -1003,15 +1112,30 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
      * Implementation of {@link #positionReferenceSystem} property.
      */
     private final class PositionSystem extends 
ReadOnlyObjectPropertyBase<ReferenceSystem> {
-        @Override public Object          getBean()       {return 
StatusBar.this;}
-        @Override public String          getName()       {return 
"positionReferenceSystem";}
-        @Override public ReferenceSystem get()           {return 
format.getDefaultCRS();}
+        @Override public Object getBean() {return StatusBar.this;}
+        @Override public String getName() {return "positionReferenceSystem";}
+        @Override public ReferenceSystem get() {
+            final ReferencingByIdentifiers.Coder f = formatAsIdentifiers;
+            return (f != null) ? f.getReferenceSystem() : getPositionCRS();
+        }
         @Override protected void fireValueChangedEvent() 
{super.fireValueChangedEvent();}
     }
 
+    /**
+     * Returns the coordinate reference system of the position shown in this 
status bar.
+     * This is valid only if {@link #formatAsIdentifiers} is null.
+     *
+     * @see #setPositionCRS(CoordinateReferenceSystem)
+     */
+    private CoordinateReferenceSystem getPositionCRS() {
+        return format.getDefaultCRS();
+    }
+
     /**
      * Resets {@link #localToPositionCRS} to its default value. This is 
invoked either when the
      * target CRS is {@link #objectiveCRS}, or when an attempt to use another 
CRS failed.
+     *
+     * @param  textFill  the color to assign to position text. It depends on 
the reason why we reset the position.
      */
     private void resetPositionCRS(final Color textFill) {
         objectiveToPositionCRS = null;
@@ -1037,8 +1161,8 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
         /**
          * Overwrite previous value without any check. This method is invoked 
when the {@link #objectiveCRS}
          * is changed at the same time that the {@link #localToObjectiveCRS} 
transform, so the number of dimensions
-         * may be temporarily mismatched. This method does not invoke {@link 
#updateLocalToPositionCRS()};
-         * that call must be done by the caller when ready.
+         * may be temporarily mismatched. This method does not update {@link 
#localToPositionCRS};
+         * that update must be done by the caller when ready.
          */
         final void setNoCheck(final MathTransform newValue) {
             super.set(newValue);
@@ -1050,17 +1174,113 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
          * @param  newValue  the new conversion from local coordinates to 
"real world" coordinates of rendered data.
          * @throws MismatchedDimensionException if the number of dimensions is 
not the same than previous conversion.
          */
-        @Override public void set(final MathTransform newValue) {
+        @Override public void set(MathTransform newValue) {
             ArgumentChecks.ensureNonNull("newValue", newValue);
             final MathTransform oldValue = get();
             ArgumentChecks.ensureDimensionsMatch("newValue",
                     oldValue.getSourceDimensions(),
                     oldValue.getTargetDimensions(), newValue);
+            final MathTransform tr = objectiveToPositionCRS;
+            if (tr != null) {
+                newValue = MathTransforms.concatenate(newValue, tr);
+            }
+            localToPositionCRS = newValue;
             super.set(newValue);
-            updateLocalToPositionCRS();
         }
     }
 
+    /**
+     * Sets the reference system of the position shown in this status bar.
+     * This is similar to {@link #setPositionCRS(CoordinateReferenceSystem)} 
but for referencing by identifiers.
+     * This method tries to format the current position in a background thread 
because the first invocation of
+     * {@link ReferencingByIdentifiers.Coder#encode(DirectPosition)} may 
require an access to the EPSG database.
+     *
+     * @param  system  the new reference system (shall not be {@code null}).
+     */
+    private void setPositionRID(final ReferencingByIdentifiers system) {
+        resetPositionCRS(Styles.OUTDATED_TEXT);
+        final DirectPosition poi;
+        final MathTransform toObjective;
+        final CoordinateReferenceSystem crs = objectiveCRS;
+        final Quantity<Length> accuracy = lowestAccuracy.get();
+        if (Double.isFinite(lastX) && Double.isFinite(lastY)) {
+            poi = new DirectPosition2D(lastX, lastY);
+            toObjective = localToPositionCRS;
+        } else {
+            poi = (canvas != null) ? canvas.getPointOfInterest(true) : null;
+            toObjective = null;
+        }
+        cancelWorker();
+        BackgroundThreads.execute(worker = new Task<String>() {
+            /**
+             * The object to use for formatting identifiers. This is the value 
to assign
+             * to {@link StatusBar#formatAsIdentifiers} after successful task 
completion.
+             */
+            private ReferencingByIdentifiers.Coder coder;
+
+            /**
+             * Invoked in a background thread for formatting the identifier. 
The point to format is transformed
+             * from local coordinates to objective CRS. The CRS needs to be 
specified for allowing the coder to
+             * transform again the point to whatever internal CRS it needs for 
encoding purpose.
+             */
+            @Override protected String call() {
+                coder = system.createCoder();
+                try {
+                    DirectPosition p = poi;
+                    if (p != null && toObjective != null) {
+                        p = toObjective.transform(p, new 
GeneralDirectPosition(crs));
+                    }
+                    if (accuracy != null) {
+                        coder.setPrecision(accuracy, p);
+                    }
+                    if (p != null) {
+                        return coder.encode(p);
+                    }
+                } catch (IncommensurableException | TransformException e) {
+                    recoverableException("setPositionRID", e);
+                }
+                return null;
+            }
+
+            /**
+             * Invoked in JavaFX thread for reporting a failure.
+             * The reference system in use stay the previous one.
+             */
+            @Override protected void failed() {
+                terminated(this);
+                setReferenceSystemError(system, getException());
+            }
+
+            /** Invoked in JavaFX thread on success for applying the actual 
reference system change. */
+            @Override protected void succeeded() {
+                terminated(this);
+                setPositionRID(coder, getValue(), (toObjective != null) ? poi 
: null);
+            }
+        });
+    }
+
+    /**
+     * Invoked after the background thread prepared the new reference system.
+     * The identifier formatted by the background thread is written, but needs 
to be rewritten
+     * again if {@link #lastX} or {@link #lastY} changed since background task 
execution.
+     *
+     * @param  coder       the coder to use for formatting identifiers.
+     * @param  identifier  identifier formatted using mouse position.
+     * @param  current     the local coordinates used for current text, or 
{@code null} if not valid.
+     *
+     * @see #setPositionCRS(OperationFinder, Quantity)
+     */
+    private void setPositionRID(final ReferencingByIdentifiers.Coder coder, 
final String identifier, final DirectPosition current) {
+        formatAsIdentifiers = coder;
+        fullOperationSearchRequired = null;
+        outsideText = null;
+        setErrorMessage(null, null);
+        setTooltip(coder.getReferenceSystem());
+        position.setText(identifier);
+        ((PositionSystem) positionReferenceSystem).fireValueChangedEvent();
+        rewritePosition(current);
+    }
+
     /**
      * Returns the indices of <var>x</var> and <var>y</var> coordinate values 
in a grid coordinate tuple.
      * They are the indices where to assign the values of the <var>x</var> and 
<var>y</var> arguments in
@@ -1084,9 +1304,12 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
      * @return the lowest accuracy to append after the coordinate values, or 
{@code null} if none.
      *
      * @see CoordinateFormat#getGroundAccuracy()
+     *
+     * @deprecated Replaced by {@link #lowestAccuracy}.
      */
+    @Deprecated
     public Quantity<Length> getLowestAccuracy() {
-        return lowestAccuracy;
+        return lowestAccuracy.get();
     }
 
     /**
@@ -1101,9 +1324,12 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
      * @param  accuracy  the lowest accuracy to append after the coordinate 
values, or {@code null} if none.
      *
      * @see CoordinateFormat#setGroundAccuracy(Quantity)
+     *
+     * @deprecated Replaced by {@link #lowestAccuracy}.
      */
+    @Deprecated
     public void setLowestAccuracy(final Quantity<Length> accuracy) {
-        lowestAccuracy = accuracy;
+        lowestAccuracy.set(accuracy);
     }
 
     /**
@@ -1136,26 +1362,15 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
      */
     public void setLocalCoordinates(final double x, final double y) {
         if (x != lastX || y != lastY) {
-            sourceCoordinates[xDimension] = lastX = x;
-            sourceCoordinates[yDimension] = lastY = y;
-            String text, values = null;
-            try {
-                convertCoordinates();
-                if (isSampleValuesVisible) {
-                    values = 
sampleValuesProvider.get().evaluate(targetCoordinates);
-                }
-                targetCoordinates.normalize();
-                text = format.format(targetCoordinates);
-            } catch (TransformException | RuntimeException e) {
-                Throwable cause = Exceptions.unwrap(e);
-                text = cause.getLocalizedMessage();
-                if (text == null) {
-                    text = Classes.getShortClassName(cause);
-                }
-                values = null;
-            }
+            String text = formatLocalCoordinates(lastX = x, lastY = y);
             position.setText(text);
             if (isSampleValuesVisible) {
+                String values;
+                try {
+                    values = 
sampleValuesProvider.get().evaluate(targetCoordinates);
+                } catch (RuntimeException e) {
+                    values = cause(e);
+                }
                 sampleValues.setText(values);
             }
             /*
@@ -1170,17 +1385,17 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
     }
 
     /**
-     * Converts the local coordinates currently stored in {@link 
#sourceCoordinates} array.
-     * The conversion result is stored in {@link #targetCoordinates} and the 
{@link #format}
-     * is configured with suggested precision. Callers can use this method as 
below:
+     * Unconditionally converts and formats the given local coordinates, but 
without modifying any control.
+     * It is caller's responsibility to either change the text shown in the 
status bar, or to use the returned
+     * text for something else (for example for copying in the clipboard).
      *
-     * {@preformat java
-     *     convertCoordinates();
-     *     targetCoordinates.normalize();
-     *     String text = format.format(targetCoordinates);
-     * }
+     * @param  x  the <var>x</var> coordinate local to the view.
+     * @param  y  the <var>y</var> coordinate local to the view.
+     * @return string representation of coordinates or an error message.
      */
-    private void convertCoordinates() throws TransformException {
+    private String formatLocalCoordinates(final double x, final double y) {
+        sourceCoordinates[xDimension] = x;
+        sourceCoordinates[yDimension] = y;
         Matrix derivative;
         try {
             derivative = 
MathTransforms.derivativeAndTransform(localToPositionCRS,
@@ -1191,7 +1406,11 @@ 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(…)`).
              */
-            localToPositionCRS.transform(sourceCoordinates, 0, 
targetCoordinates.coordinates, 0, 1);
+            try {
+                localToPositionCRS.transform(sourceCoordinates, 0, 
targetCoordinates.coordinates, 0, 1);
+            } catch (TransformException e) {
+                return cause(e);
+            }
             derivative = null;
         }
         if (derivative == null) {
@@ -1218,23 +1437,43 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
                 precisions[j] = p;
             }
         }
-        format.setPrecisions(precisions);
+        targetCoordinates.normalize();
+        /*
+         * Format as an identifier or as a coordinate tuple, depending on the 
type of the reference system.
+         * The precision is determined by the size of a pixel on screen and 
controls the number of fraction
+         * digits to print. Precision should not be confused with accuracy, 
which depends on transformation
+         * applied on coordinate values and determines the "± accuracy" text 
shown after coordinates.
+         */
+        try {
+            if (formatAsIdentifiers != null) {
+                double precision = 0;
+                for (int i = compatiblePrecisionCount; --i >= 0;) {
+                    final double p = precisions[i];
+                    if (p > precision) precision = p;
+                }
+                return formatAsIdentifiers.encode(targetCoordinates,
+                        (precision > 0) ? Quantities.create(precision, 
precisionUnit) : null);
+            } else {
+                format.setPrecisions(precisions);
+                return format.format(targetCoordinates);
+            }
+        } catch (Exception e) {
+            return cause(e);
+        }
     }
 
     /**
      * Converts and formats the given local coordinates, but without modifying 
text shown in this status bar.
+     * This is used for copying the coordinates somewhere else, for example on 
the clipboard.
      *
      * @param  x  the <var>x</var> coordinate local to the view.
      * @param  y  the <var>y</var> coordinate local to the view.
      */
-    final String formatCoordinates(final double x, final double y) throws 
TransformException {
-        sourceCoordinates[xDimension] = x;
-        sourceCoordinates[yDimension] = y;
+    final String formatTabSeparatedCoordinates(final double x, final double y) 
throws TransformException {
         final String separator = format.getSeparator();
         try {
             format.setSeparator("\t");
-            convertCoordinates();
-            return format.format(targetCoordinates);
+            return formatLocalCoordinates(x, y);
         } finally {
             format.setSeparator(separator);
         }
@@ -1353,6 +1592,23 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
         return true;
     }
 
+    /**
+     * Sets the tooltip text to show when the mouse cursor is over the 
coordinate values.
+     */
+    private void setTooltip(final ReferenceSystem crs) {
+        String text = IdentifiedObjects.getDisplayName(crs, getLocale());
+        Tooltip tp = null;
+        if (text != null) {
+            tp = position.getTooltip();
+            if (tp == null) {
+                tp = new Tooltip(text);
+            } else {
+                tp.setText(text);
+            }
+        }
+        position.setTooltip(tp);
+    }
+
     /**
      * Returns the message currently shown. It may be an error message or an 
informative message.
      *
@@ -1405,17 +1661,10 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
         text = Strings.trimOrNull(text);
         Button more = null;
         if (details != null) {
-            final Locale locale = getLocale();
-            if (text == null) {
-                text = Exceptions.getLocalizedMessage(details, locale);
-                if (text == null) {
-                    text = details.getClass().getSimpleName();
-                }
-            }
-            final String alert = text;
+            final String alert = (text != null) ? text : cause(details);
             more = new Button(Styles.ERROR_DETAILS_ICON);
             more.setOnAction((e) -> ExceptionReporter.show(getView(),
-                    
Resources.forLocale(locale).getString(Resources.Keys.ErrorDetails), alert, 
details));
+                    
Resources.forLocale(getLocale()).getString(Resources.Keys.ErrorDetails), alert, 
details));
         }
         message.setVisible(text != null);
         message.setGraphic(more);
@@ -1423,6 +1672,24 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
         message.setTextFill(Styles.ERROR_TEXT);
     }
 
+    /**
+     * Shows an error message for a reference system that can not be set.
+     * The previous reference system is kept unchanged but the coordinates
+     * will appear in red for telling user that there is a problem.
+     *
+     * @param system     the reference system that we failed to set.
+     * @param exception  the exception that occurred while attempting to set 
the CRS.
+     */
+    private void setReferenceSystemError(final ReferenceSystem system, final 
Throwable exception) {
+        final Locale locale = getLocale();
+        
setErrorMessage(Resources.forLocale(locale).getString(Resources.Keys.CanNotUseRefSys_1,
+                        IdentifiedObjects.getDisplayName(system, locale)), 
exception);
+        if (selectedSystem != null) {
+            selectedSystem.set(positionReferenceSystem.get());
+        }
+        resetPositionCRS(Styles.ERROR_TEXT);
+    }
+
     /**
      * Shown an error message that occurred in the context of rendering the 
{@link #canvas} content.
      * This method should not be invoked for other context like an error 
during transformation of
@@ -1435,4 +1702,30 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
         }
         setErrorMessage(text, details);
     }
+
+    /**
+     * Returns a string representation of the message of the given exception.
+     * If the exception is a wrapper, the exception cause is taken.
+     * If there is no message, the exception class name is returned.
+     *
+     * @param  e  the exception.
+     * @return the exception message or class name.
+     */
+    private String cause(Throwable e) {
+        if (e instanceof Exception) {
+            e = Exceptions.unwrap((Exception) e);
+        }
+        String text = Exceptions.getLocalizedMessage(e, getLocale());
+        if (text == null) {
+            text = Classes.getShortClassName(e);
+        }
+        return text;
+    }
+
+    /**
+     * Logs an error considered too minor for reporting on the status bar.
+     */
+    private static void recoverableException(final String caller, final 
Exception e) {
+        Logging.recoverableException(Logger.getLogger(Modules.APPLICATION), 
StatusBar.class, caller, e);
+    }
 }
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/MenuSync.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/MenuSync.java
index 90e1e95642..cfa6888b3d 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/MenuSync.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/MenuSync.java
@@ -17,6 +17,7 @@
 package org.apache.sis.gui.referencing;
 
 import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.Locale;
@@ -33,8 +34,10 @@ import org.opengis.referencing.ReferenceSystem;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.internal.referencing.ReferencingUtilities;
 import org.apache.sis.internal.gui.GUIUtilities;
+import org.apache.sis.internal.gui.Resources;
 import org.apache.sis.referencing.IdentifiedObjects;
-import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.referencing.gazetteer.GazetteerFactory;
+import org.apache.sis.referencing.gazetteer.GazetteerException;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.Utilities;
 
@@ -46,13 +49,18 @@ import org.apache.sis.util.Utilities;
  * the selected reference system directly.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.3
  * @since   1.1
  * @module
  */
 final class MenuSync extends SimpleObjectProperty<ReferenceSystem> implements 
EventHandler<ActionEvent> {
     /**
-     * Keys where to store the reference system in {@link MenuItem}.
+     * The {@value} value, for identifying code that assume two-dimensional 
objects.
+     */
+    private static final int BIDIMENSIONAL = 2;
+
+    /**
+     * Keys where to store the reference system in {@link MenuItem} properties.
      */
     private static final String REFERENCE_SYSTEM_KEY = "ReferenceSystem";
 
@@ -61,6 +69,11 @@ final class MenuSync extends 
SimpleObjectProperty<ReferenceSystem> implements Ev
      */
     private static final String CHOOSER = "CHOOSER";
 
+    /**
+     * The list of reference systems to show as menu items.
+     */
+    private final ObservableList<? extends ReferenceSystem> systems;
+
     /**
      * The list of menu items to keep up-to-date with an {@code 
ObservableList<ReferenceSystem>}.
      */
@@ -72,12 +85,12 @@ final class MenuSync extends 
SimpleObjectProperty<ReferenceSystem> implements Ev
     private final ToggleGroup group;
 
     /**
-     * The action to execute when a reference system is selected. This is not 
directly the user-specified action, but
-     * rather an {@link 
org.apache.sis.gui.referencing.RecentReferenceSystems.Listener} instance 
wrapping that action.
-     * This listener is invoked explicitly instead of using {@link 
SimpleObjectProperty} listeners because we do not
-     * invoke it in all cases.
+     * The action to execute when a reference system is selected. This is not 
directly the user-specified action,
+     * but rather a {@link 
org.apache.sis.gui.referencing.RecentReferenceSystems.SelectionListener} 
instance wrapping
+     * that action. This listener is invoked explicitly instead of using 
{@link SimpleObjectProperty} listeners because
+     * we do not invoke it in all cases.
      */
-    private final RecentReferenceSystems.Listener action;
+    private final RecentReferenceSystems.SelectionListener action;
 
     /**
      * Creates a new synchronization for the given list of menu items.
@@ -86,33 +99,35 @@ final class MenuSync extends 
SimpleObjectProperty<ReferenceSystem> implements Ev
      * @param  bean     the menu to keep synchronized with the list of 
reference systems.
      * @param  action   the user-specified action to execute when a reference 
system is selected.
      */
-    MenuSync(final ObservableList<ReferenceSystem> systems, final Menu bean, 
final RecentReferenceSystems.Listener action) {
+    MenuSync(final ObservableList<ReferenceSystem> systems, final Menu bean, 
final RecentReferenceSystems.SelectionListener action) {
         super(bean, "value");
-        this.menus  = bean.getItems();
-        this.group  = new ToggleGroup();
-        this.action = action;
+        this.systems = systems;
+        this.menus   = bean.getItems();
+        this.group   = new ToggleGroup();
+        this.action  = action;
         /*
          * We do not register listener for `systems` list.
-         * Instead `notifyChanges(…)` will be invoked directly by 
RecentReferenceSystems.
+         * Instead `notifyChanges()` will be invoked directly by 
RecentReferenceSystems.
          */
         final MenuItem[] items = new MenuItem[systems.size()];
+        final Locale locale = action.owner().locale;
         for (int i=0; i<items.length; i++) {
-            items[i] = createItem(systems.get(i));
+            items[i] = createItem(systems.get(i), locale);
         }
         menus.setAll(items);
-        initialize(systems);
+        initialize();
     }
 
     /**
-     * Sets the initial value to the first two-dimensional item in the {@code 
systems} list, if any.
+     * Sets the initial value to the first two-dimensional item in the {@link 
#systems} list, if any.
      * This method is invoked in JavaFX thread at construction time or, if it 
didn't work,
      * at some later time when the systems list may contain an element.
      * This method should not be invoked anymore after initialization 
succeeded.
      */
-    private void initialize(final ObservableList<? extends ReferenceSystem> 
systems) {
+    private void initialize() {
         for (final ReferenceSystem system : systems) {
             if (system instanceof CoordinateReferenceSystem) {
-                if 
(ReferencingUtilities.getDimension((CoordinateReferenceSystem) system) == 2) {
+                if 
(ReferencingUtilities.getDimension((CoordinateReferenceSystem) system) == 
BIDIMENSIONAL) {
                     set(system);
                     break;
                 }
@@ -123,20 +138,40 @@ final class MenuSync extends 
SimpleObjectProperty<ReferenceSystem> implements Ev
     /**
      * Creates a new menu item for the given reference system.
      */
-    private MenuItem createItem(final ReferenceSystem system) {
-        final Locale locale = action.owner().locale;
-        if (system != RecentReferenceSystems.OTHER) {
+    private MenuItem createItem(final ReferenceSystem system, final Locale 
locale) {
+        if (system == RecentReferenceSystems.OTHER) {
+            final MenuItem item = new 
MenuItem(ObjectStringConverter.other(locale));
+            item.getProperties().put(REFERENCE_SYSTEM_KEY, CHOOSER);
+            item.setOnAction(this);
+            return item;
+        } else {
             final RadioMenuItem item = new 
RadioMenuItem(IdentifiedObjects.getDisplayName(system, locale));
             item.getProperties().put(REFERENCE_SYSTEM_KEY, system);
             item.setToggleGroup(group);
             item.setOnAction(this);
             return item;
-        } else {
-            final MenuItem item = new 
MenuItem(Vocabulary.getResources(locale).getString(Vocabulary.Keys.Others) + 
'…');
-            item.getProperties().put(REFERENCE_SYSTEM_KEY, CHOOSER);
+        }
+    }
+
+    /**
+     * Creates new menu items for references system by identifiers, offered in 
a separated sub-menu.
+     * This list of reference system is fixed; items are not added or removed 
following user's selection.
+     */
+    final void addReferencingByIdentifiers() {
+        final Locale locale = action.owner().locale;
+        final GazetteerFactory factory = new GazetteerFactory();
+        final Resources resources = Resources.forLocale(locale);
+        final Menu menu = new 
Menu(resources.getString(Resources.Keys.ReferenceByIdentifiers));
+        for (final String name : factory.getSupportedNames()) try {
+            final ReferenceSystem system = factory.forName(name);
+            final MenuItem item = new 
MenuItem(IdentifiedObjects.getDisplayName(system, locale));
+            item.getProperties().put(REFERENCE_SYSTEM_KEY, system);
             item.setOnAction(this);
-            return item;
+            menu.getItems().add(item);
+        } catch (GazetteerException e) {
+            RecentReferenceSystems.errorOccurred("createMenuItems", e);
         }
+        menus.add(menu);
     }
 
     /**
@@ -153,15 +188,20 @@ final class MenuSync extends 
SimpleObjectProperty<ReferenceSystem> implements Ev
      * Invoked when the list of reference systems changed. While it would be 
possible to trace the permutations,
      * additions, removals and replacements done on the list, it is easier to 
recreate the menu items list from
      * scratch (with recycling of existing items) and inspect the differences.
+     *
+     * @see RecentReferenceSystems#notifyChanges()
      */
-    final void notifyChanges(final ObservableList<? extends ReferenceSystem> 
systems) {
+    final void notifyChanges() {
         /*
          * Build a map of current menu items. Key are CRS objects.
          */
+        final var subMenus = new ArrayList<Menu>();
         final Map<Object,MenuItem> mapping = new IdentityHashMap<>();
         for (final Iterator<MenuItem> it = menus.iterator(); it.hasNext();) {
             final MenuItem item = it.next();
-            if 
(mapping.putIfAbsent(item.getProperties().get(REFERENCE_SYSTEM_KEY), item) != 
null) {
+            if (item instanceof Menu) {
+                subMenus.add((Menu) item);
+            } else if 
(mapping.putIfAbsent(item.getProperties().get(REFERENCE_SYSTEM_KEY), item) != 
null) {
                 it.remove();    // Remove duplicated item. Should never 
happen, but we are paranoiac.
                 dispose(item);
             }
@@ -171,8 +211,9 @@ final class MenuSync extends 
SimpleObjectProperty<ReferenceSystem> implements Ev
          * Other menu items are left to null for now; those null values may 
appear anywhere in the array. After this
          * loop, the map will contain only menu items for CRS that are no 
longer in the list of CRS to offer.
          */
-        final MenuItem[] items = new MenuItem[systems.size()];
-        for (int i=0; i<items.length; i++) {
+        final int newCount = systems.size();
+        final MenuItem[] items = new MenuItem[newCount + subMenus.size()];
+        for (int i=0; i<newCount; i++) {
             Object key = systems.get(i);
             if (key == RecentReferenceSystems.OTHER) key = CHOOSER;
             items[i] = mapping.remove(key);
@@ -184,21 +225,21 @@ final class MenuSync extends 
SimpleObjectProperty<ReferenceSystem> implements Ev
          */
         ReferenceSystem selected = get();
         final Iterator<MenuItem> recycle = mapping.values().iterator();
-        for (int i=0; i<items.length; i++) {
+        final Locale locale = action.owner().locale;
+        for (int i=0; i<newCount; i++) {
             if (items[i] == null) {
-                MenuItem item;
+                MenuItem item = null;
                 final ReferenceSystem system = systems.get(i);
                 if (system != RecentReferenceSystems.OTHER && 
recycle.hasNext()) {
                     item = recycle.next();
                     recycle.remove();
                     if (item instanceof RadioMenuItem) {
-                        item.setText(IdentifiedObjects.getDisplayName(system, 
action.owner().locale));
+                        item.setText(IdentifiedObjects.getDisplayName(system, 
locale));
                         item.getProperties().put(REFERENCE_SYSTEM_KEY, system);
-                    } else {
-                        item = createItem(system);
                     }
-                } else {
-                    item = createItem(system);
+                }
+                if (item == null) {
+                    item = createItem(system, locale);
                 }
                 if (selected != null && system == selected) {
                     ((RadioMenuItem) item).setSelected(true);       // 
ClassCastException should never occur here.
@@ -209,26 +250,30 @@ final class MenuSync extends 
SimpleObjectProperty<ReferenceSystem> implements Ev
         }
         /*
          * If there is any item left, we must remove them from the ToggleGroup 
for avoiding memory leak.
+         * The sub-menus (if any) are appended last with no change.
          */
         while (recycle.hasNext()) {
             dispose(recycle.next());
         }
+        for (int i=newCount; i<items.length; i++) {
+            items[i] = subMenus.get(i - newCount);
+        }
         GUIUtilities.copyAsDiff(Arrays.asList(items), menus);
         /*
          * If we had no previously selected item, selects it now.
          */
         if (get() == null) {
-            initialize(systems);
+            initialize();
         }
     }
 
     /**
-     * Invoked when user selects a menu item. This method gets the old and new 
values and sends them
-     * to {@link 
org.apache.sis.gui.referencing.RecentReferenceSystems.Listener} as a change 
event.
-     * That {@code Listener} will update the list of reference systems, which 
may result in a callback
-     * to {@link #notifyChanges(ObservableList)}. If the selected menu item is 
the "Other…" choice,
-     * then {@code Listener} will popup {@link CRSChooser} and callback {@link 
#set(ReferenceSystem)}
-     * for storing the result. Otherwise we need to invoke  {@link 
#set(ReferenceSystem)} ourselves.
+     * Invoked when user selects a menu item. This method gets the old and new 
values and sends them to
+     * {@link 
org.apache.sis.gui.referencing.RecentReferenceSystems.SelectionListener} as a 
change event.
+     * That {@code SelectionListener} will update the list of reference 
systems, which may result
+     * in a callback to {@link #notifyChanges()}. If the selected menu item is 
the "Other…" choice,
+     * then {@code SelectionListener} will popup {@link CRSChooser} and 
callback {@link #set(ReferenceSystem)}
+     * for storing the result. Otherwise we need to invoke {@link 
#set(ReferenceSystem)} ourselves.
      */
     @Override
     public void handle(final ActionEvent event) {
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/ObjectStringConverter.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/ObjectStringConverter.java
index a829270107..3c0be25271 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/ObjectStringConverter.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/ObjectStringConverter.java
@@ -20,14 +20,14 @@ import java.util.Locale;
 import javafx.util.StringConverter;
 import org.opengis.referencing.IdentifiedObject;
 import org.apache.sis.referencing.IdentifiedObjects;
-import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.internal.gui.Resources;
 
 
 /**
  * Converts an {@link IdentifiedObject} to {@link String} representation to 
show in JavaFX control.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.3
  * @since   1.1
  * @module
  */
@@ -70,12 +70,20 @@ final class ObjectStringConverter<T extends 
IdentifiedObject> extends StringConv
             return IdentifiedObjects.getDisplayName(object, locale);
         } else {
             if (other == null) {
-                other = 
Vocabulary.getResources(locale).getString(Vocabulary.Keys.Others) + '…';
+                other = other(locale);
             }
             return other;
         }
     }
 
+    /**
+     * Returns the localized "Other…" text to use for selecting a CRS
+     * which is not in the short list of proposed CRS.
+     */
+    static String other(final Locale locale) {
+        return Resources.forLocale(locale).getString(Resources.Keys.OtherCRS) 
+ '…';
+    }
+
     /**
      * Returns the object for the given name.
      *
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java
index 41124f6201..04be43ee97 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java
@@ -42,6 +42,8 @@ import org.apache.sis.geometry.ImmutableEnvelope;
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
 import org.apache.sis.referencing.factory.IdentifiedObjectFinder;
+import org.apache.sis.referencing.gazetteer.GazetteerException;
+import org.apache.sis.referencing.gazetteer.GazetteerFactory;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.ComparisonMode;
@@ -166,12 +168,23 @@ public class RecentReferenceSystems {
      */
     private static final class Unverified {
         /** The reference system to verify. */
-        final ReferenceSystem system;
+        private final ReferenceSystem system;
 
         /** Flags the given reference system as unverified. */
         Unverified(final ReferenceSystem system) {
             this.system = system;
         }
+
+        /** Returns the verified (if possible) reference system. */
+        ReferenceSystem find(final IdentifiedObjectFinder finder) throws 
FactoryException {
+            if (finder != null) {
+                final IdentifiedObject replacement = 
finder.findSingleton(system);
+                if (replacement instanceof ReferenceSystem) {
+                    return (ReferenceSystem) replacement;
+                }
+            }
+            return system;
+        }
     }
 
     /**
@@ -194,16 +207,39 @@ public class RecentReferenceSystems {
      * instances and duplicated values removed. This is the list given to 
JavaFX controls that we build.
      * This list includes {@link #OTHER} as its last item.
      *
-     * @see #updateItems()
+     * <p>This list is initially null and created only when first needed. 
After the list has been created,
+     * this reference is never modified. As long as the reference is null, we 
can skip the synchronization
+     * of this list content with the {@link #systemsOrCodes} content when the 
latter changed. Because that
+     * synchronization may involve accesses to the EPSG database, it is 
potentially costly.</p>
+     *
+     * @see #getReferenceSystems(boolean)
      */
     private ObservableList<ReferenceSystem> referenceSystems;
 
+    /**
+     * A view of {@link #referenceSystems} with only items that are instances 
of {@link CoordinateReferenceSystem}.
+     * This list includes also {@link #OTHER} as its last item. This list is 
used for menus shown in contexts where
+     * identifiers can not be used, for example for selecting the CRS to use 
for displaying a map.
+     *
+     * <p>This list is lazily created when first needed,
+     * because it depends on {@link #referenceSystems} which is itself lazily 
created.</p>
+     *
+     * @see #getReferenceSystems(boolean)
+     */
+    private ObservableList<ReferenceSystem> coordinateReferenceSystems;
+
     /**
      * A filtered view of {@link #referenceSystems} without the {@link #OTHER} 
item.
+     * This is the list returned to users by public API, but otherwise it is 
not used by this class.
+     * Instead, the lists used internally by this class contains the {@link 
#OTHER} item because
+     * those lists are used directly by controls like {@code 
ChoiceBox<ReferenceSystem>}.
+     *
+     * <p>This list is lazily created when first needed,
+     * because it depends on {@link #referenceSystems} which is itself lazily 
created.</p>
      *
      * @see #getItems()
      */
-    private ObservableList<ReferenceSystem> filteredSystems;
+    private ObservableList<ReferenceSystem> publicItemList;
 
     /**
      * {@code true} if the {@link #referenceSystems} list needs to be rebuilt 
from {@link #systemsOrCodes} content.
@@ -215,7 +251,8 @@ public class RecentReferenceSystems {
 
     /**
      * {@code true} if {@code RecentReferenceSystems} is in the process of 
modifying {@link #referenceSystems} list.
-     * In such case we want to temporarily disable the {@link Listener}. This 
field is read and updated in JavaFX thread.
+     * In such case we want to temporarily disable the {@link 
SelectionListener}.
+     * This field is read and updated in JavaFX thread.
      */
     private boolean isAdjusting;
 
@@ -261,12 +298,16 @@ public class RecentReferenceSystems {
      * @since 1.3
      */
     public void configure(final GridGeometry gg) {
+        Envelope aoi = null;
         if (gg != null) {
-            areaOfInterest.set(gg.isDefined(GridGeometry.ENVELOPE) ? 
gg.getEnvelope() : null);
+            if (gg.isDefined(GridGeometry.ENVELOPE)) {
+                aoi = gg.getEnvelope();
+            }
             if (gg.isDefined(GridGeometry.CRS)) {
                 setPreferred(true, gg.getCoordinateReferenceSystem());
             }
         }
+        areaOfInterest.set(aoi);
     }
 
     /**
@@ -382,7 +423,7 @@ public class RecentReferenceSystems {
 
     /**
      * Adds the coordinate reference systems saved in user preferences. The 
user preferences are determined
-     * from the reference systems observed during current execution or 
previous execution of JavaFX application.
+     * from the reference systems observed during current execution or 
previous executions of JavaFX application.
      * If an {@linkplain #areaOfInterest area of interest} (AOI) is specified,
      * then reference systems that do not intersect the AOI will be ignored.
      */
@@ -402,6 +443,8 @@ public class RecentReferenceSystems {
      * Filters the {@link #systemsOrCodes} list by making sure that it 
contains only {@link ReferenceSystem} instances.
      * Authority codes are resolved if possible or removed if they can not be 
resolved. Unverified CRSs are compared
      * with authoritative definitions and replaced when a match is found. 
Duplications are removed.
+     * Finally reference systems with a domain of validity outside the {@link 
#geographicAOI} are omitted
+     * from the returned list (but not removed from the original {@link 
#systemsOrCodes} list).
      *
      * <p>This method can be invoked from any thread. In practice, it is 
invoked from a background thread.</p>
      *
@@ -411,6 +454,7 @@ public class RecentReferenceSystems {
      */
     private List<ReferenceSystem> filterReferenceSystems(final 
ImmutableEnvelope domain, final ComparisonMode mode) {
         final List<ReferenceSystem> systems;
+        final GazetteerFactory gf = new GazetteerFactory();     // Cheap to 
construct.
         synchronized (systemsOrCodes) {
             CRSAuthorityFactory factory = this.factory;         // Hide 
volatile field by local field.
             if (!isModified) {
@@ -419,54 +463,57 @@ public class RecentReferenceSystems {
             boolean noFactoryFound = false;
             boolean searchedFinder = false;
             IdentifiedObjectFinder finder = null;
-            for (int i=systemsOrCodes.size(); --i >= 0;) try {
+            for (int i=systemsOrCodes.size(); --i >= 0;) {
                 final Object item = systemsOrCodes.get(i);
-                if (item == OTHER) {
-                    systemsOrCodes.remove(i);
-                } else if (item instanceof String) {
-                    /*
-                     * The current list element is an authority code such as 
"EPSG::4326".
-                     * Replace that code by the full 
`CoordinateReferenceSystem` instance.
-                     * Note that authority factories are optional, so it is 
okay if we can
-                     * not resolve the code. In such case the item will be 
removed.
-                     */
-                    if (!noFactoryFound) {
-                        if (factory == null) {
-                            factory = Utils.getDefaultFactory();
-                        }
-                        systemsOrCodes.set(i, 
factory.createCoordinateReferenceSystem((String) item));
-                    } else {
-                        systemsOrCodes.remove(i);
-                    }
-                } else if (item instanceof Unverified) {
-                    /*
-                     * The current list element is a `ReferenceSystem` 
instance but maybe not
-                     * conform to authoritative definition, for example 
regarding axis order.
-                     * If we can find an authoritative definition, do the 
replacement.
-                     * If this operation can not be done, accept the reference 
system as-is.
-                     */
-                    if (!searchedFinder) {
-                        searchedFinder = true;                              // 
Set now in case an exception is thrown.
-                        if (factory instanceof GeodeticAuthorityFactory) {
-                            finder = ((GeodeticAuthorityFactory) 
factory).newIdentifiedObjectFinder();
-                        } else {
-                            finder = IdentifiedObjects.newFinder(null);
+                if (item instanceof ReferenceSystem) {
+                    continue;
+                }
+                ReferenceSystem system = null;
+                if (item != OTHER) try {
+                    if (item instanceof String) {
+                        /*
+                         * The current list element is an authority code such 
as "EPSG::4326".
+                         * Replace that code by the full 
`CoordinateReferenceSystem` instance.
+                         * Note that authority factories are optional, so it 
is okay if we can
+                         * not resolve the code. In such case the item will be 
removed.
+                         */
+                        system = gf.forNameIfKnown((String) item).orElse(null);
+                        if (system == null && !noFactoryFound) {
+                            if (factory == null) {
+                                factory = Utils.getDefaultFactory();
+                            }
+                            system = 
factory.createCoordinateReferenceSystem((String) item);
                         }
-                        finder.setIgnoringAxes(true);
-                    }
-                    ReferenceSystem system = ((Unverified) item).system;
-                    if (finder != null) {
-                        final IdentifiedObject replacement = 
finder.findSingleton(system);
-                        if (replacement instanceof ReferenceSystem) {
-                            system = (ReferenceSystem) replacement;
+                    } else if (item instanceof Unverified) {
+                        /*
+                         * The current list element is a `ReferenceSystem` 
instance but maybe not
+                         * conform to authoritative definition, for example 
regarding axis order.
+                         * If we can find an authoritative definition, do the 
replacement.
+                         * If this operation can not be done, accept the 
reference system as-is.
+                         */
+                        if (!searchedFinder) {
+                            searchedFinder = true;          // Set now in case 
an exception is thrown.
+                            if (factory instanceof GeodeticAuthorityFactory) {
+                                finder = ((GeodeticAuthorityFactory) 
factory).newIdentifiedObjectFinder();
+                            } else {
+                                finder = IdentifiedObjects.newFinder(null);
+                            }
+                            finder.setIgnoringAxes(true);
                         }
+                        system = ((Unverified) item).find(finder);
                     }
+                } catch (FactoryException e) {
+                    errorOccurred(e);
+                    noFactoryFound = (factory == null);
+                } catch (GazetteerException e) {
+                    errorOccurred("getReferenceSystems", e);
+                    // Note: `getReferenceSystems(…)` is indirectly the caller 
of this method.
+                }
+                if (system != null) {
                     systemsOrCodes.set(i, system);
+                } else {
+                    systemsOrCodes.remove(i);
                 }
-            } catch (FactoryException e) {
-                errorOccurred(e);
-                systemsOrCodes.remove(i);
-                noFactoryFound = (factory == null);
             }
             /*
              * Search for duplicated values after we finished filtering. This 
block is inefficient
@@ -499,7 +546,7 @@ public class RecentReferenceSystems {
              * in a separated list as a protection against changes in 
`systemsOrCodes` list that
              * could happen after this method returned, and also for retaining 
only the reference
              * systems that are valid in the area of interest. We do not 
remove "invalid" CRS
-             * because they would become valid later if the area of interest 
changes.
+             * because they may become valid later if the area of interest 
changes.
              */
             final int n = systemsOrCodes.size();
             systems = new ArrayList<>(Math.min(NUM_SHOWN_ITEMS, n) + 
NUM_OTHER_ITEMS);
@@ -531,7 +578,7 @@ public class RecentReferenceSystems {
             isModified = true;
             if (referenceSystems != null) {
                 // ChoiceBox or Menu already created. They will observe the 
changes in item list.
-                updateItems();
+                getReferenceSystems(false);
             }
         }
     }
@@ -541,10 +588,11 @@ public class RecentReferenceSystems {
      * The new items may not be added immediately; instead the CRS will be 
processed in background thread
      * and copied to the {@link #referenceSystems} list when ready.
      *
+     * @param  filtered  whether to filter the list for retaining only {@link 
CoordinateReferenceSystem} instances.
      * @return the list of items. May be empty on return and filled later.
      */
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    private ObservableList<ReferenceSystem> updateItems() {
+    private ObservableList<ReferenceSystem> getReferenceSystems(final boolean 
filtered) {
         if (referenceSystems == null) {
             referenceSystems = FXCollections.observableArrayList();
         }
@@ -589,9 +637,23 @@ public class RecentReferenceSystems {
                 }
             });
         }
+        if (filtered) {
+            if (coordinateReferenceSystems == null) {
+                coordinateReferenceSystems = new 
FilteredList<>(referenceSystems, RecentReferenceSystems::isCRS);
+            }
+            return coordinateReferenceSystems;
+        }
         return referenceSystems;
     }
 
+    /**
+     * Returns {@code true} if the given reference system can be included
+     * in the {@link #coordinateReferenceSystems} list.
+     */
+    private static boolean isCRS(final ReferenceSystem system) {
+        return (system == OTHER) || (system instanceof 
CoordinateReferenceSystem);
+    }
+
     /**
      * Sets the reference systems to the given content. The given list is 
often similar to current content,
      * for example with only a reference system that moved to a different 
index. This method compares the
@@ -648,12 +710,12 @@ public class RecentReferenceSystems {
      * and the selected reference system is added to the list of choices. If 
the selected CRS is different than
      * the previous one, then {@link RecentChoices} is notified and the 
user-specified listener is notified.
      */
-    final class Listener implements ChangeListener<ReferenceSystem> {
+    final class SelectionListener implements ChangeListener<ReferenceSystem> {
         /** The user-specified action to execute when a reference system is 
selected. */
         private final ChangeListener<ReferenceSystem> action;
 
         /** Creates a new listener of reference system selection. */
-        private Listener(final ChangeListener<ReferenceSystem> action) {
+        private SelectionListener(final ChangeListener<ReferenceSystem> 
action) {
             this.action = action;
         }
 
@@ -771,7 +833,7 @@ public class RecentReferenceSystems {
     private void notifyChanges() {
         for (final WritableValue<ReferenceSystem> value : controlValues) {
             if (value instanceof MenuSync) {
-                ((MenuSync) value).notifyChanges(referenceSystems);
+                ((MenuSync) value).notifyChanges();
             }
         }
     }
@@ -785,10 +847,10 @@ public class RecentReferenceSystems {
      */
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
     public ObservableList<ReferenceSystem> getItems() {
-        if (filteredSystems == null) {
-            filteredSystems = new FilteredList<>(updateItems(), 
Objects::nonNull);
+        if (publicItemList == null) {
+            publicItemList = new FilteredList<>(getReferenceSystems(false), 
Objects::nonNull);
         }
-        return filteredSystems;
+        return publicItemList;
     }
 
     /**
@@ -854,15 +916,27 @@ next:       for (int i=0; i<count; i++) {
      * The returned control may be initially empty, in which case its content 
will be automatically set at
      * a later time (after a background thread finished to process the {@link 
CoordinateReferenceSystem}s).
      *
-     * @param  action  the action to execute when a reference system is 
selected.
+     * <p>If the {@code filtered} argument is {@code true}, then the choice 
box will contain only reference systems
+     * that can be used for rendering purposes. That filtered list can contain 
{@link CoordinateReferenceSystem}
+     * instances but not reference systems by identifiers such as {@linkplain 
MilitaryGridReferenceSystem MGRS}.
+     * The latter are usable only for the purposes of formatting coordinate 
values as texts.</p>
+     *
+     * <h4>Limitations</h4>
+     * There is currently no mechanism for disposing the returned control. For 
garbage collecting the
+     * returned {@code ChoiceBox}, this {@code RecentReferenceSystems} must be 
garbage-collected as well.
+     *
+     * @param  filtered  whether the choice box should contain only {@link 
CoordinateReferenceSystem} instances.
+     * @param  action    the action to execute when a reference system is 
selected.
      * @return a choice box with reference systems specified by {@code 
setPreferred(…)}
      *         and {@code addAlternatives(…)} methods.
+     *
+     * @since 1.3
      */
-    public ChoiceBox<ReferenceSystem> createChoiceBox(final 
ChangeListener<ReferenceSystem> action) {
+    public ChoiceBox<ReferenceSystem> createChoiceBox(final boolean filtered, 
final ChangeListener<ReferenceSystem> action) {
         ArgumentChecks.ensureNonNull("action", action);
-        final ChoiceBox<ReferenceSystem> choices = new 
ChoiceBox<>(updateItems());
+        final ChoiceBox<ReferenceSystem> choices = new 
ChoiceBox<>(getReferenceSystems(filtered));
         choices.setConverter(new ObjectStringConverter<>(choices.getItems(), 
locale));
-        choices.valueProperty().addListener(new Listener(action));
+        choices.valueProperty().addListener(new SelectionListener(action));
         controlValues.add(choices.valueProperty());
         return choices;
     }
@@ -872,18 +946,49 @@ next:       for (int i=0; i<count; i++) {
      * The items will be inserted in the {@linkplain Menu#getItems() menu 
list}. The content of that list will
      * change at any time after this method returned: items will be added or 
removed as a result of user actions.
      *
-     * @param  action  the action to execute when a reference system is 
selected.
+     * <p>If the {@code filtered} argument is {@code true}, then the menu 
items will contain only reference systems
+     * that can be used for rendering purposes. That filtered list can contain 
{@link CoordinateReferenceSystem}
+     * instances but not reference systems by identifiers such as {@linkplain 
MilitaryGridReferenceSystem MGRS}.
+     * The latter are usable only for the purposes of formatting coordinate 
values as texts.</p>
+     *
+     * <h4>Limitations</h4>
+     * There is currently no mechanism for disposing the returned control. For 
garbage collecting the
+     * returned {@code Menu}, this {@code RecentReferenceSystems} must be 
garbage-collected as well.
+     *
+     * @param  filtered  whether the menu should contain only {@link 
CoordinateReferenceSystem} instances.
+     * @param  action    the action to execute when a reference system is 
selected.
      * @return the menu containing items for reference systems.
+     *
+     * @since 1.3
      */
-    public Menu createMenuItems(final ChangeListener<ReferenceSystem> action) {
+    public Menu createMenuItems(final boolean filtered, final 
ChangeListener<ReferenceSystem> action) {
         ArgumentChecks.ensureNonNull("action", action);
         final Menu menu = new 
Menu(Vocabulary.getResources(locale).getString(Vocabulary.Keys.ReferenceSystem));
-        final MenuSync property = new MenuSync(updateItems(), menu, new 
Listener(action));
+        final MenuSync property = new MenuSync(getReferenceSystems(filtered), 
menu, new SelectionListener(action));
+        if (!filtered) {
+            property.addReferencingByIdentifiers();
+        }
         menu.getProperties().put(SELECTED_ITEM_KEY, property);
         controlValues.add(property);
         return menu;
     }
 
+    /**
+     * @deprecated Replaced by {@link #createChoiceBox(boolean, 
ChangeListener)}.
+     */
+    @Deprecated
+    public ChoiceBox<ReferenceSystem> createChoiceBox(final 
ChangeListener<ReferenceSystem> action) {
+        return createChoiceBox(true, action);
+    }
+
+    /**
+     * @deprecated Replaced by {@link #createMenuItems(boolean, 
ChangeListener)}.
+     */
+    @Deprecated
+    public Menu createMenuItems(final ChangeListener<ReferenceSystem> action) {
+        return createMenuItems(true, action);
+    }
+
     /**
      * Returns the property for the selected value in a menu created by {@link 
#createMenuItems(ChangeListener)}.
      *
@@ -917,6 +1022,17 @@ next:       for (int i=0; i<count; i++) {
      */
     protected void errorOccurred(final FactoryException e) {
         OptionalDataDownloader.reportIfInstalling(e);
-        Logging.recoverableException(getLogger(Modules.APPLICATION), 
RecentReferenceSystems.class, "updateItems", e);
+        Logging.recoverableException(getLogger(Modules.APPLICATION), 
RecentReferenceSystems.class, "getReferenceSystems", e);
+    }
+
+    /**
+     * Invoked when an error occurred while fetching a reference system be 
identifier.
+     * This is the complement of {@link #errorOccurred(FactoryException)} but 
for referencing by identifiers.
+     *
+     * @param  caller  the method to report as the source the in log record.
+     * @param  e  the error that occurred.
+     */
+    static void errorOccurred(final String caller, final GazetteerException e) 
{
+        Logging.recoverableException(getLogger(Modules.APPLICATION), 
RecentReferenceSystems.class, caller, e);
     }
 }
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 5877c7c3a3..d1f17be4f3 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
@@ -332,6 +332,11 @@ public final class Resources extends IndexedResourceBundle 
{
          */
         public static final short Orthographic = 52;
 
+        /**
+         * Other coordinate reference system…
+         */
+        public static final short OtherCRS = 72;
+
         /**
          * Property value
          */
@@ -342,6 +347,11 @@ public final class Resources extends IndexedResourceBundle 
{
          */
         public static final short RangeOfValues = 56;
 
+        /**
+         * Reference system by identifiers
+         */
+        public static final short ReferenceByIdentifiers = 73;
+
         /**
          * Select a coordinate reference system
          */
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 fb4077b072..6da5d3e06e 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
@@ -75,8 +75,10 @@ OpenContainingFolder   = Open containing folder
 OpenDataFile           = Open data file
 OpenRecentFile         = Open recent file
 Orthographic           = Orthographic
+OtherCRS               = Other coordinate reference system\u2026
 PropertyValue          = Property value
 RangeOfValues          = Range of values\u2026
+ReferenceByIdentifiers = Reference system by identifiers
 SelectCRS              = Select a coordinate reference system
 SelectCrsByContextMenu = For changing the projection, use contextual menu on 
the map.
 SelectParentLogger     = Select parent logger
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 678d1a296e..dc879df892 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
@@ -80,8 +80,10 @@ OpenContainingFolder   = Ouvrir le dossier contenant
 OpenDataFile           = Ouvrir un fichier de donn\u00e9es
 OpenRecentFile         = Ouvrir un fichier r\u00e9cent
 Orthographic           = Orthographique
+OtherCRS               = Autre syst\u00e8me de r\u00e9f\u00e9rence par 
coordonn\u00e9es\u2026
 PropertyValue          = Valeur de la propri\u00e9t\u00e9
 RangeOfValues          = Plage de valeurs\u2026
+ReferenceByIdentifiers = Syst\u00e8me de r\u00e9f\u00e9rence par identifiants
 SelectCRS              = Choisir un syst\u00e8me de r\u00e9f\u00e9rence des 
coordonn\u00e9es
 SelectCrsByContextMenu = Pour changer la projection, utilisez le menu 
contextuel sur la carte.
 SelectParentLogger     = Choisir le journal parent

Reply via email to