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) {