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 610f4bc122ac8ec5434c54c582778160edaa5381
Author: jsorel <johann.so...@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);

Reply via email to