sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Provides more guidance to user when selecting a CRS by warning when the CRS domain of validity does not intersect the area of interest.
Date Wed, 15 Apr 2020 16:42:51 GMT
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 da1e14d  Provides more guidance to user when selecting a CRS by warning when the
CRS domain of validity does not intersect the area of interest.
da1e14d is described below

commit da1e14d8bea23e3ef16cc7cf49e4a970d0c415a7
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Wed Apr 15 18:40:45 2020 +0200

    Provides more guidance to user when selecting a CRS by warning when the CRS domain of
validity does not intersect the area of interest.
---
 .../java/org/apache/sis/gui/coverage/Controls.java |  30 +--
 .../apache/sis/gui/coverage/CoverageControls.java  |   6 +-
 .../apache/sis/gui/coverage/CoverageExplorer.java  |   8 +-
 .../org/apache/sis/gui/coverage/GridControls.java  |   4 +-
 .../org/apache/sis/gui/dataset/ResourceTree.java   |   2 +-
 .../java/org/apache/sis/gui/map/StatusBar.java     |   2 +-
 .../org/apache/sis/gui/referencing/CRSChooser.java | 260 ++++++++++++++++++---
 .../org/apache/sis/internal/gui/Resources.java     |   5 +
 .../apache/sis/internal/gui/Resources.properties   |   1 +
 .../sis/internal/gui/Resources_fr.properties       |   1 +
 .../java/org/apache/sis/internal/gui/Styles.java   |  85 ++++++-
 .../apache/sis/gui/referencing/CRSChooserApp.java  |  92 ++++++++
 .../apache/sis/metadata/iso/extent/Extents.java    |  17 +-
 .../org/apache/sis/util/resources/Vocabulary.java  |  20 ++
 .../sis/util/resources/Vocabulary.properties       |   4 +
 .../sis/util/resources/Vocabulary_fr.properties    |   4 +
 16 files changed, 460 insertions(+), 81 deletions(-)

diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/Controls.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/Controls.java
index 7636376..39e6ee3 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/Controls.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/Controls.java
@@ -17,16 +17,13 @@
 package org.apache.sis.gui.coverage;
 
 import javafx.geometry.Insets;
-import javafx.scene.Node;
 import javafx.scene.control.ButtonBase;
 import javafx.scene.control.Control;
 import javafx.scene.control.Label;
 import javafx.scene.layout.Border;
 import javafx.scene.layout.BorderStroke;
 import javafx.scene.layout.BorderStrokeStyle;
-import javafx.scene.layout.ColumnConstraints;
 import javafx.scene.layout.GridPane;
-import javafx.scene.layout.Priority;
 import javafx.scene.layout.Region;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.internal.gui.Styles;
@@ -49,11 +46,6 @@ abstract class Controls {
     static final Insets CAPTION_MARGIN = new Insets(12, 0, 9, 0);
 
     /**
-     * Space between a group of controls and the border encompassing the group.
-     */
-    private static final Insets GROUP_INSETS = new Insets(12);
-
-    /**
      * The border to use for grouping some controls together.
      */
     private static final Border GROUP_BORDER = new Border(new BorderStroke(
@@ -80,25 +72,9 @@ abstract class Controls {
      * @return a pane with each (label, control) pair on a row.
      */
     static GridPane createControlGrid(final Label... controls) {
-        final GridPane gp = new GridPane();
-        final ColumnConstraints controlColumn = new ColumnConstraints();
-        controlColumn.setHgrow(Priority.ALWAYS);
-        gp.getColumnConstraints().setAll(new ColumnConstraints(), controlColumn);
-        gp.setPadding(GROUP_INSETS);
-        gp.setBorder(GROUP_BORDER);
-        gp.setVgap(9);
-        gp.setHgap(9);
-        int row = 0;
-        for (final Label label : controls) {
-            if (label != null) {
-                final Node control = label.getLabelFor();
-                GridPane.setConstraints(label,   0, row);
-                GridPane.setConstraints(control, 1, row);
-                gp.getChildren().addAll(label, control);
-                row++;
-            }
-        }
+        final GridPane gp = Styles.createControlGrid(controls);
         Styles.setAllRowToSameHeight(gp);
+        gp.setBorder(GROUP_BORDER);
         return gp;
     }
 
@@ -135,7 +111,7 @@ abstract class Controls {
 
     /**
      * Invoked after {@link CoverageExplorer#setCoverage(ImageRequest)} for updating the
table of
-     * sample dimensions with information become available. This method is invoked in JavaFX
thread.
+     * sample dimensions when information become available. This method is invoked in JavaFX
thread.
      *
      * @param  data  the new coverage, or {@code null} if none.
      */
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageControls.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageControls.java
index e824a83..f38ddc7 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageControls.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageControls.java
@@ -59,7 +59,7 @@ final class CoverageControls extends Controls {
     /**
      * Creates a new set of coverage controls.
      *
-     * @param  vocabulary  localized set of words, provided in argument before often known
by the caller.
+     * @param  vocabulary  localized set of words, provided in argument because often known
by the caller.
      * @param  coverage    property containing the coverage to show.
      */
     CoverageControls(final Vocabulary vocabulary, final ObjectProperty<GridCoverage>
coverage) {
@@ -89,7 +89,7 @@ final class CoverageControls extends Controls {
          * Put all sections together and have the first one expanded by default.
          */
         controls = new Accordion(
-            new TitledPane(vocabulary.getString(Vocabulary.Keys.Display),  displayPane)
+            new TitledPane(vocabulary.getString(Vocabulary.Keys.Display), displayPane)
             // TODO: more controls to be added in a future version.
         );
         controls.setExpandedPane(controls.getPanes().get(0));
@@ -109,7 +109,7 @@ final class CoverageControls extends Controls {
 
     /**
      * Invoked after {@link CoverageExplorer#setCoverage(ImageRequest)} for updating the
table of
-     * sample dimensions with information become available. This method is invoked in JavaFX
thread.
+     * sample dimensions when information become available. This method is invoked in JavaFX
thread.
      *
      * @param  data  the new coverage, or {@code null} if none.
      */
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageExplorer.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageExplorer.java
index b57e342..194eab8 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageExplorer.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageExplorer.java
@@ -28,7 +28,6 @@ import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.SimpleObjectProperty;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.internal.gui.Resources;
-import org.apache.sis.internal.gui.Styles;
 import org.apache.sis.internal.gui.ToolbarButton;
 import org.apache.sis.internal.gui.NonNullObjectProperty;
 import org.apache.sis.util.resources.Vocabulary;
@@ -50,6 +49,11 @@ import org.apache.sis.gui.Widget;
  */
 public class CoverageExplorer extends Widget {
     /**
+     * Initial position of divider in split panes.
+     */
+    private static final double INITIAL_SPLIT = 200;
+
+    /**
      * Type of view shown in the explorer.
      * It may be either an image or a table of numerical values.
      */
@@ -170,7 +174,7 @@ public class CoverageExplorer extends Widget {
         final Controls c = views[0];                            // First View enumeration
is default value.
         group.selectToggle(group.getToggles().get(0));
         content = new SplitPane(c.controls(), c.view());
-        content.setDividerPosition(0, Styles.INITIAL_SPLIT);
+        content.setDividerPosition(0, INITIAL_SPLIT);
         ToolbarButton.insert(content, buttons);
     }
 
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridControls.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridControls.java
index 75e8dda..a4a477b 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridControls.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridControls.java
@@ -60,7 +60,7 @@ final class GridControls extends Controls {
     /**
      * Creates a new set of grid controls.
      *
-     * @param  vocabulary  localized set of words, provided in argument before often known
by the caller.
+     * @param  vocabulary  localized set of words, provided in argument because often known
by the caller.
      */
     GridControls(final Vocabulary vocabulary) {
         view = new GridView();
@@ -130,7 +130,7 @@ final class GridControls extends Controls {
 
     /**
      * Invoked after {@link CoverageExplorer#setCoverage(ImageRequest)} for updating the
table of
-     * sample dimensions with information become available. This method is invoked in JavaFX
thread.
+     * sample dimensions when information become available. This method is invoked in JavaFX
thread.
      *
      * @param  data  the new coverage, or {@code null} if none.
      */
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceTree.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceTree.java
index 8be8853..a32df07 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceTree.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceTree.java
@@ -454,7 +454,7 @@ public class ResourceTree extends TreeView<Resource> {
                     if (tree != null) {
                         final Throwable failure = ((Unloadable) resource).failure;
                         text = tree.string(failure);
-                        more = new Button(Styles.ERROR_DETAILS);
+                        more = new Button(Styles.ERROR_DETAILS_ICON);
                         more.setOnAction((e) -> ExceptionReporter.show(
                                 tree.localized.getString(Resources.Keys.ErrorDetails),
                                 tree.localized.getString(Resources.Keys.CanNotReadResource),
failure));
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
index baf82c0..166ee20 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
@@ -550,7 +550,7 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
                 }
             }
             final String alert = text;
-            more = new Button(Styles.ERROR_DETAILS);
+            more = new Button(Styles.ERROR_DETAILS_ICON);
             more.setOnAction((e) -> ExceptionReporter.show(
                     Resources.forLocale(locale).getString(Resources.Keys.ErrorDetails), alert,
details));
         }
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 c21c3c4..e606fc7 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
@@ -20,6 +20,7 @@ import java.util.Locale;
 import java.util.Optional;
 import javafx.collections.ObservableList;
 import javafx.collections.transformation.FilteredList;
+import javafx.concurrent.Task;
 import javafx.event.ActionEvent;
 import javafx.geometry.Insets;
 import javafx.geometry.Pos;
@@ -33,23 +34,51 @@ import javafx.scene.control.TableColumn;
 import javafx.scene.control.TableView;
 import javafx.scene.control.TextField;
 import javafx.scene.control.ToggleButton;
+import javafx.scene.control.Tooltip;
 import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.GridPane;
 import javafx.scene.layout.HBox;
 import javafx.scene.layout.Priority;
+import javafx.scene.paint.Color;
 import javafx.scene.text.Font;
 import javafx.stage.Window;
-import org.apache.sis.internal.gui.ExceptionReporter;
+import javafx.util.Duration;
+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.GeodeticCRS;
+import org.opengis.referencing.crs.GeographicCRS;
+import org.opengis.referencing.crs.GeocentricCRS;
+import org.opengis.referencing.crs.ProjectedCRS;
+import org.opengis.referencing.crs.VerticalCRS;
+import org.opengis.referencing.crs.TemporalCRS;
+import org.opengis.referencing.crs.CompoundCRS;
+import org.opengis.referencing.crs.EngineeringCRS;
+import org.opengis.referencing.crs.GeneralDerivedCRS;
 import org.opengis.referencing.crs.CRSAuthorityFactory;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.operation.Conversion;
+import org.opengis.referencing.operation.OperationMethod;
+import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.internal.referencing.ReferencingUtilities;
+import org.apache.sis.internal.gui.BackgroundThreads;
+import org.apache.sis.internal.gui.ExceptionReporter;
 import org.apache.sis.internal.gui.IdentityValueFactory;
 import org.apache.sis.internal.gui.Resources;
+import org.apache.sis.internal.gui.Styles;
+import org.apache.sis.geometry.ImmutableEnvelope;
+import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
+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;
 
 
 /**
  * A list of Coordinate Reference Systems (CRS) from which the user can select.
+ * The CRS choices is built in a background thread from a specified {@link CRSAuthorityFactory}.
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
@@ -88,18 +117,51 @@ public class CRSChooser extends Dialog<CoordinateReferenceSystem>
{
     private final TableView<Code> table;
 
     /**
+     * A panel showing the type and domain of validity of selected CRS.
+     */
+    private final GridPane summary;
+
+    /**
+     * The label where to write the CRS type and domain of validity.
+     */
+    private final Label type, domain;
+
+    /**
+     * The area of interest, or {@code null} if none.
+     * Axis order is (<var>longitude</var>, <var>latitude</var>).
+     */
+    private final ImmutableEnvelope areaOfInterest;
+
+    /**
      * The pane showing the CRS in Well Known Text format.
      * Created when first needed.
      */
     private WKTPane wktPane;
 
     /**
+     * Creates a chooser proposing all coordinate reference systems from the default factory.
+     */
+    public CRSChooser() {
+        this(null, null);
+    }
+
+    /**
      * Creates a chooser proposing all coordinate reference systems from the given factory.
      *
-     * @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 the {@linkplain CRS#getAuthorityFactory(String) Apache
SIS default factory}.
+     * @param  areaOfInterest  geographic area for which to choose a CRS, or {@code null}
if no restriction.
      */
-    public CRSChooser(final CRSAuthorityFactory factory) {
+    public CRSChooser(final CRSAuthorityFactory factory, Envelope areaOfInterest) {
+        if (areaOfInterest == null) {
+            this.areaOfInterest = null;
+        } else try {
+            final DefaultGeographicBoundingBox bbox = new DefaultGeographicBoundingBox();
+            bbox.setBounds(areaOfInterest);
+            this.areaOfInterest = new ImmutableEnvelope(bbox);
+        } catch (TransformException e) {
+            throw new IllegalArgumentException(e);
+        }
         final Locale         locale     = Locale.getDefault();
         final Resources      i18n       = Resources.forLocale(locale);
         final Vocabulary     vocabulary = Vocabulary.getResources(locale);
@@ -124,34 +186,61 @@ public class CRSChooser extends Dialog<CoordinateReferenceSystem>
{
         clock.setFont(Font.font(30));
         table.setPlaceholder(clock);
         /*
-         * Text field for filtering the list of CRS codes using keywords.
-         * The filtering is applied when the "Enter" key is pressed in that field.
-         */
-        searchField = new TextField();
-        searchField.setOnAction((ActionEvent event) -> {
-            CodeFilter.apply(table, searchField.getText());
-        });
-        HBox.setHgrow(searchField, Priority.ALWAYS);
-        final Label label = new Label(i18n.getString(Resources.Keys.Filter));
-        label.setLabelFor(searchField);
-        /*
-         * Button for showing the CRS description in Well Known Text (WKT) format.
-         * The button is enabled only if a row in the table is selected.
+         * Controls on the top of CRS list. This is either a filter or a combox box
+         * giving WKT format choices, depending on what is currently shown.
          */
-        final ToggleButton info = new ToggleButton("\uD83D\uDDB9"); // Unicode U+1F5B9: Document
With Text.
-        table.getSelectionModel().selectedItemProperty().addListener((e,o,n) -> info.setDisable(n
== null));
-        info.setOnAction((ActionEvent event) -> {
-            setTools(info.isSelected());
-        });
-        info.setDisable(true);
+        {// block for keeping variable locales.
+            /*
+             * Text field for filtering the list of CRS codes using keywords.
+             * The filtering is applied when the "Enter" key is pressed in that field.
+             */
+            searchField = new TextField();
+            searchField.setOnAction((ActionEvent event) -> {
+                CodeFilter.apply(table, searchField.getText());
+            });
+            HBox.setHgrow(searchField, Priority.ALWAYS);
+            final Label label = new Label(i18n.getString(Resources.Keys.Filter));
+            label.setLabelFor(searchField);
+            /*
+             * Button for showing the CRS description in Well Known Text (WKT) format.
+             * The button is enabled only if a row in the table is selected.
+             */
+            final ToggleButton infoButton = new ToggleButton("\uD83D\uDDB9");   // Unicode
U+1F5B9: Document With Text.
+            table.getSelectionModel().selectedItemProperty().addListener((e,o,n) -> {
+                infoButton.setDisable(n == null);
+                updateSummary(n);
+            });
+            infoButton.setOnAction((ActionEvent event) -> {
+                setTools(infoButton.isSelected());
+            });
+            infoButton.setDisable(true);
+            /*
+             * Creates the tools bar to show above the table of codes.
+             * The tools bar contains the search field and the button for showing the WKT.
+             */
+            tools = new HBox(label, searchField, infoButton);
+            tools.setSpacing(9);
+            tools.setAlignment(Pos.BASELINE_LEFT);
+            BorderPane.setMargin(tools, new Insets(0, 0, 9, 0));
+        }
         /*
-         * Creates the tools bar to show above the table of codes.
-         * The tools bar contains the search field and the button for showing the WKT.
+         * Details about the selected items. This is a form with the following lines:
+         *   - Type (e.g. "Projected — Transverse Mercator").
+         *   - Domain of validity.
          */
-        tools = new HBox(label, searchField, info);
-        tools.setSpacing(9);
-        tools.setAlignment(Pos.BASELINE_LEFT);
-        BorderPane.setMargin(tools, new Insets(0, 0, 9, 0));
+        {// block for keeping variable locales.
+            final Label lt = new Label(vocabulary.getLabel(Vocabulary.Keys.Type));
+            final Label ld = new Label(vocabulary.getLabel(Vocabulary.Keys.Domain));
+            lt.setLabelFor(type   = new Label());
+            ld.setLabelFor(domain = new Label());
+            summary = Styles.createControlGrid(lt, ld);
+            final Tooltip tp = new Tooltip();
+            tp.setShowDelay(Duration.seconds(0.5));
+            tp.setShowDuration(Duration.minutes(1));
+            tp.maxWidthProperty().bind(summary.widthProperty());
+            tp.setWrapText(true);
+            domain.setTooltip(tp);
+        }
         /*
          * Layout table and tools bar inside the dialog content.
          * Configure the dialog buttons.
@@ -160,6 +249,7 @@ public class CRSChooser extends Dialog<CoordinateReferenceSystem>
{
         content = new BorderPane();
         content.setCenter(table);
         content.setTop(tools);
+        content.setBottom(summary);
         pane.setContent(content);
         pane.getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL);
         setTitle(i18n.getString(Resources.Keys.SelectCRS));
@@ -168,15 +258,16 @@ public class CRSChooser extends Dialog<CoordinateReferenceSystem>
{
     }
 
     /**
-     * Sets the tools bar and content to controls for the given mode.
+     * Sets the tools bar and its content to controls for the given mode.
      * If {@code wkt} is {@code true}, then this method set the controls for showing the
WKT.
      * If {@code wkt} is {@code false} (the default), then this method set the controls to
the table of CRS codes.
      */
     private void setTools(final boolean wkt) {
         final Locale locale = getAuthorityCodes().locale;
         final short labelText;
-        final Control control;
-        final Control main;
+        final Control  control;
+        final Control  main;
+        final GridPane info;
         if (wkt) {
             if (wktPane == null) {
                 wktPane = new WKTPane(locale);
@@ -185,10 +276,12 @@ public class CRSChooser extends Dialog<CoordinateReferenceSystem>
{
             labelText = Resources.Keys.Format;
             control   = wktPane.convention;
             main      = wktPane.text;
+            info      = null;
         } else {
             labelText = Resources.Keys.Filter;
             control   = searchField;
             main      = table;
+            info      = summary;
         }
         final ObservableList<Node> children = tools.getChildren();
         final Label label = (Label) children.get(0);
@@ -197,6 +290,7 @@ public class CRSChooser extends Dialog<CoordinateReferenceSystem>
{
         label.setLabelFor(control);
         children.set(1, control);
         content.setCenter(main);
+        content.setBottom(info);
     }
 
     /**
@@ -212,6 +306,108 @@ public class CRSChooser extends Dialog<CoordinateReferenceSystem>
{
     }
 
     /**
+     * Invoked when a new CRS is selected in the table. This method updates
+     * the {@link #type} and {@link #domain} fields with CRS information.
+     */
+    private void updateSummary(final Code selected) {
+        final AuthorityCodes source = getAuthorityCodes();
+        final String code = selected.code;
+        BackgroundThreads.execute(new Task<CoordinateReferenceSystem>() {
+            /** Invoked in background thread for fetching the CRS from an authority code.
*/
+            @Override protected CoordinateReferenceSystem call() throws FactoryException
{
+                return source.getFactory().createCoordinateReferenceSystem(code);
+            }
+
+            /** Invoked in JavaFX thread on success. */
+            @Override protected void succeeded() {
+                final CoordinateReferenceSystem crs = getValue();
+                type.setTextFill(Styles.NORMAL_TEXT);
+                type.setText(typeOf(crs, source.locale));
+                setDomainOfValidity(crs.getDomainOfValidity(), source.locale);
+            }
+
+            /** Invoked in JavaFX thread on cancellation. */
+            @Override protected void cancelled() {
+                type.setText(null);
+                domain.setText(null);
+            }
+
+            /** Invoked in JavaFX thread on failure. */
+            @Override protected void failed() {
+                cancelled();
+                type.setTextFill(Styles.ERROR_TEXT);
+                type.setText(Exceptions.getLocalizedMessage(getException(), source.locale));
+            }
+        });
+    }
+
+    /**
+     * Sets the text that describes the domain of validity.
+     */
+    private void setDomainOfValidity(final Extent domainOfValidity, final Locale locale)
{
+        String text  = Extents.getDescription(domainOfValidity, locale);
+        String tip   = text;
+        Color  color = Styles.NORMAL_TEXT;
+        if (areaOfInterest != null) {
+            final GeographicBoundingBox bbox = Extents.getGeographicBoundingBox(domainOfValidity);
+            if (bbox != null && !areaOfInterest.intersects(new ImmutableEnvelope(bbox)))
{
+                tip   = Resources.forLocale(locale).getString(Resources.Keys.DoesNotCoverAOI);
+                text  = Styles.WARNING_ICON + " " + (text != null ? text : tip);
+                color = Styles.ERROR_TEXT;
+            }
+        }
+        domain.setTextFill(color);
+        domain.setText(text);
+        domain.getTooltip().setText(tip);
+    }
+
+    /**
+     * Returns the text to show of right of the "type" label.
+     */
+    private static String typeOf(CoordinateReferenceSystem crs, final Locale locale) {
+        while (crs instanceof CompoundCRS) {
+            crs = ((CompoundCRS) crs).getComponents().get(0);
+        }
+        final short key;
+        final int expected;
+             if (crs instanceof GeographicCRS)  {key = Vocabulary.Keys.Geographic;  expected
= 2;}
+        else if (crs instanceof GeocentricCRS)  {key = Vocabulary.Keys.Geocentric;  expected
= 3;}
+        else if (crs instanceof GeodeticCRS)    {key = Vocabulary.Keys.Geodetic;    expected
= 0;}
+        else if (crs instanceof VerticalCRS)    {key = Vocabulary.Keys.Vertical;    expected
= 1;}
+        else if (crs instanceof TemporalCRS)    {key = Vocabulary.Keys.Temporal;    expected
= 1;}
+        else if (crs instanceof ProjectedCRS)   {key = Vocabulary.Keys.Projected;   expected
= 2;}
+        else if (crs instanceof EngineeringCRS) {key = Vocabulary.Keys.Engineering; expected
= 0;}
+        else {
+            key = Vocabulary.Keys.Unknown;
+            expected = 0;
+        }
+        String text = Vocabulary.getResources(locale).getString(key);
+        final int     dimension = ReferencingUtilities.getDimension(crs);
+        final boolean addDimension = (dimension != expected && expected != 0);
+        final boolean isProjection = (crs instanceof GeneralDerivedCRS);
+        if (addDimension | isProjection) {
+            final StringBuilder buffer = new StringBuilder(text);
+            if (addDimension) {
+                buffer.append(" (").append(dimension).append("D)");
+            }
+            if (isProjection) {
+                final Conversion conversion = ((GeneralDerivedCRS) crs).getConversionFromBase();
+                if (conversion != null) {
+                    final OperationMethod method = conversion.getMethod();
+                    if (method != null) {
+                        final String name = IdentifiedObjects.getDisplayName(method, locale);
+                        if (name != null) {
+                            buffer.append(" — ").append(name);
+                        }
+                    }
+                }
+            }
+            text = buffer.toString();
+        }
+        return text;
+    }
+
+    /**
      * Returns the currently selected CRS, or {@code null} if none.
      *
      * @return the currently selected CRS, or {@code null}.
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
index 1356c30..27817d3 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
@@ -151,6 +151,11 @@ public final class Resources extends IndexedResourceBundle {
         public static final short Display = 41;
 
         /**
+         * Does not cover the area of interest.
+         */
+        public static final short DoesNotCoverAOI = 57;
+
+        /**
          * Error closing file
          */
         public static final short ErrorClosingFile = 13;
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
index dd5714f..bcac615e 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
@@ -39,6 +39,7 @@ Data                   = Data
 Date                   = Date:
 Dimensions             = Dimensions:
 Display                = Display
+DoesNotCoverAOI        = Does not cover the area of interest.
 ErrorDetails           = Details about error
 ErrorExportingData     = Error exporting data
 ErrorOpeningFile       = Error opening file
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
index c8cd341..37b1099 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
@@ -44,6 +44,7 @@ Data                   = Donn\u00e9es
 Date                   = Date\u00a0:
 Dimensions             = Dimensions\u00a0:
 Display                = Affichage
+DoesNotCoverAOI        = Ne couvre pas la r\u00e9gion d\u2019int\u00e9r\u00eat.
 ErrorDetails           = D\u00e9tails \u00e0 propos de l\u2019erreur
 ErrorExportingData     = Erreur \u00e0 l\u2019exportation de donn\u00e9es
 ErrorOpeningFile       = Erreur \u00e0 l\u2019ouverture du fichier
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Styles.java
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Styles.java
index 0cc704a..ab4a0f5 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Styles.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Styles.java
@@ -19,23 +19,29 @@ package org.apache.sis.internal.gui;
 import java.util.Arrays;
 import java.io.IOException;
 import java.io.InputStream;
+import javafx.geometry.Insets;
+import javafx.scene.Node;
+import javafx.scene.control.Label;
 import javafx.scene.paint.Color;
 import javafx.scene.image.Image;
 import javafx.scene.layout.Background;
 import javafx.scene.layout.BackgroundFill;
+import javafx.scene.layout.ColumnConstraints;
 import javafx.scene.layout.GridPane;
+import javafx.scene.layout.Priority;
 import javafx.scene.layout.RowConstraints;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.internal.system.Modules;
 
 
 /**
- * A central place where to store all colors used by SIS application.
- * This provides a single place to revisit if we learn more about how
- * to make those color more dynamic with JavaFX styling.
+ * A central place where to store some appearance choices such as colors used by SIS application.
+ * This provides a single place to revisit if we learn more about how to make those choices
more
+ * configurable with JavaFX styling.
  *
- * <p>This class also opportunistically provides a few utility methods
- * related to appearance.</p>
+ * <p>This class also opportunistically provides a few utility methods related to appearance.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
@@ -44,12 +50,6 @@ import org.apache.sis.internal.system.Modules;
  */
 public final class Styles {
     /**
-     * Initial position of divider in split panes. This is used in data windows,
-     * but not necessarily in the main window (the resource explorer).
-     */
-    public static final double INITIAL_SPLIT = 200;
-
-    /**
      * Approximate size of vertical scroll bar.
      */
     public static final int SCROLLBAR_WIDTH = 20;
@@ -107,8 +107,15 @@ public final class Styles {
 
     /**
      * The Unicode character to put in a button for requesting more information about an
error.
+     * The symbol is {@value}.
      */
-    public static final String ERROR_DETAILS = "ℹ";
+    public static final String ERROR_DETAILS_ICON = "\u2139\uFE0F";     // ℹ
+
+    /**
+     * The Unicode character to put in a label for representing a warning.
+     * The symbol is {@value}.
+     */
+    public static final String WARNING_ICON = "\u26A0\uFE0F";           // ⚠
 
     /**
      * Do not allow instantiation of this class.
@@ -142,6 +149,60 @@ public final class Styles {
     }
 
     /**
+     * Space between a group of controls and the border encompassing the group.
+     */
+    private static final Insets FORM_INSETS = new Insets(12);
+
+    /**
+     * Creates a grid pane of two columns and an arbitrary number of rows.
+     * Each row contains a (label, control) pair, with all growths and shrinks applied on
the second column.
+     * The controls must be associated to the given labels by {@link Label#getLabelFor()}.
+     * If a label is {@code null}, then no row is created for that label.
+     *
+     * @param  controls  (label, control) pairs to layout in rows.
+     * @return a pane with each (label, control) pair on a row.
+     */
+    public static GridPane createControlGrid(final Label... controls) {
+        final GridPane gp = new GridPane();
+        final ColumnConstraints labelColumn   = new ColumnConstraints();
+        final ColumnConstraints controlColumn = new ColumnConstraints();
+        labelColumn  .setHgrow(Priority.NEVER);
+        controlColumn.setHgrow(Priority.ALWAYS);
+        /*
+         * I'm not aware of a way to specify that the first column should be always wide
enough
+         * for showing fully the labels. As a workaround, we set the minimum width to the
width
+         * of the widest label.
+         */
+        final ChangeListener<Number> widthFixer = new ChangeListener<>() {
+            @Override public void changed(final ObservableValue<? extends Number> e,
final Number o, final Number n) {
+                final double v = n.doubleValue();
+                if (v > o.doubleValue()) {
+                    if (v > labelColumn.getMinWidth()) {
+                        labelColumn.setMinWidth(v);
+                    }
+                    e.removeListener(this);
+                }
+            }
+        };
+        gp.getColumnConstraints().setAll(labelColumn, controlColumn);
+        gp.setPadding(FORM_INSETS);
+        gp.setVgap(9);
+        gp.setHgap(9);
+        int row = 0;
+        for (final Label label : controls) {
+            if (label != null) {
+                final Node control = label.getLabelFor();
+                GridPane.setConstraints(label,   0, row);
+                GridPane.setConstraints(control, 1, row);
+                gp.getChildren().addAll(label, control);
+                label.widthProperty().addListener(widthFixer);
+                row++;
+            }
+        }
+        return gp;
+    }
+
+    /**
      * Sets all rows in the given grid pane to the same height.
      *
      * @param  gp  the grid pane in which to set row constraints.
diff --git a/application/sis-javafx/src/test/java/org/apache/sis/gui/referencing/CRSChooserApp.java
b/application/sis-javafx/src/test/java/org/apache/sis/gui/referencing/CRSChooserApp.java
new file mode 100644
index 0000000..648ace5
--- /dev/null
+++ b/application/sis-javafx/src/test/java/org/apache/sis/gui/referencing/CRSChooserApp.java
@@ -0,0 +1,92 @@
+/*
+ * 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.Optional;
+import javafx.scene.Scene;
+import javafx.stage.Stage;
+import javafx.scene.control.Label;
+import javafx.application.Application;
+import javafx.geometry.Insets;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.internal.gui.BackgroundThreads;
+import org.apache.sis.referencing.CommonCRS;
+
+
+/**
+ * Shows {@link CRSChooser}. The area of interest is set to Canada
+ * for allowing to test the CRS domain of validity checks.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public final strictfp class CRSChooserApp extends Application {
+    /**
+     * Starts the test application.
+     *
+     * @param args  ignored.
+     */
+    public static void main(final String[] args) {
+        launch(args);
+    }
+
+    /**
+     * Sets a dummy scene for the given window.
+     * The main purpose is to provide a scene for testing dialog boxes
+     * that user can close after the test is finished.
+     */
+    private static void setDummyScene(final Stage window) {
+        Label note = new Label("Close this window for stopping the application.");
+        note.setPadding(new Insets(15));
+        Scene scene = new Scene(note);
+        window.setTitle("CRSChooserApp");
+        window.setScene(scene);
+    }
+
+    /**
+     * Creates and starts the test application.
+     *
+     * @param  window  where to show the application.
+     */
+    @Override
+    public void start(final Stage window) {
+        setDummyScene(window);
+        window.show();
+
+        final GeneralEnvelope bbox = new GeneralEnvelope(CommonCRS.defaultGeographic());
+        bbox.setRange(0, -140.99778, -52.6480987209);
+        bbox.setRange(1, 41.6751050889, 83.23324);              // Canada
+        final CRSChooser chooser = new CRSChooser(null, bbox);
+        final Optional<CoordinateReferenceSystem> crs = chooser.showDialog(window);
+
+        System.out.println("The selected CRS is: " + crs);
+    }
+
+    /**
+     * Stops the test application.
+     *
+     * @throws Exception if an error occurred while stopping the application.
+     */
+    @Override
+    public void stop() throws Exception {
+        BackgroundThreads.stop();
+        super.stop();
+    }
+}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java
index 3ac8015..4b3f623 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java
@@ -19,6 +19,7 @@ package org.apache.sis.metadata.iso.extent;
 import java.util.Date;
 import java.util.List;
 import java.util.ArrayList;
+import java.util.Locale;
 import javax.measure.Unit;
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
@@ -44,6 +45,7 @@ import org.apache.sis.metadata.InvalidMetadataException;
 import org.apache.sis.measure.Longitude;
 import org.apache.sis.measure.MeasurementRange;
 import org.apache.sis.measure.Range;
+import org.apache.sis.util.iso.Types;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
@@ -70,7 +72,7 @@ import org.opengis.geometry.Geometry;
  * </ul>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.1
  *
  * @see org.apache.sis.geometry.Envelopes
  *
@@ -415,6 +417,19 @@ public final class Extents extends Static {
     }
 
     /**
+     * Returns the description of the given extent, or {@code null} if none.
+     *
+     * @param  extent  the extent from which to get a description, or {@code null}.
+     * @param  locale  desired locale, or {@code null} for default.
+     * @return description of the given extent, or {@code null} if none.
+     *
+     * @since 1.1
+     */
+    public static String getDescription(final Extent extent, final Locale locale) {
+        return (extent != null) ? Types.toString(extent.getDescription(), locale) : null;
+    }
+
+    /**
      * Returns the position at the median longitude and latitude values of the given bounding
box.
      * This method does not check the {@linkplain DefaultGeographicBoundingBox#getInclusion()
inclusion} status.
      * This method takes in account bounding boxes that cross the anti-meridian.
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
index e91fcb2..982319a 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
@@ -400,6 +400,11 @@ public final class Vocabulary extends IndexedResourceBundle {
         public static final short EndPoint = 59;
 
         /**
+         * Engineering
+         */
+        public static final short Engineering = 205;
+
+        /**
          * {0} entr{0,choice,0#y|2#ies}
          */
         public static final short EntryCount_1 = 60;
@@ -445,11 +450,21 @@ public final class Vocabulary extends IndexedResourceBundle {
         public static final short GeodesicDistance = 67;
 
         /**
+         * Geodetic
+         */
+        public static final short Geodetic = 202;
+
+        /**
          * Geodetic dataset
          */
         public static final short GeodeticDataset = 68;
 
         /**
+         * Geographic
+         */
+        public static final short Geographic = 203;
+
+        /**
          * Geographic extent
          */
         public static final short GeographicExtent = 69;
@@ -780,6 +795,11 @@ public final class Vocabulary extends IndexedResourceBundle {
         public static final short Preprocessing = 125;
 
         /**
+         * Projected
+         */
+        public static final short Projected = 204;
+
+        /**
          * “{0}”
          */
         public static final short Quoted_1 = 126;
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
index 8f5c86e..f68e1bd 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
@@ -83,6 +83,7 @@ EllipsoidChange         = Ellipsoid change
 EllipsoidalHeight       = Ellipsoidal height
 EndDate                 = End date
 EndPoint                = End point
+Engineering             = Engineering
 EntryCount_1            = {0} entr{0,choice,0#y|2#ies}
 Envelope                = Envelope
 Errors                  = Errors
@@ -92,7 +93,9 @@ Geocentric              = Geocentric
 GeocentricRadius        = Geocentric radius
 GeocentricConversion    = Geocentric conversion
 GeodesicDistance        = Geodesic distance
+Geodetic                = Geodetic
 GeodeticDataset         = Geodetic dataset
+Geographic              = Geographic
 GeographicExtent        = Geographic extent
 GeographicIdentifier    = Geographic identifier
 Gray                    = Gray
@@ -159,6 +162,7 @@ Parenthesis_2           = {0} ({1})
 Paths                   = Paths
 Plugins                 = Plug-ins
 Preprocessing           = Preprocessing
+Projected               = Projected
 Quoted_1                = \u201c{0}\u201d
 Read                    = Read
 Red                     = Red
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
index 93d9fa9..d70b496 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
@@ -91,6 +91,7 @@ EllipsoidalHeight       = Hauteur ellipso\u00efdale
 EntryCount_1            = {0} entr\u00e9e{0,choice,0#|2#s}
 EndDate                 = Date de fin
 EndPoint                = Point d\u2019arriv\u00e9
+Engineering             = Ing\u00e9nierie
 Envelope                = Enveloppe
 Errors                  = Erreurs
 FillValue               = Valeur de remplissage
@@ -99,7 +100,9 @@ Geocentric              = G\u00e9ocentrique
 GeocentricRadius        = Rayon g\u00e9ocentrique
 GeocentricConversion    = Conversion g\u00e9ocentrique
 GeodesicDistance        = Distance g\u00e9od\u00e9sique
+Geodetic                = G\u00e9od\u00e9sique
 GeodeticDataset         = Base de donn\u00e9es g\u00e9od\u00e9sique
+Geographic              = G\u00e9ographique
 GeographicExtent        = \u00c9tendue g\u00e9ographique
 GeographicIdentifier    = Identifiant g\u00e9ographique
 Gray                    = Gris
@@ -166,6 +169,7 @@ Parenthesis_2           = {0} ({1})
 Paths                   = Chemins
 Plugins                 = Modules d\u2019extension
 Preprocessing           = Pr\u00e9traitement
+Projected               = Projet\u00e9
 Quoted_1                = \u00ab\u202f{0}\u202f\u00bb
 Read                    = Lecture
 Red                     = Rouge


Mime
View raw message