This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit 69dc8bbe5b53a831ead8918a0f6d574ffa4dc092 Author: Martin Desruisseaux <[email protected]> AuthorDate: Wed Apr 15 17:38:26 2026 +0200 Rename `interiorCoordinateStream()` as `latticePointStream(boolean parallel)` and implement using `Spliterator` (more efficient, specify characteristics). --- .../org/apache/sis/coverage/grid/GridExtent.java | 167 +++++++++++++++++---- .../apache/sis/coverage/grid/GridExtentTest.java | 55 ++++--- 2 files changed, 174 insertions(+), 48 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 45b296cdae..6cfb4fa004 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 @@ -22,13 +22,15 @@ import java.util.SortedMap; import java.util.Arrays; import java.util.Optional; import java.util.Objects; +import java.util.Spliterator; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import java.util.function.Consumer; import java.util.logging.Logger; import java.io.Serializable; import java.io.IOException; import java.io.UncheckedIOException; import java.awt.Rectangle; -import java.util.stream.LongStream; -import java.util.stream.Stream; import org.opengis.util.FactoryException; import org.opengis.util.InternationalString; import org.opengis.geometry.Envelope; @@ -2143,35 +2145,144 @@ public class GridExtent implements GridEnvelope, LenientComparable, Serializable } /** - * Create a stream of coordinates contained in the GridExtent. - * Iteration goes from {@linkplain GridExtent#getLow() } to {@linkplain GridExtent#getHigh() } coordinates inclusive. - * <p> - * No assumption of the iteration order should be made. + * Create 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. + * No assumption about iteration order should be made. * - * @return stream of coordinates inside the GridExtent. + * @param parallel whether to return a parallel stream. + * @return stream of lattice points inside this grid extent. * @since 1.7 */ - public Stream<long[]> interiorCoordinateStream() { - final int dimension = getDimension(); - final long[] low = getLow().getCoordinateValues(); - final long[] high = getHigh().getCoordinateValues(); - - Stream<long[]> stream = LongStream.range(low[0], high[0]+1) - .mapToObj((long value) -> { - final long[] array = new long[dimension]; - array[0] = value; - return array; - }); - for (int i = 1; i <dimension; i++) { - final int idx = i; - stream = stream.flatMap((long[] t) -> LongStream.range(low[idx], high[idx]+1) - .mapToObj((long value) -> { - final long[] array = t.clone(); - array[idx] = value; - return array; - })); - } - return stream; + public Stream<long[]> latticePointStream(final boolean parallel) { + return StreamSupport.stream(new Iter(coordinates.clone()), parallel); + } + + /** + * Iterator over all grid coordinates included inside the grid extent, low and high values included. + */ + 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); + done = !next(extent.length >>> 1); + return true; + } + + /** + * 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; + } + } } /** diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridExtentTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridExtentTest.java index a0656270dd..9db31faf35 100644 --- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridExtentTest.java +++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridExtentTest.java @@ -19,8 +19,6 @@ package org.apache.sis.coverage.grid; import java.util.Map; import java.util.Locale; import java.io.IOException; -import java.util.List; -import java.util.function.Function; import org.opengis.geometry.Envelope; import org.opengis.geometry.DirectPosition; import org.opengis.metadata.spatial.DimensionNameType; @@ -420,26 +418,43 @@ public final class GridExtentTest extends TestCase { } /** - * Tests {@link GridExtent#interiorStream() }. + * Tests {@link GridExtent#latticePointStream(boolean)} in the sequential case. */ @Test - public void testInteriorCoordinateStream() { - final record Pt(long x, long y, long z){}; - var extent = new GridExtent(null, new long[]{1,20,33}, new long[]{3,23,35}, false); - var lst = extent.interiorCoordinateStream().map((long[] t) -> new Pt(t[0], t[1], t[2])).toList(); - assertEquals(2*3*2, lst.size()); - assertTrue(lst.contains(new Pt(1,20,33))); - assertTrue(lst.contains(new Pt(1,20,34))); - assertTrue(lst.contains(new Pt(1,21,33))); - assertTrue(lst.contains(new Pt(1,21,34))); - assertTrue(lst.contains(new Pt(1,22,33))); - assertTrue(lst.contains(new Pt(1,22,34))); - assertTrue(lst.contains(new Pt(2,20,33))); - assertTrue(lst.contains(new Pt(2,20,34))); - assertTrue(lst.contains(new Pt(2,21,33))); - assertTrue(lst.contains(new Pt(2,21,34))); - assertTrue(lst.contains(new Pt(2,22,33))); - assertTrue(lst.contains(new Pt(2,22,34))); + public void testSequentialStream() { + testLatticePointStream(false); + } + + /** + * Tests {@link GridExtent#latticePointStream(boolean)} in the parallel case. + */ + @Test + public void testParallelStream() { + testLatticePointStream(true); + } + + /** + * Implementation of {@link #testSequentialStream()} and {@link #testParallelStream()}. + */ + private void testLatticePointStream(final boolean parallel) { + final var extent = new GridExtent(null, new long[] {1, 20, 33}, new long[] {2, 22, 34}, true); + assertEquals(2 * 3 * 2, extent.latticePointStream(parallel).count()); + final long[][] lattice = extent.latticePointStream(parallel).toArray(long[][]::new); + assertArrayEquals( + new long[][] { + new long[] {1, 20, 33}, + new long[] {2, 20, 33}, + new long[] {1, 21, 33}, + new long[] {2, 21, 33}, + new long[] {1, 22, 33}, + new long[] {2, 22, 33}, + new long[] {1, 20, 34}, + new long[] {2, 20, 34}, + new long[] {1, 21, 34}, + new long[] {2, 21, 34}, + new long[] {1, 22, 34}, + new long[] {2, 22, 34}}, + lattice); } /**
