This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new b0cf8d5 Consolidation of CRSChooser in situations where an error
occurs during the construction of a CRS. Before this commit, the CRSChooser
behavior in such case was confusion (e.g. filtering not working anymore).
b0cf8d5 is described below
commit b0cf8d53cfd20866d60d4b2e044d1a4c2bdbd321
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Mon Apr 20 18:17:11 2020 +0200
Consolidation of CRSChooser in situations where an error occurs during the
construction of a CRS.
Before this commit, the CRSChooser behavior in such case was confusion
(e.g. filtering not working anymore).
---
.../apache/sis/gui/referencing/AuthorityCodes.java | 284 +++++++++++++--------
.../org/apache/sis/gui/referencing/CRSChooser.java | 7 +-
.../org/apache/sis/gui/referencing/CodeFilter.java | 8 +-
.../gui/referencing/RecentReferenceSystems.java | 58 ++---
.../java/org/apache/sis/gui/referencing/Utils.java | 18 ++
.../org/apache/sis/gui/referencing/WKTPane.java | 30 ++-
6 files changed, 240 insertions(+), 165 deletions(-)
diff --git
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/AuthorityCodes.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/AuthorityCodes.java
index ac6f0bc..9468350 100644
---
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/AuthorityCodes.java
+++
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/AuthorityCodes.java
@@ -22,6 +22,7 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.IdentityHashMap;
import java.util.List;
+import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import javafx.application.Platform;
@@ -36,23 +37,25 @@ import org.opengis.util.FactoryException;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.crs.CRSAuthorityFactory;
-import org.apache.sis.referencing.CRS;
+import org.apache.sis.util.iso.Types;
import org.apache.sis.util.Exceptions;
+import org.apache.sis.util.logging.Logging;
import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.util.collection.BackingStoreException;
import org.apache.sis.internal.util.StandardDateFormat;
import org.apache.sis.internal.gui.BackgroundThreads;
-import org.apache.sis.internal.gui.ExceptionReporter;
-import org.apache.sis.internal.util.Constants;
+import org.apache.sis.internal.system.Modules;
+import org.apache.sis.internal.util.Strings;
/**
* A list of authority codes (usually for CRS) which fetch code values in a
background thread
- * and descriptions only when needed.
+ * and CRS names 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.
+ * 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)
* @version 1.1
@@ -65,7 +68,7 @@ final class AuthorityCodes extends ObservableListBase<Code>
/**
* Delay in nanoseconds before to refresh the list with new content.
* Data will be transferred from background threads to JavaFX threads
every time this delay is elapsed.
- * Delay value is a compromise between fast user experience and giving
enough time for allowing a few
+ * The delay value is a compromise between fast user experience and giving
enough time for doing a few
* large data transfers instead than many small data transfers.
*/
private static final long REFRESH_DELAY =
StandardDateFormat.NANOS_PER_SECOND / 10;
@@ -87,12 +90,13 @@ final class AuthorityCodes extends ObservableListBase<Code>
* The authority codes obtained from the factory. The list elements are
provided by a background thread.
* Elements are initially {@link String} instances and can be replaced
later by {@link Code} instances.
*/
- private Object[] codes;
+ private final List<Object> codes;
/**
* Count of the number of {@linkplain #codes} for which we completed the
{@link Code#name} information.
* This is used for notifying the {@linkplain #owner} when we do not
expect more information to be loaded.
- * This notification is only indicative and may not be fully accurate.
Effect should be only visual.
+ * This notification is only indicative and may not be fully accurate. Its
effect should be only visual
+ * (removing the hour glass icon).
*/
private int describedCount;
@@ -102,36 +106,55 @@ final class AuthorityCodes extends
ObservableListBase<Code>
final Locale locale;
/**
+ * The factory to use for creating coordinate reference systems,
+ * or {@code null} if not yet determined.
+ *
+ * @see #getFactory()
+ */
+ private CRSAuthorityFactory factory;
+
+ /**
* The task where to send requests for CRS descriptions (never {@code
null}).
+ * The task is not necessarily running; it may have been created and not
yet scheduled,
+ * in which case the task is waiting in {@link Task.State#READY} state for
work to arrive.
*/
private Loader loader;
/**
- * Non-null if an error occurred while fetching CRS codes.
+ * {@code true} if an error occurred. This is used for reporting only one
error
+ * for avoiding to flood the logger.
*
- * @todo Provide a button for showing this error in an {@link
ExceptionReporter}.
+ * @see #errorOccurred(Throwable)
*/
- private Throwable error;
+ private volatile boolean hasError;
/**
* Creates a new deferred list and starts a background process for loading
CRS codes.
+ * 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.
*
* @param factory the authority factory, or {@code null} for default
factory.
* @param locale the preferred locale of CRS descriptions.
*/
AuthorityCodes(final CRSAuthorityFactory factory, final Locale locale) {
- this.locale = locale;
- codes = new Object[0];
- loader = new Loader(factory);
- BackgroundThreads.execute(loader);
+ this.locale = locale;
+ this.factory = factory;
+ this.codes = new ArrayList<>();
+ this.loader = new Loader();
+ loader.start();
}
/**
- * Returns the authority factory. If no explicit factory has been given at
construction time,
- * the {@linkplain CRS#getAuthorityFactory(String) Apache SIS default
factory} is returned.
+ * Returns the authority factory. This method may be invoked from any
thread.
+ * The factory is not fetched at construction time for giving {@link
Loader}
+ * a chance to fetch it in a background thread.
*/
- final CRSAuthorityFactory getFactory() throws FactoryException {
- return loader.getFactory();
+ final synchronized CRSAuthorityFactory getFactory() throws
FactoryException {
+ if (factory == null) {
+ factory = Utils.getDefaultFactory();
+ }
+ return factory;
}
/**
@@ -140,7 +163,7 @@ final class AuthorityCodes extends ObservableListBase<Code>
*/
@Override
public int size() {
- return codes.length;
+ return codes.size();
}
/**
@@ -148,25 +171,24 @@ final class AuthorityCodes extends
ObservableListBase<Code>
*/
@Override
public Code get(final int index) {
- final Object value = codes[index];
+ final Object value = codes.get(index);
if (value instanceof Code) {
return (Code) value;
}
// Wraps the String only when first needed.
final Code c = new Code((String) value);
- codes[index] = c;
+ codes.set(index, c);
return c;
}
/**
- * Adds a single code. This method should never be invoked except of an
error occurred
+ * Adds a single code. This method should never be invoked except if an
error occurred
* while loading codes, in which case we add a single pseudo-code with
error message.
*/
@Override
public boolean add(final Code code) {
- final int i = codes.length;
- codes = Arrays.copyOf(codes, i + 1);
- codes[i] = code;
+ final int i = codes.size();
+ codes.add(code);
beginChange();
nextAdd(i, i+1);
endChange();
@@ -176,7 +198,7 @@ final class AuthorityCodes extends ObservableListBase<Code>
/**
* Invoked when the name or description of an authority code is requested.
* If the name is not available, then this method sends to the background
thread a
- * request for fetching that name and update this property when name
become known.
+ * request for fetching that name and update cell property when name
become known.
*/
@Override
public ObservableValue<String> call(final
TableColumn.CellDataFeatures<Code,String> cell) {
@@ -185,8 +207,8 @@ final class AuthorityCodes extends ObservableListBase<Code>
/**
* Returns the name (or description) for the given code.
- * If the name is not available, then this method sends to the background
thread a
- * request for fetching that name and update this property when name
become known.
+ * If the name is not available, then this method sends to the background
thread a request
+ * for fetching that name and will update the returned property when the
name become known.
*/
final ReadOnlyStringWrapper getName(final Code code) {
final ReadOnlyStringWrapper p = code.name();
@@ -202,32 +224,36 @@ final class AuthorityCodes extends
ObservableListBase<Code>
* This method is invoked after the background thread has loaded new codes,
* and/or after that thread has fetched names (descriptions) of some codes.
* We combine those two tasks in a single method in order to send a single
event.
- *
- * @param newCodes new codes as {@link String} instances, or {@code null}
if none.
- * @param updated {@link Code} instances to update with new names, or
{@code null} if none.
+ * This method must be invoked in JavaFX thread.
*/
- private void update(final Object[] newCodes, final Map<Code,String>
updated) {
- final int s = codes.length;
- int n = s;
- if (newCodes != null) {
- codes = Arrays.copyOf(codes, n += newCodes.length);
- System.arraycopy(newCodes, 0, codes, s, newCodes.length);
+ private void update(final PartialResult result) {
+ assert Platform.isFxApplicationThread();
+ final int s = codes.size();
+ if (result.codes != null) {
+ codes.addAll(Arrays.asList(result.codes));
}
beginChange();
- if (updated != null) {
- for (int i=0; i<s; i++) { // Update
names first for having increasing indices.
- final Object value = codes[i];
- final String name = updated.remove(value);
+ nextAdd(s, codes.size());
+ if (result.names != null) {
+ final ListIterator<Object> it = codes.listIterator();
+ while (it.hasNext()) {
+ final Object value = it.next();
+ final String name = result.names.remove(value);
if (name != null) {
- ((Code) value).name().set(name); // The name
needs to be set in JavaFX thread.
- describedCount++;
- nextUpdate(i);
+ final int i = it.previousIndex();
+ if (name.isEmpty()) {
+ it.remove(); // Remove code
that we can not resolve.
+ nextRemove(i, (Code) value); //
ClassCastException should never happen here.
+ } else {
+ ((Code) value).name().set(name); //
ClassCastException should never happen here.
+ describedCount++;
+ nextUpdate(i);
+ }
}
}
}
- nextAdd(s, n);
endChange();
- if (describedCount >= n) {
+ if (describedCount >= codes.size()) {
removeHourglass();
}
}
@@ -236,7 +262,7 @@ final class AuthorityCodes extends ObservableListBase<Code>
* Removes the hourglass icon which was shown in the table during initial
data loading phase.
* Removing this icon restores the JavaFX default behavior, which is to
show "no data" when the
* list is empty. We want this default behavior when we think that there
is no more data to load.
- * This is especially important when the user apply a filter which
produces an empty result.
+ * This is especially important when the user applies a filter which
produces an empty result.
* Since the effect is only visual, its okay if the criterion for invoking
this method is approximate.
*/
private void removeHourglass() {
@@ -247,19 +273,37 @@ final class AuthorityCodes extends
ObservableListBase<Code>
}
/**
- * Loads a {@link AuthorityCodes} codes in background thread. This
background thread may send tasks
- * to be executed in JavaFX thread before the final result. The final
result contains only the codes
- * that have not been processed by above-cited tasks or the codes for
which names need to be updated
- * (see {@link #call()} for more information).
+ * The result of fetching authority codes and/or fetching CRS names in a
background thread.
*/
- private final class Loader extends Task<Object> {
+ private static final class PartialResult {
+ /**
+ * New CRS authority codes, or {@code null} if none.
+ */
+ final Object[] codes;
+
/**
- * The factory to use for creating coordinate reference systems,
- * or {@code null} if not yet determined.
+ * Names for some CRS codes as a modifiable map, or {@code null} if
none.
+ * Empty values mean that the code should be removed (because it has
an error).
*/
- private CRSAuthorityFactory factory;
+ final Map<Code,String> names;
/**
+ * Creates a new partial result.
+ */
+ PartialResult(final Object[] codes, final Map<Code,String> names) {
+ this.codes = codes;
+ this.names = names;
+ }
+ }
+
+ /**
+ * Loads CRS authority codes in background thread. The background thread
may send tasks to be executed
+ * in JavaFX thread before the final result. The final result returned by
{@link #getValue()} contains
+ * only codes that have not been fetched by previous {@code Loader} task
executions, or the codes for
+ * which names need to be updated (see {@link #call()} for more
information).
+ */
+ private final class Loader extends Task<PartialResult> {
+ /**
* The items for which {@link Code#name} has been requested.
* Completing those items have priority over completing {@link
AuthorityCodes} because
* those completion requests should happen only for cells that are
currently visible.
@@ -275,13 +319,21 @@ final class AuthorityCodes extends
ObservableListBase<Code>
private final boolean loadCodes;
/**
- * Creates a new loader using the given factory. If the given factory
is null, then the
- * {@linkplain CRS#getAuthorityFactory(String) Apache SIS default
factory} will be used.
+ * Wether this task has been scheduled for execution or is already
executing.
+ * This flag shall be read and updated in JavaFX thread only. We can
not rely
+ * on {@link #isRunning()} because that method does not return {@code
true}
+ * immediately after {@link BackgroundThreads#execute(Runnable)}
invocation.
+ *
+ * @see #start()
*/
- Loader(final CRSAuthorityFactory factory) {
- this.factory = factory;
- toDescribe = new ArrayList<>();
- loadCodes = true;
+ private boolean isRunning;
+
+ /**
+ * Creates a new loader.
+ */
+ Loader() {
+ toDescribe = new ArrayList<>();
+ loadCodes = true;
}
/**
@@ -289,43 +341,50 @@ final class AuthorityCodes extends
ObservableListBase<Code>
* for loading names (descriptions) for authority codes listed in
{@link #toDescribe}.
*/
private Loader(final Loader previous) {
- factory = previous.factory;
toDescribe = previous.toDescribe;
loadCodes = false;
}
/**
- * Returns the authority factory. This method is normally invoked from
the background thread,
- * but we nevertheless synchronize it in case {@link
AuthorityCodes#getFactory()} is invoked
- * concurrently.
+ * Schedule for execution in a background thread.
+ * This method shall be invoked in JavaFX thread.
*/
- final synchronized CRSAuthorityFactory getFactory() throws
FactoryException {
- if (factory == null) {
- factory = CRS.getAuthorityFactory(Constants.EPSG);
- }
- return factory;
+ final void start() {
+ isRunning = true;
+ BackgroundThreads.execute(this);
}
/**
* Sends to this background thread a request for fetching the name
(description) of given code.
* The {@link AuthorityCodes} list will receive an update event after
the name has been fetched.
- * This method is invoked from JavaFX thread.
+ * This method must be invoked from JavaFX thread.
+ *
+ * @param code the CRS authority code for which to fetch the name in
background thread.
*/
final void requestName(final Code code) {
+ assert Platform.isFxApplicationThread();
synchronized (toDescribe) {
toDescribe.add(code);
}
- if (!isRunning()) { // Include "scheduled"
state.
- BackgroundThreads.execute(this);
+ /*
+ * This task may be created and ready but not yet started. It
happens if `scheduleNewLoader()`
+ * found no code to process in the `toDescribe` list at the time
that method has been invoked.
+ */
+ if (!isRunning) {
+ start();
}
}
/**
* Fetches the names of all objects in the {@link #toDescribe} array
and clears that array.
- * The names are returned as a map with {@link Code} as keys and names
(descriptions) as values.
- * This method is invoked from background thread and returned value
will be consumed in JavaFX thread.
+ * The names are returned as a map with {@link Code} as keys and names
(or descriptions) as values.
+ * This method is invoked from a background thread and the returned
value will be consumed in JavaFX thread.
+ * Some entries in the returned map be empty strings if the
corresponding code should be removed.
+ *
+ * @param factory value of {@link #getFactory()}.
+ * @return the names of CRS authority codes submitted to {@link
#requestName(Code)}, or {@code null} if none.
*/
- private Map<Code,String> processNameRequests() throws FactoryException
{
+ private Map<Code,String> processNameRequests(final CRSAuthorityFactory
factory) {
final Code[] snapshot;
synchronized (toDescribe) {
final int size = toDescribe.size();
@@ -333,11 +392,19 @@ final class AuthorityCodes extends
ObservableListBase<Code>
snapshot = toDescribe.toArray(new Code[size]);
toDescribe.clear();
}
- final CRSAuthorityFactory factory = getFactory();
final Map<Code,String> updated = new
IdentityHashMap<>(snapshot.length);
for (final Code code : snapshot) {
- // Do not update code in this thread; it will be updated in
JavaFX thread.
- updated.put(code,
factory.getDescriptionText(code.code).toString(locale));
+ String text;
+ try {
+ text =
Strings.trimOrNull(Types.toString(factory.getDescriptionText(code.code),
locale));
+ if (text == null) {
+ text =
Vocabulary.getResources(locale).getString(Vocabulary.Keys.Unnamed);
+ }
+ } catch (FactoryException e) {
+ errorOccurred(e);
+ text = ""; // Tells
`AuthorityCodes.update(PartialResult)` to remove this code.
+ }
+ updated.put(code, text); // Do not update code in this
thread; it will be updated in JavaFX thread.
}
return updated;
}
@@ -345,12 +412,12 @@ final class AuthorityCodes extends
ObservableListBase<Code>
/**
* Invoked in background thread for reading authority codes.
Intermediate results are sent
* to the JavaFX thread every {@value #REFRESH_DELAY} nanoseconds.
Requests for code names
- * are also handled in priority since they are typically for visible
cells.
+ * are also handled in priority since they are typically required for
visible cells.
*
- * @return one of the followings:
+ * @return one or both of the followings:
* <ul>
- * <li>A {@code List<String>} which contains the remaining codes
that need to be
- * sent to {@link AuthorityCodes} list.</li>
+ * <li>An array of {@code String}s which contains the remaining
codes that need
+ * to be sent to {@link AuthorityCodes} list.</li>
* <li>A {@code Map<Code,String>} which contains the codes for
which the names
* or descriptions have been updated.</li>
* </ul>
@@ -358,7 +425,7 @@ final class AuthorityCodes extends ObservableListBase<Code>
* @throws Exception if an error occurred while fetching the codes or
the names/descriptions.
*/
@Override
- protected Object call() throws Exception {
+ protected PartialResult call() throws Exception {
long lastTime = System.nanoTime();
List<String> codes = Collections.emptyList();
final CRSAuthorityFactory factory = getFactory();
@@ -369,10 +436,9 @@ final class AuthorityCodes extends ObservableListBase<Code>
while (it.hasNext()) {
codes.add(it.next());
if (System.nanoTime() - lastTime > REFRESH_DELAY) {
- final Object[] newCodes = codes.toArray();
// Snapshot of current content.
+ final PartialResult p = new
PartialResult(codes.toArray(), processNameRequests(factory));
codes.clear();
- final Map<Code,String> updated =
processNameRequests(); // Must be outside lambda expression.
- Platform.runLater(() -> update(newCodes, updated));
+ Platform.runLater(() -> update(p));
lastTime = System.nanoTime();
}
}
@@ -385,12 +451,12 @@ final class AuthorityCodes extends
ObservableListBase<Code>
*/
if (codes.isEmpty()) {
Thread.sleep(REFRESH_DELAY /
StandardDateFormat.NANOS_PER_MILLISECOND);
- return processNameRequests();
+ return new PartialResult(null,
processNameRequests(factory));
}
} catch (BackingStoreException e) {
throw e.unwrapOrRethrow(Exception.class);
}
- return codes;
+ return new PartialResult(codes.toArray(), null);
}
/**
@@ -401,19 +467,8 @@ final class AuthorityCodes extends ObservableListBase<Code>
@Override
@SuppressWarnings("unchecked")
protected void succeeded() {
- Object[] newCodes = null;
- Map<Code,String> updated = null;
- final Object result = getValue();
- if (result instanceof List<?>){
- final List<?> codes = (List<?>) result;
- if (!codes.isEmpty()) {
- newCodes = codes.toArray();
- }
- } else {
- updated = (Map<Code,String>) result;
- }
- update(newCodes, updated);
- scheduleNewLoader();
+ update(getValue());
+ prepareNewLoader();
}
/**
@@ -424,7 +479,8 @@ final class AuthorityCodes extends ObservableListBase<Code>
@Override
protected void failed() {
final Throwable e = getException();
- if (error == null) {
+ errorOccurred(e);
+ if (loadCodes) {
final Code code = new
Code(Vocabulary.getResources(locale).getString(Vocabulary.Keys.Errors));
String message = Exceptions.getLocalizedMessage(e, locale);
if (message == null) {
@@ -433,9 +489,8 @@ final class AuthorityCodes extends ObservableListBase<Code>
code.name().set(message);
add(code);
}
- error = e;
removeHourglass();
- scheduleNewLoader();
+ prepareNewLoader();
}
/**
@@ -443,15 +498,28 @@ final class AuthorityCodes extends
ObservableListBase<Code>
* between the end of {@link #call()} execution and the start of the
{@link #succeeded()} or
* {@link #failed()} execution, starts the new task immediately.
*/
- private void scheduleNewLoader() {
+ private void prepareNewLoader() {
+ assert Platform.isFxApplicationThread();
+ isRunning = false;
loader = new Loader(this);
final boolean isEmpty;
synchronized (toDescribe) {
isEmpty = toDescribe.isEmpty();
}
if (!isEmpty) {
- BackgroundThreads.execute(loader);
+ loader.start();
}
}
}
+
+ /**
+ * Invoked when an error occurred. This method may be invoked from any
thread.
+ * Current implementation logs the first error.
+ */
+ private void errorOccurred(final Throwable e) {
+ if (!hasError) {
+ hasError = true; // Not a big problem if we have race
condition; error will just be logged twice.
+
Logging.unexpectedException(Logging.getLogger(Modules.APPLICATION),
AuthorityCodes.class, "get", e);
+ }
+ }
}
diff --git
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSChooser.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSChooser.java
index 28a4eab..f2c427d 100644
---
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSChooser.java
+++
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSChooser.java
@@ -69,7 +69,6 @@ import org.apache.sis.geometry.ImmutableEnvelope;
import org.apache.sis.metadata.iso.extent.Extents;
import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.util.Exceptions;
-import org.apache.sis.referencing.CRS;
import org.apache.sis.referencing.IdentifiedObjects;
@@ -144,9 +143,11 @@ public class CRSChooser extends
Dialog<CoordinateReferenceSystem> {
/**
* 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.
*
- * @param factory the factory to use for creating coordinate
reference systems, or {@code null}
- * for the {@linkplain
CRS#getAuthorityFactory(String) Apache SIS default factory}.
+ * @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.
*/
public CRSChooser(final CRSAuthorityFactory factory, final Envelope
areaOfInterest) {
diff --git
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CodeFilter.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CodeFilter.java
index 59dcfab..103c0c7 100644
---
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CodeFilter.java
+++
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CodeFilter.java
@@ -95,13 +95,11 @@ final class CodeFilter implements Predicate<Code> {
*/
@Override
public boolean test(final Code code) {
+ final String id = code.code.toLowerCase(allCodes.locale);
String name = allCodes.getName(code).getValue();
- if (name == null) {
- return false;
- }
- name = name.toLowerCase(allCodes.locale);
+ name = (name != null) ? name.toLowerCase(allCodes.locale) : "";
for (final String token : tokens) {
- if (!name.contains(token)) {
+ if (!name.contains(token) && !id.equals(token)) {
return false;
}
}
diff --git
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java
index 9670f14..35d9869 100644
---
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java
+++
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java
@@ -34,10 +34,7 @@ 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.metadata.extent.GeographicBoundingBox;
-import org.apache.sis.metadata.iso.extent.Extents;
import org.apache.sis.geometry.ImmutableEnvelope;
-import org.apache.sis.referencing.CRS;
import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
import org.apache.sis.referencing.factory.IdentifiedObjectFinder;
@@ -74,27 +71,21 @@ import org.apache.sis.internal.util.Strings;
*/
public class RecentReferenceSystems {
/**
- * The authority of the {@link #factory} (for example "EPSG"),
- * or {@code null} for all authorities known to SIS.
- */
- private static final String AUTHORITY = null;
-
- /**
* Number of reference systems to always show before all other reference
systems.
* They are the native of preferred reference system for the visualized
data.
*/
- private static final int NUM_CORE_SYSTEMS = 1;
+ private static final int NUM_CORE_ITEMS = 1;
/**
* Number of reference systems to shown in {@link ChoiceBox} or {@link
MenuItem}s.
- * The {@value #NUM_CORE_SYSTEMS} core systems are included but not {@link
#OTHER}.
+ * The {@value #NUM_CORE_ITEMS} core systems are included but not {@link
#OTHER}.
*/
- private static final int NUM_SHOWN_SYSTEMS = 9;
+ private static final int NUM_SHOWN_ITEMS = 9;
/**
* Number of reference systems to keep at the end of the list.
*/
- private static final int NUM_OTHER_SYSTEMS = 1;
+ private static final int NUM_OTHER_ITEMS = 1;
/**
* A pseudo-reference system for the "Other…" choice. We use a null value
because {@link ChoiceBox}
@@ -104,8 +95,7 @@ public class RecentReferenceSystems {
/**
* The factory to use for creating a Coordinate Reference System from an
authority code.
- * If {@code null}, then the {@linkplain CRS#getAuthorityFactory(String)
default factory}
- * will be fetched when first needed.
+ * If {@code null}, then a default factory will be fetched when first
needed.
*/
private volatile CRSAuthorityFactory factory;
@@ -189,7 +179,8 @@ public class RecentReferenceSystems {
private boolean isAdjusting;
/**
- * Creates a builder which will use the {@linkplain
CRS#getAuthorityFactory(String) default authority factory}.
+ * Creates a builder which will use a default authority factory.
+ * The factory will be capable to handle at least some EPSG codes.
*/
public RecentReferenceSystems() {
systemsOrCodes = new ArrayList<>();
@@ -208,7 +199,7 @@ public class RecentReferenceSystems {
*
* @param factory the factory to use for building CRS from authority
codes.
*
- * @see CRS#getAuthorityFactory(String)
+ * @see org.apache.sis.referencing.CRS#getAuthorityFactory(String)
*/
public RecentReferenceSystems(final CRSAuthorityFactory factory) {
this();
@@ -358,7 +349,7 @@ public class RecentReferenceSystems {
*/
if (!noFactoryFound) {
if (factory == null) {
- factory = CRS.getAuthorityFactory(AUTHORITY);
+ factory = Utils.getDefaultFactory();
}
systemsOrCodes.set(i,
factory.createCoordinateReferenceSystem((String) item));
} else {
@@ -376,7 +367,7 @@ public class RecentReferenceSystems {
if (factory instanceof GeodeticAuthorityFactory) {
finder = ((GeodeticAuthorityFactory)
factory).newIdentifiedObjectFinder();
} else {
- finder = IdentifiedObjects.newFinder(AUTHORITY);
+ finder = IdentifiedObjects.newFinder(null);
}
finder.setIgnoringAxes(true);
}
@@ -420,17 +411,14 @@ public class RecentReferenceSystems {
* because they would become valid later if the area of interest
changes.
*/
final int n = systemsOrCodes.size();
- systems = new ArrayList<>(Math.min(NUM_SHOWN_SYSTEMS, n) +
NUM_OTHER_SYSTEMS);
+ systems = new ArrayList<>(Math.min(NUM_SHOWN_ITEMS, n) +
NUM_OTHER_ITEMS);
for (int i=0; i<n; i++) {
final ReferenceSystem system = (ReferenceSystem)
systemsOrCodes.get(i);
- if (i >= NUM_CORE_SYSTEMS && domain != null) {
- final GeographicBoundingBox bbox =
Extents.getGeographicBoundingBox(system.getDomainOfValidity());
- if (bbox != null && !domain.intersects(new
ImmutableEnvelope(bbox))) {
- continue;
- }
+ if (i >= NUM_CORE_ITEMS && !Utils.intersects(domain,
system.getDomainOfValidity())) {
+ continue;
}
systems.add(system);
- if (systems.size() >= NUM_SHOWN_SYSTEMS) break;
+ if (systems.size() >= NUM_SHOWN_ITEMS) break;
}
systems.add(OTHER);
isModified = false;
@@ -466,7 +454,7 @@ public class RecentReferenceSystems {
referenceSystems = FXCollections.observableArrayList();
}
synchronized (systemsOrCodes) {
- systemsOrCodes.addAll(Math.min(systemsOrCodes.size(),
NUM_CORE_SYSTEMS), referenceSystems);
+ systemsOrCodes.addAll(Math.min(systemsOrCodes.size(),
NUM_CORE_ITEMS), referenceSystems);
// Duplicated values will be filtered by the background task below.
isModified = true;
final ImmutableEnvelope domain = geographicAOI;
@@ -566,11 +554,11 @@ public class RecentReferenceSystems {
} else {
final ObservableList<ReferenceSystem> items =
referenceSystems;
final ComparisonMode mode = duplicationCriterion.get();
- final int count = items.size() - NUM_OTHER_SYSTEMS;
+ final int count = items.size() - NUM_OTHER_ITEMS;
boolean found = false;
for (int i=0; i<count; i++) {
if (Utilities.deepEquals(newValue, items.get(i),
mode)) {
- if (i >= NUM_CORE_SYSTEMS) {
+ if (i >= NUM_CORE_ITEMS) {
items.set(i, newValue);
}
found = true;
@@ -578,10 +566,10 @@ public class RecentReferenceSystems {
}
}
if (!found) {
- if (count >= NUM_SHOWN_SYSTEMS) {
+ if (count >= NUM_SHOWN_ITEMS) {
items.remove(count - 1); // Remove the last
item before `OTHER`.
}
- items.add(Math.min(count, NUM_CORE_SYSTEMS), newValue);
+ items.add(Math.min(count, NUM_CORE_ITEMS), newValue);
}
}
/*
@@ -603,19 +591,19 @@ public class RecentReferenceSystems {
* to confuse the list.
*/
final ObservableList<ReferenceSystem> items = referenceSystems;
- final int count = items.size() - NUM_OTHER_SYSTEMS;
- for (int i=Math.min(count, NUM_CORE_SYSTEMS + 1); --i >= 0;) {
+ final int count = items.size() - NUM_OTHER_ITEMS;
+ for (int i=Math.min(count, NUM_CORE_ITEMS + 1); --i >= 0;) {
if (items.get(i) == newValue) {
return;
}
}
- for (int i=count; --i >= NUM_CORE_SYSTEMS;) {
+ for (int i=count; --i >= NUM_CORE_ITEMS;) {
if (items.get(i) == newValue) {
items.remove(i);
break;
}
}
- items.add(Math.max(0, Math.min(count, NUM_CORE_SYSTEMS)),
newValue);
+ items.add(Math.max(0, Math.min(count, NUM_CORE_ITEMS)),
newValue);
}
}
}
diff --git
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/Utils.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/Utils.java
index ba451d4..06b547f 100644
---
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/Utils.java
+++
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/Utils.java
@@ -17,14 +17,18 @@
package org.apache.sis.gui.referencing;
import org.opengis.geometry.Envelope;
+import org.opengis.util.FactoryException;
import org.opengis.metadata.extent.Extent;
import org.opengis.metadata.extent.GeographicBoundingBox;
+import org.opengis.referencing.crs.CRSAuthorityFactory;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
import org.apache.sis.metadata.iso.extent.Extents;
import org.apache.sis.geometry.ImmutableEnvelope;
import org.apache.sis.internal.system.Modules;
+import org.apache.sis.internal.util.Constants;
import org.apache.sis.util.logging.Logging;
+import org.apache.sis.referencing.CRS;
/**
@@ -43,6 +47,20 @@ final class Utils {
}
/**
+ * Returns the default authority factory. Current implementation uses only
the EPSG factory for avoiding
+ * problems with "AUTO" factory (which requires parameters) and PROJ
factory (which requires native code).
+ * We lost the "CRS" factory, but it does not provide interesting new CRS
compared to EPSG factory.
+ * It provides CRS with different axis order such as "CRS:84", but widgets
in this package ignore axis order.
+ *
+ * <p>If a future version uses more than one authority factory, note that
it would have the side effect
+ * of making authority namespaces visible in the {@link CRSChooser} "Code"
column, requiring more space.
+ * For example "4326" would become "EPSG:4326". We may need to revisit the
widget layout in such case.</p>
+ */
+ static CRSAuthorityFactory getDefaultFactory() throws FactoryException {
+ return CRS.getAuthorityFactory(Constants.EPSG);
+ }
+
+ /**
* Converts an arbitrary envelope to an envelope with (longitude,
latitude) axis order in degrees.
* The datum is unspecified. This is used for approximate comparisons of
geographic area.
*/
diff --git
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/WKTPane.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/WKTPane.java
index 384f136..f5ddb5b 100644
---
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/WKTPane.java
+++
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/WKTPane.java
@@ -145,23 +145,34 @@ final class WKTPane extends StringConverter<Convention>
implements ChangeListene
@Override
public void changed(ObservableValue<? extends Convention> observable,
Convention oldValue, Convention newValue) {
format.setConvention(newValue);
- refresh();
+ if (crs != null) {
+ text.setText(format.format(crs));
+ }
}
/**
* Sets the CRS to show in this pane. The CRS is constructed in a
background thread.
+ * The execution will usually be very quick because {@link CRSChooser}
already started
+ * a background thread for fetching the selected CRS, and WKT formatting
is fast.
*/
final void setContent(final AuthorityCodes source, final String code) {
text.setDisable(true);
BackgroundThreads.execute(new Task<CoordinateReferenceSystem>() {
+ /** The WKT text formatted in background thread. */
+ private String wkt;
+
/** Invoked in background thread for fetching the CRS from an
authority code. */
@Override protected CoordinateReferenceSystem call() throws
FactoryException {
- return
source.getFactory().createCoordinateReferenceSystem(code);
+ final CoordinateReferenceSystem crs =
source.getFactory().createCoordinateReferenceSystem(code);
+ if (crs != null) {
+ wkt = format.format(crs);
+ }
+ return crs;
}
/** Invoked in JavaFX thread on success. */
@Override protected void succeeded() {
- setContent(getValue());
+ setContent(getValue(), wkt);
}
/** Invoked in JavaFX thread on cancellation. */
@@ -181,21 +192,12 @@ final class WKTPane extends StringConverter<Convention>
implements ChangeListene
/**
* Sets the content to the given coordinate reference system.
*/
- private void setContent(final CoordinateReferenceSystem newCRS) {
+ private void setContent(final CoordinateReferenceSystem newCRS, final
String wkt) {
text.setEditable(false); // TODO: make editable if we allow WKT
parsing in a future version.
text.setDisable(false);
if (newCRS != crs) {
crs = newCRS;
- refresh();
- }
- }
-
- /**
- * Rewrites the WKT using current conventions.
- */
- private void refresh() {
- if (crs != null) {
- text.setText(format.format(crs));
+ text.setText(wkt);
}
}
}