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 <johann.so...@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()); + } }