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;
+ }
+}