You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2022/06/27 15:02:56 UTC

[sis] 04/04: 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.

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 <ma...@geomatys.com>
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