This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new f76a6ab457 Add 3 methods for getting real world coordinates from a 
`GridGeometry`:
f76a6ab457 is described below

commit f76a6ab4574f4d0b79bc8a3485c18a7e971c561a
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri Nov 6 19:20:45 2026 +0100

    Add 3 methods for getting real world coordinates from a `GridGeometry`:
    
    * latticePointStream(PixelInCell, CoordinateReferenceSystem)
    * latticePointStream1D(PixelInCell, CoordinateReferenceSystem)
    * instantStream(PixelInCell)
---
 .../org/apache/sis/coverage/grid/GridExtent.java   | 187 +++++---------
 .../org/apache/sis/coverage/grid/GridGeometry.java | 144 +++++++++--
 .../sis/coverage/grid/GridPointIterator.java       | 286 +++++++++++++++++++++
 .../apache/sis/coverage/grid/LatticeIterator.java  | 209 +++++++++++++++
 .../apache/sis/coverage/grid/GridGeometryTest.java |  56 ++++
 .../org/apache/sis/geometry/AbstractEnvelope.java  |   2 +-
 .../main/org/apache/sis/geometry/Envelopes.java    |   2 +-
 .../org/apache/sis/geometry/GeneralEnvelope.java   |   2 +-
 .../referencing/internal/ServicesForMetadata.java  |   6 +-
 .../internal/shared/CoordinateOperations.java      |   8 +
 .../internal/shared/DirectPositionView.java        |  10 +-
 .../internal/shared/TemporalAccessor.java          |  15 +-
 .../operation/builder/ProjectedTransformTry.java   |  11 +-
 .../operation/transform/AbstractMathTransform.java |   9 +-
 .../operation/transform/ConcatenatedTransform.java |   1 +
 .../transform/AbstractMathTransformTest.java       |   3 +-
 .../operation/transform/MathTransformTestCase.java |   3 +-
 .../apache/sis/storage/tiling/TileReadEvent.java   |   6 +-
 18 files changed, 783 insertions(+), 177 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java
index f96be9ba4c..1e4dad52fd 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java
@@ -23,10 +23,9 @@ import java.util.Arrays;
 import java.util.Optional;
 import java.util.Objects;
 import java.util.Comparator;
-import java.util.Spliterator;
 import java.util.stream.Stream;
+import java.util.stream.DoubleStream;
 import java.util.stream.StreamSupport;
-import java.util.function.Consumer;
 import java.util.logging.Logger;
 import java.io.Serializable;
 import java.io.IOException;
@@ -100,7 +99,7 @@ import org.opengis.coverage.grid.GridCoordinates;
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Alexis Manin (Geomatys)
  * @author  Johann Sorel (Geomatys)
- * @version 1.7
+ * @version 1.8
  * @since SIS 1.0
  */
 public class GridExtent implements GridEnvelope, LenientComparable, 
Serializable {
@@ -2246,7 +2245,7 @@ public class GridExtent implements GridEnvelope, 
LenientComparable, Serializable
     }
 
     /**
-     * Create a stream of all lattice points contained inside this grid extent.
+     * Creates a stream of all lattice points contained inside this grid 
extent.
      * For each dimension, the coordinate values range from {@linkplain 
#getLow(int) low}
      * to {@linkplain #getHigh(int) high} inclusive.
      * The stream is ordered from first dimension varying the fastest to last 
dimension varying the slowest.
@@ -2256,138 +2255,70 @@ public class GridExtent implements GridEnvelope, 
LenientComparable, Serializable
      * @return stream of lattice points inside this grid extent.
      *
      * @see #getLatticePointCount()
+     *
      * @since SIS 1.7
      */
     public Stream<long[]> latticePointStream(final boolean parallel) {
-        return StreamSupport.stream(new Iter(coordinates.clone()), parallel);
+        return StreamSupport.stream(new 
LatticeIterator.Grid(coordinates.clone()), parallel);
     }
 
     /**
-     * Iterator over all grid coordinates included inside the grid extent, low 
and high values included.
-     * Iteration is ordered from first dimension varying the fastest to last 
dimension varying the slowest.
+     * Creates a stream of all lattice points inside this grid extent 
transformed with the given {@code gridToCRS}.
+     * The source coordinates given to {@code gridToCRS} are as computed by 
{@link #latticePointStream(boolean)} in
+     * the same iteration order. The target coordinates computed by {@code 
gridToCRS} are the elements traversed by
+     * the stream, in the same order.
+     *
+     * <p>If the given {@code gridToCRS} throws a {@link TransformException} 
during traversal, the exception will be
+     * wrapped in a {@link BackingStoreException}. There is no guarantee that 
points successfully transformed before
+     * the exception will be consumed before the {@link BackingStoreException} 
is thrown.</p>
+     *
+     * @param  gridToCRS  the transform to apply on grid coordinates.
+     * @param  parallel   whether to return a parallel stream.
+     * @return stream of all transformed lattice points.
+     * @throws MismatchedDimensionException if the number of source dimensions 
of {@code gridToCRS}
+     *         is not the {@linkplain #getDimension() number of dimensions} of 
this grid extent.
+     *
+     * @see #getLatticePointCount()
+     * @see #latticePointStream(boolean)
+     * @see GridGeometry#latticePointStream(PixelInCell, 
CoordinateReferenceSystem)
+     *
+     * @since SIS 1.8
      */
-    private static final class Iter implements Spliterator<long[]> {
-        /**
-         * The low coordinates followed by the high coordinates, inclusive.
-         * This is either a copy of the {@link GridExtent#coordinates} array,
-         * or a subregion if the iterator has been split.
-         */
-        private final long[] extent;
-
-        /**
-         * The current position.
-         */
-        private final long[] current;
-
-        /**
-         * Whether the iteration is finished.
-         */
-        private boolean done;
-
-        /**
-         * Creates a new iterator with the given low and high coordinate 
values.
-         * The given array may be modified in-place if the iterator is split.
-         *
-         * @param  extent  the low coordinates followed by the high 
coordinates, inclusive.
-         */
-        Iter(final long[] extent) {
-            this.extent  = extent;
-            this.current = Arrays.copyOf(extent, extent.length >>> 1);
-        }
-
-        /**
-         * Returns the characteristics of this iterator: ordered, size known, 
without duplicated value.
-         */
-        @Override
-        public int characteristics() {
-            return ORDERED | DISTINCT | SIZED | NONNULL | IMMUTABLE | SUBSIZED;
-        }
-
-        /**
-         * Returns the exact number of points inside the extent if that number 
does not overflow.
-         * In case of size exceeding the capacity of 64-bits integers, returns 
{@link Long#MAX_VALUE}.
-         */
-        @Override
-        public long estimateSize() {
-            long count = 1;
-            final int dimension = extent.length >>> 1;
-            for (int i=0; i<dimension; i++) {
-                try {
-                    long span = Math.subtractExact(extent[dimension + i], 
extent[i]);
-                    count = Math.multiplyExact(count, 
Math.incrementExact(span));
-                } catch (ArithmeticException e) {
-                    Logging.ignorableException(LOGGER, GridExtent.class, 
"latticePointStream", e);
-                    return Long.MAX_VALUE;
-                }
-            }
-            return count;
-        }
-
-        /**
-         * Tries to return an iterator covering a prefix of the points covered 
by this iterator.
-         * If successful, then after this method call this iterator covers the 
remaining suffix.
-         */
-        @Override
-        public Spliterator<long[]> trySplit() {
-            final int dimension = extent.length >>> 1;
-            for (int i = dimension; --i >= 0;) {
-                long span = Math.subtractExact(extent[dimension + i], 
extent[i]);
-                if (span != 0) {
-                    final long[] prefix = extent.clone();
-                    final long separation = prefix[i] + (span >>> 1);
-                    prefix[i + dimension] = separation;
-                    current[i] = extent[i] = separation + 1;
-                    return new Iter(prefix);
-                }
-            }
-            return null;
-        }
-
-        /**
-         * Moves to the next point and checks whether there are more lattice 
points to iterate over.
-         *
-         * @param  dimension  value of {@code extent.length >>> 1}.
-         * @return whether there is more lattice points after this method call.
-         */
-        private boolean next(final int dimension) {
-            for (int i=0; i<dimension; i++) {
-                if (current[i] < extent[i + dimension]) {
-                    current[i]++;
-                    return true;
-                }
-                current[i] = extent[i];
-            }
-            return false;
-        }
-
-        /**
-         * Preforms the given action on the next lattice point if it exists.
-         *
-         * @param  action  the action to execute.
-         * @return whether a remaining point existed.
-         */
-        @Override
-        public boolean tryAdvance(final Consumer<? super long[]> action) {
-            if (done) return false;
-            action.accept(current.clone());
-            done = !next(extent.length >>> 1);
-            return true;
-        }
+    public Stream<double[]> latticePointStream(final MathTransform gridToCRS, 
final boolean parallel) {
+        ArgumentChecks.ensureDimensionsMatch("gridToCRS", getDimension(), 
gridToCRS.getTargetDimensions(), gridToCRS);
+        return StreamSupport.stream(new 
GridPointIterator.MultiDimensional(coordinates.clone(), gridToCRS), parallel);
+    }
 
-        /**
-         * Preforms the given action on all remaining lattice points.
-         *
-         * @param  action  the action to execute.
-         */
-        @Override
-        public void forEachRemaining(Consumer<? super long[]> action) {
-            final int dimension = extent.length >>> 1;
-            if (!done) {
-                do action.accept(current.clone());
-                while (next(dimension));
-                done = true;
-            }
-        }
+    /**
+     * Creates a stream of all lattice points inside this grid extent 
transformed with the given {@code gridToCRS}.
+     * The source coordinates given to {@code gridToCRS} are as computed by 
{@link #latticePointStream(boolean)} in
+     * the same iteration order. The target coordinates computed by {@code 
gridToCRS} are the elements traversed by
+     * the stream, in the same order. The number of target dimensions in the 
{@code gridToCRS} transform must be 1.
+     *
+     * <p>This method is a variant of the {@linkplain 
#latticePointStream(MathTransform, boolean)
+     * multi-dimensional method} with coordinates returned as primitive types 
instead of arrays of length 1.
+     * This alternative avoids the creations of a temporary array for each 
point.</p>
+     *
+     * <p>If the given {@code gridToCRS} throws a {@link TransformException} 
during traversal, the exception will be
+     * wrapped in a {@link BackingStoreException}. There is no guarantee that 
points successfully transformed before
+     * the exception will be consumed before the {@link BackingStoreException} 
is thrown.</p>
+     *
+     * @param  gridToCRS  the transform to apply on grid coordinates. The 
number of target dimension must be 1.
+     * @param  parallel   whether to return a parallel stream.
+     * @return stream of all transformed lattice points.
+     * @throws MismatchedDimensionException if the number of source dimensions 
of {@code gridToCRS}
+     *         is not the {@linkplain #getDimension() number of dimensions} of 
this grid extent,
+     *         or if the number of target dimensions is not 1.
+     *
+     * @see #getLatticePointCount()
+     * @see #latticePointStream(boolean)
+     * @see GridGeometry#latticePointStream1D(PixelInCell, 
CoordinateReferenceSystem)
+     *
+     * @since SIS 1.8
+     */
+    public DoubleStream latticePointStream1D(final MathTransform gridToCRS, 
final boolean parallel) {
+        ArgumentChecks.ensureDimensionsMatch("gridToCRS", getDimension(), 1, 
Objects.requireNonNull(gridToCRS));
+        return StreamSupport.doubleStream(new 
GridPointIterator.OneDimensional(coordinates.clone(), gridToCRS), parallel);
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
index 62128a741c..5715e1cc1f 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
@@ -21,6 +21,8 @@ import java.util.BitSet;
 import java.util.Locale;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.stream.Stream;
+import java.util.stream.DoubleStream;
 import java.time.Instant;
 import java.text.NumberFormat;
 import java.math.RoundingMode;
@@ -54,11 +56,13 @@ import org.apache.sis.geometry.ImmutableDirectPosition;
 import org.apache.sis.coordinate.DefaultCoordinateMetadata;
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.crs.AbstractCRS;
+import org.apache.sis.referencing.crs.DefaultTemporalCRS;
 import org.apache.sis.referencing.operation.CoordinateOperationContext;
 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.LinearTransform;
+import org.apache.sis.referencing.operation.transform.TransformSeparator;
 import org.apache.sis.referencing.operation.transform.PassThroughTransform;
 import 
org.apache.sis.referencing.operation.transform.TransformFactoryException;
 import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
@@ -90,7 +94,6 @@ import org.apache.sis.util.logging.Logging;
 import org.apache.sis.io.TableAppender;
 import org.apache.sis.xml.NilObject;
 import org.apache.sis.xml.NilReason;
-import static org.apache.sis.referencing.CRS.getDimensionOrZero;
 import static org.apache.sis.referencing.CRS.findOperation;
 import static org.apache.sis.referencing.CRS.SeparationMode;
 
@@ -1191,6 +1194,8 @@ public class GridGeometry implements LenientComparable, 
Serializable {
      * in the first element and the end time (exclusive) in the last element.
      *
      * @return time range as an array of length 0 (if none), 1 or 2.
+     *
+     * @see #instantStream(PixelInCell)
      */
     public Instant[] getTemporalExtent() {
         Instant[] times = timeRange();
@@ -1207,7 +1212,7 @@ public class GridGeometry implements LenientComparable, 
Serializable {
     private Instant[] timeRange() {
         Instant[] times = timeRange;
         if (times == null) {
-            final TemporalAccessor t = 
TemporalAccessor.of(getCoordinateReferenceSystem(envelope), 0);
+            final TemporalAccessor t = 
TemporalAccessor.of(getCoordinateReferenceSystem(envelope));
             times = (t != null) ? t.getTimeBounds(envelope) : 
TemporalAccessor.EMPTY;
             timeRange = times;
         }
@@ -1220,17 +1225,16 @@ public class GridGeometry implements LenientComparable, 
Serializable {
      * All non-constant coordinates are set to NaN. If this method returns a 
non-empty value,
      * then it is guaranteed to contain at least one non-NaN value.
      *
-     * <h4>Coordinate Reference System</h4>
-     * The <abbr>CRS</abbr> of the returned position may be {@link 
#getCoordinateReferenceSystem()}.
+     * <p>The <abbr>CRS</abbr> of the returned position may be {@link 
#getCoordinateReferenceSystem()}.
      * But it may also be a subset of the <abbr>CRS</abbr> components 
containing only the components
      * having at least one non-NaN value. For example, if only the time 
coordinate is constant, then
-     * the <abbr>CRS</abbr> of the returned position may contain only the 
temporal component.
+     * the <abbr>CRS</abbr> of the returned position may contain only the 
temporal component.</p>
      *
-     * <h4>Usage</h4>
-     * This is a helper method for computing coordinate operations with grid 
geometries that are,
+     * <p><b>Usage:</b>
+     * this is a helper method for computing coordinate operations with grid 
geometries that are,
      * for example, two-dimensional slices in a three- or four-dimensional 
data cube.
      * The algorithm should work with any number of dimensions, the 
two-dimensional slice is only an example.
-     * This method is intended to be used with {@link 
CoordinateOperationContext} as below:
+     * This method is intended to be used with {@link 
CoordinateOperationContext} as below:</p>
      *
      * {@snippet lang="java" :
      *     GridGeometry gg = ...;
@@ -1946,9 +1950,6 @@ public class GridGeometry implements LenientComparable, 
Serializable {
      * to the given <abbr>CRS</abbr>. The {@linkplain #getGeographicExtent() 
geographic bounding box} of this grid
      * geometry is used as the desired domain of validity.
      *
-     * <p>If this grid geometry has no <abbr>CRS</abbr> but the number of 
dimensions of the "real world" space is
-     * equal to the number of dimensions of the {@code target}, then this 
method returns an identity operation.</p>
-     *
      * @param  target  the target <abbr>CRS</abbr> of the desired operation.
      * @return coordinate operation from the <abbr>CRS</abbr> of this grid 
geometry to the given <abbr>CRS</abbr>.
      * @throws IncompleteGridGeometryException if this grid geometry does not 
have sufficient information.
@@ -1958,11 +1959,7 @@ public class GridGeometry implements LenientComparable, 
Serializable {
      */
     public CoordinateOperation createChangeOfCRS(final 
CoordinateReferenceSystem target) throws FactoryException {
         ArgumentChecks.ensureNonNull("target", target);
-        CoordinateReferenceSystem crs = getCoordinateReferenceSystem(envelope);
-        if (crs == null) {
-            crs = (getDimensionOrZero(target) == getTargetDimension()) ? 
target : getCoordinateReferenceSystem();
-        }
-        return findOperation(crs, target, geographicBBox());
+        return findOperation(getCoordinateReferenceSystem(), target, 
geographicBBox());
     }
 
     /**
@@ -2000,6 +1997,121 @@ public class GridGeometry implements LenientComparable, 
Serializable {
         return MathTransforms.concatenate(getGridToCRS(anchor), tr);
     }
 
+    /**
+     * Creates a stream of lattice points inside the grid extent and 
transformed to the given <abbr>CRS</abbr>.
+     * If the given <abbr>CRS</abbr> is {@code null}, then it is assumed the 
same <abbr>CRS</abbr> as this
+     * grid geometry — that <abbr>CRS</abbr> does not need to be {@linkplain 
#isDefined(int) defined}.
+     * If the given <abbr>CRS</abbr> contains only a subset of the dimensions 
of
+     * {@linkplain #getCoordinateReferenceSystem() this grid geometry 
<abbr>CRS</abbr>},
+     * then the stream will iterate only over the necessary grid dimensions.
+     *
+     * <p><b>Example:</b> if this grid geometry is multi-dimensional with 
horizontal, vertical and temporal axes,
+     * and if the horizontal axes are independent of the vertical and temporal 
axes, then the stream created for
+     * the {@code EPSG:4326} <abbr>CRS</abbr> (<abbr>WGS</abbr> 1984) will 
iterate only on the rows and columns.
+     * It will not iterate on the grid coordinates for the vertical and 
temporal axes.</p>
+     *
+     * Grid coordinates are traversed in the order defined by {@link 
GridExtent#latticePointStream(boolean)},
+     * but the order seen after transformation from grid to <abbr>CRS</abbr> 
coordinates can be anything.
+     * If a {@link TransformException} occurs during traversal, the exception 
will be wrapped in a
+     * {@link BackingStoreException}. There is no guarantee that points 
successfully transformed
+     * before the exception will be consumed before the {@link 
BackingStoreException} is thrown.
+     *
+     * @param  source  the cell part to map (center or corner).
+     * @param  target  the <abbr>CRS</abbr> in which to get the coordinate 
tuples,
+     *                 or {@code null} for no change compared to the 
<abbr>CRS</abbr> of this grid geometry.
+     * @return lattice points as coordinate tuples expressed in the given 
<abbr>CRS</abbr>.
+     * @throws IncompleteGridGeometryException if this grid geometry does not 
have sufficient information.
+     * @throws FactoryException if the coordinate operation cannot be found.
+     *
+     * @see GridExtent#latticePointStream(MathTransform, booleam)
+     *
+     * @since SIS 1.8
+     */
+    public Stream<double[]> latticePointStream(final PixelInCell source, final 
CoordinateReferenceSystem target)
+            throws FactoryException
+    {
+        MathTransform tr = getGridToCRS(source);
+        GridExtent slice = getExtent();
+        if (target != null) {
+            final var trs = new 
TransformSeparator(MathTransforms.concatenate(tr, 
createChangeOfCRS(target).getMathTransform()));
+            tr = trs.separate();    // Drop unused source dimensions.
+            slice = slice.selectDimensions(trs.getSourceDimensions());
+        }
+        return slice.latticePointStream(tr, false);
+    }
+
+    /**
+     * Creates a stream of lattice coordinates inside the grid extent and 
transformed to the given <abbr>CRS</abbr>.
+     * The {@code target} argument shall be a one-dimensional <abbr>CRS</abbr> 
such as a vertical or temporal component.
+     * The stream will traverse all coordinate values on that axis with 
duplicated values avoided on a best-effort basis.
+     * Grid coordinates are traversed in the order defined by {@link 
GridExtent#latticePointStream(boolean)},
+     * but the order seen after transformation from grid to <abbr>CRS</abbr> 
coordinates can be anything.
+     *
+     * <p>This method is a variant of the {@linkplain 
#latticePointStream(PixelInCell, CoordinateReferenceSystem)
+     * multi-dimensional method} with coordinates returned as primitive types 
instead of arrays of length 1.
+     * This alternative avoids the creations of a temporary array for each 
point.</p>
+     *
+     * <p>If a {@link TransformException} occurs during traversal, the 
exception will be wrapped in a
+     * {@link BackingStoreException}. There is no guarantee that points 
successfully transformed before
+     * the exception will be consumed before the {@link BackingStoreException} 
is thrown.</p>
+     *
+     * @param  source  the cell part to map (center or corner).
+     * @param  target  the one-dimensional <abbr>CRS</abbr> in which to get 
the coordinate values, or {@code null}
+     *                 if this grid geometry is one dimensional and no 
<abbr>CRS</abbr> change is desired.
+     * @return values from a subset of grid extent dimensions expressed in the 
given <abbr>CRS</abbr>.
+     * @throws MismatchedDimensionException if the given <abbr>CRS</abbr> is 
not one-dimensional,
+     *         or is {@code null} and this grid geometry is not 
one-dimensional.
+     * @throws IncompleteGridGeometryException if this grid geometry does not 
have sufficient information.
+     * @throws FactoryException if the coordinate operation cannot be found.
+     *
+     * @see GridExtent#latticePointStream1D(MathTransform, booleam)
+     *
+     * @since SIS 1.8
+     */
+    public DoubleStream latticePointStream1D(final PixelInCell source, final 
CoordinateReferenceSystem target)
+            throws FactoryException
+    {
+        ArgumentChecks.ensureDimensionMatches("target", 1, target);
+        GridExtent slice = getExtent();
+        MathTransform tr = getGridToCRS(source);
+        if (target != null) {
+            final var trs = new 
TransformSeparator(MathTransforms.concatenate(tr, 
createChangeOfCRS(target).getMathTransform()));
+            tr = trs.separate();    // Drop unused source dimensions.
+            slice = slice.selectDimensions(trs.getSourceDimensions());
+        }
+        return slice.latticePointStream1D(tr, false);
+    }
+
+    /**
+     * Creates a stream of instants for all lattices inside the grid extent.
+     * This grid geometry must have an extent, a <abbr>CRS</abbr> and "grid to 
<abbr>CRS</abbr>" transform.
+     * If the <abbr>CRS</abbr> has no temporal component, then the stream will 
be empty.
+     *
+     * @param  anchor  the cell part to map (center or corner).
+     * @return a stream of all instants in the grid, or an empty stream if 
there is no temporal axis.
+     * @throws IncompleteGridGeometryException if this grid geometry does not 
have sufficient information.
+     * @throws FactoryException if an error occurred while isolating the 
temporal component of the transform.
+     *
+     * @see #getTemporalExtent()
+     *
+     * @since SIS 1.8
+     */
+    public Stream<Instant> instantStream(final PixelInCell anchor) throws 
FactoryException {
+        GridExtent slice = getExtent();     // Cause an exception early if 
this information is missing.
+        MathTransform tr = getGridToCRS(anchor);
+        final CoordinateReferenceSystem crs = getCoordinateReferenceSystem();
+        final TemporalAccessor t = TemporalAccessor.of(crs);
+        if (t == null) {
+            return Stream.empty();
+        }
+        final DefaultTemporalCRS timeCRS = t.timeCRS;
+        final var trs = new TransformSeparator(tr);
+        trs.addTargetDimensions(t.dimension);
+        tr = trs.separate();
+        slice = slice.selectDimensions(trs.getSourceDimensions());
+        return slice.latticePointStream1D(tr, 
false).mapToObj(timeCRS::toInstant);
+    }
+
     /**
      * Returns the coordinate range of another grid geometry in units of cells 
of this grid geometry.
      * This method gets the {@linkplain #createTransformTo transform between 
cell coordinates} and
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridPointIterator.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridPointIterator.java
new file mode 100644
index 0000000000..66799900af
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridPointIterator.java
@@ -0,0 +1,286 @@
+/*
+ * 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.coverage.grid;
+
+import java.util.Spliterator;
+import java.util.function.Consumer;
+import java.util.function.DoubleConsumer;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.TransformException;
+import static 
org.apache.sis.referencing.internal.shared.CoordinateOperations.MAXIMUM_BUFFER_SIZE;
+import org.apache.sis.util.collection.BackingStoreException;
+
+
+/**
+ * Iterator over all grid coordinates included inside the grid extent, 
transformed to <abbr>CRS</abbr> coordinates.
+ * If a {@link TransformException} is thrown during traversal, it will be 
wrapped in a {@link BackingStoreException}.
+ *
+ * @param  <T>  the type of elements returned by this iterator: {@code 
double[]} or {@link Double}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+abstract class GridPointIterator<T> extends LatticeIterator<T> {
+    /**
+     * The transform to apply on grid coordinates for obtaining target 
coordinates.
+     */
+    private final MathTransform gridToCRS;
+
+    /**
+     * Number of dimensions of the coordinate tuples to return.
+     */
+    private final int crsDimension;
+
+    /**
+     * A buffer for transforming a group of many points in one transform call.
+     */
+    private final double[] buffer;
+
+    /**
+     * Index after the last valid coordinate in the {@linkplain #buffer}.
+     */
+    private int limit;
+
+    /**
+     * Index of the next coordinate in the {@linkplain #buffer} to return.
+     */
+    private int currentPosition;
+
+    /**
+     * Creates a new iterator with the given low and high grid coordinate 
values and the given transform.
+     * The given array is initially a <strong>clone</strong> of {@link 
GridExtent#coordinates}.
+     *
+     * @param  extent     the low coordinates followed by the high coordinates 
(inclusive) in a modifiable array.
+     * @param  gridToCRS  the transform to apply on each grid coordinate 
values.
+     */
+    GridPointIterator(final long[] extent, final MathTransform gridToCRS) {
+        super(extent);
+        this.gridToCRS = gridToCRS;
+        crsDimension = gridToCRS.getTargetDimensions();
+        final int dimension = Math.max(crsDimension, 
gridToCRS.getSourceDimensions());
+        final int capacity  = (int) Math.min(estimateSize(), 
Math.max(MAXIMUM_BUFFER_SIZE / dimension, 1));
+        buffer = new double[capacity * dimension];
+    }
+
+    /**
+     * Creates a new iterator which will iterate over a prefix of the given 
iterator.
+     *
+     * @param  suffix  the iterator which will become a suffix later.
+     * @param  prefix  extent of the prefix of the given iterator.
+     */
+    GridPointIterator(final GridPointIterator<T> suffix, final long[] prefix) {
+        this(prefix, suffix.gridToCRS);
+    }
+
+    /**
+     * Tests whether there are more points to return and move the cursor to 
the next point.
+     * Newly created iterator are positioned (from the point of view of this 
method) before the first point.
+     * This method is invoked before each point to return, for example in a 
{@code while (next()) {…}} loop.
+     *
+     * @return whether the next point exists and can be returned.
+     */
+    protected final boolean next() {
+        if (currentPosition >= limit) {
+            if (done) {
+                return false;
+            }
+            limit = 0;
+            currentPosition = 0;
+            final int dimension = dimension();
+            int j = 0;
+            do {
+                for (int i=0; i<dimension; i++) {
+                    buffer[j++] = current[i];
+                }
+                if (!next(dimension)) {
+                    done = true;
+                    break;
+                }
+            } while (j < buffer.length);
+            try {
+                gridToCRS.transform(buffer, 0, buffer, 0, j / dimension);
+            } catch (TransformException e) {
+                throw new BackingStoreException(e);
+            }
+            limit = j;
+        }
+        return true;
+    }
+
+    /**
+     * Returns the multi-dimensional coordinate values at the current position.
+     * This method or the {@link #coordinate()} method must be invoked
+     * exactly once after {@link #next()} returned {@code true}.
+     *
+     * @return the coordinate values.
+     */
+    protected final double[] coordinates() {
+        final double[] coordinates = new double[crsDimension];
+        System.arraycopy(buffer, currentPosition, coordinates, 0, 
crsDimension);
+        currentPosition += crsDimension;
+        return coordinates;
+    }
+
+    /**
+     * Returns the one-dimensional coordinate value at the current position.
+     * This method or the {@link #coordinates()} method must be invoked
+     * exactly once after {@link #next()} returned {@code true}.
+     *
+     * @return the coordinate value.
+     */
+    protected final double coordinate() {
+        return buffer[currentPosition++];
+    }
+
+    /**
+     * Iterator over coordinates in a <abbr>CRS</abbr> having an arbitrary 
number of dimensions.
+     */
+    static final class MultiDimensional extends GridPointIterator<double[]> {
+        /**
+         * Creates a new iterator with the given low and high grid coordinate 
values and the given transform.
+         * The given array is initially a <strong>clone</strong> of {@link 
GridExtent#coordinates}.
+         *
+         * @param  extent     the low coordinates followed by the high 
coordinates (inclusive) in a modifiable array.
+         * @param  gridToCRS  the transform to apply on each grid coordinate 
values.
+         */
+        MultiDimensional(final long[] extent, final MathTransform gridToCRS) {
+            super(extent, gridToCRS);
+        }
+
+        /**
+         * Creates a new iterator which will iterate over a prefix of the 
given iterator.
+         *
+         * @param  suffix  the iterator which will become a suffix later.
+         * @param  prefix  extent of the prefix of the given iterator.
+         */
+        MultiDimensional(final MultiDimensional suffix, final long[] prefix) {
+            super(suffix, prefix);
+        }
+
+        /**
+         * Creates an iterator which will iterate over a prefix of this 
iterator.
+         * The extent of the prefix (in grid coordinates) is given in argument.
+         *
+         * @return a new iterator over a prefix of this iterator.
+         */
+        @Override
+        protected LatticeIterator<double[]> createPrefixIterator(final long[] 
extent) {
+            return new MultiDimensional(this, extent);
+        }
+
+        /**
+         * Preforms the given action on the next point if it exists.
+         * The coordinates are transformed in batch for performance reasons.
+         *
+         * @param  action  the action to execute.
+         * @return whether a remaining point existed.
+         */
+        @Override
+        public boolean tryAdvance(final Consumer<? super double[]> action) {
+            if (next()) {
+                action.accept(coordinates());
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * Preforms the given action on all remaining lattice points.
+         *
+         * @param  action  the action to execute.
+         */
+        @Override
+        public void forEachRemaining(final Consumer<? super double[]> action) {
+            while (next()) {
+                action.accept(coordinates());
+            }
+        }
+    }
+
+    /**
+     * Iterator over coordinates in a <abbr>CRS</abbr> having exactly one 
dimension.
+     */
+    static final class OneDimensional extends GridPointIterator<Double> 
implements Spliterator.OfDouble {
+        /**
+         * Creates a new iterator with the given low and high grid coordinate 
values and the given transform.
+         * The given array is initially a <strong>clone</strong> of {@link 
GridExtent#coordinates}.
+         *
+         * @param  extent     the low coordinates followed by the high 
coordinates (inclusive) in a modifiable array.
+         * @param  gridToCRS  the transform to apply on each grid coordinate 
values.
+         */
+        OneDimensional(final long[] extent, final MathTransform gridToCRS) {
+            super(extent, gridToCRS);
+        }
+
+        /**
+         * Creates a new iterator which will iterate over a prefix of the 
given iterator.
+         *
+         * @param  suffix  the iterator which will become a suffix later.
+         * @param  prefix  extent of the prefix of the given iterator.
+         */
+        OneDimensional(final OneDimensional suffix, final long[] prefix) {
+            super(suffix, prefix);
+        }
+
+        /**
+         * Creates an iterator which will iterate over a prefix of this 
iterator.
+         * The extent of the prefix (in grid coordinates) is given in argument.
+         *
+         * @return a new iterator over a prefix of this iterator.
+         */
+        @Override
+        protected LatticeIterator<Double> createPrefixIterator(final long[] 
extent) {
+            return new OneDimensional(this, extent);
+        }
+
+        /**
+         * Tries to return an iterator covering a prefix of the points covered 
by this iterator.
+         * If successful, then after this method call this iterator covers the 
remaining suffix.
+         */
+        @Override
+        public Spliterator.OfDouble trySplit() {
+            return (Spliterator.OfDouble) super.trySplit();
+        }
+
+        /**
+         * Preforms the given action on the next point if it exists.
+         * The coordinates are transformed in batch for performance reasons.
+         *
+         * @param  action  the action to execute.
+         * @return whether a remaining point existed.
+         */
+        @Override
+        public boolean tryAdvance(final DoubleConsumer action) {
+            if (next()) {
+                action.accept(coordinate());
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * Preforms the given action on all remaining lattice points.
+         *
+         * @param  action  the action to execute.
+         */
+        @Override
+        public void forEachRemaining(final DoubleConsumer action) {
+            while (next()) {
+                action.accept(coordinate());
+            }
+        }
+    }
+}
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/LatticeIterator.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/LatticeIterator.java
new file mode 100644
index 0000000000..cf984fd884
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/LatticeIterator.java
@@ -0,0 +1,209 @@
+/*
+ * 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.coverage.grid;
+
+import java.util.Arrays;
+import java.util.Spliterator;
+import java.util.function.Consumer;
+import org.apache.sis.util.logging.Logging;
+
+
+/**
+ * Iterator over all grid coordinates included inside the grid extent, low and 
high values included.
+ * Iteration is ordered from first dimension varying the fastest to last 
dimension varying the slowest.
+ *
+ * @param  <T>  the type of elements returned by this iterator.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ *
+ * @see GridExtent#latticePointStream(boolean)
+ */
+abstract class LatticeIterator<T> implements Spliterator<T> {
+    /**
+     * The low coordinates followed by the high coordinates, inclusive.
+     * This is either a copy of the {@link GridExtent#coordinates} array,
+     * or a subregion if the iterator has been split.
+     */
+    private final long[] extent;
+
+    /**
+     * The current position.
+     */
+    final long[] current;
+
+    /**
+     * Whether the iteration is finished.
+     */
+    boolean done;
+
+    /**
+     * Creates a new iterator with the given low and high grid coordinate 
values.
+     * The given {@code extent} array is initially a <strong>clone</strong> of 
the
+     * {@link GridExtent#coordinates} array and may be modified in-place if 
the iterator is split.
+     *
+     * @param  extent  the low coordinates followed by the high coordinates 
(inclusive) in a modifiable array.
+     */
+    LatticeIterator(final long[] extent) {
+        this.extent  = extent;
+        this.current = Arrays.copyOf(extent, dimension());
+    }
+
+    /**
+     * Creates an iterator which will iterate over a prefix of this iterator.
+     * The extent of the prefix (in grid coordinates) is given in argument.
+     *
+     * @param  extent  extent of the prefix in a modifiable array.
+     * @return a new iterator over a prefix of this iterator.
+     */
+    protected abstract LatticeIterator<T> createPrefixIterator(final long[] 
extent);
+
+    /**
+     * Returns the characteristics of this iterator: ordered, size known, 
without duplicated value.
+     */
+    @Override
+    public final int characteristics() {
+        return ORDERED | DISTINCT | SIZED | NONNULL | IMMUTABLE | SUBSIZED;
+    }
+
+    /**
+     * Returns the exact number of points inside the extent if that number 
does not overflow.
+     * In case of size exceeding the capacity of 64-bits integers, returns 
{@link Long#MAX_VALUE}.
+     */
+    @Override
+    public final long estimateSize() {
+        long count = 1;
+        final int dimension = extent.length >>> 1;
+        for (int i=0; i<dimension; i++) {
+            try {
+                long span = Math.subtractExact(extent[dimension + i], 
extent[i]);
+                count = Math.multiplyExact(count, Math.incrementExact(span));
+            } catch (ArithmeticException e) {
+                Logging.ignorableException(GridExtent.LOGGER, 
GridExtent.class, "latticePointStream", e);
+                return Long.MAX_VALUE;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Tries to return an iterator covering a prefix of the points covered by 
this iterator.
+     * If successful, then after this method call this iterator covers the 
remaining suffix.
+     */
+    @Override
+    public Spliterator<T> trySplit() {
+        final int dimension = extent.length >>> 1;
+        for (int i = dimension; --i >= 0;) {
+            long span = Math.subtractExact(extent[dimension + i], extent[i]);
+            if (span != 0) {
+                final long[] prefix = extent.clone();
+                final long separation = prefix[i] + (span >>> 1);
+                prefix[i + dimension] = separation;
+                current[i] = extent[i] = separation + 1;
+                return createPrefixIterator(prefix);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Moves to the next point and checks whether there are more lattice 
points to iterate over.
+     * Newly created iterators are positioned on their first point, which is 
guaranteed to exist.
+     * This method is invoked <em>after</em> each point, in preparation for 
the next call to the
+     * {@code tryAdvance(…)} method.
+     *
+     * @param  dimension  value of {@link #dimension()} (because often known 
by the caller).
+     * @return whether there are more lattice points after this method call.
+     */
+    final boolean next(final int dimension) {
+        for (int i=0; i<dimension; i++) {
+            if (current[i] < extent[i + dimension]) {
+                current[i]++;
+                return true;
+            }
+            current[i] = extent[i];
+        }
+        return false;
+    }
+
+    /**
+     * Returns the number of dimensions of grid points.
+     * This is the value of {@link GridExtent#getDimension()}.
+     *
+     * @return number of dimensions of grid points.
+     */
+    protected final int dimension() {
+        return extent.length >>> 1;
+    }
+
+    /**
+     * Iterator over all grid coordinates included inside the grid extent, low 
and high values included.
+     * Iteration is ordered from first dimension varying the fastest to last 
dimension varying the slowest.
+     * The grid coordinates are returned directly (untransformed) as 64-bits 
integers.
+     *
+     * @see GridExtent#latticePointStream(boolean)
+     */
+    static final class Grid extends LatticeIterator<long[]> {
+        /**
+         * Creates a new iterator with the given low and high grid coordinate 
values.
+         * The given array is initially a <strong>clone</strong> of {@link 
GridExtent#coordinates}.
+         * That array may be modified in-place if the iterator is split.
+         *
+         * @param  extent  the low coordinates followed by the high 
coordinates (inclusive) in a modifiable array.
+         */
+        Grid(final long[] extent) {
+            super(extent);
+        }
+
+        /**
+         * Creates an iterator which will iterate over a prefix of this 
iterator.
+         * The extent of the prefix (in grid coordinates) is given in argument.
+         */
+        @Override
+        protected LatticeIterator<long[]> createPrefixIterator(final long[] 
extent) {
+            return new Grid(extent);
+        }
+
+        /**
+         * Preforms the given action on the next lattice point if it exists.
+         *
+         * @param  action  the action to execute.
+         * @return whether a remaining point existed.
+         */
+        @Override
+        public boolean tryAdvance(final Consumer<? super long[]> action) {
+            if (done) return false;
+            action.accept(current.clone());
+            done = !next(dimension());
+            return true;
+        }
+
+        /**
+         * Preforms the given action on all remaining lattice points.
+         *
+         * @param  action  the action to execute.
+         */
+        @Override
+        public void forEachRemaining(final Consumer<? super long[]> action) {
+            final int dimension = dimension();
+            if (!done) {
+                do action.accept(current.clone());
+                while (next(dimension));
+                done = true;
+            }
+        }
+    }
+}
diff --git 
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridGeometryTest.java
 
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridGeometryTest.java
index 083f470984..28a8a2038b 100644
--- 
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridGeometryTest.java
+++ 
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridGeometryTest.java
@@ -1031,6 +1031,62 @@ public final class GridGeometryTest extends TestCase {
         assertMatrixEquals(expected, source.createTransformTo(target, 
PixelInCell.CELL_CENTER), "createTransformTo");
     }
 
+    /**
+     * Tests {@link GridGeometry#latticePointStream(PixelInCell, 
CoordinateReferenceSystem)}
+     * and its one-dimensional variant.
+     *
+     * @throws FactoryException if an error occurred while creating the 
transform.
+     */
+    @Test
+    public void testLatticePointStream() throws FactoryException {
+        final var grid = new GridGeometry(
+                new GridExtent(null, new long[] {1, 4, 10}, new long[] {3, 5, 
14}, true),
+                PixelInCell.CELL_CENTER,
+                MathTransforms.linear(new Matrix4(
+                    0.25, 0,   0,  2,
+                    0,    0.5, 0,  3,
+                    0,    0,   2,  7,
+                    0,    0,   0,  1)),
+                HardCodedCRS.WGS84_3D);
+
+        assertArrayEquals(new double[][] {
+            new double[] {5.0, 2.25},
+            new double[] {5.0, 2.50},
+            new double[] {5.0, 2.75},
+            new double[] {5.5, 2.25},
+            new double[] {5.5, 2.50},
+            new double[] {5.5, 2.75}
+        }, grid.latticePointStream(PixelInCell.CELL_CENTER, 
HardCodedCRS.WGS84_LATITUDE_FIRST).toArray());
+
+        assertArrayEquals(new double[] {2700, 2900, 3100, 3300, 3500},
+                grid.latticePointStream1D(PixelInCell.CELL_CENTER, 
HardCodedCRS.ELLIPSOIDAL_HEIGHT_cm).toArray());
+    }
+
+    /**
+     * Tests {@link GridGeometry#instantStream(PixelInCell)}.
+     *
+     * @throws FactoryException if an error occurred while creating the 
transform.
+     */
+    @Test
+    public void testInstantStream() throws FactoryException {
+        final var grid = new GridGeometry(
+                new GridExtent(null, null, new long[] {3, 5, 3}, true),
+                PixelInCell.CELL_CENTER,
+                MathTransforms.linear(new Matrix4(
+                    0.25, 0,   0,  2,
+                    0,    0.5, 0,  3,
+                    0,    0,   2,  7,
+                    0,    0,   0,  1)),
+                HardCodedCRS.WGS84_WITH_TIME);
+
+        assertArrayEquals(new String[] {
+            "1858-11-24T00:00:00Z",     // Value zero is 1858-11-17T00:00:00Z.
+            "1858-11-26T00:00:00Z",
+            "1858-11-28T00:00:00Z",
+            "1858-11-30T00:00:00Z"
+        }, 
grid.instantStream(PixelInCell.CELL_CENTER).map(Object::toString).toArray());
+    }
+
     /**
      * Tests {@link GridGeometry#contains(GridGeometry)} and {@link 
GridGeometry#intersects(GridGeometry)}.
      */
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractEnvelope.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractEnvelope.java
index c5fdab0e32..6144ab2831 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractEnvelope.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractEnvelope.java
@@ -564,7 +564,7 @@ public abstract class AbstractEnvelope extends 
FormattableObject implements Enve
      * @since SIS 1.1
      */
     public Optional<Range<Instant>> getTimeRange() {
-        final TemporalAccessor t = 
TemporalAccessor.of(getCoordinateReferenceSystem(), 0);
+        final TemporalAccessor t = 
TemporalAccessor.of(getCoordinateReferenceSystem());
         return (t != null) ? Optional.of(t.getTimeRange(this)) : 
Optional.empty();
     }
 
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/Envelopes.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/Envelopes.java
index 784c3834c0..6dbed14c1d 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/Envelopes.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/Envelopes.java
@@ -1162,7 +1162,7 @@ poles:  for (int i=0; i<dimension; i++) {
      */
     public static Optional<Range<Instant>> toTimeRange(final Envelope 
envelope) {
         if (envelope != null) {
-            final TemporalAccessor t = 
TemporalAccessor.of(envelope.getCoordinateReferenceSystem(), 0);
+            final TemporalAccessor t = 
TemporalAccessor.of(envelope.getCoordinateReferenceSystem());
             if (t != null) {
                 return Optional.of(t.getTimeRange(envelope));
             }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/GeneralEnvelope.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/GeneralEnvelope.java
index a23d0b4dda..705da08def 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/GeneralEnvelope.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/GeneralEnvelope.java
@@ -486,7 +486,7 @@ public class GeneralEnvelope extends ArrayEnvelope 
implements Cloneable, Seriali
      * @see Envelopes#toTimeRange(Envelope)
      */
     public boolean setTimeRange(final Instant startTime, final Instant 
endTime) {
-        final TemporalAccessor t = TemporalAccessor.of(crs, 0);
+        final TemporalAccessor t = TemporalAccessor.of(crs);
         if (t != null) {
             double lower = t.timeCRS.toValue(startTime);
             double upper = t.timeCRS.toValue(endTime);
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/ServicesForMetadata.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/ServicesForMetadata.java
index 5c8ba22051..d93b202f7b 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/ServicesForMetadata.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/ServicesForMetadata.java
@@ -282,7 +282,7 @@ public final class ServicesForMetadata extends 
ReferencingServices {
     @Override
     public boolean setBounds(final Envelope envelope, final 
DefaultTemporalExtent target) {
         final CoordinateReferenceSystem crs = 
envelope.getCoordinateReferenceSystem();
-        final TemporalAccessor accessor = TemporalAccessor.of(crs, 0);
+        final TemporalAccessor accessor = TemporalAccessor.of(crs);
         if (accessor == null) {                     // Mandatory for the 
conversion from numbers to dates.
             return false;
         }
@@ -305,7 +305,7 @@ public final class ServicesForMetadata extends 
ReferencingServices {
         final CoordinateReferenceSystem crs = 
envelope.getCoordinateReferenceSystem();
         final SingleCRS horizontalCRS = CRS.getHorizontalComponent(crs);
         final VerticalCRS verticalCRS = CRS.getVerticalComponent(crs, true);
-        final TemporalAccessor accessor = TemporalAccessor.of(crs, 0);
+        final TemporalAccessor accessor = TemporalAccessor.of(crs);
         if (horizontalCRS == null && verticalCRS == null && accessor == null) {
             return false;
         }
@@ -389,7 +389,7 @@ public final class ServicesForMetadata extends 
ReferencingServices {
             target.getVerticalElements().add(extent);
             found = true;
         }
-        final TemporalAccessor accessor = TemporalAccessor.of(crs, 0);
+        final TemporalAccessor accessor = TemporalAccessor.of(crs);
         if (accessor != null) {
             final DefaultTemporalExtent extent = new DefaultTemporalExtent();
             accessor.setTemporalExtent(envelope, extent);
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/CoordinateOperations.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/CoordinateOperations.java
index 6f7937bdee..4640291533 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/CoordinateOperations.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/CoordinateOperations.java
@@ -88,6 +88,14 @@ public final class CoordinateOperations {
      */
     public static final String OPERATION_TYPE_KEY = "operationType";
 
+    /**
+     * Maximum buffer size when creating temporary arrays. Must not be too 
big, otherwise the
+     * cost of allocating the buffer may be greater than the benefit of 
transforming array of
+     * coordinates. Remember that each double-precision number occupy 8 bytes, 
so a buffer of
+     * size 512 will actually consumes 4 kb of memory.
+     */
+    public static final int MAXIMUM_BUFFER_SIZE = 512;
+
     /**
      * Cached values of {@link #wrapAroundChanges wrapAroundChanges(…)}, 
created when first needed.
      * Indices are bit masks computed by {@link #changes changes(…)}. Since 
the most common "wrap around" axes
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/DirectPositionView.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/DirectPositionView.java
index e34fa06be7..32963158b6 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/DirectPositionView.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/DirectPositionView.java
@@ -43,7 +43,7 @@ public abstract class DirectPositionView extends 
AbstractDirectPosition {
      * Creates a new direct position wrapping the given array.
      *
      * @param  offset     the first value index in the coordinates array.
-     * @param  dimension  the number of valid coordinate values.
+     * @param  dimension  the number of valid coordinate values in one 
coordinate tuple.
      */
     DirectPositionView(final int offset, final int dimension) {
         this.offset    = offset;
@@ -84,8 +84,8 @@ public abstract class DirectPositionView extends 
AbstractDirectPosition {
          * Creates a new direct position wrapping the given array.
          *
          * @param  coordinates  the coordinate values.
-         * @param  offset     the first value index in the coordinates array.
-         * @param  dimension  the number of valid coordinate values.
+         * @param  offset       the first value index in the coordinates array.
+         * @param  dimension    the number of valid coordinate values in one 
coordinate tuple.
          */
         public Double(final double[] coordinates, final int offset, final int 
dimension) {
             super(offset, dimension);
@@ -130,8 +130,8 @@ public abstract class DirectPositionView extends 
AbstractDirectPosition {
          * Creates a new direct position wrapping the given array.
          *
          * @param  coordinates  the coordinate values.
-         * @param  offset     the first value index in the coordinates array.
-         * @param  dimension  the number of valid coordinate values.
+         * @param  offset       the first value index in the coordinates array.
+         * @param  dimension    the number of valid coordinate values in one 
coordinate tuple.
          */
         public Float(final float[] coordinates, final int offset, final int 
dimension) {
             super(offset, dimension);
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/TemporalAccessor.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/TemporalAccessor.java
index 632e147f05..993cafa5d9 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/TemporalAccessor.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/TemporalAccessor.java
@@ -62,13 +62,13 @@ public final class TemporalAccessor {
 
     /**
      * Creates a new temporal accessor for elements at the given dimensions.
-     * This method searches for a temporal component in the given CRS.
+     * This method searches for a temporal component in the given 
<abbr>CRS</abbr>.
      *
      * @param  crs  the coordinate reference system which may contain a 
temporal component, or {@code null}.
      * @param  dim  offset to add to the dimension indices. This is usually 
zero.
      * @return the temporal accessor, or {@code null} if no temporal component 
has been found.
      */
-    public static TemporalAccessor of(final CoordinateReferenceSystem crs, int 
dim) {
+    private static TemporalAccessor of(final CoordinateReferenceSystem crs, 
int dim) {
         if (crs instanceof TemporalCRS) {
             return new TemporalAccessor(dim, (TemporalCRS) crs);
         }
@@ -84,6 +84,17 @@ public final class TemporalAccessor {
         return null;
     }
 
+    /**
+     * Creates a new temporal accessor for elements at the given dimensions.
+     * This method searches for a temporal component in the given 
<abbr>CRS</abbr>.
+     *
+     * @param  crs  the coordinate reference system which may contain a 
temporal component, or {@code null}.
+     * @return the temporal accessor, or {@code null} if no temporal component 
has been found.
+     */
+    public static TemporalAccessor of(final CoordinateReferenceSystem crs) {
+        return of(crs, 0);
+    }
+
     /**
      * Returns the lower and upper values in the given envelope. It is 
caller's responsibility to ensure
      * that the envelope CRS is the same as the one used for creating this 
{@code TemporalAccessor}.
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/builder/ProjectedTransformTry.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/builder/ProjectedTransformTry.java
index cc2356183b..3e83c8756c 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/builder/ProjectedTransformTry.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/builder/ProjectedTransformTry.java
@@ -33,6 +33,7 @@ import org.apache.sis.util.resources.Errors;
 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 static 
org.apache.sis.referencing.internal.shared.CoordinateOperations.MAXIMUM_BUFFER_SIZE;
 
 // Specific to the geoapi-4.0 branch:
 import org.opengis.coordinate.MismatchedDimensionException;
@@ -64,14 +65,6 @@ import org.opengis.coordinate.MismatchedDimensionException;
  * @author  Martin Desruisseaux (Geomatys)
  */
 final class ProjectedTransformTry implements 
Comparable<ProjectedTransformTry>, Map.Entry<String,MathTransform> {
-    /**
-     * Number of points in the temporary buffer used for transforming data.
-     * The buffer length will be this capacity multiplied by the number of 
dimensions.
-     *
-     * @see 
org.apache.sis.referencing.operation.transform.AbstractMathTransform#MAXIMUM_BUFFER_SIZE
-     */
-    private static final int BUFFER_CAPACITY = 512;
-
     /**
      * A name by witch this projection attempt is identified.
      *
@@ -233,7 +226,7 @@ final class ProjectedTransformTry implements 
Comparable<ProjectedTransformTry>,
             if (numDimensions == 1) {
                 projection.transform(coordinates[projToGrid[0]], 0, 
results[0], 0, numPoints);
             } else {
-                final int bufferCapacity = Math.min(numPoints, 
BUFFER_CAPACITY);                 // In number of points.
+                final int bufferCapacity = Math.min(numPoints, 
MAXIMUM_BUFFER_SIZE);    // In number of points.
                 final double[] buffer = new double[bufferCapacity * 
numDimensions];
                 int dataOffset = 0;
                 while (dataOffset < numPoints) {
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java
index 8f98407559..88e44e76cb 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java
@@ -47,6 +47,7 @@ import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.LenientComparable;
 import org.apache.sis.util.OptionalCandidate;
 import org.apache.sis.util.resources.Errors;
+import static 
org.apache.sis.referencing.internal.shared.CoordinateOperations.MAXIMUM_BUFFER_SIZE;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.coordinate.MismatchedDimensionException;
@@ -100,14 +101,6 @@ public abstract class AbstractMathTransform extends 
FormattableObject
      */
     static final Logger LOGGER = 
Logger.getLogger(Loggers.COORDINATE_OPERATION);
 
-    /**
-     * Maximum buffer size when creating temporary arrays. Must not be too 
big, otherwise the
-     * cost of allocating the buffer may be greater than the benefit of 
transforming array of
-     * coordinates. Remember that double number occupy 8 bytes, so a buffer of 
size 512 will
-     * actually consumes 4 kb of memory.
-     */
-    static final int MAXIMUM_BUFFER_SIZE = 512;
-
     /**
      * Maximum number of {@link TransformException}s to catch while 
transforming a block of
      * {@value #MAXIMUM_BUFFER_SIZE} coordinate values in an array. The 
default implementation of
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java
index b1680013c1..d6cad7fbf1 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java
@@ -46,6 +46,7 @@ import org.apache.sis.io.wkt.Convention;
 import org.apache.sis.io.wkt.Formatter;
 import org.apache.sis.io.wkt.FormattableObject;
 import org.apache.sis.util.resources.Errors;
+import static 
org.apache.sis.referencing.internal.shared.CoordinateOperations.MAXIMUM_BUFFER_SIZE;
 
 // Specific to the geoapi-4.0 branch:
 import org.opengis.coordinate.MismatchedDimensionException;
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/AbstractMathTransformTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/AbstractMathTransformTest.java
index 57b8c9e1aa..cecaf47e5f 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/AbstractMathTransformTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/AbstractMathTransformTest.java
@@ -20,8 +20,8 @@ import java.util.Arrays;
 import java.util.Random;
 import static java.lang.StrictMath.*;
 import org.opengis.referencing.operation.TransformException;
+import static 
org.apache.sis.referencing.internal.shared.CoordinateOperations.MAXIMUM_BUFFER_SIZE;
 import static 
org.apache.sis.referencing.operation.transform.AbstractMathTransform.MAXIMUM_FAILURES;
-import static 
org.apache.sis.referencing.operation.transform.AbstractMathTransform.MAXIMUM_BUFFER_SIZE;
 
 // Test dependencies
 import org.junit.jupiter.api.Test;
@@ -34,6 +34,7 @@ import org.apache.sis.test.TestCase;
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
+@SuppressWarnings("exports")
 public final class AbstractMathTransformTest extends TestCase {
     /**
      * Creates a new test case.
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java
index 09c327b50b..f69d5430c7 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java
@@ -34,6 +34,7 @@ import org.apache.sis.util.ArraysExt;
 import org.apache.sis.io.TableAppender;
 import org.apache.sis.io.wkt.Convention;
 import org.apache.sis.io.wkt.FormattableObject;
+import static 
org.apache.sis.referencing.internal.shared.CoordinateOperations.MAXIMUM_BUFFER_SIZE;
 
 // Test dependencies
 import static org.junit.jupiter.api.Assertions.*;
@@ -89,7 +90,7 @@ public abstract class MathTransformTestCase extends 
TransformTestCase {
      * encompass at least 2 time the default buffer size in order to test the 
code that use
      * the buffer. We add an arbitrary number just for making the transform 
job harder.
      */
-    static final int ORDINATE_COUNT = 
AbstractMathTransform.MAXIMUM_BUFFER_SIZE * 2 + 137;
+    static final int ORDINATE_COUNT = MAXIMUM_BUFFER_SIZE * 2 + 137;
 
     /**
      * The dimension of longitude, or {@code null} if none. If non-null, then 
the comparison of
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileReadEvent.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileReadEvent.java
index ace577f1a7..ecf47516e4 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileReadEvent.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileReadEvent.java
@@ -150,7 +150,11 @@ public final class TileReadEvent extends StoreEvent {
             @SuppressWarnings("LocalVariableHidesMemberVariable")
             CoordinateOperation crsToObjective = this.crsToObjective;
             if (crsToObjective == null || 
!CRS.equivalent(crsToObjective.getTargetCRS(), crs)) try {
-                crsToObjective = sliceGeometry.createChangeOfCRS(crs);
+                if (sliceGeometry.isDefined(GridGeometry.CRS)) {
+                    crsToObjective = sliceGeometry.createChangeOfCRS(crs);
+                } else {
+                    crsToObjective = CRS.findOperation(crs, crs, null);     // 
Create an identity operation.
+                }
                 MathTransform tr = MathTransforms.translation(offsetX, 
offsetY);
                 tr = MathTransforms.concatenate(tr, 
sliceGeometry.getGridToCRS(PixelInCell.CELL_CORNER));
                 tr = MathTransforms.concatenate(tr, 
crsToObjective.getMathTransform());

Reply via email to