You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by js...@apache.org on 2021/11/23 10:25:41 UTC
[sis] 01/01: feat(JTS): add JTS.asShape functions to view a JTS Geometry as a Java2D Shape
This is an automated email from the ASF dual-hosted git repository.
jsorel pushed a commit to branch feat/toShape
in repository https://gitbox.apache.org/repos/asf/sis.git
commit 32dee92a8d84c0e6cade0eab24ffe38069b61500
Author: jsorel <jo...@geomatys.com>
AuthorDate: Tue Nov 23 11:25:05 2021 +0100
feat(JTS): add JTS.asShape functions to view a JTS Geometry as a Java2D Shape
---
.../sis/internal/feature/jts/AbstractJTSShape.java | 251 ++++++++
.../feature/jts/DecimateJTSPathIterator.java | 166 ++++++
.../sis/internal/feature/jts/DecimateJTSShape.java | 73 +++
.../org/apache/sis/internal/feature/jts/JTS.java | 25 +
.../sis/internal/feature/jts/JTSPathIterator.java | 636 +++++++++++++++++++++
.../apache/sis/internal/feature/jts/JTSShape.java | 96 ++++
.../apache/sis/internal/feature/jts/JTSTest.java | 310 ++++++++++
7 files changed, 1557 insertions(+)
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/AbstractJTSShape.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/AbstractJTSShape.java
new file mode 100644
index 0000000..556a4d1
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/AbstractJTSShape.java
@@ -0,0 +1,251 @@
+/*
+ * 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.internal.feature.jts;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.LinearRing;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.Arrays;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.apache.sis.util.logging.Logging;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.TransformException;
+
+/**
+ * A thin wrapper that adapts a JTS geometry to the Shape interface so that the geometry can be used
+ * by java2d without coordinate cloning.
+ *
+ * @author Johann Sorel (Puzzle-GIS + Geomatys)
+ * @version 2.0
+ * @since 2.0
+ * @module
+ */
+abstract class AbstractJTSShape<T extends Geometry> implements Shape, Cloneable {
+
+ static final Logger LOGGER = Logging.getLogger("org.geotoolkit.geometry");
+
+ /** The wrapped JTS geometry */
+ protected final T geometry;
+
+ /** An additional AffineTransform */
+ protected final MathTransform transform;
+
+ public AbstractJTSShape(final T geom) {
+ this(geom, null);
+ }
+
+ /**
+ * Creates a new GeometryJ2D object.
+ *
+ * @param geom - the wrapped geometry
+ */
+ public AbstractJTSShape(final T geom, final MathTransform trs) {
+ this.geometry = geom;
+ this.transform = (trs == null) ? JTSPathIterator.IDENTITY : trs;
+ }
+
+ /**
+ * @return the current wrapped geometry
+ */
+ public T getGeometry() {
+ return geometry;
+ }
+
+ public MathTransform getTransform() {
+ return transform;
+ }
+
+ protected MathTransform getInverse(){
+ try {
+ return transform.inverse();
+ } catch (org.opengis.referencing.operation.NoninvertibleTransformException ex) {
+ Logging.getLogger("org.geotoolkit.display2d.primitive.jts").log(Level.WARNING, ex.getMessage(), ex);
+ return null;
+ }
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public boolean contains(final Rectangle2D r) {
+ return contains(r.getMinX(),r.getMinY(),r.getWidth(),r.getHeight());
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public boolean contains(final Point2D p) {
+ final MathTransform inverse = getInverse();
+ if (inverse != null) {
+ final double[] a = new double[]{p.getX(), p.getY()};
+ safeTransform(inverse, a, a);
+ final Coordinate coord = new Coordinate(a[0], a[1]);
+ final Geometry point = geometry.getFactory().createPoint(coord);
+ return geometry.contains(point);
+ }
+
+ //inverse transform could not be computed
+ //fallback on AWT geometries
+ return new GeneralPath(this).contains(p);
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public boolean contains(final double x, final double y) {
+ return contains(new Point2D.Double(x, y));
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public boolean contains(final double x, final double y, final double w, final double h) {
+ return intersectOrContains(x, y, w, h, false);
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public Rectangle getBounds() {
+ return getBounds2D().getBounds();
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public Rectangle2D getBounds2D() {
+ if (geometry == null) return null;
+
+ final Envelope env = geometry.getEnvelopeInternal();
+ final double[] p1 = new double[]{env.getMinX(), env.getMinY()};
+ safeTransform(transform,p1, p1);
+ final double[] p2 = new double[]{env.getMaxX(), env.getMaxY()};
+ safeTransform(transform,p2, p2);
+
+ final Rectangle2D rect = new Rectangle2D.Double(p1[0], p1[1], 0, 0);
+ rect.add(p2[0],p2[1]);
+ return rect;
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public PathIterator getPathIterator(final AffineTransform at, final double flatness) {
+ return getPathIterator(at);
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public boolean intersects(final Rectangle2D r) {
+ return intersects(r.getX(),r.getY(),r.getWidth(),r.getHeight());
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public boolean intersects(final double x, final double y, final double w, final double h) {
+ return intersectOrContains(x, y, w, h, true);
+ }
+
+ /**
+ * Test rectangle intersection or containment.
+ *
+ * @param x left coordinate
+ * @param y bottom coordinate
+ * @param w width
+ * @param h height
+ * @param intersect true for intersection, false for contains
+ * @return true
+ */
+ private boolean intersectOrContains(final double x, final double y, final double w, final double h, boolean intersect) {
+ final MathTransform inverse = getInverse();
+ if (inverse != null) {
+ final double[] p1 = new double[]{x, y};
+ safeTransform(inverse, p1, p1);
+ final double[] p2 = new double[]{x + w, y + h};
+ safeTransform(inverse, p2, p2);
+
+ final Coordinate[] coords = {
+ new Coordinate(p1[0], p1[1]),
+ new Coordinate(p1[0], p2[1]),
+ new Coordinate(p2[0], p2[1]),
+ new Coordinate(p2[0], p1[1]),
+ new Coordinate(p1[0], p1[1])
+ };
+ final LinearRing lr = geometry.getFactory().createLinearRing(coords);
+ final Geometry rect = geometry.getFactory().createPolygon(lr, null);
+ return intersect ? geometry.intersects(rect) : geometry.contains(rect);
+ }
+
+ //inverse transform could not be computed
+ //fallback on AWT geometries
+ final GeneralPath path = new GeneralPath(this);
+ return intersect ? path.intersects(x, y, w, h) : path.contains(x, y, w, h);
+ }
+
+ @Override
+ public AbstractJTSShape clone() {
+ return null; //TODO
+ }
+
+
+ protected void safeTransform(MathTransform trs, double[] in, double[] out) {
+ try {
+ trs.transform(in, 0, out, 0, 1);
+ } catch (TransformException ex) {
+ LOGGER.log(Level.WARNING, ex.getMessage(),ex);
+ Arrays.fill(out, Double.NaN);
+ }
+ }
+
+ protected void safeTransform(double[] in, int offset, float[] out, int outOffset, int nb) {
+ try {
+ transform.transform(in, offset, out, outOffset, nb);
+ } catch (TransformException ex) {
+ LOGGER.log(Level.WARNING, ex.getMessage(),ex);
+ Arrays.fill(out, outOffset, outOffset+nb*2, Float.NaN);
+ }
+ }
+
+ protected void safeTransform(double[] in, int offset, double[] out, int outOffset, int nb) {
+ try {
+ transform.transform(in, offset, out, outOffset, nb);
+ } catch (TransformException ex) {
+ LOGGER.log(Level.WARNING, ex.getMessage(),ex);
+ Arrays.fill(out, outOffset, outOffset+nb*2, Double.NaN);
+ }
+ }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/DecimateJTSPathIterator.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/DecimateJTSPathIterator.java
new file mode 100644
index 0000000..0d8c513
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/DecimateJTSPathIterator.java
@@ -0,0 +1,166 @@
+/*
+ * 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.internal.feature.jts;
+
+import static java.awt.geom.PathIterator.SEG_CLOSE;
+import static java.awt.geom.PathIterator.SEG_LINETO;
+import static java.awt.geom.PathIterator.SEG_MOVETO;
+import static java.awt.geom.PathIterator.WIND_NON_ZERO;
+import org.locationtech.jts.geom.CoordinateSequence;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.LinearRing;
+import org.opengis.referencing.operation.MathTransform;
+
+/**
+ * Decimating Java2D path iterators for JTS geometries.
+ *
+ * @author Johann Sorel (Puzzle-GIS + Geomatys)
+ * @version 2.0
+ * @since 2.0
+ * @module
+ */
+final class DecimateJTSPathIterator {
+
+ static final class LineString extends JTSPathIterator<org.locationtech.jts.geom.LineString> {
+
+ private final CoordinateSequence coordinates;
+ private final int coordinateCount;
+ /**
+ * True if the line is a ring
+ */
+ private final boolean isClosed;
+ private int lastCoord;
+ private int currentIndex;
+ private boolean done;
+ private final double[] resolution;
+ private final double[] currentCoord = new double[2];
+
+ /**
+ * Create a new LineString path iterator.
+ */
+ public LineString(final org.locationtech.jts.geom.LineString ls, final MathTransform trs, final double[] resolution) {
+ super(ls, trs);
+ coordinates = ls.getCoordinateSequence();
+ coordinateCount = coordinates.size();
+ isClosed = ls instanceof LinearRing;
+ this.resolution = resolution;
+ currentCoord[0] = coordinates.getX(0);
+ currentCoord[1] = coordinates.getY(0);
+ }
+
+ @Override
+ public void reset() {
+ done = false;
+ currentIndex = 0;
+ currentCoord[0] = coordinates.getX(0);
+ currentCoord[1] = coordinates.getY(0);
+ }
+
+ @Override
+ public int getWindingRule() {
+ return WIND_NON_ZERO;
+ }
+
+ @Override
+ public boolean isDone() {
+ return done;
+ }
+
+ @Override
+ public void next() {
+
+ while (true) {
+ if (((currentIndex == (coordinateCount - 1)) && !isClosed)
+ || ((currentIndex == coordinateCount) && isClosed)) {
+ done = true;
+ break;
+ }
+
+ currentIndex++;
+ double candidateX = coordinates.getX(currentIndex);
+ double candidateY = coordinates.getY(currentIndex);
+
+ if (Math.abs(candidateX - currentCoord[0]) >= resolution[0] || Math.abs(candidateY - currentCoord[1]) >= resolution[1]) {
+ currentCoord[0] = candidateX;
+ currentCoord[1] = candidateY;
+ break;
+ }
+
+ }
+ }
+
+ @Override
+ public int currentSegment(final double[] coords) {
+ if (currentIndex == 0) {
+ safeTransform(currentCoord, 0, coords, 0, 1);
+ return SEG_MOVETO;
+ } else if ((currentIndex == coordinateCount) && isClosed) {
+ return SEG_CLOSE;
+ } else {
+ safeTransform(currentCoord, 0, coords, 0, 1);
+ return SEG_LINETO;
+ }
+ }
+
+ @Override
+ public int currentSegment(final float[] coords) {
+ if (currentIndex == 0) {
+ safeTransform(currentCoord, 0, coords, 0, 1);
+ return SEG_MOVETO;
+ } else if ((currentIndex == coordinateCount) && isClosed) {
+ return SEG_CLOSE;
+ } else {
+ safeTransform(currentCoord, 0, coords, 0, 1);
+ return SEG_LINETO;
+ }
+ }
+ }
+
+ static final class GeometryCollection extends JTSPathIterator.GeometryCollection {
+
+ private final double[] resolution;
+
+ public GeometryCollection(final org.locationtech.jts.geom.GeometryCollection gc, final MathTransform trs, final double[] resolution) {
+ super(gc, trs);
+ this.resolution = resolution;
+ reset();
+ }
+
+ /**
+ * Returns the specific iterator for the geometry passed.
+ *
+ * @param candidate The geometry whole iterator is requested
+ *
+ */
+ @Override
+ protected void prepareIterator(final Geometry candidate) {
+ if (candidate.isEmpty()) {
+ currentIterator = JTSPathIterator.Empty.INSTANCE;
+ } else if (candidate instanceof org.locationtech.jts.geom.Point) {
+ currentIterator = new JTSPathIterator.Point((org.locationtech.jts.geom.Point) candidate, transform);
+ } else if (candidate instanceof org.locationtech.jts.geom.Polygon) {
+ currentIterator = new JTSPathIterator.Polygon((org.locationtech.jts.geom.Polygon) candidate, transform);
+ } else if (candidate instanceof org.locationtech.jts.geom.LineString) {
+ currentIterator = new DecimateJTSPathIterator.LineString((org.locationtech.jts.geom.LineString) candidate, transform, resolution);
+ } else if (candidate instanceof org.locationtech.jts.geom.GeometryCollection) {
+ currentIterator = new DecimateJTSPathIterator.GeometryCollection((org.locationtech.jts.geom.GeometryCollection) candidate, transform, resolution);
+ } else {
+ currentIterator = JTSPathIterator.Empty.INSTANCE;
+ }
+ }
+ }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/DecimateJTSShape.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/DecimateJTSShape.java
new file mode 100644
index 0000000..03f511e
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/DecimateJTSShape.java
@@ -0,0 +1,73 @@
+/*
+ * 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.internal.feature.jts;
+
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryCollection;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.PathIterator;
+import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
+import org.opengis.referencing.operation.MathTransform;
+
+/**
+ * A thin wrapper that adapts a JTS geometry to the Shape interface so that the
+ * geometry can be used by java2d without coordinate cloning, coordinate
+ * decimation apply on the fly.
+ *
+ * @author Johann Sorel (Puzzle-GIS + Geomatys)
+ * @version 2.0
+ * @since 2.0
+ * @module
+ */
+class DecimateJTSShape extends JTSShape {
+
+ private final double[] resolution;
+
+ /**
+ * Creates a new GeometryJ2D object.
+ *
+ * @param geom - the wrapped geometry
+ */
+ public DecimateJTSShape(final Geometry geom, final double[] resolution) {
+ super(geom);
+ this.resolution = resolution;
+ }
+
+ @Override
+ public PathIterator getPathIterator(final AffineTransform at) {
+ MathTransform t = (at == null) ? null : new AffineTransform2D(at);
+ if (iterator == null) {
+ if (this.geometry.isEmpty()) {
+ iterator = JTSPathIterator.Empty.INSTANCE;
+ } else if (this.geometry instanceof Point) {
+ iterator = new JTSPathIterator.Point((Point) geometry, t);
+ } else if (this.geometry instanceof Polygon) {
+ iterator = new JTSPathIterator.Polygon((Polygon) geometry, t);
+ } else if (this.geometry instanceof LineString) {
+ iterator = new DecimateJTSPathIterator.LineString((LineString) geometry, t, resolution);
+ } else if (this.geometry instanceof GeometryCollection) {
+ iterator = new DecimateJTSPathIterator.GeometryCollection((GeometryCollection) geometry, t, resolution);
+ }
+ } else {
+ iterator.setTransform(t);
+ }
+ return iterator;
+ }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/JTS.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/JTS.java
index 24abdd1..b5c85c2 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/JTS.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/JTS.java
@@ -16,6 +16,7 @@
*/
package org.apache.sis.internal.feature.jts;
+import java.awt.Shape;
import java.util.Map;
import org.opengis.metadata.Identifier;
import org.opengis.util.FactoryException;
@@ -33,6 +34,7 @@ import org.apache.sis.geometry.Envelope2D;
import org.apache.sis.internal.system.Loggers;
import org.apache.sis.internal.util.Constants;
import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.util.ArgumentChecks;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
@@ -294,4 +296,27 @@ public final class JTS extends Static {
}
return geometry;
}
+
+ /**
+ * Create a view of the JTS geometry as a Java2D Shape.
+ *
+ * @param geometry the geometry to view as a shape, not {@code null}.
+ * @param transform transform to apply on coordinates, or {@code null}.
+ * @return the Java2D shape view
+ */
+ public static Shape asShape(Geometry geometry, final MathTransform transform) {
+ ArgumentChecks.ensureNonNull("geometry", geometry);
+ return new JTSShape(geometry, transform);
+ }
+ /**
+ * Create a view of the JTS geometry as a Java2D Shape applying a decimation on the fly.
+ *
+ * @param geometry the geometry to view as a shape, not {@code null}.
+ * @param resolution decimation resolution, or {@code null}.
+ * @return the Java2D shape view
+ */
+ public static Shape asDecimatedShape(Geometry geometry, final double[] resolution) {
+ ArgumentChecks.ensureNonNull("geometry", geometry);
+ return new DecimateJTSShape(geometry, resolution);
+ }
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/JTSPathIterator.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/JTSPathIterator.java
new file mode 100644
index 0000000..90542b4
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/JTSPathIterator.java
@@ -0,0 +1,636 @@
+/*
+ * 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.internal.feature.jts;
+
+import org.locationtech.jts.geom.Geometry;
+import java.awt.geom.PathIterator;
+import static java.awt.geom.PathIterator.SEG_CLOSE;
+import static java.awt.geom.PathIterator.SEG_LINETO;
+import static java.awt.geom.PathIterator.SEG_MOVETO;
+import static java.awt.geom.PathIterator.WIND_EVEN_ODD;
+import static java.awt.geom.PathIterator.WIND_NON_ZERO;
+import java.util.Arrays;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
+import org.apache.sis.util.logging.Logging;
+import org.locationtech.jts.geom.CoordinateSequence;
+import org.locationtech.jts.geom.LinearRing;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.TransformException;
+
+/**
+ * Abstract Java2D path iterator for JTS Geometry.
+ *
+ * @author Johann Sorel (Puzzle-GIS + Geomatys)
+ * @version 2.0
+ * @since 2.0
+ * @module
+ */
+abstract class JTSPathIterator<T extends Geometry> implements PathIterator {
+
+ private static final Logger LOGGER = Logging.getLogger("org.apache.sis.internal.feature.jts");
+ static final AffineTransform2D IDENTITY = new AffineTransform2D(1, 0, 0, 1, 0, 0);
+
+ protected MathTransform transform;
+ protected T geometry;
+
+ protected JTSPathIterator(final MathTransform trs) {
+ this(null, trs);
+ }
+
+ protected JTSPathIterator(final T geometry, final MathTransform trs) {
+ this.transform = (trs == null) ? IDENTITY : trs;
+ this.geometry = geometry;
+ }
+
+ public void setGeometry(final T geom) {
+ this.geometry = geom;
+ }
+
+ public void setTransform(final MathTransform trs) {
+ this.transform = (trs == null) ? IDENTITY : trs;
+ reset();
+ }
+
+ public MathTransform getTransform() {
+ return transform;
+ }
+
+ public T getGeometry() {
+ return geometry;
+ }
+
+ public abstract void reset();
+
+ protected void safeTransform(float[] in, int offset, float[] out, int outOffset, int nb) {
+ try {
+ transform.transform(in, offset, out, outOffset, nb);
+ } catch (TransformException ex) {
+ LOGGER.log(Level.WARNING, ex.getMessage(), ex);
+ Arrays.fill(out, outOffset, outOffset + nb * 2, Float.NaN);
+ }
+ }
+
+ protected void safeTransform(double[] in, int offset, float[] out, int outOffset, int nb) {
+ try {
+ transform.transform(in, offset, out, outOffset, nb);
+ } catch (TransformException ex) {
+ LOGGER.log(Level.WARNING, ex.getMessage(), ex);
+ Arrays.fill(out, outOffset, outOffset + nb * 2, Float.NaN);
+ }
+ }
+
+ protected void safeTransform(double[] in, int offset, double[] out, int outOffset, int nb) {
+ try {
+ transform.transform(in, offset, out, outOffset, nb);
+ } catch (TransformException ex) {
+ LOGGER.log(Level.WARNING, ex.getMessage(), ex);
+ Arrays.fill(out, outOffset, outOffset + nb * 2, Double.NaN);
+ }
+ }
+
+ static final class Empty extends JTSPathIterator<Geometry> {
+
+ public static final Empty INSTANCE = new Empty();
+
+ private Empty() {
+ super(null, null);
+ }
+
+ @Override
+ public int getWindingRule() {
+ return WIND_NON_ZERO;
+ }
+
+ @Override
+ public boolean isDone() {
+ return true;
+ }
+
+ @Override
+ public void next() {
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public int currentSegment(final double[] coords) {
+ return 0;
+ }
+
+ @Override
+ public int currentSegment(final float[] coords) {
+ return 0;
+ }
+
+ @Override
+ public void reset() {
+ }
+ }
+
+ static final class Point extends JTSPathIterator<org.locationtech.jts.geom.Point> {
+
+ private boolean done;
+
+ /**
+ * Create a new Point path iterator.
+ */
+ public Point(final org.locationtech.jts.geom.Point point, final MathTransform trs) {
+ super(point, trs);
+ }
+
+ @Override
+ public int getWindingRule() {
+ return WIND_EVEN_ODD;
+ }
+
+ @Override
+ public void next() {
+ done = true;
+ }
+
+ @Override
+ public boolean isDone() {
+ return done;
+ }
+
+ @Override
+ public int currentSegment(final double[] coords) {
+ coords[0] = geometry.getX();
+ coords[1] = geometry.getY();
+ safeTransform(coords, 0, coords, 0, 1);
+ return SEG_MOVETO;
+ }
+
+ @Override
+ public int currentSegment(final float[] coords) {
+ coords[0] = (float) geometry.getX();
+ coords[1] = (float) geometry.getY();
+ safeTransform(coords, 0, coords, 0, 1);
+ return SEG_MOVETO;
+ }
+
+ @Override
+ public void reset() {
+ done = false;
+ }
+ }
+
+ static final class LineString extends JTSPathIterator<org.locationtech.jts.geom.LineString> {
+
+ private CoordinateSequence coordinates;
+ private int coordinateCount;
+ /**
+ * True if the line is a ring
+ */
+ private boolean isClosed;
+ private int currentCoord;
+ private boolean done;
+
+ /**
+ * Create a new LineString path iterator.
+ */
+ public LineString(final org.locationtech.jts.geom.LineString ls, final MathTransform trs) {
+ super(ls, trs);
+ setGeometry(ls);
+ }
+
+ @Override
+ public void setGeometry(final org.locationtech.jts.geom.LineString geom) {
+ super.setGeometry(geom);
+ if (geom != null) {
+ coordinates = geom.getCoordinateSequence();
+ coordinateCount = coordinates.size();
+ isClosed = geom instanceof LinearRing;
+ }
+ reset();
+ }
+
+ @Override
+ public void reset() {
+ done = false;
+ currentCoord = 0;
+ }
+
+ @Override
+ public int getWindingRule() {
+ return WIND_NON_ZERO;
+ }
+
+ @Override
+ public boolean isDone() {
+ return done;
+ }
+
+ @Override
+ public void next() {
+ if (((currentCoord == (coordinateCount - 1)) && !isClosed)
+ || ((currentCoord == coordinateCount) && isClosed)) {
+ done = true;
+ } else {
+ currentCoord++;
+ }
+ }
+
+ @Override
+ public int currentSegment(final double[] coords) {
+ if (currentCoord == 0) {
+ coords[0] = coordinates.getX(0);
+ coords[1] = coordinates.getY(0);
+ safeTransform(coords, 0, coords, 0, 1);
+ return SEG_MOVETO;
+ } else if ((currentCoord == coordinateCount) && isClosed) {
+ return SEG_CLOSE;
+ } else {
+ coords[0] = coordinates.getX(currentCoord);
+ coords[1] = coordinates.getY(currentCoord);
+ safeTransform(coords, 0, coords, 0, 1);
+ return SEG_LINETO;
+ }
+ }
+
+ @Override
+ public int currentSegment(final float[] coords) {
+ if (currentCoord == 0) {
+ coords[0] = (float) coordinates.getX(0);
+ coords[1] = (float) coordinates.getY(0);
+ safeTransform(coords, 0, coords, 0, 1);
+ return SEG_MOVETO;
+ } else if ((currentCoord == coordinateCount) && isClosed) {
+ return SEG_CLOSE;
+ } else {
+ coords[0] = (float) coordinates.getX(currentCoord);
+ coords[1] = (float) coordinates.getY(currentCoord);
+ safeTransform(coords, 0, coords, 0, 1);
+ return SEG_LINETO;
+ }
+ }
+ }
+
+ static final class Polygon extends JTSPathIterator<org.locationtech.jts.geom.Polygon> {
+
+ /**
+ * The rings describing the polygon geometry
+ */
+ private org.locationtech.jts.geom.LineString[] rings;
+ /**
+ * The current ring during iteration
+ */
+ private int currentRing;
+ /**
+ * Current line coordinate
+ */
+ private int currentCoord;
+ /**
+ * The array of coordinates that represents the line geometry
+ */
+ private CoordinateSequence coords;
+ private int csSize;
+ /**
+ * True when the iteration is terminated
+ */
+ private boolean done;
+
+ /**
+ * Create a new Polygon path iterator.
+ */
+ public Polygon(final org.locationtech.jts.geom.Polygon p, final MathTransform trs) {
+ super(p, trs);
+ setGeometry(p);
+ }
+
+ @Override
+ public void setGeometry(final org.locationtech.jts.geom.Polygon geom) {
+ this.geometry = geom;
+ if (geom != null) {
+ int numInteriorRings = geom.getNumInteriorRing();
+ rings = new org.locationtech.jts.geom.LineString[numInteriorRings + 1];
+ rings[0] = geom.getExteriorRing();
+
+ for (int i = 0; i < numInteriorRings; i++) {
+ rings[i + 1] = geom.getInteriorRingN(i);
+ }
+ }
+ reset();
+ }
+
+ @Override
+ public void reset() {
+ currentRing = 0;
+ currentCoord = 0;
+ coords = rings[0].getCoordinateSequence();
+ csSize = coords.size() - 1;
+ done = false;
+ }
+
+ @Override
+ public int currentSegment(final double[] coords) {
+ // first make sure we're not at the last element, this prevents us from exceptions
+ // in the case where coords.size() == 0
+ if (currentCoord == csSize) {
+ return SEG_CLOSE;
+ } else if (currentCoord == 0) {
+ coords[0] = this.coords.getX(0);
+ coords[1] = this.coords.getY(0);
+ safeTransform(coords, 0, coords, 0, 1);
+ return SEG_MOVETO;
+ } else {
+ coords[0] = this.coords.getX(currentCoord);
+ coords[1] = this.coords.getY(currentCoord);
+ safeTransform(coords, 0, coords, 0, 1);
+ return SEG_LINETO;
+ }
+ }
+
+ @Override
+ public int currentSegment(final float[] coords) {
+ // first make sure we're not at the last element, this prevents us from exceptions
+ // in the case where coords.size() == 0
+ if (currentCoord == csSize) {
+ return SEG_CLOSE;
+ } else if (currentCoord == 0) {
+ coords[0] = (float) this.coords.getX(0);
+ coords[1] = (float) this.coords.getY(0);
+ safeTransform(coords, 0, coords, 0, 1);
+ return SEG_MOVETO;
+ } else {
+ coords[0] = (float) this.coords.getX(currentCoord);
+ coords[1] = (float) this.coords.getY(currentCoord);
+ safeTransform(coords, 0, coords, 0, 1);
+ return SEG_LINETO;
+ }
+ }
+
+ @Override
+ public int getWindingRule() {
+ return WIND_EVEN_ODD;
+ }
+
+ @Override
+ public boolean isDone() {
+ return done;
+ }
+
+ @Override
+ public void next() {
+ if (currentCoord == csSize) {
+ if (currentRing < (rings.length - 1)) {
+ currentCoord = 0;
+ currentRing++;
+ coords = rings[currentRing].getCoordinateSequence();
+ csSize = coords.size() - 1;
+ } else {
+ done = true;
+ }
+ } else {
+ currentCoord++;
+ }
+ }
+ }
+
+ static final class MultiLineString extends JTSPathIterator<org.locationtech.jts.geom.MultiLineString> {
+
+ private int coordinateCount;
+ //global geometry state
+ private int nbGeom;
+ private int currentGeom = -1;
+ private boolean done;
+ //sub geometry state
+ private CoordinateSequence currentSequence;
+ private int currentCoord = -1;
+
+ /**
+ * Create a new MultiLineString path iterator.
+ */
+ public MultiLineString(final org.locationtech.jts.geom.MultiLineString ls, final MathTransform trs) {
+ super(ls, trs);
+ setGeometry(ls);
+ }
+
+ @Override
+ public void setGeometry(final org.locationtech.jts.geom.MultiLineString geom) {
+ super.setGeometry(geom);
+ if (geom != null) {
+ nbGeom = geom.getNumGeometries();
+ nextSubGeom();
+ }
+ reset();
+ }
+
+ private void nextSubGeom() {
+ if (++currentGeom >= nbGeom) {
+ //nothing left, we are done
+ currentSequence = null;
+ currentCoord = -1;
+ done = true;
+ } else {
+ final org.locationtech.jts.geom.LineString subGeom = ((org.locationtech.jts.geom.LineString) geometry.getGeometryN(currentGeom));
+ currentSequence = subGeom.getCoordinateSequence();
+ coordinateCount = currentSequence.size();
+
+ if (coordinateCount == 0) {
+ //no point in this line, skip it
+ nextSubGeom();
+ } else {
+ currentCoord = 0;
+ done = false;
+ }
+ }
+ }
+
+ @Override
+ public void reset() {
+ currentGeom = -1;
+ nextSubGeom();
+ }
+
+ @Override
+ public int getWindingRule() {
+ return WIND_NON_ZERO;
+ }
+
+ @Override
+ public boolean isDone() {
+ return done;
+ }
+
+ @Override
+ public void next() {
+ if (++currentCoord >= coordinateCount) {
+ //we go to the size, even if we don't have a coordinate at this index,
+ //to indicate we close the path
+ //no more points in this segment
+ nextSubGeom();
+ }
+ }
+
+ @Override
+ public int currentSegment(final double[] coords) {
+ if (currentCoord == 0) {
+ coords[0] = currentSequence.getX(currentCoord);
+ coords[1] = currentSequence.getY(currentCoord);
+ safeTransform(coords, 0, coords, 0, 1);
+ return SEG_MOVETO;
+ } else if (currentCoord == coordinateCount) {
+ return SEG_CLOSE;
+ } else {
+ coords[0] = currentSequence.getX(currentCoord);
+ coords[1] = currentSequence.getY(currentCoord);
+ safeTransform(coords, 0, coords, 0, 1);
+ return SEG_LINETO;
+ }
+ }
+
+ @Override
+ public int currentSegment(final float[] coords) {
+ if (currentCoord == 0) {
+ coords[0] = (float) currentSequence.getX(currentCoord);
+ coords[1] = (float) currentSequence.getY(currentCoord);
+ safeTransform(coords, 0, coords, 0, 1);
+ return SEG_MOVETO;
+ } else if (currentCoord == coordinateCount) {
+ return SEG_CLOSE;
+ } else {
+ coords[0] = (float) currentSequence.getX(currentCoord);
+ coords[1] = (float) currentSequence.getY(currentCoord);
+ safeTransform(coords, 0, coords, 0, 1);
+ return SEG_LINETO;
+ }
+ }
+ }
+
+ static class GeometryCollection extends JTSPathIterator<org.locationtech.jts.geom.GeometryCollection> {
+
+ protected int nbGeom = 1;
+ protected int currentGeom;
+ protected JTSPathIterator currentIterator;
+ protected boolean done;
+
+ public GeometryCollection(final org.locationtech.jts.geom.GeometryCollection gc, final MathTransform trs) {
+ super(gc, trs);
+ reset();
+ }
+
+ @Override
+ public void reset() {
+ currentGeom = 0;
+ done = false;
+ nbGeom = geometry.getNumGeometries();
+ if (geometry != null && nbGeom > 0) {
+ prepareIterator(geometry.getGeometryN(0));
+ } else {
+ done = true;
+ }
+ }
+
+ @Override
+ public void setGeometry(final org.locationtech.jts.geom.GeometryCollection geom) {
+ super.setGeometry(geom);
+ if (geom == null) {
+ nbGeom = 0;
+ } else {
+ nbGeom = geom.getNumGeometries();
+ }
+ }
+
+ /**
+ * Returns the specific iterator for the geometry passed.
+ *
+ * @param candidate The geometry whole iterator is requested
+ */
+ protected void prepareIterator(final Geometry candidate) {
+
+ //try to reuse the previous iterator.
+ if (candidate.isEmpty()) {
+ if (currentIterator instanceof JTSPathIterator.Empty) {
+ //nothing to do
+ } else {
+ currentIterator = JTSPathIterator.Empty.INSTANCE;
+ }
+ } else if (candidate instanceof org.locationtech.jts.geom.Point) {
+ if (currentIterator instanceof JTSPathIterator.Point) {
+ currentIterator.setGeometry(candidate);
+ } else {
+ currentIterator = new JTSPathIterator.Point((org.locationtech.jts.geom.Point) candidate, transform);
+ }
+ } else if (candidate instanceof org.locationtech.jts.geom.Polygon) {
+ if (currentIterator instanceof JTSPathIterator.Polygon) {
+ currentIterator.setGeometry(candidate);
+ } else {
+ currentIterator = new JTSPathIterator.Polygon((org.locationtech.jts.geom.Polygon) candidate, transform);
+ }
+ } else if (candidate instanceof org.locationtech.jts.geom.LineString) {
+ if (currentIterator instanceof JTSPathIterator.LineString) {
+ currentIterator.setGeometry(candidate);
+ } else {
+ currentIterator = new JTSPathIterator.LineString((org.locationtech.jts.geom.LineString) candidate, transform);
+ }
+ } else if (candidate instanceof org.locationtech.jts.geom.GeometryCollection) {
+ if (currentIterator instanceof JTSPathIterator.GeometryCollection) {
+ currentIterator.setGeometry(candidate);
+ } else {
+ currentIterator = new JTSPathIterator.GeometryCollection((org.locationtech.jts.geom.GeometryCollection) candidate, transform);
+ }
+ } else {
+ currentIterator = JTSPathIterator.Empty.INSTANCE;
+ }
+
+ }
+
+ @Override
+ public void setTransform(final MathTransform trs) {
+ if (currentIterator != null) {
+ currentIterator.setTransform(trs);
+ }
+ super.setTransform(trs);
+ }
+
+ @Override
+ public int currentSegment(final double[] coords) {
+ return currentIterator.currentSegment(coords);
+ }
+
+ @Override
+ public int currentSegment(final float[] coords) {
+ return currentIterator.currentSegment(coords);
+ }
+
+ @Override
+ public int getWindingRule() {
+ return WIND_NON_ZERO;
+ }
+
+ @Override
+ public boolean isDone() {
+ return done;
+ }
+
+ @Override
+ public void next() {
+ currentIterator.next();
+
+ if (currentIterator.isDone()) {
+ if (currentGeom < (nbGeom - 1)) {
+ currentGeom++;
+ prepareIterator(geometry.getGeometryN(currentGeom));
+ } else {
+ done = true;
+ }
+ }
+ }
+ }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/JTSShape.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/JTSShape.java
new file mode 100644
index 0000000..2026f38
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/JTSShape.java
@@ -0,0 +1,96 @@
+/*
+ * 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.internal.feature.jts;
+
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryCollection;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.MultiLineString;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.PathIterator;
+import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.opengis.referencing.operation.MathTransform;
+
+/**
+ * A thin wrapper that adapts a JTS geometry to the Shape interface so that the
+ * geometry can be used by java2d without coordinate cloning.
+ *
+ * @author Johann Sorel (Puzzle-GIS + Geomatys)
+ * @version 2.0
+ * @since 2.0
+ * @module
+ */
+class JTSShape extends AbstractJTSShape<Geometry> {
+
+ protected JTSPathIterator<? extends Geometry> iterator;
+
+ public JTSShape(final Geometry geom) {
+ super(geom);
+ }
+
+ /**
+ * Creates a new GeometryJ2D object.
+ *
+ * @param geom - the wrapped geometry
+ */
+ public JTSShape(final Geometry geom, final MathTransform trs) {
+ super(geom, trs);
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public PathIterator getPathIterator(final AffineTransform at) {
+
+ final MathTransform concat;
+ if (at == null) {
+ concat = transform;
+ } else {
+ concat = MathTransforms.concatenate(transform, new AffineTransform2D(at));
+ }
+
+ if (iterator == null) {
+ if (this.geometry.isEmpty()) {
+ iterator = JTSPathIterator.Empty.INSTANCE;
+ } else if (this.geometry instanceof Point) {
+ iterator = new JTSPathIterator.Point((Point) geometry, concat);
+ } else if (this.geometry instanceof Polygon) {
+ iterator = new JTSPathIterator.Polygon((Polygon) geometry, concat);
+ } else if (this.geometry instanceof LineString) {
+ iterator = new JTSPathIterator.LineString((LineString) geometry, concat);
+ } else if (this.geometry instanceof MultiLineString) {
+ iterator = new JTSPathIterator.MultiLineString((MultiLineString) geometry, concat);
+ } else if (this.geometry instanceof GeometryCollection) {
+ iterator = new JTSPathIterator.GeometryCollection((GeometryCollection) geometry, concat);
+ }
+ } else {
+ iterator.setTransform(concat);
+ }
+
+ return iterator;
+ }
+
+ @Override
+ public AbstractJTSShape clone() {
+ return new JTSShape(this.geometry, this.transform);
+ }
+
+}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/jts/JTSTest.java b/core/sis-feature/src/test/java/org/apache/sis/internal/feature/jts/JTSTest.java
index 3a1c386..48795a8 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/jts/JTSTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/feature/jts/JTSTest.java
@@ -16,6 +16,8 @@
*/
package org.apache.sis.internal.feature.jts;
+import java.awt.Shape;
+import java.awt.geom.PathIterator;
import java.util.Collections;
import org.opengis.util.FactoryException;
import org.opengis.referencing.operation.TransformException;
@@ -24,6 +26,11 @@ import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.LinearRing;
+import org.locationtech.jts.geom.MultiLineString;
+import org.locationtech.jts.geom.MultiPolygon;
+import org.locationtech.jts.geom.Polygon;
import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
import org.apache.sis.test.TestCase;
@@ -41,6 +48,10 @@ import static org.junit.Assert.*;
* @module
*/
public final strictfp class JTSTest extends TestCase {
+
+ private static double DELTA = 0.000000001d;
+ private static final GeometryFactory GF = new GeometryFactory();
+
/**
* Tests {@link JTS#getCoordinateReferenceSystem(Geometry)}.
*
@@ -109,4 +120,303 @@ public final strictfp class JTSTest extends TestCase {
assertEquals(15, ((Point) out).getX(), STRICT);
assertEquals(26, ((Point) out).getY(), STRICT);
}
+
+ /**
+ * Tests {@link JTS#asShape(org.locationtech.jts.geom.Geometry, org.opengis.referencing.operation.MathTransform)} with a point type geometry.
+ */
+ @Test
+ public void testAsShapePoint() {
+
+ final Point point = GF.createPoint(new Coordinate(10, 20));
+
+ final Shape shape = JTS.asShape(point, null);
+ final PathIterator ite = shape.getPathIterator(null);
+
+ double[] buffer = new double[2];
+ int type;
+
+ assertFalse(ite.isDone());
+ type = ite.currentSegment(buffer);
+ assertEquals(10, buffer[0], DELTA);
+ assertEquals(20, buffer[1], DELTA);
+ assertEquals(PathIterator.SEG_MOVETO, type);
+ ite.next();
+
+ assertTrue(ite.isDone());
+ }
+
+ /**
+ * Tests {@link JTS#asShape(org.locationtech.jts.geom.Geometry, org.opengis.referencing.operation.MathTransform)} with a line string type geometry.
+ */
+ @Test
+ public void testAsShapeLineString() {
+
+ final LineString line = GF.createLineString(new Coordinate[]{
+ new Coordinate(3, 1),
+ new Coordinate(7, 6),
+ new Coordinate(5, 2)
+ });
+
+ final Shape shape = JTS.asShape(line, null);
+ final PathIterator ite = shape.getPathIterator(null);
+
+ double[] buffer = new double[2];
+ int type;
+
+ assertFalse(ite.isDone());
+ type = ite.currentSegment(buffer);
+ assertEquals(3, buffer[0], DELTA);
+ assertEquals(1, buffer[1], DELTA);
+ assertEquals(PathIterator.SEG_MOVETO, type);
+ ite.next();
+
+ assertFalse(ite.isDone());
+ type = ite.currentSegment(buffer);
+ assertEquals(7, buffer[0], DELTA);
+ assertEquals(6, buffer[1], DELTA);
+ assertEquals(PathIterator.SEG_LINETO, type);
+ ite.next();
+
+ assertFalse(ite.isDone());
+ type = ite.currentSegment(buffer);
+ assertEquals(5, buffer[0], DELTA);
+ assertEquals(2, buffer[1], DELTA);
+ assertEquals(PathIterator.SEG_LINETO, type);
+ ite.next();
+
+ assertTrue(ite.isDone());
+ }
+
+ /**
+ * Tests {@link JTS#asShape(org.locationtech.jts.geom.Geometry, org.opengis.referencing.operation.MathTransform)} with a multi line string type geometry.
+ */
+ @Test
+ public void testAsShapeMultiLineString() {
+
+ final LineString line1 = GF.createLineString(new Coordinate[]{
+ new Coordinate(10, 12),
+ new Coordinate(5, 2)
+ });
+ final LineString line2 = GF.createLineString(new Coordinate[]{
+ new Coordinate(3, 1),
+ new Coordinate(7, 6),
+ new Coordinate(5, 2)
+ });
+
+ final MultiLineString ml = GF.createMultiLineString(new LineString[]{line1,line2});
+ final Shape shape = JTS.asShape(ml, null);
+ final PathIterator ite = shape.getPathIterator(null);
+
+ double[] buffer = new double[2];
+ int type;
+
+ assertFalse(ite.isDone());
+ type = ite.currentSegment(buffer);
+ assertEquals(10, buffer[0], DELTA);
+ assertEquals(12, buffer[1], DELTA);
+ assertEquals(PathIterator.SEG_MOVETO, type);
+ ite.next();
+
+ assertFalse(ite.isDone());
+ type = ite.currentSegment(buffer);
+ assertEquals(5, buffer[0], DELTA);
+ assertEquals(2, buffer[1], DELTA);
+ assertEquals(PathIterator.SEG_LINETO, type);
+ ite.next();
+
+ assertFalse(ite.isDone());
+ type = ite.currentSegment(buffer);
+ assertEquals(3, buffer[0], DELTA);
+ assertEquals(1, buffer[1], DELTA);
+ assertEquals(PathIterator.SEG_MOVETO, type);
+ ite.next();
+
+ assertFalse(ite.isDone());
+ type = ite.currentSegment(buffer);
+ assertEquals(7, buffer[0], DELTA);
+ assertEquals(6, buffer[1], DELTA);
+ assertEquals(PathIterator.SEG_LINETO, type);
+ ite.next();
+
+ assertFalse(ite.isDone());
+ type = ite.currentSegment(buffer);
+ assertEquals(5, buffer[0], DELTA);
+ assertEquals(2, buffer[1], DELTA);
+ assertEquals(PathIterator.SEG_LINETO, type);
+ ite.next();
+
+ assertTrue(ite.isDone());
+ }
+
+ /**
+ * Tests {@link JTS#asShape(org.locationtech.jts.geom.Geometry, org.opengis.referencing.operation.MathTransform)} with a polygon type geometry.
+ */
+ @Test
+ public void testAsShapePolygon() {
+
+ final LinearRing ring = GF.createLinearRing(new Coordinate[]{
+ new Coordinate(3, 1),
+ new Coordinate(7, 6),
+ new Coordinate(5, 2),
+ new Coordinate(3, 1)
+ });
+
+ final Polygon polygon = GF.createPolygon(ring, new LinearRing[0]);
+
+ final Shape shape = JTS.asShape(polygon, null);
+ final PathIterator ite = shape.getPathIterator(null);
+
+ double[] buffer = new double[2];
+ int type;
+
+ assertFalse(ite.isDone());
+ type = ite.currentSegment(buffer);
+ assertEquals(3, buffer[0], DELTA);
+ assertEquals(1, buffer[1], DELTA);
+ assertEquals(PathIterator.SEG_MOVETO, type);
+ ite.next();
+
+ assertFalse(ite.isDone());
+ type = ite.currentSegment(buffer);
+ assertEquals(7, buffer[0], DELTA);
+ assertEquals(6, buffer[1], DELTA);
+ assertEquals(PathIterator.SEG_LINETO, type);
+ ite.next();
+
+ assertFalse(ite.isDone());
+ type = ite.currentSegment(buffer);
+ assertEquals(5, buffer[0], DELTA);
+ assertEquals(2, buffer[1], DELTA);
+ assertEquals(PathIterator.SEG_LINETO, type);
+ ite.next();
+
+ assertFalse(ite.isDone());
+ type = ite.currentSegment(buffer);
+ assertEquals(PathIterator.SEG_CLOSE, type);
+ ite.next();
+
+ assertTrue(ite.isDone());
+ }
+
+ /**
+ * Tests {@link JTS#asShape(org.locationtech.jts.geom.Geometry, org.opengis.referencing.operation.MathTransform)} with a multi polygon type geometry.
+ */
+ @Test
+ public void testAsShapeMultiPolygon() {
+
+ final LinearRing ring1 = GF.createLinearRing(new Coordinate[]{
+ new Coordinate(3, 1),
+ new Coordinate(7, 6),
+ new Coordinate(5, 2),
+ new Coordinate(3, 1)
+ });
+
+ final LinearRing ring2 = GF.createLinearRing(new Coordinate[]{
+ new Coordinate(12, 3),
+ new Coordinate(1, 9),
+ new Coordinate(4, 6),
+ new Coordinate(12, 3)
+ });
+
+ final Polygon polygon1 = GF.createPolygon(ring1, new LinearRing[0]);
+ final Polygon polygon2 = GF.createPolygon(ring2, new LinearRing[0]);
+ final MultiPolygon poly = GF.createMultiPolygon(new Polygon[]{polygon1,polygon2});
+
+ final Shape shape = JTS.asShape(poly, null);
+ final PathIterator ite = shape.getPathIterator(null);
+
+ double[] buffer = new double[2];
+ int type;
+
+ //first polygon
+ assertFalse(ite.isDone());
+ type = ite.currentSegment(buffer);
+ assertEquals(3, buffer[0], DELTA);
+ assertEquals(1, buffer[1], DELTA);
+ assertEquals(PathIterator.SEG_MOVETO, type);
+ ite.next();
+
+ assertFalse(ite.isDone());
+ type = ite.currentSegment(buffer);
+ assertEquals(7, buffer[0], DELTA);
+ assertEquals(6, buffer[1], DELTA);
+ assertEquals(PathIterator.SEG_LINETO, type);
+ ite.next();
+
+ assertFalse(ite.isDone());
+ type = ite.currentSegment(buffer);
+ assertEquals(5, buffer[0], DELTA);
+ assertEquals(2, buffer[1], DELTA);
+ assertEquals(PathIterator.SEG_LINETO, type);
+ ite.next();
+
+ assertFalse(ite.isDone());
+ type = ite.currentSegment(buffer);
+ assertEquals(PathIterator.SEG_CLOSE, type);
+ ite.next();
+
+ // second polygon
+ assertFalse(ite.isDone());
+ type = ite.currentSegment(buffer);
+ assertEquals(12, buffer[0], DELTA);
+ assertEquals(3, buffer[1], DELTA);
+ assertEquals(PathIterator.SEG_MOVETO, type);
+ ite.next();
+
+ assertFalse(ite.isDone());
+ type = ite.currentSegment(buffer);
+ assertEquals(1, buffer[0], DELTA);
+ assertEquals(9, buffer[1], DELTA);
+ assertEquals(PathIterator.SEG_LINETO, type);
+ ite.next();
+
+ assertFalse(ite.isDone());
+ type = ite.currentSegment(buffer);
+ assertEquals(4, buffer[0], DELTA);
+ assertEquals(6, buffer[1], DELTA);
+ assertEquals(PathIterator.SEG_LINETO, type);
+ ite.next();
+
+ assertFalse(ite.isDone());
+ type = ite.currentSegment(buffer);
+ assertEquals(PathIterator.SEG_CLOSE, type);
+ ite.next();
+
+ assertTrue(ite.isDone());
+ }
+
+ /**
+ * Tests {@link JTS#asDecimatedShape(org.locationtech.jts.geom.Geometry, double[])} with a line string type geometry.
+ */
+ @Test
+ public void testAsDecimatedShapeLineString() {
+
+ final LineString line = GF.createLineString(new Coordinate[]{
+ new Coordinate(0, 0),
+ new Coordinate(1, 0),
+ new Coordinate(2, 0)
+ });
+
+ final Shape shape = JTS.asDecimatedShape(line, new double[]{1.5, 1.5});
+ final PathIterator ite = shape.getPathIterator(null);
+
+ double[] buffer = new double[2];
+ int type;
+
+ assertFalse(ite.isDone());
+ type = ite.currentSegment(buffer);
+ assertEquals(0, buffer[0], DELTA);
+ assertEquals(0, buffer[1], DELTA);
+ assertEquals(PathIterator.SEG_MOVETO, type);
+ ite.next();
+
+ assertFalse(ite.isDone());
+ type = ite.currentSegment(buffer);
+ assertEquals(2, buffer[0], DELTA);
+ assertEquals(0, buffer[1], DELTA);
+ assertEquals(PathIterator.SEG_LINETO, type);
+ ite.next();
+
+ assertTrue(ite.isDone());
+ }
}