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