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

Reply via email to