You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by js...@apache.org on 2025/03/25 10:51:12 UTC
(sis) 01/02: Improve AWT to JTS conversion logic
This is an automated email from the ASF dual-hosted git repository.
jsorel pushed a commit to branch feat/image2polygon
in repository https://gitbox.apache.org/repos/asf/sis.git
commit 237fd4bc1fca8b45112c2b8b1b1b974baa628a1e
Author: jsorel <jo...@geomatys.com>
AuthorDate: Tue Mar 25 11:49:31 2025 +0100
Improve AWT to JTS conversion logic
---
.../sis/geometry/wrapper/jts/ShapeConverter.java | 43 +++++++++++++++++-----
.../geometry/wrapper/jts/ShapeConverterTest.java | 12 +++++-
2 files changed, 43 insertions(+), 12 deletions(-)
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/ShapeConverter.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/ShapeConverter.java
index ddb8d707d9..2b268caf74 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/ShapeConverter.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/ShapeConverter.java
@@ -22,9 +22,12 @@ import java.util.Arrays;
import java.util.ArrayList;
import java.awt.geom.PathIterator;
import java.awt.geom.IllegalPathStateException;
+import java.util.Collections;
+import java.util.Comparator;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.apache.sis.referencing.privy.AbstractShape;
+import org.locationtech.jts.geom.util.GeometryFixer;
/**
@@ -274,17 +277,28 @@ abstract class ShapeConverter {
case POINT: return factory.createMultiPoint (GeometryFactory.toPointArray (geometries));
case LINESTRING: return factory.createMultiLineString (GeometryFactory.toLineStringArray(geometries));
case POLYGON: {
+ /*
+ * Java2D shapes and JTS geometries differ in their way to fill interior.
+ * Java2D fills the resulting contour based on visual winding rules.
+ * JTS has a system where outer shell and holes are clearly separated.
+ * We would need to draw contours as Java2D for computing JTS equivalent,
+ * but it would require a lot of work. In the meantime, the SymDifference
+ * operation is what behave the most like EVEN_ODD or NON_ZERO winding rules.
+ */
+
+ //sort by area, bigger geometries are the outter rings
+ Collections.sort(geometries, (Geometry o1, Geometry o2) -> java.lang.Double.compare(o2.getArea(), o1.getArea()));
Geometry result = geometries.get(0);
for (int i=1; i<count; i++) {
- /*
- * Java2D shapes and JTS geometries differ in their way to fill interior.
- * Java2D fills the resulting contour based on visual winding rules.
- * JTS has a system where outer shell and holes are clearly separated.
- * We would need to draw contours as Java2D for computing JTS equivalent,
- * but it would require a lot of work. In the meantime, the SymDifference
- * operation is what behave the most like EVEN_ODD or NON_ZERO winding rules.
- */
- result = result.symDifference(geometries.get(i));
+ Geometry other = geometries.get(i);
+ if (result.intersects(other)) {
+ //ring is a hole
+ result = result.symDifference(other);
+ } else {
+ //ring is a separate polygon
+ result = result.union(other);
+ }
+
}
return result;
}
@@ -299,7 +313,7 @@ abstract class ShapeConverter {
*/
private void flush(final boolean isRing) {
if (length != 0) {
- final Geometry geometry;
+ Geometry geometry;
if (length == DIMENSION) {
geometry = factory.createPoint(toSequence(false));
geometryType |= POINT;
@@ -311,6 +325,15 @@ abstract class ShapeConverter {
*/
geometry = factory.createPolygon(toSequence(true));
geometryType |= POLYGON;
+
+ /*
+ Expensive operation but java2d is very tolerant to incoherent paths.
+ We need to fix those otherwise we might have errors when aggregating
+ holes in polygons.
+ */
+ if (!geometry.isValid()) {
+ geometry = GeometryFixer.fix(geometry);
+ }
} else {
geometry = factory.createLineString(toSequence(false));
geometryType |= LINESTRING;
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/jts/ShapeConverterTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/jts/ShapeConverterTest.java
index 9a50222b84..42de55e164 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/jts/ShapeConverterTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/jts/ShapeConverterTest.java
@@ -26,6 +26,9 @@ import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
@@ -171,16 +174,21 @@ public final class ShapeConverterTest extends TestCase {
}
final Geometry geometry = ShapeConverter.create(factory, shape, 0.1);
assertInstanceOf(MultiPolygon.class, geometry);
- final MultiPolygon mp = (MultiPolygon) geometry;
+ final MultiPolygon mp = (MultiPolygon) geometry;
/*
* The "Labi" text contaons 4 characters but `i` is split in two ploygons,
* for a total of 5 polygons. Two letters ("a" and "b") are polyogns whith
* hole inside them.
*/
assertEquals(5, mp.getNumGeometries());
+ // sort on X
+ final List<Geometry> parts = new ArrayList<>(5);
+ for (int i=0; i<5; i++) parts.add(mp.getGeometryN(i));
+ parts.sort((Geometry o1, Geometry o2) -> Double.compare(o1.getEnvelopeInternal().getMinX(), o2.getEnvelopeInternal().getMinX()));
+
for (int i=0; i<5; i++) {
final String message = "Glyph #" + i;
- final Geometry glyph = mp.getGeometryN(i);
+ final Geometry glyph = parts.get(i);
assertInstanceOf(Polygon.class, glyph, message);
assertEquals((i == 1 || i == 2) ? 1 : 0, // `a` and `b` should contain a hole.
((Polygon) glyph).getNumInteriorRing(), message);