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 fc5d74d945 When the position given to `GridEvaluator.apply(…)` does 
not have enough dimensions, default to the grid coordinates specified by 
`setDefaultSlice(…)` method call.
fc5d74d945 is described below

commit fc5d74d945a8d3f6979764e1ff1440b2afbd7870
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Tue May 31 19:11:44 2022 +0200

    When the position given to `GridEvaluator.apply(…)` does not have enough 
dimensions,
    default to the grid coordinates specified by `setDefaultSlice(…)` method 
call.
---
 .../org/apache/sis/gui/map/ValuesUnderCursor.java  |  70 ++++----
 .../org/apache/sis/internal/gui/GUIUtilities.java  |  26 ++-
 .../sis/coverage/grid/BufferedGridCoverage.java    |   3 +-
 .../sis/coverage/grid/ConvertedGridCoverage.java   |  41 +++++
 .../apache/sis/coverage/grid/GridCoverage2D.java   |   3 +-
 .../apache/sis/coverage/grid/GridEvaluator.java    | 199 ++++++++++++++++++---
 .../org/apache/sis/coverage/grid/GridExtent.java   |  24 +++
 .../apache/sis/coverage/grid/GridExtentTest.java   |   3 +
 .../apache/sis/internal/util/CollectionsExt.java   |   2 +-
 9 files changed, 304 insertions(+), 67 deletions(-)

diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/ValuesUnderCursor.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/ValuesUnderCursor.java
index ab13e5af93..8a8a1e0c2a 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/ValuesUnderCursor.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/ValuesUnderCursor.java
@@ -28,7 +28,6 @@ import java.text.NumberFormat;
 import java.text.DecimalFormat;
 import javafx.beans.value.ChangeListener;
 import javafx.beans.value.ObservableValue;
-import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.ReadOnlyProperty;
 import javafx.beans.value.WeakChangeListener;
 import javafx.collections.ObservableList;
@@ -41,17 +40,18 @@ import org.opengis.coverage.CannotEvaluateException;
 import org.opengis.metadata.content.TransferFunctionType;
 import org.apache.sis.referencing.operation.transform.TransferFunction;
 import org.apache.sis.gui.coverage.CoverageCanvas;
+import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.coverage.grid.GridEvaluator;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.coverage.Category;
 import org.apache.sis.internal.system.Modules;
+import org.apache.sis.internal.gui.GUIUtilities;
 import org.apache.sis.math.DecimalFunctions;
 import org.apache.sis.math.MathFunctions;
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.measure.UnitFormat;
 import org.apache.sis.util.Characters;
-import org.apache.sis.util.Localized;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Vocabulary;
 
@@ -68,7 +68,7 @@ import static java.util.logging.Logger.getLogger;
  * {@code ValuesUnderCursor} methods will be invoked from JavaFX thread.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
  * @since   1.1
  * @module
  */
@@ -142,22 +142,6 @@ public abstract class ValuesUnderCursor {
         return (owner == null) || owner.computeSizeOfSampleValues(main, 
others);
     }
 
-    /**
-     * Returns the locale of the JavaBean containing the given property, or 
{@code null} if unknown.
-     * The bean is typically an instance of {@link MapCanvas}.
-     *
-     * @see MapCanvas#getLocale()
-     */
-    private static Locale getLocale(final ObservableValue<?> property) {
-        if (property instanceof ReadOnlyProperty<?>) {
-            final Object bean = ((ReadOnlyProperty<?>) property).getBean();
-            if (bean instanceof Localized) {
-                return ((Localized) bean).getLocale();
-            }
-        }
-        return null;
-    }
-
     /**
      * Invoked when {@link StatusBar#sampleValuesProvider} changed. Each 
{@link ValuesUnderCursor} instance
      * can be used by at most one {@link StatusBar} instance. Current 
implementation silently does nothing
@@ -181,12 +165,14 @@ public abstract class ValuesUnderCursor {
      */
     static ValuesUnderCursor create(final MapCanvas canvas) {
         if (canvas instanceof CoverageCanvas) {
+            final CoverageCanvas cc = (CoverageCanvas) canvas;
             final FromCoverage listener = new FromCoverage();
-            final ObjectProperty<GridCoverage> coverageProperty = 
((CoverageCanvas) canvas).coverageProperty;
-            coverageProperty.addListener(new WeakChangeListener<>(listener));
-            final GridCoverage coverage = coverageProperty.get();
+            cc.coverageProperty.addListener(new 
WeakChangeListener<>(listener));
+            cc.sliceExtentProperty.addListener((p,o,n) -> 
listener.setSlice(n));
+            final GridCoverage coverage = cc.coverageProperty.get();
             if (coverage != null) {
                 listener.changed(null, null, coverage);
+                listener.setSlice(cc.getSliceExtent());
             }
             return listener;
         } else {
@@ -201,7 +187,7 @@ public abstract class ValuesUnderCursor {
      * values to show when the coverage is changed.
      *
      * @author  Martin Desruisseaux (Geomatys)
-     * @version 1.1
+     * @version 1.3
      * @since   1.1
      * @module
      */
@@ -285,18 +271,6 @@ public abstract class ValuesUnderCursor {
             
valueChoices.setText(Vocabulary.format(Vocabulary.Keys.SampleDimensions));
         }
 
-        /**
-         * Returns the grid coverage used as the source of sample values.
-         * This is usually the value of {@link 
CoverageCanvas#coverageProperty}.
-         *
-         * @return the source coverage, or {@code null} if none.
-         *
-         * @see CoverageCanvas#coverageProperty
-         */
-        public final GridCoverage getCoverage() {
-            return (evaluator != null) ? evaluator.getCoverage() : null;
-        }
-
         /**
          * Returns {@code true} if all bands are unselected.
          */
@@ -305,6 +279,19 @@ public abstract class ValuesUnderCursor {
             return selectedBands.isEmpty();
         }
 
+        /**
+         * Returns the canvas which contains the given property.
+         */
+        private static Optional<CoverageCanvas> canvas(final 
ObservableValue<?> property) {
+            if (property instanceof ReadOnlyProperty<?>) {
+                final Object bean = ((ReadOnlyProperty<?>) property).getBean();
+                if (bean instanceof CoverageCanvas) {
+                    return Optional.of((CoverageCanvas) bean);
+                }
+            }
+            return Optional.empty();
+        }
+
         /**
          * Notifies this {@code ValuesUnderCursor} object that it needs to 
display values for a new coverage.
          * The {@code previous} argument should be the argument given in the 
last call to this method and is
@@ -331,6 +318,7 @@ public abstract class ValuesUnderCursor {
             }
             evaluator = coverage.forConvertedValues(true).evaluator();
             evaluator.setNullIfOutside(true);
+            canvas(property).ifPresent((c) -> setSlice(c.getSliceExtent()));
             if (previous != null && 
bands.equals(previous.getSampleDimensions())) {
                 // Same configuration than previous coverage.
                 return;
@@ -354,7 +342,7 @@ public abstract class ValuesUnderCursor {
              */
             final Map<Integer,NumberFormat> sharedFormats = new HashMap<>();
             final Map<Unit<?>,String>       sharedSymbols = new HashMap<>();
-            final Locale                    locale        = 
getLocale(property);
+            final Locale                    locale        = 
GUIUtilities.getLocale(property);
             final UnitFormat                unitFormat    = new 
UnitFormat(locale);
             final CheckMenuItem[]           menuItems     = new 
CheckMenuItem[numBands];
             for (int b=0; b<numBands; b++) {
@@ -474,6 +462,16 @@ public abstract class ValuesUnderCursor {
             return item;
         }
 
+        /**
+         * Tells to the evaluator in which slice to evaluate coordinates.
+         * This method is invoked when {@link 
CoverageCanvas#sliceExtentProperty} changed its value.
+         */
+        final void setSlice(final GridExtent extent) {
+            if (evaluator != null) {
+                evaluator.setDefaultSlice(extent != null ? 
extent.getSliceCoordinates() : null);
+            }
+        }
+
         /**
          * Returns a string representation of data under given position.
          * The position may be in any CRS; this method will convert 
coordinates as needed.
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/GUIUtilities.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/GUIUtilities.java
index 63ec905c0f..939570eb0e 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/GUIUtilities.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/GUIUtilities.java
@@ -19,7 +19,8 @@ package org.apache.sis.internal.gui;
 import java.util.List;
 import java.util.ArrayList;
 import java.util.Collections;
-import javafx.beans.property.ObjectProperty;
+import java.util.Locale;
+import javafx.beans.property.ReadOnlyProperty;
 import javafx.beans.value.ObservableValue;
 import javafx.collections.ObservableList;
 import javafx.scene.Node;
@@ -38,6 +39,7 @@ import org.apache.sis.internal.referencing.Formulas;
 import org.apache.sis.measure.Quantities;
 import org.apache.sis.measure.Units;
 import org.apache.sis.util.Static;
+import org.apache.sis.util.Localized;
 import org.apache.sis.util.Workaround;
 
 
@@ -45,7 +47,7 @@ import org.apache.sis.util.Workaround;
  * Miscellaneous utility methods.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
  * @since   1.1
  * @module
  */
@@ -56,6 +58,22 @@ public final class GUIUtilities extends Static {
     private GUIUtilities() {
     }
 
+    /**
+     * Returns the locale of the JavaBean containing the given property, or 
{@code null} if unknown.
+     *
+     * @param  property  the property for which to get the locale, or {@code 
null}.
+     * @return the locale for the container of the given property, or {@code 
null}.
+     */
+    public static Locale getLocale(final ObservableValue<?> property) {
+        if (property instanceof ReadOnlyProperty<?>) {
+            final Object bean = ((ReadOnlyProperty<?>) property).getBean();
+            if (bean instanceof Localized) {
+                return ((Localized) bean).getLocale();
+            }
+        }
+        return null;
+    }
+
     /**
      * Returns the window of the bean associated to the given property.
      *
@@ -63,8 +81,8 @@ public final class GUIUtilities extends Static {
      * @return the window, or {@code null} if unknown.
      */
     public static Window getWindow(final ObservableValue<?> property) {
-        if (property instanceof ObjectProperty<?>) {
-            final Object bean = ((ObjectProperty<?>) property).getBean();
+        if (property instanceof ReadOnlyProperty<?>) {
+            final Object bean = ((ReadOnlyProperty<?>) property).getBean();
             if (bean instanceof Node) {
                 final Scene scene = ((Node) bean).getScene();
                 if (scene != null) {
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BufferedGridCoverage.java
 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BufferedGridCoverage.java
index 7759b9e5dc..0304be0419 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BufferedGridCoverage.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BufferedGridCoverage.java
@@ -26,6 +26,7 @@ import java.awt.image.DataBufferShort;
 import java.awt.image.DataBufferUShort;
 import java.awt.image.RasterFormatException;
 import java.awt.image.RenderedImage;
+import org.opengis.util.FactoryException;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.coverage.SampleDimension;
@@ -296,7 +297,7 @@ public class BufferedGridCoverage extends GridCoverage {
                  * So it should not be rethrown as 
PointOutsideCoverageException.
                  */
                 pos = Math.toIntExact(index);
-            } catch (ArithmeticException | TransformException ex) {
+            } catch (ArithmeticException | FactoryException | 
TransformException ex) {
                 throw new CannotEvaluateException(ex.getMessage(), ex);
             }
             final double[] values = this.values;
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
index e4dc6d2b3c..c2f6b8870a 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.coverage.grid;
 
+import java.util.Map;
 import java.util.List;
 import java.util.Arrays;
 import java.util.ArrayList;
@@ -248,6 +249,38 @@ final class ConvertedGridCoverage extends GridCoverage {
             evaluator = source.evaluator();
         }
 
+        /**
+         * Returns the default slice where to perform evaluation, or an empty 
map if unspecified.
+         */
+        @Override
+        public Map<Integer,Long> getDefaultSlice() {
+            return evaluator.getDefaultSlice();
+        }
+
+        /**
+         * Sets the default slice where to perform evaluation when the points 
do not have enough dimensions.
+         */
+        @Override
+        public void setDefaultSlice(Map<Integer,Long> slice) {
+            evaluator.setDefaultSlice(slice);
+        }
+
+        /**
+         * Returns {@code true} if this evaluator is allowed to wraparound 
coordinates that are outside the grid.
+         */
+        @Override
+        public boolean isWraparoundEnabled() {
+            return evaluator.isWraparoundEnabled();
+        }
+
+        /**
+         * Specifies whether this evaluator is allowed to wraparound 
coordinates that are outside the grid.
+         */
+        @Override
+        public void setWraparoundEnabled(final boolean allow) {
+            evaluator.setWraparoundEnabled(allow);
+        }
+
         /**
          * Forwards configuration to the wrapped evaluator.
          */
@@ -276,6 +309,14 @@ final class ConvertedGridCoverage extends GridCoverage {
             }
             return values;
         }
+
+        /**
+         * Converts the specified geospatial position to grid coordinates.
+         */
+        @Override
+        public FractionalGridCoordinates toGridCoordinates(final 
DirectPosition point) throws TransformException {
+            return evaluator.toGridCoordinates(point);
+        }
     }
 
     /**
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
index b6bf54843b..273e8b2e53 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
@@ -32,6 +32,7 @@ import java.awt.image.SampleModel;
 import org.opengis.metadata.spatial.DimensionNameType;
 import org.opengis.util.NameFactory;
 import org.opengis.util.InternationalString;
+import org.opengis.util.FactoryException;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
 import org.opengis.referencing.datum.PixelInCell;
@@ -545,7 +546,7 @@ public class GridCoverage2D extends GridCoverage {
             } catch (PointOutsideCoverageException ex) {
                 ex.setOffendingLocation(point);
                 throw ex;
-            } catch (RuntimeException | TransformException ex) {
+            } catch (RuntimeException | FactoryException | TransformException 
ex) {
                 throw new CannotEvaluateException(ex.getMessage(), ex);
             }
         }
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridEvaluator.java
 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridEvaluator.java
index 8feb79b4ef..d79883df73 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridEvaluator.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridEvaluator.java
@@ -16,25 +16,36 @@
  */
 package org.apache.sis.coverage.grid;
 
+import java.util.Map;
 import java.util.Arrays;
+import java.util.TreeMap;
+import java.util.Objects;
 import java.awt.image.RenderedImage;
 import org.opengis.util.FactoryException;
 import org.opengis.geometry.DirectPosition;
+import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.opengis.referencing.datum.PixelInCell;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.operation.CoordinateOperation;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.opengis.coverage.CannotEvaluateException;
 import org.opengis.coverage.PointOutsideCoverageException;
 import org.apache.sis.coverage.SampleDimension;
+import org.apache.sis.internal.feature.Resources;
+import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.internal.coverage.j2d.ImageUtilities;
 import org.apache.sis.internal.referencing.DirectPositionView;
 import org.apache.sis.internal.referencing.WraparoundAxesFinder;
+import org.apache.sis.referencing.operation.matrix.Matrices;
+import org.apache.sis.referencing.operation.matrix.MatrixSIS;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.referencing.operation.transform.TransformSeparator;
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.internal.system.Modules;
+import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.logging.Logging;
 
@@ -54,7 +65,7 @@ import static java.util.logging.Logger.getLogger;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
  *
  * @see GridCoverage#evaluator()
  *
@@ -68,20 +79,22 @@ public class GridEvaluator implements 
GridCoverage.Evaluator {
     private final GridCoverage coverage;
 
     /**
-     * The source coordinate reference system of the converter,
+     * The coordinate reference system of input points given to this converter,
      * or {@code null} if assumed the same than the coverage CRS.
+     * This is used by {@link #toGridPosition(DirectPosition)} for checking if 
{@link #inputToGrid} needs
+     * to be recomputed. As long at the evaluated points have the same CRS, 
the same transform is reused.
      */
-    private CoordinateReferenceSystem sourceCRS;
+    private CoordinateReferenceSystem inputCRS;
 
     /**
-     * The transform from {@link #sourceCRS} to grid coordinates.
+     * The transform from {@link #inputCRS} to grid coordinates.
      * This is cached for avoiding the costly process of fetching a coordinate 
operation
      * in the common case where the coordinate reference systems did not 
changed.
      */
-    private MathTransform sourceToGrid;
+    private MathTransform inputToGrid;
 
     /**
-     * Grid coordinates after {@link #sourceToGrid} conversion.
+     * Grid coordinates after {@link #inputToGrid} conversion.
      *
      * @see #toGridPosition(DirectPosition)
      */
@@ -130,6 +143,17 @@ public class GridEvaluator implements 
GridCoverage.Evaluator {
      */
     private double[] periods;
 
+    /**
+     * The slice where to perform evaluation, or {@code null} if not yet 
computed.
+     * This information allows to specify for example two-dimensional points 
for
+     * evaluating in a three-dimensional data cube. This is used for completing
+     * the missing coordinate values.
+     *
+     * @see #getDefaultSlice()
+     * @see #setDefaultSlice(Map)
+     */
+    private Map<Integer,Long> slice;
+
     /**
      * Creates a new evaluator for the given coverage. This constructor is 
protected for allowing
      * {@link GridCoverage} subclasses to provide their own {@code 
GridEvaluator} implementations.
@@ -154,6 +178,57 @@ public class GridEvaluator implements 
GridCoverage.Evaluator {
         return coverage;
     }
 
+    /**
+     * Returns the default slice where to perform evaluation, or an empty map 
if unspecified.
+     * Keys are dimensions from 0 inclusive to {@link 
GridGeometry#getDimension()} exclusive,
+     * and values are the grid coordinate of the slice in that dimension.
+     *
+     * <p>This information allows to invoke {@link #apply(DirectPosition)} 
with for example two-dimensional points
+     * even if the underlying coverage is three-dimensional. The missing 
coordinate values are replaced by the
+     * values provided in the map.</p>
+     *
+     * @return the default slice where to perform evaluation, or an empty map 
if unspecified.
+     *
+     * @since 1.3
+     */
+    @SuppressWarnings("ReturnOfCollectionOrArrayField")     // Because the map 
is unmodifiable.
+    public Map<Integer,Long> getDefaultSlice() {
+        if (slice == null) {
+            final GridExtent extent = coverage.getGridGeometry().getExtent();
+            slice = 
CollectionsExt.unmodifiableOrCopy(extent.getSliceCoordinates());
+        }
+        return slice;
+    }
+
+    /**
+     * Sets the default slice where to perform evaluation when the points do 
not have enough dimensions.
+     * A {@code null} argument restore the default value, which is to infer 
the slice from the coverage
+     * grid geometry.
+     *
+     * @param  slice  the default slice where to perform evaluation, or an 
empty map if none.
+     * @throws IllegalArgumentException if the map contains an illegal 
dimension or grid coordinate value.
+     *
+     * @since 1.3
+     */
+    public void setDefaultSlice(Map<Integer,Long> slice) {
+        if (!Objects.equals(this.slice, slice)) {
+            if (slice != null) {
+                slice = CollectionsExt.unmodifiableOrCopy(new 
TreeMap<>(slice));
+                final GridExtent extent = 
coverage.getGridGeometry().getExtent();
+                final int max = extent.getDimension() - 1;
+                for (final Map.Entry<Integer,Long> entry : slice.entrySet()) {
+                    final int dim = entry.getKey();
+                    ArgumentChecks.ensureBetween("slice.key", 0, max, dim);
+                    
ArgumentChecks.ensureBetween(extent.getAxisIdentification(dim, dim).toString(),
+                                        extent.getLow(dim), 
extent.getHigh(dim), entry.getValue());
+                }
+            }
+            this.slice  = slice;
+            inputCRS    = null;
+            inputToGrid = null;
+        }
+    }
+
     /**
      * Returns {@code true} if this evaluator is allowed to wraparound 
coordinates that are outside the grid.
      * The initial value is {@code false}. This method may continue to return 
{@code false} even after a call
@@ -305,7 +380,7 @@ public class GridEvaluator implements 
GridCoverage.Evaluator {
         } catch (PointOutsideCoverageException ex) {
             ex.setOffendingLocation(point);
             throw ex;
-        } catch (RuntimeException | TransformException ex) {
+        } catch (RuntimeException | FactoryException | TransformException ex) {
             throw new CannotEvaluateException(ex.getMessage(), ex);
         }
         return null;        // May reach this point only if `nullIfOutside` is 
true.
@@ -350,7 +425,11 @@ public class GridEvaluator implements 
GridCoverage.Evaluator {
      */
     public FractionalGridCoordinates toGridCoordinates(final DirectPosition 
point) throws TransformException {
         ArgumentChecks.ensureNonNull("point", point);
-        return new FractionalGridCoordinates(toGridPosition(point));
+        try {
+            return new FractionalGridCoordinates(toGridPosition(point));
+        } catch (FactoryException e) {
+            throw new TransformException(e.getMessage(), e);
+        }
     }
 
     /**
@@ -360,30 +439,25 @@ public class GridEvaluator implements 
GridCoverage.Evaluator {
      *
      * @param  point  the geospatial position.
      * @return the given position converted to grid coordinates (possibly out 
of grid bounds).
+     * @throws FactoryException if no operation is found form given point CRS 
to coverage CRS.
      * @throws TransformException if the given position can not be converted.
      */
-    final FractionalGridCoordinates.Position toGridPosition(final 
DirectPosition point) throws TransformException {
+    final FractionalGridCoordinates.Position toGridPosition(final 
DirectPosition point)
+            throws FactoryException, TransformException
+    {
+        /*
+         * If the `inputToGrid` transform has not yet been computed or is 
outdated, compute now.
+         * The result will be cached and reused as long as the `inputCRS` is 
the same.
+         */
         final CoordinateReferenceSystem crs = 
point.getCoordinateReferenceSystem();
-        if (crs != sourceCRS || sourceToGrid == null) {
-            final GridGeometry gridGeometry = coverage.getGridGeometry();
-            MathTransform tr = 
gridGeometry.getGridToCRS(PixelInCell.CELL_CENTER).inverse();
-            if (crs != null) try {
-                CoordinateOperation op = CRS.findOperation(crs,
-                        coverage.getCoordinateReferenceSystem(),
-                        gridGeometry.geographicBBox());
-                tr = MathTransforms.concatenate(op.getMathTransform(), tr);
-            } catch (FactoryException e) {
-                throw new TransformException(e.getMessage(), e);
-            }
-            position     = new 
FractionalGridCoordinates.Position(tr.getTargetDimensions());
-            sourceCRS    = crs;
-            sourceToGrid = tr;
+        if (crs != inputCRS || inputToGrid == null) {
+            setInputCRS(crs);
         }
         /*
          * Transform geospatial coordinates to grid coordinates. Result is 
unconditionally stored
          * in the `position` object, which will be copied by the caller if 
needed for public API.
          */
-        final DirectPosition result = sourceToGrid.transform(point, position);
+        final DirectPosition result = inputToGrid.transform(point, position);
         if (result != position) {
             // Should not happen, but be paranoiac.
             final double[] coordinates = position.coordinates;
@@ -460,6 +534,83 @@ public class GridEvaluator implements 
GridCoverage.Evaluator {
         return position;
     }
 
+    /**
+     * Recomputes the {@link #inputToGrid} field. This method should be 
invoked when the transform
+     * has not yet been computed or became outdated because {@link #inputCRS} 
needs to be changed.
+     *
+     * @param  crs  the new value to assign to {@link #inputCRS}.
+     */
+    private void setInputCRS(final CoordinateReferenceSystem crs)
+            throws FactoryException, NoninvertibleTransformException
+    {
+        final GridGeometry gridGeometry = coverage.getGridGeometry();
+        MathTransform gridToCRS = 
gridGeometry.getGridToCRS(PixelInCell.CELL_CENTER);
+        MathTransform crsToGrid = gridToCRS.inverse();
+        if (crs != null) {
+            final CoordinateReferenceSystem stepCRS = 
coverage.getCoordinateReferenceSystem();
+            final GeographicBoundingBox areaOfInterest = 
gridGeometry.geographicBBox();
+            try {
+                CoordinateOperation op = CRS.findOperation(crs, stepCRS, 
areaOfInterest);
+                crsToGrid = MathTransforms.concatenate(op.getMathTransform(), 
crsToGrid);
+            } catch (FactoryException main) {
+                /*
+                 * Above block tried to compute a "CRS to grid" transform in 
the most direct way.
+                 * It covers the usual case where the point has the required 
number of dimensions,
+                 * and works better if the point has more dimensions (extra 
dimensions are ignored).
+                 * The following block covers the opposite case, where the 
point does not have enough
+                 * dimensions. We try to fill missing dimensions with the help 
of the `slice` map.
+                 */
+                final Map<Integer,Long> slice = getDefaultSlice();
+                try {
+                    CoordinateOperation op = CRS.findOperation(stepCRS, crs, 
areaOfInterest);
+                    gridToCRS = MathTransforms.concatenate(gridToCRS, 
op.getMathTransform());
+                    final TransformSeparator ts = new 
TransformSeparator(gridToCRS);
+                    final int  crsDim = gridToCRS.getTargetDimensions();
+                    final int gridDim = gridToCRS.getSourceDimensions();
+                    int[] mandatory = new int[gridDim];
+                    int n = 0;
+                    for (int i=0; i<gridDim; i++) {
+                        if (!slice.containsKey(i)) {
+                            mandatory[n++] = i;
+                        }
+                    }
+                    mandatory = ArraysExt.resize(mandatory, n);
+                    ts.addSourceDimensions(mandatory);          // Retain grid 
dimensions having no default value.
+                    ts.setSourceExpandable(true);               // Retain more 
grid dimensions if they are required.
+                    ts.addTargetDimensionRange(0, crsDim);      // Force 
retention of all CRS dimensions.
+                    gridToCRS = ts.separate();
+                    crsToGrid = gridToCRS.inverse();            // With less 
source dimensions, may be invertible now.
+                    mandatory = ts.getSourceDimensions();       // Output grid 
dimensions computed by `crsToGrid`.
+                    final int valueColumn = mandatory.length;   // Matrix 
column where to write default values.
+                    final MatrixSIS m = Matrices.createZero(gridDim+1, 
valueColumn+1);
+                    m.setElement(gridDim, valueColumn, 1);
+                    n = 0;
+                    for (int j=0; j<gridDim; j++) {
+                        if (Arrays.binarySearch(mandatory, j) >= 0) {
+                            m.setElement(j, n++, 1);            // Computed 
value to pass through.
+                        } else {
+                            final Long value = slice.get(j);
+                            if (value == null) {
+                                final GridExtent extent = gridGeometry.extent;
+                                throw new 
FactoryException(Resources.format(Resources.Keys.NoNDimensionalSlice_3,
+                                                crsDim, 
extent.getAxisIdentification(j, j), extent.getSize(j)));
+                            }
+                            m.setElement(j, valueColumn, value);
+                        }
+                    }
+                    crsToGrid = MathTransforms.concatenate(crsToGrid, 
MathTransforms.linear(m));
+                } catch (RuntimeException | FactoryException | 
NoninvertibleTransformException ex) {
+                    main.addSuppressed(ex);
+                    throw main;
+                }
+            }
+        }
+        // Modify fields only after everything else succeeded.
+        position     = new 
FractionalGridCoordinates.Position(crsToGrid.getTargetDimensions());
+        inputCRS    = crs;
+        inputToGrid = crsToGrid;
+    }
+
     /**
      * Invoked when a recoverable exception occurred.
      * Those exceptions must be minor enough that they can be silently ignored 
in most cases.
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
index 0dab502a06..2616661c94 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
@@ -18,6 +18,8 @@ package org.apache.sis.coverage.grid;
 
 import java.util.Map;
 import java.util.HashMap;
+import java.util.TreeMap;
+import java.util.SortedMap;
 import java.util.Arrays;
 import java.util.Optional;
 import java.util.Locale;
@@ -831,6 +833,28 @@ public class GridExtent implements GridEnvelope, 
LenientComparable, Serializable
         return center;
     }
 
+    /**
+     * Returns the grid coordinates for all dimensions where the grid has a 
size of 1.
+     * Keys are dimensions as values from 0 inclusive to {@link 
#getDimension()} exclusive.
+     * Values are the {@linkplain #getLow(int) low} and {@linkplain 
#getHigh(int) high} coordinates
+     * (which are equal) in the associated dimension.
+     *
+     * @return grid coordinates for all dimensions where the grid has a size 
of 1.
+     *
+     * @since 1.3
+     */
+    public SortedMap<Integer,Long> getSliceCoordinates() {
+        final TreeMap<Integer,Long> slice = new TreeMap<>();
+        final int dimension = getDimension();
+        for (int i=0; i<dimension; i++) {
+            final long value = coordinates[i];
+            if (value == coordinates[i + dimension]) {
+                slice.put(i, value);
+            }
+        }
+        return slice;
+    }
+
     /**
      * Returns indices of all dimensions where this grid extent has a size 
greater than 1.
      * This method can be used for getting the grid extent of a 
<var>s</var>-dimensional slice
diff --git 
a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
 
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
index 5bbdbc32ff..843d330b46 100644
--- 
a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
+++ 
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
@@ -34,6 +34,7 @@ import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.operation.matrix.Matrix3;
 import org.apache.sis.referencing.crs.HardCodedCRS;
 import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.internal.jdk9.JDK9;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
@@ -310,10 +311,12 @@ public final strictfp class GridExtentTest extends 
TestCase {
 
     /**
      * Tests {@link GridExtent#getSubspaceDimensions(int)}.
+     * Opportunistically tests {@link GridExtent#getSliceCoordinates()} since 
the two methods closely related.
      */
     @Test
     public void testGetSubspaceDimensions() {
         final GridExtent extent = new GridExtent(null, new long[] {100, 5, 
200, 40}, new long[] {500, 5, 800, 40}, true);
+        assertMapEquals(JDK9.mapOf(1, 5L, 3, 40L), 
extent.getSliceCoordinates());
         assertArrayEquals(new int[] {0,  2  }, 
extent.getSubspaceDimensions(2));
         assertArrayEquals(new int[] {0,1,2  }, 
extent.getSubspaceDimensions(3));
         assertArrayEquals(new int[] {0,1,2,3}, 
extent.getSubspaceDimensions(4));
diff --git 
a/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java
 
b/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java
index e286296ac1..1564bd970f 100644
--- 
a/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java
+++ 
b/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java
@@ -427,7 +427,7 @@ public final class CollectionsExt extends Static {
      *
      * @see #compact(Map)
      *
-     * @todo Replace by {@code Map.copyOf(Map)} on JDK10.
+     * @todo Replace by {@code Map.copyOf(Map)} on JDK10, except when order 
matter ({@link LinkedHashMap}).
      */
     public static <K,V> Map<K,V> unmodifiableOrCopy(Map<K,V> map) {
         if (map != null) {

Reply via email to