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());