From 69c89153490747fdbebccb1a14c8fb1d2742509c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Harrtell?= Date: Wed, 23 Sep 2026 22:22:01 +0200 Subject: [PATCH] Use GeometryFixer in simplification and densification Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../locationtech/jts/densify/Densifier.java | 10 ++++------ .../simplify/DouglasPeuckerSimplifier.java | 19 ++++++++++--------- .../jts/densify/DensifierTest.java | 7 +++++++ .../DouglasPeuckerSimplifierTest.java | 4 ++-- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/modules/core/src/main/java/org/locationtech/jts/densify/Densifier.java b/modules/core/src/main/java/org/locationtech/jts/densify/Densifier.java index 47b91759cf..31396407a6 100644 --- a/modules/core/src/main/java/org/locationtech/jts/densify/Densifier.java +++ b/modules/core/src/main/java/org/locationtech/jts/densify/Densifier.java @@ -20,6 +20,7 @@ import org.locationtech.jts.geom.MultiPolygon; import org.locationtech.jts.geom.Polygon; import org.locationtech.jts.geom.PrecisionModel; +import org.locationtech.jts.geom.util.GeometryFixer; import org.locationtech.jts.geom.util.GeometryTransformer; /** @@ -183,11 +184,8 @@ protected Geometry transformMultiPolygon(MultiPolygon geom, Geometry parent) { /** * Creates a valid area geometry from one that possibly has bad topology - * (i.e. self-intersections). Since buffer can handle invalid topology, but - * always returns valid geometry, constructing a 0-width buffer "corrects" - * the topology. Note this only works for area geometries, since buffer - * always returns areas. This also may return empty geometries, if the input - * has no actual area. + * (i.e. self-intersections). GeometryFixer is used to correct the topology + * while preserving as much of the input geometry as possible. * * @param roughAreaGeom * an area geometry possibly containing self-intersections @@ -196,7 +194,7 @@ protected Geometry transformMultiPolygon(MultiPolygon geom, Geometry parent) { private Geometry createValidArea(Geometry roughAreaGeom) { // if valid no need to process to make valid if (! isValidated || roughAreaGeom.isValid()) return roughAreaGeom; - return roughAreaGeom.buffer(0.0); + return GeometryFixer.fix(roughAreaGeom); } } diff --git a/modules/core/src/main/java/org/locationtech/jts/simplify/DouglasPeuckerSimplifier.java b/modules/core/src/main/java/org/locationtech/jts/simplify/DouglasPeuckerSimplifier.java index e14d0481c3..9d8a1b1240 100644 --- a/modules/core/src/main/java/org/locationtech/jts/simplify/DouglasPeuckerSimplifier.java +++ b/modules/core/src/main/java/org/locationtech/jts/simplify/DouglasPeuckerSimplifier.java @@ -18,6 +18,7 @@ import org.locationtech.jts.geom.LinearRing; import org.locationtech.jts.geom.MultiPolygon; import org.locationtech.jts.geom.Polygon; +import org.locationtech.jts.geom.util.GeometryFixer; import org.locationtech.jts.geom.util.GeometryTransformer; /** @@ -190,14 +191,8 @@ protected Geometry transformMultiPolygon(MultiPolygon geom, Geometry parent) { /** * Creates a valid area geometry from one that possibly has * bad topology (i.e. self-intersections). - * Since buffer can handle invalid topology, but always returns - * valid geometry, constructing a 0-width buffer "corrects" the - * topology. - * Note this only works for area geometries, since buffer always returns - * areas. This also may return empty geometries, if the input - * has no actual area. - * If the input is empty or is not polygonal, - * this ensures that POLYGON EMPTY is returned. + * GeometryFixer is used to correct the topology while preserving + * as much of the input geometry as possible. * * @param rawAreaGeom an area geometry possibly containing self-intersections * @return a valid area geometry @@ -207,7 +202,13 @@ private Geometry createValidArea(Geometry rawAreaGeom) boolean isValidArea = rawAreaGeom.getDimension() == 2 && rawAreaGeom.isValid(); // if geometry is invalid then make it valid if (isEnsureValidTopology && ! isValidArea) - return rawAreaGeom.buffer(0.0); + { + Geometry fixed = GeometryFixer.fix(rawAreaGeom); + // Preserve the historical result type for collapsed polygonal output. + if (fixed.isEmpty() && rawAreaGeom.getDimension() < 2) + return rawAreaGeom.getFactory().createPolygon(); + return fixed; + } return rawAreaGeom; } } diff --git a/modules/core/src/test/java/org/locationtech/jts/densify/DensifierTest.java b/modules/core/src/test/java/org/locationtech/jts/densify/DensifierTest.java index dbf8a7770a..2ff7d6305d 100644 --- a/modules/core/src/test/java/org/locationtech/jts/densify/DensifierTest.java +++ b/modules/core/src/test/java/org/locationtech/jts/densify/DensifierTest.java @@ -69,6 +69,13 @@ public void testBoxNoValidate() { 10, "POLYGON ((10 10, 10 20, 10 30, 20 30, 30 30, 30 20, 30 10, 20 10, 10 10))"); } + public void testInvalidPolygonFixed() { + checkDensify( + "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (20 20, 22 20, 22 22, 20 22, 20 20))", + 100, + "MULTIPOLYGON (((0 0, 10 0, 10 10, 0 10, 0 0)), ((20 20, 22 20, 22 22, 20 22, 20 20)))"); + } + public void testLineDensify3D() { checkDensifyXYZ("POLYGON Z((10 30 10, 30 30 10, 30 10 15, 10 10 10, 10 30 20))", 10, "POLYGON Z((10 30 10, 20 30 10, 30 30 10, 30 20 12.5, 30 10 15, 20 10 12.5, 10 10 10, 10 20 15, 10 30 20))"); diff --git a/modules/core/src/test/java/org/locationtech/jts/simplify/DouglasPeuckerSimplifierTest.java b/modules/core/src/test/java/org/locationtech/jts/simplify/DouglasPeuckerSimplifierTest.java index 7fa33786b7..011b7fefad 100644 --- a/modules/core/src/test/java/org/locationtech/jts/simplify/DouglasPeuckerSimplifierTest.java +++ b/modules/core/src/test/java/org/locationtech/jts/simplify/DouglasPeuckerSimplifierTest.java @@ -124,14 +124,14 @@ public void testGeometryCollection() { /** * Test that a polygon made invalid by simplification * is fixed in a sensible way. - * Fixed by buffer(0) area-base orientation + * GeometryFixer preserves both lobes of the invalid ring. * See https://github.com/locationtech/jts/issues/498 */ public void testInvalidPolygonFixed() { checkDP( "POLYGON ((21.32686 47.78723, 21.32386 47.79023, 21.32186 47.80223, 21.31486 47.81023, 21.32786 47.81123, 21.33986 47.80223, 21.33886 47.81123, 21.32686 47.82023, 21.32586 47.82723, 21.32786 47.82323, 21.33886 47.82623, 21.34186 47.82123, 21.36386 47.82223, 21.40686 47.81723, 21.32686 47.78723))", 0.0036, - "POLYGON ((21.32686 47.78723, 21.31486 47.81023, 21.32786 47.81123, 21.33986 47.80223, 21.328068201892744 47.823286782334385, 21.33886 47.82623, 21.34186 47.82123, 21.40686 47.81723, 21.32686 47.78723))" + "MULTIPOLYGON (((21.31486 47.81023, 21.32786 47.81123, 21.33986 47.80223, 21.328068201892744 47.823286782334385, 21.33886 47.82623, 21.34186 47.82123, 21.40686 47.81723, 21.32686 47.78723, 21.31486 47.81023)), ((21.32586 47.82723, 21.328068201892744 47.823286782334385, 21.32786 47.82323, 21.32586 47.82723)))" ); }