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);
     }
 
     /**

Reply via email to