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
The following commit(s) were added to refs/heads/feat/image2polygon by this push: new 5d7e5f99e6 Fix AWT Shape to JTS Geometry adding unwanted NaN Z values 5d7e5f99e6 is described below commit 5d7e5f99e6db35f9554f13c404a97a1fd50da306 Author: jsorel <johann.so...@geomatys.com> AuthorDate: Fri Apr 18 08:39:25 2025 +0200 Fix AWT Shape to JTS Geometry adding unwanted NaN Z values --- .../sis/geometry/wrapper/jts/ShapeConverter.java | 190 +++++++++++++++++---- 1 file changed, 160 insertions(+), 30 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 2b268caf74..8b9d7548c0 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 @@ -27,6 +27,15 @@ 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.CoordinateSequence; +import org.locationtech.jts.geom.GeometryCollection; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.LinearRing; +import org.locationtech.jts.geom.MultiLineString; +import org.locationtech.jts.geom.MultiPoint; +import org.locationtech.jts.geom.MultiPolygon; +import org.locationtech.jts.geom.Point; +import org.locationtech.jts.geom.Polygon; import org.locationtech.jts.geom.util.GeometryFixer; @@ -268,43 +277,164 @@ abstract class ShapeConverter { } flush(false); final int count = geometries.size(); + + Geometry result; if (count == 1) { - return geometries.get(0); - } - switch (geometryType) { - case 0: return factory.createEmpty(DIMENSION); - default: return factory.createGeometryCollection(GeometryFactory.toGeometryArray (geometries)); - 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++) { - 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); + result = geometries.get(0); + } else { + switch (geometryType) { + case 0: + result = factory.createEmpty(DIMENSION); + break; + default: + result = factory.createGeometryCollection(GeometryFactory.toGeometryArray (geometries)); + break; + case POINT: + result = factory.createMultiPoint (GeometryFactory.toPointArray (geometries)); + break; + case LINESTRING: + result = factory.createMultiLineString (GeometryFactory.toLineStringArray(geometries)); + break; + 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())); + result = geometries.get(0); + for (int i=1; i<count; 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); + } } - + break; + } + } + + return enforce2D(result); + } + + /** + * JTS has the bad habit of expending the dimension of CoordinateSequence + * from 2D to 3D adding NaN Z values. + * Since we do not want any Z ordinates, we have to check and fix those. + * + * @param geometry to fix + */ + private <T extends Geometry> T enforce2D(T geometry) { + if (geometry instanceof Point) { + final Point pt = (Point) geometry; + final CoordinateSequence cs = pt.getCoordinateSequence(); + final CoordinateSequence cs2d = enforce2D(cs); + return (T) (cs2d != cs ? factory.createPoint(cs2d) : geometry); + } else if (geometry instanceof LinearRing) { + final LinearRing ls = (LinearRing) geometry; + final CoordinateSequence cs = ls.getCoordinateSequence(); + final CoordinateSequence cs2d = enforce2D(cs); + return (T) (cs2d != cs ? factory.createLinearRing(cs2d) : geometry); + } else if (geometry instanceof LineString) { + final LineString ls = (LineString) geometry; + final CoordinateSequence cs = ls.getCoordinateSequence(); + final CoordinateSequence cs2d = enforce2D(cs); + return (T) (cs2d != cs ? factory.createLineString(cs2d) : geometry); + } else if (geometry instanceof MultiLineString) { + final MultiLineString ml = (MultiLineString) geometry; + boolean changed = false; + final LineString[] news = new LineString[ml.getNumGeometries()]; + for (int i = 0; i < news.length; i++) { + news[i] = (LineString) ml.getGeometryN(i); + LineString cp = enforce2D(news[i]); + if (cp != news[i]) { + news[i] = cp; + changed = true; + } + } + return (T) (changed ? factory.createMultiLineString(news) : geometry); + } else if (geometry instanceof Polygon) { + final Polygon pl = (Polygon) geometry; + boolean changed = false; + final LinearRing exterior = pl.getExteriorRing(); + final LinearRing copy = enforce2D(exterior); + if (exterior != copy) { + changed = true; + } + + final LinearRing[] news = new LinearRing[pl.getNumInteriorRing()]; + for (int i = 0; i < news.length; i++) { + news[i] = pl.getInteriorRingN(i); + LinearRing cp = enforce2D(news[i]); + if (cp != news[i]) { + news[i] = cp; + changed = true; } - return result; } + return (T) (changed ? factory.createPolygon(copy, news) : geometry); + } else if (geometry instanceof MultiPoint) { + final MultiPoint ml = (MultiPoint) geometry; + boolean changed = false; + final Point[] news = new Point[ml.getNumGeometries()]; + for (int i = 0; i < news.length; i++) { + news[i] = (Point) ml.getGeometryN(i); + Point cp = enforce2D(news[i]); + if (cp != news[i]) { + news[i] = cp; + changed = true; + } + } + return (T) (changed ? factory.createMultiPoint(news) : geometry); + } else if (geometry instanceof MultiPolygon) { + final MultiPolygon ml = (MultiPolygon) geometry; + boolean changed = false; + final Polygon[] news = new Polygon[ml.getNumGeometries()]; + for (int i = 0; i < news.length; i++) { + news[i] = (Polygon) ml.getGeometryN(i); + Polygon cp = enforce2D(news[i]); + if (cp != news[i]) { + news[i] = cp; + changed = true; + } + } + return (T) (changed ? factory.createMultiPolygon(news) : geometry); + } else if (geometry instanceof GeometryCollection) { + final GeometryCollection ml = (GeometryCollection) geometry; + boolean changed = false; + final Geometry[] news = new Geometry[ml.getNumGeometries()]; + for (int i = 0; i < news.length; i++) { + news[i] = ml.getGeometryN(i); + Geometry cp = enforce2D(news[i]); + if (cp != news[i]) { + news[i] = cp; + changed = true; + } + } + return (T) (changed ? factory.createGeometryCollection(news) : geometry); + } else { + throw new UnsupportedOperationException("Unexpected JTS geometry type " + geometry.getGeometryType()); } } + private CoordinateSequence enforce2D(CoordinateSequence cs) { + if (cs.getDimension() == 2) return cs; + final int size = cs.size(); + final CoordinateSequence copy = factory.getCoordinateSequenceFactory().create(size, 2); + for (int i = 0; i < size; i++) { + copy.setOrdinate(i, 0, cs.getX(i)); + copy.setOrdinate(i, 1, cs.getY(i)); + } + return copy; + } + /** * Copies current coordinates in a new JTS geometry, * then resets {@link #length} to 0 in preparation for the next geometry.