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

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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new c114f0db6b Try harder to restrict the CRS choices of a 
`CoverageCanvas` to valid CRS. We use the datum as a quick filter. We also need 
to ensure that the canvas is initialized with the engineering CRS of the 
coverage, not of the canvas, when the coverage has no geodetic CRS.
c114f0db6b is described below

commit c114f0db6bf031e424bc52a1738727d8d440e98b
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Thu Apr 30 18:40:04 2026 +0200

    Try harder to restrict the CRS choices of a `CoverageCanvas` to valid CRS.
    We use the datum as a quick filter. We also need to ensure that the canvas
    is initialized with the engineering CRS of the coverage, not of the canvas,
    when the coverage has no geodetic CRS.
---
 .../apache/sis/gui/coverage/CoverageCanvas.java    |  51 +-
 .../apache/sis/gui/coverage/CoverageExplorer.java  |   2 +-
 .../org/apache/sis/gui/coverage/ImageRequest.java  |   2 +-
 .../main/org/apache/sis/gui/map/MapCanvas.java     |   2 +-
 .../apache/sis/gui/referencing/AuthorityCodes.java |  17 +-
 .../org/apache/sis/gui/referencing/CRSChooser.java |  34 +-
 .../apache/sis/gui/referencing/FilterByDatum.java  | 182 ++++++
 .../org/apache/sis/gui/referencing/MenuSync.java   |  29 +-
 .../gui/referencing/RecentReferenceSystems.java    | 642 ++++++++++-----------
 .../org/apache/sis/gui/referencing/Unverified.java |  60 ++
 10 files changed, 639 insertions(+), 382 deletions(-)

diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
index 3544b294fb..3571b97a07 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
@@ -58,9 +58,11 @@ import javax.measure.Quantity;
 import javax.measure.quantity.Length;
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
+import org.opengis.util.FactoryException;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.referencing.ImmutableIdentifier;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.referencing.operation.transform.LinearTransform;
 import org.apache.sis.referencing.operation.matrix.AffineTransforms2D;
@@ -599,17 +601,17 @@ public class CoverageCanvas extends MapCanvasAWT {
         final GridCoverageResource resource;
         final GridCoverage coverage;
         final GridExtent sliceExtent;
-        final GridGeometry zoom;
+        final GridGeometry visibleArea;
         if (request != null) {
             resource    = request.resource;
             coverage    = request.coverage;
             sliceExtent = request.slice;
-            zoom        = request.zoom;
+            visibleArea = request.visibleArea;
         } else {
             resource    = null;
             coverage    = null;
             sliceExtent = null;
-            zoom        = null;
+            visibleArea = null;
         }
         final GridCoverageResource discard = getResource();
         if (discard != resource || getCoverage() != coverage || 
getSliceExtent() != sliceExtent) {
@@ -622,7 +624,7 @@ public class CoverageCanvas extends MapCanvasAWT {
             } finally {
                 isCoverageAdjusting = p;
             }
-            onPropertySpecified(discard, resource, coverage, null, zoom);
+            onPropertySpecified(discard, resource, coverage, null, 
visibleArea);
         }
     }
 
@@ -632,18 +634,21 @@ public class CoverageCanvas extends MapCanvasAWT {
      * Those information will be used for initializing "objective CRS" and 
"objective to display" to new values.
      * Rendering will happen in another background computation.
      *
-     * @param  discard   the old resource, or {@code null} if none.
-     * @param  resource  the new resource, or {@code null} if none.
-     * @param  coverage  the new coverage, or {@code null} if none.
-     * @param  toClear   the property which is an alternative to the property 
that has been set.
-     * @param  zoom      initial "objective to display" transform to use, or 
{@code null} for automatic.
+     * <p>The {@code visibleArea} argument is used when we want to create a 
new canvas
+     * initialized to the same viewing region and zoom level than an existing 
canvas.</p>
+     *
+     * @param  discard      the old resource, or {@code null} if none.
+     * @param  resource     the new resource, or {@code null} if none.
+     * @param  coverage     the new coverage, or {@code null} if none.
+     * @param  toClear      the property which is an alternative to the 
property that has been set.
+     * @param  visibleArea  initial "objective to display" transform to use, 
or {@code null} for automatic.
      */
     private void onPropertySpecified(
             final GridCoverageResource discard,
             final GridCoverageResource resource,
             final GridCoverage         coverage,
             final ObjectProperty<?>    toClear,
-            final GridGeometry         zoom)
+            final GridGeometry         visibleArea)
     {
         hasCoverageOrResource = (resource != null || coverage != null);
         if (isCoverageAdjusting) {
@@ -750,7 +755,7 @@ public class CoverageCanvas extends MapCanvasAWT {
                 @Override protected void succeeded() {
                     runAfterRendering(() -> {
                         try {
-                            setNewSource(getValue(), ranges, zoom);
+                            setNewSource(getValue(), ranges, visibleArea);
                             requestRepaint();                   // Cause 
`Worker` class to be executed.
                         } catch (RuntimeException ex) {         // Mostly for 
`BackingStoreException`.
                             clear();
@@ -798,14 +803,18 @@ public class CoverageCanvas extends MapCanvasAWT {
      * Caller should invoke {@link #requestRepaint()} after this method
      * for loading and resampling the image in a background thread.
      *
+     * <p>The {@code visibleArea} argument is used when we want to create a 
new canvas
+     * initialized to the same viewing region and zoom level than an existing 
canvas.
+     * It should have a <abbr>CRS</abbr> compatible with the one of the data 
to show.</p>
+     *
      * <p>All arguments can be {@code null} for clearing the canvas.
      * This method is invoked in JavaFX thread.</p>
      *
-     * @param  domain  the multi-dimensional grid geometry, or {@code null} if 
there is no data.
-     * @param  ranges  descriptions of bands, or {@code null} if there is no 
data.
-     * @param  zoom    initial "objective to display" transform to use, or 
{@code null} for automatic.
+     * @param  domain       the multi-dimensional grid geometry, or {@code 
null} if there is no data.
+     * @param  ranges       descriptions of bands, or {@code null} if there is 
no data.
+     * @param  visibleArea  initial "objective to display" transform to use, 
or {@code null} for automatic.
      */
-    private void setNewSource(GridGeometry domain, final List<SampleDimension> 
ranges, final GridGeometry zoom) {
+    private void setNewSource(GridGeometry domain, final List<SampleDimension> 
ranges, final GridGeometry visibleArea) {
         if (TRACE) {
             trace("setNewSource(…): the new domain of data is:%n\t%s", domain);
         }
@@ -837,16 +846,26 @@ public class CoverageCanvas extends MapCanvasAWT {
         }
         /*
          * Notify the `RenderingData` and `MapCanvas`. All information below 
must be two-dimensional.
+         * The objective CRS is set indirectly through the envelope. 
Therefore, we should try hard to
+         * provide a CRS compatible with the coverage.
          */
         Envelope bounds = null;
         if (domain != null) {
             domain = domain.selectDimensions(xyDimensions);
             if (domain.isDefined(GridGeometry.ENVELOPE)) {
                 bounds = domain.getEnvelope();
+                if (bounds.getCoordinateReferenceSystem() == null) try {
+                    final var name = new ImmutableIdentifier(null, null, "Cell 
indices");
+                    final var copy = new GeneralEnvelope(bounds);
+                    
copy.setCoordinateReferenceSystem(domain.createGridCRS(name, 
PixelInCell.CELL_CORNER));
+                    bounds = copy;
+                } catch (FactoryException e) {
+                    unexpectedException(e);
+                }
             }
         }
         data.setImageSpace(domain, ranges, xyDimensions);
-        initialize(zoom);
+        initialize(visibleArea);
         setObjectiveBounds(bounds);
     }
 
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageExplorer.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageExplorer.java
index 948387bf01..f400d012e8 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageExplorer.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageExplorer.java
@@ -657,7 +657,7 @@ public class CoverageExplorer extends Widget {
             final ImageRequest request = new ImageRequest(resource, coverage, 
null);
             final CoverageControls c = (CoverageControls) 
views.get(View.IMAGE);
             if (c != null) try {
-                request.zoom = c.view.getGridGeometry();
+                request.visibleArea = c.view.getGridGeometry();
             } catch (RenderException e) {
                 CoverageCanvas.unexpectedException("getGridGeometry", e);
             }
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/ImageRequest.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/ImageRequest.java
index f0153d4750..608bd0c55e 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/ImageRequest.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/ImageRequest.java
@@ -82,7 +82,7 @@ public class ImageRequest {
      * This is used only if we want to create a new canvas initialized to the 
same viewing region and zoom
      * level than an existing canvas.
      */
-    GridGeometry zoom;
+    GridGeometry visibleArea;
 
     /**
      * Creates a new request with both a resource and a coverage. At least one 
argument shall be non-null.
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/MapCanvas.java 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/MapCanvas.java
index 177e7ab95d..5dbe9dfed7 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/MapCanvas.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/MapCanvas.java
@@ -413,7 +413,7 @@ public abstract class MapCanvas extends PlanarCanvas {
      * to discard any zoom or translation and reset the view to the given 
bounds. This method does not
      * cause new repaint event; {@link #requestRepaint()} must be invoked by 
the caller if desired.</p>
      *
-     * @param  visibleArea  bounding box, objective CRS and or initial zoom 
level,
+     * @param  visibleArea  bounding box, objective <abbr>CRS</abbr> and or 
initial zoom level,
      *         or {@code null} if unknown (in which case an identity transform 
will be set).
      * @throws MismatchedDimensionException if the given grid geometry is not 
two-dimensional.
      *
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/AuthorityCodes.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/AuthorityCodes.java
index d0c0bc0062..3c6b0b6663 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/AuthorityCodes.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/AuthorityCodes.java
@@ -52,11 +52,6 @@ import static org.apache.sis.gui.internal.LogHandler.LOGGER;
  * A list of authority codes (usually for <abbr>CRS</abbr>) for which to fetch 
code values in a background thread.
  * The <abbr>CRS</abbr> names are fetched only when needed.
  *
- * @todo {@link org.apache.sis.referencing.factory.sql.EPSGDataAccess} 
internally uses a {@link java.util.Map}
- *       from codes to descriptions. We could open an access to this map for a 
little bit more efficiency.
- *       It will be necessary if we want to use {@link AuthorityCodes} for 
other kinds of objects than CRS
- *       (see {@link #type} field).
- *
  * @author  Martin Desruisseaux (Geomatys)
  */
 final class AuthorityCodes extends ObservableListBase<Code>
@@ -77,11 +72,9 @@ final class AuthorityCodes extends ObservableListBase<Code>
     TableView<Code> owner;
 
     /**
-     * The type of object for which we want authority codes. Fixed to {@link 
CoordinateReferenceSystem} for now,
-     * but could be made configurable in a future version. Making this field 
configurable would require resolving
-     * the "todo" documented in class javadoc.
+     * The type of object for which we want authority codes.
      */
-    private static final Class<? extends IdentifiedObject> type = 
CoordinateReferenceSystem.class;
+    private final Class<? extends IdentifiedObject> type;
 
     /**
      * The authority codes obtained from the factory. The list elements are 
provided by a background thread.
@@ -132,11 +125,13 @@ final class AuthorityCodes extends 
ObservableListBase<Code>
      * capable to handle at least some EPSG codes will be used.
      *
      * @param  factory  the authority factory, or {@code null} for default 
factory.
+     * @param  filter   provides the base type of objects for which we want 
authority codes.
      * @param  locale   the preferred locale of CRS descriptions.
      */
-    AuthorityCodes(final CRSAuthorityFactory factory, final Locale locale) {
-        this.locale  = locale;
+    AuthorityCodes(final CRSAuthorityFactory factory, final FilterByDatum 
filter, final Locale locale) {
         this.factory = factory;
+        this.type    = (filter != null) ? filter.baseType() : 
CoordinateReferenceSystem.class;
+        this.locale  = locale;
         this.codes   = new ArrayList<>();
         this.loader  = new Loader();
         loader.start();
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/CRSChooser.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/CRSChooser.java
index 5c98a942b8..97de98e3d9 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/CRSChooser.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/CRSChooser.java
@@ -76,7 +76,7 @@ import org.opengis.referencing.crs.DerivedCRS;
 
 
 /**
- * A list of Coordinate Reference Systems (CRS) from which the user can select.
+ * A list of Coordinate Reference Systems (<abbr>CRS</abbr>) from which the 
user can select.
  * The CRS choices is built in a background thread from a specified {@link 
CRSAuthorityFactory}.
  *
  * @author  Johann Sorel (Geomatys)
@@ -137,29 +137,45 @@ public class CRSChooser extends 
Dialog<CoordinateReferenceSystem> {
     private WKTPane wktPane;
 
     /**
-     * Creates a chooser proposing all coordinate reference systems from the 
default factory.
+     * Creates a chooser proposing coordinate reference systems from the 
default factory.
      */
     public CRSChooser() {
-        this(null, null, null);
+        this(null, null, null, null);
     }
 
     /**
-     * Creates a chooser proposing all coordinate reference systems from the 
given factory.
-     * If the given factory is {@code null}, then a
-     * {@linkplain org.apache.sis.referencing.CRS#getAuthorityFactory(String) 
default factory}
-     * capable to handle at least some EPSG codes will be used.
+     * Creates a chooser proposing coordinate reference systems from the given 
factory.
+     * If the given factory is {@code null}, then a default factory capable to 
handle
+     * at least some <abbr>EPSG</abbr> codes will be used.
      *
      * @param  factory         the factory to use for creating coordinate 
reference systems, or {@code null} for default.
      * @param  areaOfInterest  geographic area for which to choose a CRS, or 
{@code null} if no restriction.
      * @param  locale          the preferred locale for displaying object 
name, or {@code null} for the default locale.
      */
-    @SuppressWarnings({"unchecked", "this-escape"})
     public CRSChooser(final CRSAuthorityFactory factory, final Envelope 
areaOfInterest, Locale locale) {
+        this(factory, null, areaOfInterest, locale);
+    }
+
+    /**
+     * Creates a chooser proposing identified objects from the given factory.
+     * If the given factory is {@code null}, then a default factory capable to 
handle
+     * at least some <abbr>EPSG</abbr> codes will be used.
+     *
+     * @param  factory         the factory to use for creating coordinate 
reference systems, or {@code null} for default.
+     * @param  filter          provides the base type of objects for which we 
want authority codes.
+     * @param  areaOfInterest  geographic area for which to choose a CRS, or 
{@code null} if no restriction.
+     * @param  locale          the preferred locale for displaying object 
name, or {@code null} for the default locale.
+     */
+    @SuppressWarnings({"unchecked", "this-escape"})
+    CRSChooser(final CRSAuthorityFactory factory,
+               final FilterByDatum filter,
+               final Envelope areaOfInterest, Locale locale)
+    {
         this.areaOfInterest = Utils.toGeographic(CRSChooser.class, "<init>", 
areaOfInterest);
         if (locale == null)  locale     = Locale.getDefault();
         final Resources      i18n       = Resources.forLocale(locale);
         final Vocabulary     vocabulary = Vocabulary.forLocale(locale);
-        final AuthorityCodes codeList   = new AuthorityCodes(factory, locale);
+        final AuthorityCodes codeList   = new AuthorityCodes(factory, filter, 
locale);
         table = new TableView<>(codeList);
         codeList.owner = table;
         /*
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/FilterByDatum.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/FilterByDatum.java
new file mode 100644
index 0000000000..625cf397cd
--- /dev/null
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/FilterByDatum.java
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.gui.referencing;
+
+import java.util.Set;
+import java.util.List;
+import java.util.HashSet;
+import java.util.ArrayList;
+import java.util.function.Predicate;
+import org.opengis.referencing.ReferenceSystem;
+import org.opengis.referencing.datum.Datum;
+import org.opengis.referencing.datum.GeodeticDatum;
+import org.opengis.referencing.datum.TemporalDatum;
+import org.opengis.referencing.datum.VerticalDatum;
+import org.opengis.referencing.datum.EngineeringDatum;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.crs.SingleCRS;
+import org.opengis.referencing.crs.TemporalCRS;
+import org.opengis.referencing.crs.VerticalCRS;
+import org.opengis.referencing.crs.EngineeringCRS;
+import org.apache.sis.util.Classes;
+import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.internal.shared.ReferencingUtilities;
+import org.apache.sis.util.Utilities;
+
+
+/**
+ * Filter of reference systems that are compatible with the data to render.
+ * Instances of this class are immutable and thread-safe.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+final class FilterByDatum implements Predicate<ReferenceSystem> {
+    /**
+     * The types of datum allowed by the coordinate reference systems shown in 
the menus of combo boxes.
+     * If a <abbr>CRS</abbr> has a datum of a type which is not in this array, 
then it will not be shown.
+     * A {@code null} array means that no filtering is applied.
+     *
+     * <p>The current version of this class filters only by datum type because 
this is quick.
+     * But a more advanced version should check if a path exists between 
<abbr>CRS</abbr>s.</p>
+     */
+    private final Class<? extends Datum>[] allowedDatumTypes;
+
+    /**
+     * Datum instances for which an exact match is required.
+     * We handle engineering datum in a special way because they are the kind 
of datum created
+     * when we cannot map the <abbr>CRS</abbr> of a grid coverage to a 
geodetic <abbr>CRS</abbr>.
+     * We no such pivot, we cannot transform from one engineering 
<abbr>CRS</abbr> to another.
+     */
+    private final EngineeringDatum[] instances;
+
+    /**
+     * Creates a new filter for coordinate reference systems to show in the 
menu or combo box.
+     * If the <abbr>CRS</abbr> of a menu item has a datum of a type which is 
different than the
+     * datum types of all elements in the {@code refsys} array, then that menu 
item is hidden.
+     *
+     * @param  refsys  <abbr>CRS</abbr>s from which to get the datum types. 
Null elements are ignored.
+     * @return filter, or {@code null} if none.
+     */
+    static FilterByDatum create(final CoordinateReferenceSystem[] refsys) {
+        final var types = new HashSet<Class<? extends Datum>>();
+        final var instances = new ArrayList<EngineeringDatum>();
+        for (final CoordinateReferenceSystem crs : refsys) {
+            for (final SingleCRS component : CRS.getSingleComponents(crs)) {
+                add(component.getDatum(), types, instances);
+                final var ensemble = component.getDatumEnsemble();
+                if (ensemble != null) {
+                    for (final Datum datum : ensemble.getMembers()) {
+                        add(datum, types, instances);
+                    }
+                }
+            }
+        }
+        types.remove(null);
+        if (types.isEmpty()) {
+            return null;
+        }
+        return new FilterByDatum(types.toArray(Class[]::new), 
instances.toArray(EngineeringDatum[]::new));
+    }
+
+    /**
+     * Adds the given datum in the given collections.
+     *
+     * @param datum      the datum to add.
+     * @param types      where to add the datum type.
+     * @param instances  where to add the datum instance if not a duplicate.
+     */
+    private static void add(final Datum datum, final Set<Class<? extends 
Datum>> types, final List<EngineeringDatum> instances) {
+        types.add(ReferencingUtilities.getInterface(Datum.class, datum));
+        if (datum instanceof EngineeringDatum) {
+            for (int i = instances.size(); --i >= 0;) {
+                if (Utilities.equalsIgnoreMetadata(datum, instances.get(i))) {
+                    return;
+                }
+            }
+            instances.add((EngineeringDatum) datum);
+        }
+    }
+
+    /**
+     * Creates a new filter for the given datum types.
+     */
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    private FilterByDatum(final Class[] types, final EngineeringDatum[] 
instances) {
+        allowedDatumTypes = types;
+        this.instances = instances;
+    }
+
+    /**
+     * Returns whether the given reference system has valid datum types.
+     *
+     * @param  system  the reference system to test.
+     * @return whether the given reference system can be accepted.
+     */
+    @Override
+    public boolean test(final ReferenceSystem system) {
+        if (system instanceof CoordinateReferenceSystem) {
+next:       for (final SingleCRS component : 
CRS.getSingleComponents((CoordinateReferenceSystem) system)) {
+                if (!accept(component.getDatum())) {
+                    final var ensemble = component.getDatumEnsemble();
+                    if (ensemble != null) {
+                        for (final Datum datum : ensemble.getMembers()) {
+                            if (accept(datum)) {
+                                continue next;
+                            }
+                        }
+                    }
+                    return false;
+                }
+            }
+            return true;
+        } else {
+            // Assume that referencing by identifiers depend on geodetic datum.
+            return Classes.isAssignableToAny(GeodeticDatum.class, 
allowedDatumTypes);
+        }
+    }
+
+    /**
+     * Tests whether the given datum is accepted.
+     */
+    private boolean accept(final Datum datum) {
+        if (datum != null && Classes.isAssignableToAny(datum.getClass(), 
allowedDatumTypes)) {
+            if (!(datum instanceof EngineeringDatum)) {
+                return true;
+            }
+            for (final EngineeringDatum instance : instances) {
+                if (Utilities.equalsIgnoreMetadata(datum, instance)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the base type of coordinate reference system for the datum 
accepted by this filter.
+     */
+    final Class<? extends CoordinateReferenceSystem> baseType() {
+        if (allowedDatumTypes.length == 1) {
+            final Class<? extends Datum> type = allowedDatumTypes[0];
+            if    (VerticalDatum.class.isAssignableFrom(type)) return 
VerticalCRS.class;
+            if    (TemporalDatum.class.isAssignableFrom(type)) return 
TemporalCRS.class;
+            if (EngineeringDatum.class.isAssignableFrom(type)) return 
EngineeringCRS.class;
+            return SingleCRS.class;
+        }
+        return CoordinateReferenceSystem.class;
+    }
+}
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/MenuSync.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/MenuSync.java
index 63c147bcf2..df64435a15 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/MenuSync.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/MenuSync.java
@@ -69,8 +69,8 @@ final class MenuSync extends 
SimpleObjectProperty<ReferenceSystem> implements Ev
     /**
      * The list of reference systems to show in the root menu, not including 
items in sub-menus.
      * This is the list of most recently used reference systems, so its 
content may change often.
-     * {@code MenuSync} does not register listeners on this list;
-     * if the content is changed, then {@link #notifyChanges()} should be 
invoked explicitly.
+     * {@code MenuSync} does not register listeners on this list. Instead, If 
the content is changed,
+     * {@link #notifyChanges()} should be invoked explicitly.
      */
     private final List<ReferenceSystem> recentSystems;
 
@@ -124,8 +124,10 @@ final class MenuSync extends 
SimpleObjectProperty<ReferenceSystem> implements Ev
      * @param  bean     the menu to keep synchronized with the list of 
reference systems.
      * @param  action   a wrapper over the user-specified action to execute 
when a reference system is selected.
      */
-    MenuSync(final List<ReferenceSystem> systems, final boolean byIds, final 
List<CoordinateReferenceSystem> derived,
-             final Menu bean, final RecentReferenceSystems.SelectionListener 
action)
+    MenuSync(final List<ReferenceSystem> systems, final boolean byIds,
+             final List<CoordinateReferenceSystem> derived,
+             final Menu bean,
+             final RecentReferenceSystems.SelectionListener action)
     {
         super(bean, "value");
         recentSystems      = systems;
@@ -275,7 +277,7 @@ final class MenuSync extends 
SimpleObjectProperty<ReferenceSystem> implements Ev
          */
         SeparatorMenuItem separator = null;
         final var subMenus = new ArrayList<Menu>(2);
-        final var mapping  = new IdentityHashMap<Object,MenuItem>(10);
+        final var mapping  = new IdentityHashMap<Object, MenuItem>(10);
         for (final Iterator<MenuItem> it = rootMenus.iterator(); 
it.hasNext();) {
             final MenuItem item = it.next();
             switch (item) {
@@ -291,7 +293,7 @@ final class MenuSync extends 
SimpleObjectProperty<ReferenceSystem> implements Ev
         }
         /*
          * Prepare a list of menu items and assign a value to all elements 
where the menu item can be reused as-is.
-         * Other menu items are left to null for now; those null values may 
appear anywhere in the array. After this
+         * Other menu items are left to null for now. Such 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 int newCount = recentSystems.size();
@@ -388,11 +390,14 @@ final class MenuSync extends 
SimpleObjectProperty<ReferenceSystem> implements Ev
     }
 
     /**
-     * Selects the specified reference system. This method is invoked by 
{@link RecentReferenceSystems} when the
-     * selected CRS changed, either programmatically or by user action. 
User-specified {@link #action} is invoked,
-     * which will typically start a background thread for transforming data. 
This method does nothing if the given
-     * reference system is same as current one; this is important both for 
avoiding infinite loop and for avoiding
-     * to invoke the potentially costly {@link #action}.
+     * Selects the specified reference system. This method is invoked by 
{@link RecentReferenceSystems}
+     * when the reference system selected in a menu changed, either 
programmatically or by user action.
+     * The user-specified {@link #action} is invoked, which will typically 
start a background thread
+     * for transforming data.
+     *
+     * <p>This method does nothing if the given reference system is same as 
current one.
+     * This is important both for avoiding infinite loop and for avoiding to 
invoke the
+     * potentially costly {@link #action}.</p>
      */
     @Override
     public void set(ReferenceSystem system) {
@@ -418,7 +423,7 @@ final class MenuSync extends 
SimpleObjectProperty<ReferenceSystem> implements Ev
             }
             super.set(system);
             group.selectToggle(null);
-            action.owner().addSelected(system);
+            action.owner().addSelectedItem(system);
             if (system != RecentReferenceSystems.OTHER) {
                 action.changed(this, old, system);
             }
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/RecentReferenceSystems.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/RecentReferenceSystems.java
index a0925bc82e..1086d8c3d6 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/RecentReferenceSystems.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/RecentReferenceSystems.java
@@ -38,7 +38,6 @@ import javafx.concurrent.Task;
 import org.opengis.util.FactoryException;
 import org.opengis.geometry.Envelope;
 import org.opengis.referencing.ReferenceSystem;
-import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.crs.CRSAuthorityFactory;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.TransformException;
@@ -71,7 +70,7 @@ import static org.apache.sis.gui.internal.LogHandler.LOGGER;
 
 /**
  * A short list (~10 items) of most recently used {@link ReferenceSystem}s.
- * The list can be shown in a {@link ChoiceBox} or in a list of {@link 
MenuItem} controls.
+ * The reference systems can be listed in {@link ChoiceBox} or i{@link Menu} 
controls.
  * The last choice is an "Other…" item which, when selected, popups the {@link 
CRSChooser}.
  *
  * <p>The choices are listed in following order:</p>
@@ -125,7 +124,14 @@ public class RecentReferenceSystems {
      * The factory to use for creating a Coordinate Reference System from an 
authority code.
      * If {@code null}, then a default factory will be fetched when first 
needed.
      */
-    private volatile CRSAuthorityFactory factory;
+    private CRSAuthorityFactory factory;
+
+    /**
+     * Filter for reference systems to shown in the menus of combo boxes.
+     * Used for showing only the reference systems that are compatible with 
the data.
+     * A {@code null} value means that no filtering is applied.
+     */
+    private FilterByDatum filterByDatum;
 
     /**
      * The area of interest, or {@code null} if none. This is used for 
filtering the reference systems added by
@@ -134,11 +140,15 @@ public class RecentReferenceSystems {
      * <h4>API note</h4>
      * We do not provide getter/setter for this property; use {@link 
ObjectProperty#set(Object)}
      * directly instead. We omit the "Property" suffix for making this 
operation more natural.
+     *
+     * @see #addAlternatives(String...)
+     * @see #addAlternatives(boolean, ReferenceSystem...)
      */
     public final ObjectProperty<Envelope> areaOfInterest;
 
     /**
      * Area of interest converted to geographic coordinates, or {@code null} 
if none.
+     * This is recomputed automatically when {@link #areaOfInterest} is 
modified.
      */
     private ImmutableEnvelope geographicAOI;
 
@@ -147,16 +157,17 @@ public class RecentReferenceSystems {
      * The default value is {@link ComparisonMode#ALLOW_VARIANT}, i.e. axis 
orders are ignored.
      *
      * <h4>API note</h4>
-     * We do not provide getter/setter for this property; use {@link 
ObjectProperty#set(Object)}
+     * We do not provide getter/setter for this property, use {@link 
ObjectProperty#set(Object)}
      * directly instead. We omit the "Property" suffix for making this 
operation more natural.
      */
     public final ObjectProperty<ComparisonMode> duplicationCriterion;
 
     /**
-     * Values of controls created by this {@code RecentReferenceSystems} 
instance. We retain those properties
-     * because modifying the {@link #referenceSystems} list sometimes causes 
controls to clear their selection
-     * if we removed the selected item from the list. We use {@code 
controlValues} for saving currently selected
-     * values before to modify the item list, and restore selections after we 
finished to modify the list.
+     * Values of controls created by this {@code RecentReferenceSystems} 
instance. We memorize those properties
+     * because we sometime move items in the {@link #referenceSystems} list in 
a way that looks like we removed
+     * items (before to insert them elsewhere), which can cause menus or combo 
boxes to clear their selection.
+     * For preventing that, we use {@code controlValues} for saving the 
currently selected values before to
+     * modify the item list, then restore the selections after we finished to 
modify the list.
      */
     private final List<WritableValue<ReferenceSystem>> controlValues;
 
@@ -165,32 +176,6 @@ public class RecentReferenceSystems {
      */
     final Locale locale;
 
-    /**
-     * Wrapper for a {@link ReferenceSystem} which has not yet been compared 
with authoritative definitions.
-     * Those wrappers are created when {@link ReferenceSystem} instances have 
been specified to {@code setPreferred(…)}
-     * or {@code addAlternatives(…)} methods with {@code 
replaceByAuthoritativeDefinition} argument set to {@code true}.
-     *
-     * @see #setPreferred(boolean, ReferenceSystem)
-     * @see #addAlternatives(boolean, ReferenceSystem...)
-     */
-    private static final class Unverified {
-        /** The reference system to verify. */
-        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 && finder.findSingleton(system) instanceof 
ReferenceSystem replacement) {
-                return replacement;
-            }
-            return system;
-        }
-    }
-
     /**
      * The reference systems either as {@link ReferenceSystem} instances, 
{@link Unverified} wrappers or
      * {@link String} codes. All {@code String} elements should be authority 
codes that {@link #factory}
@@ -198,11 +183,12 @@ public class RecentReferenceSystems {
      * The {@link #OTHER} reference system is <em>not</em> included in this 
list.
      *
      * <p>The list content is specified by calls to {@code setPreferred(…)} 
and {@code addAlternatives(…)} methods,
-     * then is filtered by {@link #filterReferenceSystems(ImmutableEnvelope, 
ComparisonMode)} for resolving authority
-     * codes and removing duplicated elements.</p>
+     * then is filtered by {@link #filterReferenceSystems 
filterReferenceSystems(…)} for resolving authority codes
+     * and removing duplicated elements. A view is returned by {@code 
filterReferenceSystems(…)} for excluding the
+     * elements that are not applicable for the current <abbr>AOI</abbr> and 
datum types.</p>
      *
      * <p>All accesses to this field and all accesses to the {@link 
#isModified}
-     * field shall be done in a block synchronized on {@code 
systemsOrCodes}.</p>
+     * field shall be done in a block synchronized on {@code this}.</p>
      */
     private final List<Object> systemsOrCodes;
 
@@ -226,7 +212,7 @@ public class RecentReferenceSystems {
 
     /**
      * 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.
+     * This is the list returned to users by public <abbr>API</abbr>.
      *
      * <h4>Design notes</h4>
      * The {@link #OTHER} item needs to exist in the list used internally by 
this class because those lists
@@ -249,9 +235,9 @@ public class RecentReferenceSystems {
 
     /**
      * {@code true} if the {@link #referenceSystems} list needs to be rebuilt 
from {@link #systemsOrCodes} content.
-     * This field shall be read and modified in a block synchronized on {@link 
#systemsOrCodes}.
+     * This field shall be read and modified in a block synchronized on {@code 
this}.
      *
-     * @see #listModified()
+     * @see #refreshObservedReferenceSystemList()
      */
     private boolean isModified;
 
@@ -270,7 +256,7 @@ public class RecentReferenceSystems {
 
     /**
      * Creates a builder which will use a default authority factory.
-     * The factory will be capable to handle at least some EPSG codes.
+     * The factory will be capable to handle at least some <abbr>EPSG</abbr> 
codes.
      */
     public RecentReferenceSystems() {
         this(null, null);
@@ -294,10 +280,10 @@ public class RecentReferenceSystems {
         modificationCount    = new AtomicInteger();
         areaOfInterest       = new SimpleObjectProperty<>(this, 
"areaOfInterest");
         duplicationCriterion = new NonNullObjectProperty<>(this, 
"duplicationCriterion", ComparisonMode.ALLOW_VARIANT);
-        duplicationCriterion.addListener((e) -> listModified());
+        duplicationCriterion.addListener((e) -> 
refreshObservedReferenceSystemList());
         areaOfInterest.addListener((e,o,n) -> {
             geographicAOI = Utils.toGeographic(RecentReferenceSystems.class, 
"areaOfInterest", n);
-            listModified();
+            refreshObservedReferenceSystemList();
         });
         referenceSystems = FXCollections.observableArrayList();
         publicItemList = new FilteredList<>(referenceSystems, 
Objects::nonNull);
@@ -308,11 +294,12 @@ public class RecentReferenceSystems {
 
     /**
      * Sets the reference systems, area of interest and "referencing by grid 
indices" systems.
-     * This method performs all the following work:
+     * Contrarily to other methods in this class, this method can be invoked 
from any thread.
+     * This method performs the following tasks, whith the other methods 
invoked from the JavaFX thread:
      *
      * <ul>
-     *   <li>Invokes {@link #setPreferred(boolean, ReferenceSystem)} with the 
first CRS in iteration order.</li>
-     *   <li>Invokes {@link #addAlternatives(boolean, ReferenceSystem...)} for 
all other CRS (single call).</li>
+     *   <li>Invokes {@link #setPreferred(boolean, ReferenceSystem)} with the 
first <abbr>CRS</abbr> in iteration order.</li>
+     *   <li>Invokes {@link #addAlternatives(boolean, ReferenceSystem...)} 
once for all other <abbr>CRS</abbr>s.</li>
      *   <li>Sets the {@link #areaOfInterest} to the union of all 
envelopes.</li>
      *   <li>Sets the content of "Referencing by cell indices" sub-menu.</li>
      * </ul>
@@ -322,9 +309,10 @@ public class RecentReferenceSystems {
      * the resource that provided the {@link GridGeometry} value, or other 
text allowing the user to identify the resource.
      * Those keys are used for naming the <abbr>CRS</abbr>s of cell 
coordinates, which are different for each grid coverage.
      *
-     * <p>This method can be invoked from any thread. The reference systems 
are collected in the current thread,
-     * then the state of this {@code RecentReferenceSystems} is updated in the 
JavaFX thread after all reference
-     * systems are ready.</p>
+     * <p>The {@code replaceByAuthoritativeDefinition} argument specifies 
whether the coordinate reference systems
+     * should be replaced by authoritative definitions when such definitions 
are found. If {@code true} then,
+     * for example, a <q>WGS 84</q> geographic <abbr>CRS</abbr> with (λ, ϕ) 
axis order may be replaced by the
+     * "EPSG::4326" definition with (ϕ, λ) axis order.</p>
      *
      * @param  replaceByAuthoritativeDefinition  whether the reference systems 
should be replaced by authoritative definition.
      * @param  geometries  grid coverage names together with their grid 
geometry. May be empty.
@@ -361,7 +349,6 @@ public class RecentReferenceSystems {
         }
         if (countCRS == 0 && countCIR != 0) {
             refsys[0] = derived[0];
-            countCRS = 1;
         }
         Envelope union;
         try {
@@ -370,26 +357,29 @@ public class RecentReferenceSystems {
             errorOccurred("setGridReferencing", e);
             union = null;
         }
-        /*
-         * Modify now the state of `this` object but with `listModified()` 
made almost no-op.
-         * The intent is to have only one effective call to `listModified()` 
at the end,
-         * in order to have only one call to `filterReferenceSystems(…)`.
-         */
         final Envelope aoi = union;     // Because lambda functions want a 
final variable.
+        final FilterByDatum filter = FilterByDatum.create(refsys);
         final List<CoordinateReferenceSystem> cellCRS = 
Containers.viewAsUnmodifiableList(derived, 0, countCIR);
         final int stamp = modificationCount.incrementAndGet();
         Platform.runLater(() -> {
             if (modificationCount.get() == stamp) {
-                final CoordinateReferenceSystem preferred = refsys[0];
-                if (preferred != null) {
-                    refsys[0] = null;
-                    setPreferred(replaceByAuthoritativeDefinition, preferred);
-                    addAlternatives(replaceByAuthoritativeDefinition, refsys); 
 // Null elements are ignored.
-                    cellIndiceSystems.clear();
-                    cellIndiceSystems.addAll(cellCRS);
+                final boolean old = isAdjusting;
+                try {
+                    isAdjusting = true;
+                    final CoordinateReferenceSystem preferred = refsys[0];
+                    if (preferred != null) {
+                        refsys[0] = null;
+                        setPreferred(replaceByAuthoritativeDefinition, 
preferred);
+                        addAlternatives(replaceByAuthoritativeDefinition, 
refsys);  // Null elements are ignored.
+                        cellIndiceSystems.clear();
+                        cellIndiceSystems.addAll(cellCRS);
+                    }
+                    filterByDatum = filter;
+                    areaOfInterest.set(aoi);
+                } finally {
+                    isAdjusting = old;
                 }
-                areaOfInterest.set(aoi);
-                listModified();
+                refreshObservedReferenceSystemList();
             }
         });
     }
@@ -399,21 +389,18 @@ public class RecentReferenceSystems {
      * choice and should typically be the native {@link 
CoordinateReferenceSystem} of visualized data.
      * If a previous preferred system existed, the previous system will be 
moved to alternative choices.
      *
-     * <p>The {@code replaceByAuthoritativeDefinition} argument specifies 
whether the given reference system should
-     * be replaced by authoritative definition. If {@code true} then for 
example a <q>WGS 84</q> geographic
-     * CRS with (<var>longitude</var>, <var>latitude</var>) axis order may be 
replaced by "EPSG::4326" definition with
-     * (<var>latitude</var>, <var>longitude</var>) axis order.</p>
+     * <p>The {@code replaceByAuthoritativeDefinition} argument specifies 
whether the given reference system
+     * should be replaced by an authoritative definition when such definition 
is found. If {@code true} then,
+     * for example, a <q>WGS 84</q> geographic <abbr>CRS</abbr> with (λ, ϕ) 
axis order may be replaced by the
+     * "EPSG::4326" definition with (ϕ, λ) axis order.</p>
      *
      * @param  replaceByAuthoritativeDefinition  whether the given system 
should be replaced by authoritative definition.
      * @param  system  the native or preferred reference system to show as the 
first choice.
      */
-    public final void setPreferred(final boolean 
replaceByAuthoritativeDefinition, final ReferenceSystem system) {
-        // Final because `setGridReferencing(…)` needs to be sure that 
`referenceSystems` is not rebuilt.
+    public synchronized void setPreferred(final boolean 
replaceByAuthoritativeDefinition, final ReferenceSystem system) {
         ArgumentChecks.ensureNonNull("system", system);
-        synchronized (systemsOrCodes) {
-            systemsOrCodes.add(0, replaceByAuthoritativeDefinition ? new 
Unverified(system) : system);
-            listModified();
-        }
+        systemsOrCodes.add(0, replaceByAuthoritativeDefinition ? new 
Unverified(system) : system);
+        refreshObservedReferenceSystemList();
     }
 
     /**
@@ -428,62 +415,58 @@ public class RecentReferenceSystems {
      *
      * @param  code  authority code of the native of preferred reference 
system to show as the first choice.
      */
-    public void setPreferred(final String code) {
+    public synchronized void setPreferred(final String code) {
         ArgumentChecks.ensureNonEmpty("code", code);
-        synchronized (systemsOrCodes) {
-            systemsOrCodes.add(0, code);
-            listModified();
-        }
+        systemsOrCodes.add(0, code);
+        refreshObservedReferenceSystemList();
     }
 
     /**
-     * Invoked when a new CRS is selected and that CRS has not been found in 
the list.
-     * The new CRS is added after the CRS and menu items will be added in 
background thread.
+     * Invoked when a new reference system is selected and has not been found 
in the current list of systems.
+     * It may be, for example, a system selected among the content of a 
registry shown by {@link CRSChooser}.
+     * The new system is added at the beginning of the list but after the 
preferred (native) reference system.
+     * Because the added system is considered as one alternative among others, 
it may be ignored.
+     * Menu items will be updated in background thread.
      */
-    final void addSelected(final ReferenceSystem system) {
-        if (isAccepted(system)) {
-            synchronized (systemsOrCodes) {
-                systemsOrCodes.add(Math.min(systemsOrCodes.size(), 
NUM_CORE_ITEMS), system);
-                listModified();
-            }
+    final synchronized void addSelectedItem(final ReferenceSystem system) {
+        if (isAuthoritative(system)) {
+            systemsOrCodes.add(Math.min(systemsOrCodes.size(), 
NUM_CORE_ITEMS), system);
+            refreshObservedReferenceSystemList();
         }
     }
 
     /**
      * Adds the given reference systems to the list of alternative choices.
-     * If there is duplicated values in the given list or with previously 
added systems,
+     * If there are duplicated values in the given list or with previously 
added systems,
      * then only the first occurrence of duplicated values is retained.
      * If an {@linkplain #areaOfInterest area of interest} (AOI) is specified,
      * then reference systems that do not intersect the AOI will be hidden.
      *
-     * <p>The {@code replaceByAuthoritativeDefinition} argument specifies 
whether the given reference systems should
-     * be replaced by authoritative definitions. If {@code true} then for 
example a <q>WGS 84</q> geographic
-     * CRS with (<var>longitude</var>, <var>latitude</var>) axis order may be 
replaced by "EPSG::4326" definition with
-     * (<var>latitude</var>, <var>longitude</var>) axis order.</p>
+     * <p>The {@code replaceByAuthoritativeDefinition} argument specifies 
whether the given reference systems
+     * should be replaced by authoritative definitions when such definitions 
are found. If {@code true} then,
+     * for example, a <q>WGS 84</q> geographic <abbr>CRS</abbr> with (λ, ϕ) 
axis order may be replaced by the
+     * "EPSG::4326" definition with (ϕ, λ) axis order.</p>
      *
      * @param  replaceByAuthoritativeDefinition  whether the given systems 
should be replaced by authoritative definitions.
      * @param  systems  the reference systems to add as alternative choices. 
Null elements are ignored.
      */
-    public final void addAlternatives(final boolean 
replaceByAuthoritativeDefinition, final ReferenceSystem... systems) {
-        // Final because `setGridReferencing(…)` needs to be sure that 
`referenceSystems` is not rebuilt.
+    public synchronized void addAlternatives(final boolean 
replaceByAuthoritativeDefinition, final ReferenceSystem... systems) {
         ArgumentChecks.ensureNonNull("systems", systems);
-        synchronized (systemsOrCodes) {
-            for (final ReferenceSystem system : systems) {
-                if (system != null) {
-                    systemsOrCodes.add(replaceByAuthoritativeDefinition ? new 
Unverified(system) : system);
-                }
+        for (final ReferenceSystem system : systems) {
+            if (system != null) {
+                systemsOrCodes.add(replaceByAuthoritativeDefinition ? new 
Unverified(system) : system);
             }
-            listModified();
         }
-        // Check for duplication will be done in `filterReferenceSystems()` 
method.
+        refreshObservedReferenceSystemList();
+        // Check for duplication will be done in `filterReferenceSystems(…)` 
method.
     }
 
     /**
      * Adds the coordinate reference system identified by the given authority 
codes.
-     * If there is duplicated values in the given list or with previously 
added systems,
+     * If there are duplicated values in the given list or with previously 
added systems,
      * then only the first occurrence of duplicated values is retained.
-     * If an {@linkplain #areaOfInterest area of interest} (AOI) is specified,
-     * then reference systems that do not intersect the AOI will be hidden.
+     * If an {@linkplain #areaOfInterest area of interest} (<abbr>AOI</abbr>) 
is specified,
+     * then reference systems that do not intersect the <abbr>AOI</abbr> will 
be hidden.
      *
      * <p>If a code is not recognized, then the error will be notified at some 
later time by a call to
      * {@link #errorOccurred(FactoryException)} in a background thread and the 
code will be silently ignored.
@@ -493,236 +476,224 @@ public class RecentReferenceSystems {
      * @param  codes  authority codes of the coordinate reference systems to 
add as alternative choices.
      *                Null or empty elements are ignored.
      */
-    public void addAlternatives(final String... codes) {
+    public synchronized void addAlternatives(final String... codes) {
         ArgumentChecks.ensureNonNull("codes", codes);
-        synchronized (systemsOrCodes) {
-            for (String code : codes) {
-                code = Strings.trimOrNull(code);
-                if (code != null) {
-                    systemsOrCodes.add(code);
-                }
+        for (String code : codes) {
+            code = Strings.trimOrNull(code);
+            if (code != null) {
+                systemsOrCodes.add(code);
             }
-            listModified();
         }
-        // Parsing will be done in `filterReferenceSystems()` method.
+        refreshObservedReferenceSystemList();
+        // Parsing will be done in `filterReferenceSystems(…)` method.
     }
 
     /**
      * Adds the coordinate reference systems saved in user preferences. The 
user preferences are determined
      * 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.
+     * If an {@linkplain #areaOfInterest area of interest} (<abbr>AOI</abbr>) 
is specified,
+     * then reference systems that do not intersect the <abbr>AOI</abbr> will 
be ignored.
      */
     public void addUserPreferences() {
         addAlternatives(RecentChoices.getReferenceSystems());
     }
 
     /**
-     * Returns whether the given object is accepted for inclusion in the list 
of CRS choices.
-     * In current implementation we accept a CRS if it has an authority code 
(typically an EPSG code).
+     * Returns whether the given object seems authoritative. For example, if 
two objects are considered
+     * equivalent according the {@linkplain #duplicationCriterion duplication 
criterion}, then the one
+     * with an identifier (typically an <abbr>EPSG</abbr> code) is preferred. 
Objects without identifier
+     * are typically a <abbr>CRS</abbbr> with non-standard axis order, for 
example for an image which has
+     * just been read.
      */
-    private static boolean isAccepted(final IdentifiedObject object) {
+    private static boolean isAuthoritative(final ReferenceSystem object) {
         return IdentifiedObjects.getIdentifier(object, null) != null;
     }
 
     /**
-     * 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 cannot 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).
+     * Filters the {@link #systemsOrCodes} list by resolving and verifying the 
reference systems.
+     * This is invoked by {@link #refreshObservedReferenceSystemList()} in a 
background thread.
+     * First, this method modifies {@link #systemsOrCodes} as below:
+     *
+     * <ul>
+     *   <li>Authority codes are resolved if possible or removed if they 
cannot be resolved.</li>
+     *   <li>Unverified <abbr>CRS</abbr>s are replaced by authoritative 
definitions when a match is found.</li>
+     *   <li>Duplications are removed.</li>
+     * </ul>
+     *
+     * Then, this method returns a list which hides the following elements,
+     * but without removing them from the {@link #systemsOrCodes} list:
+     *
+     * <ul>
+     *   <li>Reference systems with incompatible datum types.</li>
+     *   <li>Reference systems with a domain of validity outside the {@link 
#geographicAOI}.</li>
+     * </ul>
      *
-     * <p>This method can be invoked from any thread. In practice, it is 
invoked from a background thread.</p>
+     * The caller will copy the returned list into {@link #referenceSystems} 
in the JavaFX thread.
      *
+     * @param  filter  the {@link #filterByDatum} value read from JavaFX 
thread, or {@code null} if none.
      * @param  domain  the {@link #areaOfInterest} value read from JavaFX 
thread, or {@code null} if none.
      * @param  mode    the {@link #duplicationCriterion} value read from 
JavaFX thread.
      * @return the filtered reference systems, or {@code null} if already 
filtered.
      */
-    private List<ReferenceSystem> filterReferenceSystems(final 
ImmutableEnvelope domain, final ComparisonMode mode) {
-        final List<ReferenceSystem> systems;
-        final var gf = new GazetteerFactory();                  // Cheap to 
construct.
-        synchronized (systemsOrCodes) {
-            @SuppressWarnings("LocalVariableHidesMemberVariable")
-            CRSAuthorityFactory factory = this.factory;         // Hide 
volatile field by local field.
-            if (!isModified) {
-                return null;                                    // Another 
thread already did the work.
+    private synchronized List<ReferenceSystem> filterReferenceSystems(
+            final FilterByDatum     filter,
+            final ImmutableEnvelope domain,
+            final ComparisonMode    mode)
+    {
+        if (!isModified) {
+            return null;    // Another thread already did the work.
+        }
+        boolean noFactoryFound = false;
+        boolean searchedFinder = false;
+        IdentifiedObjectFinder finder = null;
+        final var gf = new GazetteerFactory();
+        for (int i = systemsOrCodes.size(); --i >= 0;) {
+            final Object item = systemsOrCodes.get(i);
+            if (item instanceof ReferenceSystem) {
+                continue;
             }
-            boolean noFactoryFound = false;
-            boolean searchedFinder = false;
-            IdentifiedObjectFinder finder = null;
-            for (int i = systemsOrCodes.size(); --i >= 0;) {
-                final Object item = systemsOrCodes.get(i);
-                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);
+            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();
                         }
-                    } 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 cannot 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 = 
factory.createCoordinateReferenceSystem((String) item);
+                    }
+                } 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 cannot be done, accept the reference 
system as-is.
+                     */
+                    if (!searchedFinder) {
+                        searchedFinder = true;          // Set now in case an 
exception is thrown.
+                        if (factory instanceof GeodeticAuthorityFactory f) {
+                            finder = f.newIdentifiedObjectFinder();
+                        } else {
+                            finder = IdentifiedObjects.newFinder(null);
                         }
-                        system = ((Unverified) item).find(finder);
+                        finder.setIgnoringAxes(true);
                     }
-                } 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);
+                    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.
             }
-            /*
-             * Search for duplicated values after we finished filtering. This 
block is inefficient
-             * (execution time of O(N²)) but it should not be an issue if this 
list is short (e.g.
-             * 20 elements). We cut the list if we reach the maximal number of 
systems to keep.
-             */
-            for (int i=0, j; i < (j = systemsOrCodes.size()); i++) {
-                if (i >= RecentChoices.MAXIMUM_REFERENCE_SYSTEMS) {
-                    systemsOrCodes.subList(i, j).clear();
-                    break;
-                }
-                Object item = systemsOrCodes.get(i);
-                while (--j > i) {
-                    if (Utilities.deepEquals(item, systemsOrCodes.get(j), 
mode)) {
-                        final Object removed = systemsOrCodes.remove(j);
-                        if (isAccepted((IdentifiedObject) removed) && 
!isAccepted((IdentifiedObject) item)) {
-                            /*
-                             * Keep the instance which has an identifier. The 
instance without identifier
-                             * is typically a CRS with non-standard axis 
order. It happens when it is the
-                             * CRS associated to an image that has just been 
read.
-                             */
-                            systemsOrCodes.set(i, item = removed);
-                        }
+            if (system != null) {
+                systemsOrCodes.set(i, system);
+            } else {
+                systemsOrCodes.remove(i);
+            }
+        }
+        /*
+         * Search for duplicated values after we finished filtering. This 
block is inefficient
+         * (execution time of O(N²)) but it should not be an issue if this 
list is short (e.g.
+         * 20 elements). We cut the list if we reach the maximal number of 
systems to keep.
+         */
+        for (int i=0, j; i < (j = systemsOrCodes.size()); i++) {
+            if (i >= RecentChoices.MAXIMUM_REFERENCE_SYSTEMS) {
+                systemsOrCodes.subList(i, j).clear();
+                break;
+            }
+            var item = (ReferenceSystem) systemsOrCodes.get(i);
+            while (--j > i) {
+                if (Utilities.deepEquals(item, systemsOrCodes.get(j), mode)) {
+                    final var removed = (ReferenceSystem) 
systemsOrCodes.remove(j);
+                    if (isAuthoritative(removed) && !isAuthoritative(item)) {
+                        // Keep the instance which has an authority code.
+                        systemsOrCodes.set(i, item = removed);
                     }
                 }
             }
-            /*
-             * Finished to filter the `systemsOrCodes` list: all elements are 
now guaranteed to be
-             * `ReferenceSystem` instances with no duplicated values. Copy 
those reference systems
-             * 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 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);
-            for (int i=0; i<n; i++) {
-                final var system = (ReferenceSystem) systemsOrCodes.get(i);
-                if (i >= NUM_CORE_ITEMS && !Utils.intersects(domain, system)) {
-                    continue;
-                }
-                if (Utils.isIgnoreable(system)) {       // Ignore "Computer 
display" CRS.
-                    continue;
+        }
+        /*
+         * Finished to filter the `systemsOrCodes` list: all elements are now 
guaranteed to be
+         * `ReferenceSystem` instances with no duplicated values. Copy those 
reference systems
+         * in a separated list as a protection against concurrent changes in 
`systemsOrCodes`,
+         * and for retaining only the reference systems that are valid in the 
area of interest.
+         * We do not remove hidden CRS because they may become valid later if 
the AOI changes.
+         */
+        final int n = systemsOrCodes.size();
+        final var systems = new 
ArrayList<ReferenceSystem>(Math.min(NUM_SHOWN_ITEMS, n) + NUM_OTHER_ITEMS);
+        for (int i=0; i<n; i++) {
+            final var system = (ReferenceSystem) systemsOrCodes.get(i);
+            if (filter == null || filter.test(system)) {
+                if (i < NUM_CORE_ITEMS || Utils.intersects(domain, system)) {
+                    if (Utils.isIgnoreable(system)) continue;   // Ignore 
"Computer display" CRS.
+                    systems.add(system);
+                    if (systems.size() >= NUM_SHOWN_ITEMS) break;
                 }
-                systems.add(system);
-                if (systems.size() >= NUM_SHOWN_ITEMS) break;
             }
-            systems.add(OTHER);
-            isModified   = false;
-            this.factory = factory;         // Save in volatile field.
         }
+        systems.add(OTHER);
+        isModified = false;
         return systems;
     }
 
     /**
-     * Invoked when {@link #systemsOrCodes} has been modified. If the 
modification happens after
-     * some controls have been created ({@link ChoiceBox} or {@link 
MenuItem}s), then this method
-     * updates their list of items. The update may happen at some time after 
this method returned.
+     * Updates the {@link #referenceSystems} list with the elements added into 
the {@link #systemsOrCodes} list.
+     * This method must be invoked from the JavaFX thread. The 
<abbr>CRS</abbr> will be processed in background
+     * thread and transferred later (in the JavaFX thread) to the {@link 
#referenceSystems} list when ready.
+     * This copy will cause an update of the {@link ChoiceBox} and {@link 
MenuItem} controls created by this class.
      */
-    private void listModified() {
-        synchronized (systemsOrCodes) {
-            isModified = true;
-            getReferenceSystems(false);
+    private synchronized void refreshObservedReferenceSystemList() {
+        isModified = true;  // Will be reset to `false` after 
`filterReferenceSystems(…)` finished.
+        if (isAdjusting) {
+            return;         // `refreshObservedReferenceSystemList()` will be 
invoked again later.
         }
-    }
-
-    /**
-     * Updates {@link #referenceSystems} with the reference systems added to 
{@link #systemsOrCodes} list.
-     * 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> getReferenceSystems(final boolean 
filtered) {
-        synchronized (systemsOrCodes) {
-            /*
-             * Prepare a temporary list as the concatenation of all items that 
are currently visible in JavaFX
-             * controls with all items that were specified by 
`setPreferred(…)` or `addAlternatives(…)` methods.
-             * This concatenation creates a lot of duplicated values, but 
those duplications will be filtered by
-             * `filterReferenceSystems(…)` method. The intent is to preserve 
following order:
-             *
-             *   - NUM_CORE_ITEMS preferred reference systems first.
-             *   - All reference systems that are currently selected by JavaFX 
controls.
-             *   - All reference systems offered as choice in JavaFX controls.
-             *   - All reference systems specified by `addAlternatives(…)`.
-             *   - NUM_OTHER_ITEMS systems (will be handled in a special way 
by `filterReferenceSystems(…)`).
-             *
-             * The list will be truncated to NUM_SHOWN_ITEMS after 
duplications are removed and before OTHER
-             * is added. The first occurrence of duplicated values is kept, 
which will result in above-cited
-             * order as the priority order where to insert the CRS.
-             */
-            if (isModified) {
-                final int insertAt = Math.min(systemsOrCodes.size(), 
NUM_CORE_ITEMS);
-                final List<ReferenceSystem> selected = getSelectedItems();
-                systemsOrCodes.addAll(insertAt, selected);
-                systemsOrCodes.addAll(insertAt + selected.size(), 
referenceSystems);
-                final ImmutableEnvelope domain = geographicAOI;
-                final ComparisonMode mode = duplicationCriterion.get();
-                BackgroundThreads.execute(new Task<List<ReferenceSystem>>() {
-                    /** Filters the {@link ReferenceSystem}s in a background 
thread. */
-                    @Override protected List<ReferenceSystem> call() {
-                        return filterReferenceSystems(domain, mode);
-                    }
+        /*
+         * Prepare a temporary list as the concatenation of all items that are 
currently visible in JavaFX
+         * controls with all items that were specified by `setPreferred(…)` or 
`addAlternatives(…)` methods.
+         * This concatenation creates a lot of duplicated values, but those 
duplications will be filtered by
+         * `filterReferenceSystems(…)` method. The intent is to preserve 
following order:
+         *
+         *   - NUM_CORE_ITEMS preferred reference systems first.
+         *   - All reference systems that are currently selected by JavaFX 
controls.
+         *   - All reference systems offered as choice in JavaFX controls.
+         *   - All reference systems specified by `addAlternatives(…)`.
+         *   - NUM_OTHER_ITEMS systems (will be handled in a special way by 
`filterReferenceSystems(…)`).
+         *
+         * The list will be truncated to NUM_SHOWN_ITEMS after duplications 
are removed and before OTHER
+         * is added. The first occurrence of duplicated values is kept, which 
will result in above-cited
+         * order as the priority order where to insert the CRS.
+         */
+        final int insertAt = Math.min(systemsOrCodes.size(), NUM_CORE_ITEMS);
+        final List<ReferenceSystem> selected = getSelectedItems();
+        systemsOrCodes.addAll(insertAt, selected);
+        systemsOrCodes.addAll(insertAt + selected.size(), referenceSystems);
+        final FilterByDatum filter = filterByDatum;
+        final ImmutableEnvelope domain = geographicAOI;
+        final ComparisonMode mode = duplicationCriterion.get();
+        BackgroundThreads.execute(new Task<List<ReferenceSystem>>() {
+            /** Filters the {@link ReferenceSystem}s in a background thread. */
+            @Override protected List<ReferenceSystem> call() {
+                return filterReferenceSystems(filter, domain, mode);
+            }
 
-                    /** Should never happen. */
-                    @Override protected void failed() {
-                        ExceptionReporter.show(null, this);
-                    }
+            /** Should never happen. */
+            @Override protected void failed() {
+                ExceptionReporter.show(null, this);
+            }
 
-                    /** Sets the {@link ChoiceBox} content to the list 
computed in background thread. */
-                    @Override protected void succeeded() {
-                        setReferenceSystems(getValue(), mode);
-                    }
-                });
+            /** Sets the {@link ChoiceBox} content to the list computed in 
background thread. */
+            @Override protected void succeeded() {
+                setReferenceSystems(getValue(), mode);
             }
-        }
-        if (filtered) {
-            return coordinateReferenceSystems;
-        }
-        return referenceSystems;
+        });
     }
 
     /**
@@ -741,21 +712,17 @@ public class RecentReferenceSystems {
             /*
              * The call to `copyAsDiff(…)` may cause some ChoiceBox values to 
be lost if the corresponding
              * item in the `referenceSystems` list is temporarily removed 
before to be inserted elsewhere.
-             * Save the values before to modify the list. Note that if 
`referenceSystems` was empty before
-             * the copy, `controlValues` should be null before the copy but 
may become non-null after the
-             * copy because listeners will have initialized them to the first 
`ReferenceSystem` available.
-             * Those non-null values will not be reflected in the `values` 
array, so we should ignore them.
+             * Save the values before to modify the list.
              */
             final ReferenceSystem[] values = 
controlValues.stream().map(WritableValue::getValue).toArray(ReferenceSystem[]::new);
             if (GUIUtilities.copyAsDiff(systems, referenceSystems)) {
                 notifyChanges();
             }
             /*
-             * Restore the previous selections. This code also serves another 
purpose: the previous selection
-             * may not be an item in the list.  If the value was set by a call 
to `ChoiceBox.setValue(…)` and
-             * is a `GeographicCRS` with (λ,φ) axis order, it may have been 
replaced in the list by a CRS with
-             * (φ,λ) axis order. We need to replace the previous value by the 
instance in the list, otherwise
-             * `ChoiceBox` will not show the CRS as selected.
+             * Restore the previous selections, or a variant of these 
selections. For example, if the value
+             * is a `GeographicCRS` with (λ,φ) axis order which was set by a 
call to `ChoiceBox.setValue(…)`,
+             * that value may have been replaced in the list by a CRS with 
(φ,λ) axis order. We must replace
+             * the selected value by an instance from the list, otherwise it 
will not appear as selected.
              */
             final int n = referenceSystems.size();
             for (int j=0; j<values.length; j++) {
@@ -777,9 +744,25 @@ public class RecentReferenceSystems {
     }
 
     /**
-     * Invoked when user selects a reference system. If the choice is 
"Other…", then {@link CRSChooser} popups
-     * 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.
+     * Returns the list of reference systems to show in a {@link Menu} or 
{@link ChoiceBox} control.
+     * The returned list may be temporarily empty or outdated, and updated 
later after a background
+     * thread finished to update the list.
+     *
+     * @param  renderable  whether the list should be restricted to items that 
can be used for rendering maps.
+     * @return the list of items. May be empty on return and populated later.
+     */
+    @SuppressWarnings("ReturnOfCollectionOrArrayField")
+    private synchronized ObservableList<ReferenceSystem> 
getReferenceSystems(final boolean renderable) {
+        if (isModified) {
+            refreshObservedReferenceSystemList();
+        }
+        return renderable ? coordinateReferenceSystems : referenceSystems;
+    }
+
+    /**
+     * Invoked when the user selects a reference system in a choice box or a 
menu.
+     * This listener intercepts the case where the selected item is "Other…", 
in which case {@code SelectionListener}
+     * popups a {@link CRSChooser}, then {@linkplain #addSelectedItem adds the 
user's selection} to the system list.
      */
     final class SelectionListener implements ChangeListener<ReferenceSystem> {
         /** The user-specified action to execute when a reference system is 
selected. */
@@ -806,7 +789,7 @@ public class RecentReferenceSystems {
             }
             final ComparisonMode mode = duplicationCriterion.get();
             if (newValue == OTHER) {
-                final var chooser = new CRSChooser(factory, geographicAOI, 
locale);
+                final var chooser = new CRSChooser(factory, filterByDatum, 
geographicAOI, locale);
                 newValue = 
chooser.showDialog(GUIUtilities.getWindow(property)).orElse(null);
                 if (newValue == null) {
                     newValue = oldValue;
@@ -898,8 +881,8 @@ public class RecentReferenceSystems {
 
     /**
      * Notifies all {@link MenuSync} that the list of reference systems 
changed. We send a notification manually
-     * instead of relying on {@code ListChangeListener} in order to process 
only one event after we have done
-     * a bunch of changes instead of an event after each individual add or 
remove operation.
+     * instead of relying on {@code ListChangeListener} because we want to 
process only one event after we have
+     * done a bunch of changes instead of an event after each individual add 
or remove operation.
      */
     private void notifyChanges() {
         for (final WritableValue<ReferenceSystem> value : controlValues) {
@@ -946,7 +929,7 @@ public class RecentReferenceSystems {
          * Now filter the `referenceSystems` list, retaining only elements 
that are present in `selected`.
          * We do that way for having selected elements in the same order as 
they appear in JavaFX controls.
          */
-        final List<ReferenceSystem> ordered = new ArrayList<>(count);
+        final var ordered = new ArrayList<ReferenceSystem>(count);
         if (count != 0) {
             // (count > 0) implies (referenceSystems != null).
             for (final ReferenceSystem system : referenceSystems) {
@@ -981,28 +964,26 @@ next:       for (int i=0; i<count; i++) {
 
     /**
      * Creates a box offering choices among the reference systems specified to 
this {@code RecentReferenceSystems}.
-     * 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).
+     * The returned control may be initially empty and be automatically 
populated at a later time,
+     * after a background thread finished to process the list of reference 
systems.
      *
-     * <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>
+     * <p>If the {@code renderable} argument is {@code true}, then the choice 
box will contain only reference systems
+     * that can be used for rendering purposes. The list includes {@link 
CoordinateReferenceSystem} instances
+     * but not reference systems by identifiers such as {@linkplain 
MilitaryGridReferenceSystem MGRS}.</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.
+     * @param  renderable  whether the choice box should be restricted to 
items that can be used for rendering maps.
+     * @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 boolean filtered, 
final ChangeListener<ReferenceSystem> action) {
+    public ChoiceBox<ReferenceSystem> createChoiceBox(final boolean 
renderable, final ChangeListener<ReferenceSystem> action) {
         ArgumentChecks.ensureNonNull("action", action);
-        final var choices = new 
ChoiceBox<ReferenceSystem>(getReferenceSystems(filtered));
+        final var choices = new 
ChoiceBox<ReferenceSystem>(getReferenceSystems(renderable));
         choices.setConverter(new ObjectStringConverter<>(choices.getItems(), 
locale));
         choices.valueProperty().addListener(new SelectionListener(action));
         controlValues.add(choices.valueProperty());
@@ -1010,38 +991,37 @@ next:       for (int i=0; i<count; i++) {
     }
 
     /**
-     * Creates menu items offering choices among the reference systems 
specified to this {@code RecentReferenceSystems}.
-     * 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.
+     * Creates a menu offering choices among the reference systems specified 
to this {@code RecentReferenceSystems}.
+     * The returned control may be initially empty and be automatically 
populated at a later time,
+     * after a background thread finished to process the list of reference 
systems.
      *
-     * <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>
+     * <p>If the {@code renderable} argument is {@code true}, then the menu 
will contain only reference systems
+     * that can be used for rendering purposes. The list includes {@link 
CoordinateReferenceSystem} instances
+     * but not reference systems by identifiers such as {@linkplain 
MilitaryGridReferenceSystem MGRS}.</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.
+     * @param  renderable  whether the menu should be restricted to items that 
can be used for rendering maps.
+     * @param  action      the action to execute when a reference system is 
selected.
+     * @return a menu with reference systems specified by {@code 
setPreferred(…)} and {@code addAlternatives(…)} methods.
      *
      * @since 1.3
      */
-    public Menu createMenuItems(final boolean filtered, final 
ChangeListener<ReferenceSystem> action) {
+    public Menu createMenuItems(final boolean renderable, final 
ChangeListener<ReferenceSystem> action) {
         ArgumentChecks.ensureNonNull("action", action);
-        final List<ReferenceSystem> main = getReferenceSystems(filtered);
-        final List<CoordinateReferenceSystem> derived = (filtered) ? null : 
cellIndiceSystems;
+        final List<ReferenceSystem> main = getReferenceSystems(renderable);
+        final List<CoordinateReferenceSystem> derived = (renderable) ? null : 
cellIndiceSystems;
         final var menu = new 
Menu(Vocabulary.forLocale(locale).getString(Vocabulary.Keys.ReferenceSystem));
-        final var property = new MenuSync(main, !filtered, derived, menu, new 
SelectionListener(action));
+        final var property = new MenuSync(main, !renderable, derived, menu, 
new SelectionListener(action));
         menu.getProperties().put(SELECTED_ITEM_KEY, property);
         controlValues.add(property);
         return menu;
     }
 
     /**
-     * Returns the property for the selected value in a menu created by {@link 
#createMenuItems(boolean, ChangeListener)}.
+     * Returns the property for the selected value in a menu created by {@code 
createMenuItems(…)}.
      *
      * @param  menu  the menu, or {@code null} if none.
      * @return the property for the selected value, or {@code null} if none.
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/Unverified.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/Unverified.java
new file mode 100644
index 0000000000..892e5fb043
--- /dev/null
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/Unverified.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.gui.referencing;
+
+import org.opengis.util.FactoryException;
+import org.opengis.referencing.ReferenceSystem;
+import org.apache.sis.referencing.factory.IdentifiedObjectFinder;
+
+
+/**
+ * Wrapper for a {@link ReferenceSystem} which has not yet been compared with 
authoritative definitions.
+ * Those wrappers are created when {@link ReferenceSystem} instances have been 
specified to {@code setPreferred(…)}
+ * or {@code addAlternatives(…)} methods with {@code 
replaceByAuthoritativeDefinition} argument set to {@code true}.
+ *
+ * @see RecentReferenceSystems#setPreferred(boolean, ReferenceSystem)
+ * @see RecentReferenceSystems#addAlternatives(boolean, ReferenceSystem...)
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+final class Unverified {
+    /**
+     * The reference system to verify.
+     */
+    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.
+     *
+     * @param  finder  the finder to use.
+     * @return the resolved reference system.
+     * @throws FactoryException if an error occurred while resolving the 
reference system.
+     */
+    ReferenceSystem find(final IdentifiedObjectFinder finder) throws 
FactoryException {
+        if (finder != null && finder.findSingleton(system) instanceof 
ReferenceSystem replacement) {
+            return replacement;
+        }
+        return system;
+    }
+}


Reply via email to