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 0a67ac8b72 feat(Geometry): add SIS geometry to Java2D Shape adaptor
0a67ac8b72 is described below

commit 0a67ac8b729e818ea60ace257094be000c965750
Author: jsorel <[email protected]>
AuthorDate: Wed Aug 26 14:32:23 2026 +0200

    feat(Geometry): add SIS geometry to Java2D Shape adaptor
---
 .../org.apache.sis.feature/main/module-info.java   |   3 +-
 .../src/org.apache.sis.util/main/module-info.java  |   1 +
 .../main/org/apache/sis/geometries/Geometries.java |  12 +
 .../geometries/adapter/PathIteratorAdapter.java    | 269 +++++++++++++++++++++
 .../sis/geometries/adapter/ShapeAdapter.java       | 189 +++++++++++++++
 .../apache/sis/geometries/math/SampleSystem.java   |   5 +
 .../geometries/operation/GeometryProcessor.java    |   3 +
 .../sis/geometries/adapter/ShapeAdapterTest.java   | 217 +++++++++++++++++
 8 files changed, 698 insertions(+), 1 deletion(-)

diff --git a/endorsed/src/org.apache.sis.feature/main/module-info.java 
b/endorsed/src/org.apache.sis.feature/main/module-info.java
index 3ba6d33f15..268f9be02e 100644
--- a/endorsed/src/org.apache.sis.feature/main/module-info.java
+++ b/endorsed/src/org.apache.sis.feature/main/module-info.java
@@ -88,7 +88,8 @@ module org.apache.sis.feature {
             org.apache.sis.cql;                     // In the "incubator" 
sub-project.
 
     exports org.apache.sis.geometry.wrapper.j2d to
-            org.apache.sis.gui;                     // In the "optional" 
sub-project.
+            org.apache.sis.gui,                     // In the "optional" 
sub-project.
+            org.apache.sis.geometry;                // In the "incubator" 
sub-project.
 
     exports org.apache.sis.geometry.wrapper.jts to
             org.apache.sis.geometry,                // In the "incubator" 
sub-project.
diff --git a/endorsed/src/org.apache.sis.util/main/module-info.java 
b/endorsed/src/org.apache.sis.util/main/module-info.java
index d53036c1a1..aee6c12b11 100644
--- a/endorsed/src/org.apache.sis.util/main/module-info.java
+++ b/endorsed/src/org.apache.sis.util/main/module-info.java
@@ -106,6 +106,7 @@ module org.apache.sis.util {
             org.apache.sis.referencing.gazetteer,
             org.apache.sis.referencing.dggs,            // In the "incubator" 
sub-project.
             org.apache.sis.feature,
+            org.apache.sis.geometry,                    // In the "incubator" 
sub-project.
             org.apache.sis.storage,
             org.apache.sis.storage.xml,
             org.apache.sis.storage.sql,
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Geometries.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Geometries.java
index 4669d5dadf..30fc748a1b 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Geometries.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Geometries.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.geometries;
 
+import java.awt.Shape;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -31,6 +32,7 @@ import org.locationtech.jts.geom.Coordinate;
 import org.locationtech.jts.geom.CoordinateSequence;
 import javax.measure.Unit;
 import org.apache.sis.geometries.adapter.JTSAdapter;
+import org.apache.sis.geometries.adapter.ShapeAdapter;
 import org.opengis.geometry.Envelope;
 import org.opengis.referencing.IdentifiedObject;
 import static org.opengis.referencing.IdentifiedObject.ALIAS_KEY;
@@ -726,4 +728,14 @@ public final class Geometries {
         return JTSAdapter.asJTS(geom, copy, gf);
     }
 
+    /**
+     * Returns a view of the given SIS geometry as a Java2D shape.
+     *
+     * @param  geometry  the geometry to view as a shape, not {@code null}.
+     * @return the Java2D shape view.
+     */
+    public static Shape asShape(final Geometry geometry) {
+        // Null value check in the invoked constructor.
+        return new ShapeAdapter(geometry);
+    }
 }
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/PathIteratorAdapter.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/PathIteratorAdapter.java
new file mode 100644
index 0000000000..4291943544
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/PathIteratorAdapter.java
@@ -0,0 +1,269 @@
+/*
+ * 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.geometries.adapter;
+
+import java.util.List;
+import java.util.Iterator;
+import java.util.Collection;
+import java.awt.geom.PathIterator;
+import java.awt.geom.AffineTransform;
+import org.apache.sis.geometries.Geometry;
+import org.apache.sis.geometries.GeometryCollection;
+import org.apache.sis.geometries.LineString;
+import org.apache.sis.geometries.Point;
+import org.apache.sis.geometries.PointSequence;
+import org.apache.sis.geometries.Polygon;
+import org.apache.sis.geometries.math.Tuple;
+import org.apache.sis.util.Classes;
+import org.apache.sis.util.resources.Errors;
+
+
+/**
+ * Java2D path iterator for SIS geometry.
+ * This iterator gets coordinates from the {@link CoordinateSequence} 
associated to each geometry.
+ *
+ * @author  Johann Sorel (Puzzle-GIS, Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+final class PathIteratorAdapter implements PathIterator {
+    /**
+     * The transform to apply on returned coordinate values.
+     * Never null (may be the identity transform instead).
+     */
+    private final AffineTransform at;
+
+    /**
+     * Provider of point sequences.
+     */
+    private final Iterator<PointSequence> sequences;
+
+    /**
+     * The sequence of point tuples to return,
+     * or {@code null} if the iteration is finished.
+     */
+    private PointSequence coordinates;
+
+    /**
+     * Number of points to return in the sequence.
+     */
+    private int pointCount;
+
+    /**
+     * Index of the coordinates tuple which closes the current polygon, or -1 
if none.
+     */
+    private int closingPoint;
+
+    /**
+     * Index of current position in the sequence of coordinate tuples.
+     */
+    private int currentIndex;
+
+    /**
+     * Creates a new iterator which will transform coordinates using the given 
transform.
+     *
+     * @param  geometry  the geometry on which to iterator.
+     * @param  at  the transform to apply, or {@code null} for the identity 
transform.
+     */
+    PathIteratorAdapter(final Geometry geometry, final AffineTransform at) {
+        this.at = (at != null) ? at : new AffineTransform();
+        sequences = iterator(geometry);
+        nextSequence();
+    }
+
+    /**
+     * Moves to the next sequence of coordinate tuples. The {@link 
#coordinates} sequence
+     * should be null when this method is invoked. If there are no more 
sequences,
+     * then the {@link #coordinates} will be left unchanged (i.e. null).
+     */
+    private void nextSequence() {
+        while (sequences.hasNext()) {
+            coordinates  = sequences.next();
+            pointCount   = coordinates.size();
+            closingPoint = pointCount - 1;
+            if (closingPoint < 1 || 
!coordinates.getPosition(0).equals(coordinates.getPosition(closingPoint))) {
+                closingPoint = -1;      // No closing point.
+            }
+            if (pointCount > 0) {
+                return;
+            }
+        }
+    }
+
+    /**
+     * Moves the iterator to the next segment.
+     */
+    @Override
+    public void next() {
+        if (++currentIndex >= pointCount) {
+            currentIndex = 0;
+            coordinates = null;
+            nextSequence();
+        }
+    }
+
+    /**
+     * Returns {@code true} if iteration is finished.
+     */
+    @Override
+    public boolean isDone() {
+        return coordinates == null;
+    }
+
+    /**
+     * Returns the winding rule for determining the interior of the path.
+     * Current implementation returns the same rule as the one returned
+     * by {@link org.locationtech.jts.awt.ShapeCollectionPathIterator}.
+     */
+    @Override
+    public int getWindingRule() {
+        return WIND_EVEN_ODD;
+    }
+
+    /**
+     * Returns the coordinates and type of the current path segment in the 
iteration.
+     *
+     * @param  coords an array where to store the data returned from this 
method.
+     * @return the path-segment type of the current path segment.
+     */
+    @Override
+    public int currentSegment(final double[] coords) {
+        if (currentIndex == closingPoint) {
+            return SEG_CLOSE;
+        }
+        Tuple<?> position = coordinates.getPosition(currentIndex);
+        coords[0] = position.get(0);
+        coords[1] = position.get(1);
+        at.transform(coords, 0, coords, 0, 1);
+        return (currentIndex == 0) ? SEG_MOVETO : SEG_LINETO;
+    }
+
+    /**
+     * Returns the coordinates and type of the current path segment in the 
iteration.
+     *
+     * @param  coords an array where to store the data returned from this 
method.
+     * @return the path-segment type of the current path segment.
+     */
+    @Override
+    public int currentSegment(final float[] coords) {
+        if (currentIndex == closingPoint) {
+            return SEG_CLOSE;
+        }
+        Tuple<?> position = coordinates.getPosition(currentIndex);
+        coords[0] = (float) position.get(0);
+        coords[1] = (float) position.get(1);
+        at.transform(coords, 0, coords, 0, 1);
+        return (currentIndex == 0) ? SEG_MOVETO : SEG_LINETO;
+    }
+
+    /**
+     * Returns an iterator over the coordinate sequences of the given geometry.
+     *
+     * @param  geometry  the geometry for which to get coordinate sequences.
+     * @return coordinate sequences over the given geometry.
+     */
+    private static Iterator<PointSequence> iterator(final Geometry geometry) {
+        final Collection<PointSequence> sequences;
+        if (geometry instanceof LineString) {
+            sequences = List.of(((LineString) geometry).getPoints());
+        } else if (geometry instanceof Point) {
+            sequences = List.of(((Point) geometry).asPointSequence());
+        } else if (geometry instanceof Polygon) {
+            return new RingIterator((Polygon) geometry);
+        } else if (geometry instanceof GeometryCollection) {
+            return new GeomIterator((GeometryCollection) geometry);
+        } else {
+            throw new 
IllegalArgumentException(Errors.format(Errors.Keys.UnsupportedType_1, 
Classes.getShortClassName(geometry)));
+        }
+        return sequences.iterator();
+    }
+
+    /**
+     * An iterator over the coordinate sequences of a polygon.
+     * The first coordinate sequence is the exterior ring and
+     * all other sequences are interior rings.
+     */
+    private static final class RingIterator implements Iterator<PointSequence> 
{
+        /** The polygon for which to return rings. */
+        private final Polygon polygon;
+
+        /** Index of the interior ring, or -1 for the exterior ring. */
+        private int interior;
+
+        /** Created a new iterator for the given polygon. */
+        RingIterator(final Polygon geometry) {
+            polygon  = geometry;
+            interior = -1;
+        }
+
+        /** Returns {@code true} if there is more rings to return. */
+        @Override public boolean hasNext() {
+            return interior < polygon.getNumInteriorRing();
+        }
+
+        /** Returns the coordinate sequence of the next ring. */
+        @Override public PointSequence next() {
+            final LineString current;
+            if (interior < 0) {
+                current = polygon.getExteriorRing();
+            } else {
+                current = polygon.getInteriorRingN(interior);
+            }
+            interior++;
+            return current.getPoints();
+        }
+    }
+
+    /**
+     * An iterator over the coordinate sequences of a geometry collection.
+     */
+    private static final class GeomIterator implements Iterator<PointSequence> 
{
+        /** The collection for which to return geometries. */
+        private final GeometryCollection collection;
+
+        /** Index of current geometry. */
+        private int index;
+
+        /** Coordinate sequences of the current geometry. */
+        private Iterator<PointSequence> current;
+
+        /** Created a new iterator for the given collection. */
+        GeomIterator(final GeometryCollection collection) {
+            this.collection = collection;
+            while (index < collection.getNumGeometries()) {
+                current = iterator(collection.getGeometryN(index));
+                if (current.hasNext()) break;
+                index++;
+            }
+        }
+
+        /** Returns {@code true} if there is more sequences to return. */
+        @Override public boolean hasNext() {
+            while (!current.hasNext()) {
+                if (++index >= collection.getNumGeometries()) {
+                    return false;
+                }
+                current = iterator(collection.getGeometryN(index));
+            }
+            return true;
+        }
+
+        /** Returns the coordinate sequence of the next geometry. */
+        @Override public PointSequence next() {
+            return current.next();
+        }
+    }
+}
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/ShapeAdapter.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/ShapeAdapter.java
new file mode 100644
index 0000000000..c34fb3a600
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/ShapeAdapter.java
@@ -0,0 +1,189 @@
+/*
+ * 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.geometries.adapter;
+
+import java.awt.Shape;
+import java.awt.Rectangle;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.PathIterator;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.util.Objects;
+import org.apache.sis.geometries.AttributesType;
+import org.apache.sis.geometries.Geometry;
+import org.apache.sis.geometries.GeometryFactory;
+import org.apache.sis.geometries.LinearRing;
+import org.apache.sis.geometries.PointSequence;
+import org.apache.sis.geometries.internal.shared.ArraySequence;
+import org.apache.sis.geometries.math.Array;
+import org.apache.sis.geometries.math.DataType;
+import org.apache.sis.geometries.math.NDArrays;
+import org.apache.sis.geometries.math.SampleSystem;
+import org.apache.sis.geometry.wrapper.j2d.EmptyShape;
+import org.apache.sis.referencing.internal.shared.AbstractShape;
+import org.apache.sis.referencing.internal.shared.IntervalRectangle;
+import org.opengis.geometry.Envelope;
+
+
+/**
+ * A thin wrapper that adapts a SIS geometry to the {@link Shape} interface so
+ * that the geometry can be used by Java 2D without copying coordinate values.
+ * This class does not cache any value; if the SIS geometry is changed,
+ * the modifications will be immediately visible in this {@code Shape}.
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+public final class ShapeAdapter extends AbstractShape {
+
+    /**
+     * The wrapped SIS geometry.
+     */
+    protected final Geometry geometry;
+
+    /**
+     * Creates a new wrapper for the given SIS geometry.
+     *
+     * @param  geometry  the SIS geometry to wrap.
+     */
+    public ShapeAdapter(final Geometry geometry) {
+        this.geometry = Objects.requireNonNull(geometry);
+    }
+
+    /**
+     * Returns {@code true} if this shape backed by primitive {@code float} 
values.
+     */
+    @Override
+    protected boolean isFloat() {
+        final DataType dataType = 
geometry.getAttributesType().getAttributeType(AttributesType.ATT_POSITION);
+        return dataType == DataType.FLOAT;
+    }
+
+    /**
+     * Returns an integer rectangle that completely encloses the shape.
+     * There is no guarantee that the rectangle is the smallest bounding box 
that encloses the shape.
+     */
+    @Override
+    public Rectangle getBounds() {
+        return getBounds2D().getBounds();
+    }
+
+    /**
+     * Returns a rectangle that completely encloses the shape.
+     * There is no guarantee that the rectangle is the smallest bounding box 
that encloses the shape.
+     */
+    @Override
+    public Rectangle2D getBounds2D() {
+        final Envelope e = geometry.getEnvelope();
+        return new IntervalRectangle(e.getMinimum(0), e.getMinimum(1),
+                                     e.getMaximum(0), e.getMaximum(1));
+    }
+
+    /**
+     * Tests if the specified point is inside the boundary of the shape.
+     * This method delegates to {@link #contains(double, double)}.
+     */
+    @Override
+    public boolean contains(final Point2D p) {
+        return contains(p.getX(), p.getY());
+    }
+
+    /**
+     * Tests if the specified point is inside the boundary of the shape.
+     */
+    @Override
+    public boolean contains(final double x, final double y) {
+        return 
geometry.contains(GeometryFactory.createPoint(SampleSystem.ofSize(2), x, y));
+    }
+
+    /**
+     * Tests if the specified rectangle is inside the boundary of the shape.
+     */
+    @Override
+    public boolean contains(final Rectangle2D r) {
+        return geometry.contains(createRect(r.getMinX(), r.getMinY(), 
r.getMaxX(), r.getMaxY()));
+    }
+
+    /**
+     * Tests if the specified rectangle is inside the boundary of the shape.
+     */
+    @Override
+    public boolean contains(final double x, final double y, final double 
width, final double height) {
+        return geometry.contains(createRect(x, y, x + width, y + height));
+    }
+
+    /**
+     * Tests if the specified rectangle intersects this shape.
+     */
+    @Override
+    public boolean intersects(final Rectangle2D r) {
+        return geometry.intersects(createRect(r.getMinX(), r.getMinY(), 
r.getMaxX(), r.getMaxY()));
+    }
+
+    /**
+     * Tests if the specified rectangle intersects this shape.
+     */
+    @Override
+    public boolean intersects(final double x, final double y, final double 
width, final double height) {
+        return geometry.intersects(createRect(x, y, x + width, y + height));
+    }
+
+    /**
+     * Creates a SIS polygon which is a rectangle with the given coordinates.
+     * This is a temporary shape used for union and intersection tests.
+     */
+    private static Geometry createRect(final double xmin, final double ymin, 
final double xmax, final double ymax) {
+        final Array positions = NDArrays.of(2, new double[]{
+            xmin, ymin,
+            xmin, ymax,
+            xmax, ymax,
+            xmax, ymin,
+            xmin, ymin
+        });
+        final PointSequence ps = new ArraySequence(positions);
+        final LinearRing ring = GeometryFactory.createLinearRing(ps);
+        return GeometryFactory.createPolygon(ring, null);
+    }
+
+    /**
+     * Returns an iterator for the shape outline geometry. The flatness factor 
is ignored on the assumption
+     * that this shape does not contain any Bézier curve.
+     *
+     * @param  at  optional transform to apply on coordinate values.
+     * @param  flatness  ignored.
+     * @return an iterator for the shape outline geometry.
+     */
+    @Override
+    public PathIterator getPathIterator(final AffineTransform at, final double 
flatness) {
+        return getPathIterator(at);
+    }
+
+    /**
+     * Returns an iterator for the shape outline geometry.
+     *
+     * @param  at  optional transform to apply on coordinate values.
+     * @return an iterator for the shape outline geometry.
+     */
+    @Override
+    public PathIterator getPathIterator(final AffineTransform at) {
+        if (geometry.isEmpty()) {
+            return EmptyShape.INSTANCE;
+        } else {
+            return new PathIteratorAdapter(geometry, at);
+        }
+    }
+}
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/math/SampleSystem.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/math/SampleSystem.java
index 85b7ccd5c8..fb89447d00 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/math/SampleSystem.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/math/SampleSystem.java
@@ -22,6 +22,7 @@ import java.util.Objects;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.util.GenericName;
 import org.apache.sis.coverage.SampleDimension;
+import org.apache.sis.geometries.Geometries;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.util.collection.Cache;
@@ -129,6 +130,10 @@ public final class SampleSystem {
         }
     }
 
+    public static SampleSystem cartesian(int nbDim) {
+        return of(Geometries.getUndefinedCRS(nbDim));
+    }
+
     /**
      * Returns an identification for this dimension. This is typically used as 
a way to perform a band select
      * by using human comprehensible descriptions instead of just numbers.
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/GeometryProcessor.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/GeometryProcessor.java
index e457705174..360becd4f0 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/GeometryProcessor.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/GeometryProcessor.java
@@ -367,6 +367,9 @@ public final class GeometryProcessor {
      */
     @UML(identifier="transform", specification=ISO_19107) // section 6.4.4.28
     public Geometry transform(Geometry geom, CoordinateReferenceSystem crs, 
MathTransform transform) {
+        if (crs == null) {
+            crs = geom.getCoordinateReferenceSystem();
+        }
 
         if (geom instanceof LinearRing cdt) {
             return Transform.transform(cdt, crs, transform);
diff --git 
a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/adapter/ShapeAdapterTest.java
 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/adapter/ShapeAdapterTest.java
new file mode 100644
index 0000000000..d99cb1193b
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/adapter/ShapeAdapterTest.java
@@ -0,0 +1,217 @@
+/*
+ * 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.geometries.adapter;
+
+import java.awt.Shape;
+import java.awt.geom.PathIterator;
+import org.apache.sis.geometries.Geometry;
+import org.apache.sis.geometries.GeometryFactory;
+import org.apache.sis.geometries.LineString;
+import org.apache.sis.geometries.LinearRing;
+import org.apache.sis.geometries.Polygon;
+import org.apache.sis.geometries.internal.shared.ArraySequence;
+import org.apache.sis.geometries.math.NDArrays;
+import org.apache.sis.geometries.math.SampleSystem;
+import org.apache.sis.geometry.wrapper.j2d.DecimatedShape;
+
+// Test dependencies
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+
+/**
+ * Tests {@link ShapeAdapter}.
+ *
+ * @author  Johann Sorel (Puzzle-GIS, Geomatys)
+ */
+public final class ShapeAdapterTest {
+
+    private static SampleSystem CARTESIAN_2D = SampleSystem.cartesian(2);
+
+    /**
+     * An array of length 2 where to store (x,y) coordinates during path 
iteration.
+     */
+    private final double[] buffer;
+
+    /**
+     * Iterator over the shape to verify. Value is assigned by {@link 
#initialize(Geometry)}.
+     */
+    private PathIterator iterator;
+
+    /**
+     * Build a new test case.
+     */
+    public ShapeAdapterTest() {
+        buffer  = new double[2];
+    }
+
+    /**
+     * Initializes the test with the given geometry.
+     */
+    private void initialize(final Geometry geometry) {
+        final Shape shape = new ShapeAdapter(geometry);
+        iterator = shape.getPathIterator(null);
+    }
+
+    /**
+     * Verifies that the current segment in the path iterator is of the given 
type.
+     * This method invokes {@link PathIterator#next()} after the comparison.
+     *
+     * @param  type  expected type: {@link PathIterator#SEG_MOVETO} or {@link 
PathIterator#SEG_LINETO}.
+     * @param  x     expected <var>x</var> coordinate.
+     * @param  y     expected <var>y</var> coordinate.
+     */
+    private void assertSegmentEquals(final int type, final double x, final 
double y) {
+        assertFalse(iterator.isDone());
+        assertEquals(type, iterator.currentSegment(buffer));
+        assertEquals(x, buffer[0]);
+        assertEquals(y, buffer[1]);
+        iterator.next();
+    }
+
+    /**
+     * Verifies that the current segment is a {@link PathIterator#SEG_CLOSE}.
+     * This method invokes {@link PathIterator#next()} after the verification.
+     */
+    private void assertSegmentClose() {
+        assertFalse(iterator.isDone());
+        assertEquals(PathIterator.SEG_CLOSE, iterator.currentSegment(buffer));
+        iterator.next();
+    }
+
+    /**
+     * Tests {@link ShapeAdapter} with a point.
+     */
+    @Test
+    public void testPoint() {
+        initialize(GeometryFactory.createPoint(CARTESIAN_2D, 10, 20));
+        assertSegmentEquals(PathIterator.SEG_MOVETO, 10, 20);
+        assertTrue(iterator.isDone());
+    }
+
+    /**
+     * Tests {@link ShapeAdapter} with a line string.
+     */
+    @Test
+    public void testLineString() {
+        initialize(GeometryFactory.createLineString(new 
ArraySequence(NDArrays.of(CARTESIAN_2D, new double[]{
+            3,1,
+            7,6,
+            5,2
+        }))));
+        assertSegmentEquals(PathIterator.SEG_MOVETO, 3, 1);
+        assertSegmentEquals(PathIterator.SEG_LINETO, 7, 6);
+        assertSegmentEquals(PathIterator.SEG_LINETO, 5, 2);
+        assertTrue(iterator.isDone());
+    }
+
+    /**
+     * Tests {@link ShapeAdapter} with a multi line string.
+     */
+    @Test
+    public void testMultiLineString() {
+        final LineString line1 = GeometryFactory.createLineString(new 
ArraySequence(NDArrays.of(CARTESIAN_2D, new double[]{
+            10, 12,
+            5, 2
+        })));
+        final LineString line2 = GeometryFactory.createLineString(new 
ArraySequence(NDArrays.of(CARTESIAN_2D, new double[]{
+            3, 1,
+            7, 6,
+            5, 2
+        })));
+        initialize(GeometryFactory.createMultiLineString(line1, line2));
+        assertSegmentEquals(PathIterator.SEG_MOVETO, 10, 12);
+        assertSegmentEquals(PathIterator.SEG_LINETO, 5, 2);
+        assertSegmentEquals(PathIterator.SEG_MOVETO, 3, 1);
+        assertSegmentEquals(PathIterator.SEG_LINETO, 7, 6);
+        assertSegmentEquals(PathIterator.SEG_LINETO, 5, 2);
+        assertTrue(iterator.isDone());
+    }
+
+    /**
+     * Tests {@link ShapeAdapter} with a polygon.
+     */
+    @Test
+    public void testPolygon() {
+        final LinearRing ring = GeometryFactory.createLinearRing(new 
ArraySequence(NDArrays.of(CARTESIAN_2D, new double[]{
+            3, 1,
+            7, 6,
+            5, 2,
+            3, 1
+        })));
+        initialize(GeometryFactory.createPolygon(ring, null));
+        assertSegmentEquals(PathIterator.SEG_MOVETO, 3, 1);
+        assertSegmentEquals(PathIterator.SEG_LINETO, 7, 6);
+        assertSegmentEquals(PathIterator.SEG_LINETO, 5, 2);
+        assertSegmentClose();
+        assertTrue(iterator.isDone());
+    }
+
+    /**
+     * Tests {@link ShapeAdapter} with a multi-polygon.
+     */
+    @Test
+    public void testMultiPolygon() {
+        final LinearRing ring1 = GeometryFactory.createLinearRing(new 
ArraySequence(NDArrays.of(CARTESIAN_2D, new double[]{
+            3, 1,
+            7, 6,
+            5, 2,
+            3, 1
+        })));
+        final LinearRing ring2 = GeometryFactory.createLinearRing(new 
ArraySequence(NDArrays.of(CARTESIAN_2D, new double[]{
+            12, 3,
+            1, 9,
+            4, 6,
+            12, 3
+        })));
+        final Polygon polygon1 = GeometryFactory.createPolygon(ring1, null);
+        final Polygon polygon2 = GeometryFactory.createPolygon(ring2, null);
+        initialize(GeometryFactory.createMultiPolygon(polygon1, polygon2));
+
+        // First polygon.
+        assertSegmentEquals(PathIterator.SEG_MOVETO, 3, 1);
+        assertSegmentEquals(PathIterator.SEG_LINETO, 7, 6);
+        assertSegmentEquals(PathIterator.SEG_LINETO, 5, 2);
+        assertSegmentClose();
+
+        // Second polygon.
+        assertSegmentEquals(PathIterator.SEG_MOVETO, 12, 3);
+        assertSegmentEquals(PathIterator.SEG_LINETO, 1, 9);
+        assertSegmentEquals(PathIterator.SEG_LINETO, 4, 6);
+        assertSegmentClose();
+        assertTrue(iterator.isDone());
+    }
+
+    /**
+     * Tests {@link ShapeAdapter} with the addition of a decimation.
+     */
+    @Test
+    public void testAsDecimatedShapeLineString() {
+        final LineString line = GeometryFactory.createLineString(new 
ArraySequence(NDArrays.of(CARTESIAN_2D, new double[]{
+            0, 0,
+            1, 0,
+            2, 0
+        })));
+        final DecimatedShape shape = new DecimatedShape(new 
ShapeAdapter(line), new double[] {1.5, 1.5});
+        assertTrue(shape.isValid());
+        iterator = shape.getPathIterator(null);
+
+        assertSegmentEquals(PathIterator.SEG_MOVETO, 0, 0);
+        assertSegmentEquals(PathIterator.SEG_LINETO, 2, 0);
+        assertTrue(iterator.isDone());
+    }
+}

Reply via email to