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 d3b3ce30b90c7923c71ab7c3602442baa821455e
Author: jsorel <johann.so...@geomatys.com>
AuthorDate: Tue Nov 23 11:51:35 2021 +0100

    feat(JTS): add fromAwt functions
---
 .../org/apache/sis/internal/feature/jts/JTS.java   | 157 +++++++++++++++++++++
 .../apache/sis/internal/feature/jts/JTSTest.java   | 129 +++++++++++++++++
 2 files changed, 286 insertions(+)

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 b5c85c2..c7f609f 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
@@ -17,6 +17,12 @@
 package org.apache.sis.internal.feature.jts;
 
 import java.awt.Shape;
+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 java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 import org.opengis.metadata.Identifier;
 import org.opengis.util.FactoryException;
@@ -35,8 +41,13 @@ 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.Coordinate;
 import org.locationtech.jts.geom.Envelope;
 import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
 
 
 /**
@@ -308,6 +319,7 @@ public final class JTS extends Static {
         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.
      *
@@ -319,4 +331,149 @@ public final class JTS extends Static {
         ArgumentChecks.ensureNonNull("geometry", geometry);
         return new DecimateJTSShape(geometry, resolution);
     }
+
+    /**
+     * Convert a Java2D Shape to JTS Geometry.
+     * Commodity method for {@code fromAwt(factory, shp.getPathIterator(null, 
flatness)); }
+     *
+     * @param factory, factory used to create the geometry, not null
+     * @param shp, shape to convert, not null
+     * @param flatness, the maximum distance that the line segments used
+     *        to approximate the curved segments are allowed to deviate from
+     *        any point on the original curve
+     * @return JTS Geometry, not null, can be empty
+     * @see #fromAwt(GeometryFactory, PathIterator)
+     */
+    public static Geometry fromAwt(GeometryFactory factory, Shape shp, double 
flatness) {
+        return fromAwt(factory, shp.getPathIterator(null, flatness));
+    }
+
+    /**
+     * Convert a Java2D PathIterator to JTS Geometry.
+     *
+     * @param factory, factory used to create the geometry, not null
+     * @param ite, Java2D Path iterator, not null
+     * @return JTS Geometry, not null, can be empty
+     */
+    public static Geometry fromAwt(GeometryFactory factory, PathIterator ite) {
+
+        final List<Geometry> geoms = new ArrayList<>();
+        boolean allPolygons = true;
+        boolean allPoints = true;
+        boolean allLines = true;
+        while (!ite.isDone()) {
+            final Geometry geom = nextGeometry(factory, ite);
+            if (geom != null) {
+                geoms.add(geom);
+                allPolygons &= geom instanceof Polygon;
+                allPoints &= geom instanceof Point;
+                allLines &= geom instanceof LineString;
+            }
+        }
+
+        final int count = geoms.size();
+        if (count == 0) {
+            return factory.createEmpty(2);
+        } else if (count == 1) {
+            return geoms.get(0);
+        } else {
+            if (allPoints) {
+                return 
factory.createMultiPoint(GeometryFactory.toPointArray(geoms));
+
+            } else if (allPolygons) {
+                Geometry result = geoms.get(0);
+                for (int i = 1; i < count; i++) {
+                    /*
+                     Java2D shape and JTS have fondamental differences.
+                     Java2D fills the resulting contour based on visual 
winding rules.
+                     JTS has an absolute system where outer shell and holes 
are clearly separated.
+                     We would need to process the contours as Java2D to 
compute the resulting JTS equivalent,
+                     but this would require a lot of work, maybe in the futur. 
TODO
+                     The SymDifference operation is what behave the most like 
EVEN_ODD or NON_ZERO winding rules.
+                    */
+                    result = result.symDifference(geoms.get(i));
+                }
+                return result;
+
+            } else if (allLines) {
+                return 
factory.createMultiLineString(GeometryFactory.toLineStringArray(geoms));
+            } else {
+                return 
factory.createGeometryCollection(GeometryFactory.toGeometryArray(geoms));
+            }
+        }
+    }
+
+    /**
+     * Extract the next point, line or ring from iterator.
+     */
+    private static Geometry nextGeometry(GeometryFactory factory, PathIterator 
ite) {
+        final double[] vertex = new double[6];
+
+        List<Coordinate> coords = null;
+        boolean isRing = false;
+
+        loop:
+        while (!ite.isDone()) {
+            switch (ite.currentSegment(vertex)) {
+                case SEG_MOVETO:
+                    if (coords == null) {
+                        //start of current geometry
+                        coords = new ArrayList<>();
+                        coords.add(new Coordinate(vertex[0], vertex[1]));
+                        ite.next();
+                    } else {
+                        //start of next geometry
+                        break loop;
+                    }
+                    break;
+                case SEG_LINETO:
+                    if (coords == null) {
+                        throw new IllegalArgumentException("Invalid path 
iterator, LINETO without previous MOVETO.");
+                    } else {
+                        coords.add(new Coordinate(vertex[0], vertex[1]));
+                        ite.next();
+                    }
+                    break;
+                case SEG_CLOSE:
+                    //end of current geometry
+                    if (coords == null) {
+                        throw new IllegalArgumentException("Invalid path 
iterator, CLOSE without previous MOVETO.");
+                    } else {
+                        isRing = true;
+                        if (!coords.isEmpty()) {
+                            if 
(!coords.get(0).equals2D(coords.get(coords.size()-1))) {
+                                //close operation is sometimes called after 
duplicating the first point.
+                                //dont duplicate it again
+                                coords.add(coords.get(0).copy());
+                            }
+                        }
+                        ite.next();
+                        break loop;
+                    }
+                default :
+                    throw new IllegalArgumentException("Invalid path iterator, 
must contain only flat segments.");
+            }
+        }
+
+        if (coords == null) {
+            return null;
+        }
+
+        final int size = coords.size();
+        switch (size) {
+            case 0 : return null;
+            case 1 : return factory.createPoint(coords.get(0));
+            case 2 : return factory.createLineString(new 
Coordinate[]{coords.get(0),coords.get(1)});
+            default :
+                final Coordinate[] array = coords.toArray(new 
Coordinate[size]);
+                if (isRing) {
+                    //JTS do not care about ring orientation
+                    // 
https://locationtech.github.io/jts/javadoc/org/locationtech/jts/geom/Polygon.html
+                    return factory.createPolygon(array);
+                } else {
+                    return factory.createLineString(array);
+                }
+        }
+    }
+
 }
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 48795a8..d90eded 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,8 +16,17 @@
  */
 package org.apache.sis.internal.feature.jts;
 
+import java.awt.Font;
+import java.awt.Graphics2D;
 import java.awt.Shape;
+import java.awt.font.FontRenderContext;
+import java.awt.font.GlyphVector;
+import java.awt.geom.Area;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Line2D;
 import java.awt.geom.PathIterator;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
 import java.util.Collections;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.operation.TransformException;
@@ -37,6 +46,7 @@ import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
 import static org.junit.Assert.*;
+import org.locationtech.jts.geom.Envelope;
 
 
 /**
@@ -419,4 +429,123 @@ public final strictfp class JTSTest extends TestCase {
 
         assertTrue(ite.isDone());
     }
+
+    /**
+     * Tests {@link JTS#fromAwt(org.locationtech.jts.geom.GeometryFactory, 
java.awt.Shape, double)} with a point type shape.
+     */
+    @Test
+    public void testFromAwtPoint() {
+        final GeneralPath path = new GeneralPath();
+        path.moveTo(10, 20);
+
+        final Geometry candidate = JTS.fromAwt(GF, path, 0.0001);
+        final Geometry expected = GF.createPoint(new Coordinate(10,20));
+        assertEquals(expected, candidate);
+    }
+
+    /**
+     * Tests {@link JTS#fromAwt(org.locationtech.jts.geom.GeometryFactory, 
java.awt.Shape, double)} with a line type shape.
+     */
+    @Test
+    public void testFromAwtLine() {
+        final Line2D shape = new Line2D.Double(1, 2, 3, 4);
+        final Geometry geometry = JTS.fromAwt(GF, shape, 0.1);
+        assertTrue(geometry instanceof LineString);
+        final LineString ls = (LineString) geometry;
+        final Coordinate[] coordinates = ls.getCoordinates();
+        assertEquals(2, coordinates.length);
+        assertEquals(new Coordinate(1,2), coordinates[0]);
+        assertEquals(new Coordinate(3,4), coordinates[1]);
+    }
+
+    /**
+     * Tests {@link JTS#fromAwt(org.locationtech.jts.geom.GeometryFactory, 
java.awt.Shape, double)} with a rectangle type shape.
+     */
+    @Test
+    public void testFromAwtRectangle() {
+        final Rectangle2D shape = new Rectangle2D.Double(1,2,10,20);
+        final Geometry geometry = JTS.fromAwt(GF, shape, 0.1);
+        assertTrue(geometry instanceof Polygon);
+        final Polygon ls = (Polygon) geometry;
+        final Coordinate[] coordinates = ls.getCoordinates();
+        assertEquals(5, coordinates.length);
+        assertEquals(new Coordinate(1,2), coordinates[0]);
+        assertEquals(new Coordinate(11,2), coordinates[1]);
+        assertEquals(new Coordinate(11,22), coordinates[2]);
+        assertEquals(new Coordinate(1,22), coordinates[3]);
+        assertEquals(new Coordinate(1,2), coordinates[4]);
+    }
+
+    /**
+     * Tests {@link JTS#fromAwt(org.locationtech.jts.geom.GeometryFactory, 
java.awt.Shape, double)} with a rectangle with a hole shape.
+     */
+    @Test
+    public void testFromAwtRectangleWithHole() {
+        final Rectangle2D contour = new Rectangle2D.Double(1,2,10,20);
+        final Rectangle2D hole = new Rectangle2D.Double(5,6,2,3);
+        final Area shape = new Area(contour);
+        shape.subtract(new Area(hole));
+        final Geometry geometry = JTS.fromAwt(GF, shape, 0.1);
+        assertTrue(geometry instanceof Polygon);
+        final Polygon ls = (Polygon) geometry;
+        final LinearRing exteriorRing = ls.getExteriorRing();
+        assertEquals(1, ls.getNumInteriorRing());
+        final LinearRing interiorRing = ls.getInteriorRingN(0);
+
+        final Coordinate[] coordinatesExt = exteriorRing.getCoordinates();
+        assertEquals(5, coordinatesExt.length);
+        assertEquals(new Coordinate(1,2), coordinatesExt[0]);
+        assertEquals(new Coordinate(1,22), coordinatesExt[1]);
+        assertEquals(new Coordinate(11,22), coordinatesExt[2]);
+        assertEquals(new Coordinate(11,2), coordinatesExt[3]);
+        assertEquals(new Coordinate(1,2), coordinatesExt[4]);
+
+        final Coordinate[] coordinatesInt = interiorRing.getCoordinates();
+        assertEquals(5, coordinatesInt.length);
+        assertEquals(new Coordinate(7,6), coordinatesInt[0]);
+        assertEquals(new Coordinate(7,9), coordinatesInt[1]);
+        assertEquals(new Coordinate(5,9), coordinatesInt[2]);
+        assertEquals(new Coordinate(5,6), coordinatesInt[3]);
+        assertEquals(new Coordinate(7,6), coordinatesInt[4]);
+    }
+
+    /**
+     * Tests {@link JTS#fromAwt(org.locationtech.jts.geom.GeometryFactory, 
java.awt.Shape, double)} with a text shape.
+     */
+    @Test
+    public void testFromAwtText() {
+        final BufferedImage img = new BufferedImage(1, 1, 
BufferedImage.TYPE_INT_ARGB);
+        final Graphics2D g = img.createGraphics();
+        final FontRenderContext fontRenderContext = g.getFontRenderContext();
+        final Font font = new Font("Monospaced", Font.PLAIN, 12);
+        final GlyphVector glyphs = font.createGlyphVector(fontRenderContext, 
"Labi");
+        final Shape shape = glyphs.getOutline();
+        final GeneralPath gp = new GeneralPath();
+        gp.append(shape.getPathIterator(null, 0.1), false);
+        final Rectangle2D bounds2D = gp.getBounds2D();
+
+        final Geometry geometry = JTS.fromAwt(GF, shape, 0.1);
+        assertTrue(geometry instanceof MultiPolygon);
+        final MultiPolygon mp = (MultiPolygon) geometry;
+        assertEquals(5, mp.getNumGeometries()); //4 characters but 'i' is 
split in two ploygons
+        Geometry l = mp.getGeometryN(0);
+        Geometry a = mp.getGeometryN(1);
+        Geometry b = mp.getGeometryN(2);
+        Geometry i0 = mp.getGeometryN(3);
+        Geometry i1 = mp.getGeometryN(4);
+        assertTrue(l instanceof Polygon);
+        assertTrue(a instanceof Polygon);
+        assertTrue(b instanceof Polygon);
+        assertTrue(i0 instanceof Polygon);
+        assertTrue(i1 instanceof Polygon);
+        //a must contain a hole
+        assertEquals(1, ((Polygon) a).getNumInteriorRing());
+
+        //check bounding box
+        final Envelope env = geometry.getEnvelopeInternal();
+        assertEquals(bounds2D.getMinX(), env.getMinX(), 0.0);
+        assertEquals(bounds2D.getMaxX(), env.getMaxX(), 0.0);
+        assertEquals(bounds2D.getMinY(), env.getMinY(), 0.0);
+        assertEquals(bounds2D.getMaxY(), env.getMaxY(), 0.0);
+    }
 }

Reply via email to