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
The following commit(s) were added to refs/heads/feat/toShape by this push:
new 5d32149 feat(JTS): add fromAwt functions
5d32149 is described below
commit 5d32149b13a1275b101f91ec7316522a9e3f64da
Author: jsorel <[email protected]>
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);
+ }
}