diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/valid/IsSimpleOp.java b/modules/core/src/main/java/org/locationtech/jts/operation/valid/IsSimpleOp.java index cefdb32746..df548320ab 100644 --- a/modules/core/src/main/java/org/locationtech/jts/operation/valid/IsSimpleOp.java +++ b/modules/core/src/main/java/org/locationtech/jts/operation/valid/IsSimpleOp.java @@ -20,6 +20,7 @@ import org.locationtech.jts.algorithm.LineIntersector; import org.locationtech.jts.algorithm.RobustLineIntersector; import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.CoordinateArrays; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryCollection; import org.locationtech.jts.geom.LineString; @@ -43,6 +44,8 @@ *
  • MultiPoint geometries are simple if every point is unique *
  • LineString geometries are simple if they do not self-intersect at interior points * (i.e. points other than the endpoints). + * Closed linestrings which intersect only at their endpoints are simple + * (i.e. valid LinearRingss. *
  • MultiLineString geometries are simple if * their elements are simple and they intersect only at points * which are boundary points of both elements. @@ -63,7 +66,8 @@ *

    * Note that under the Mod-2 rule, closed LineStrings (rings) * have no boundary. - * This means that an intersection at their endpoints makes the geometry non-simple. + * This means that an intersection at the endpoints of + * two closed LineStrings makes the geometry non-simple. * If it is required to test whether a set of LineStrings touch * only at their endpoints, use {@link BoundaryNodeRule#ENDPOINT_BOUNDARY_RULE}. * For example, this can be used to validate that a collection of lines @@ -281,12 +285,44 @@ private static List extractSegmentStrings(Geometry geom) { List segStrings = new ArrayList(); for (int i = 0; i < geom.getNumGeometries(); i++) { LineString line = (LineString) geom.getGeometryN(i); - SegmentString ss = new BasicSegmentString(line.getCoordinates(), null); - segStrings.add(ss); + Coordinate[] trimPts = trimRepeatedPoints(line.getCoordinates()); + if (trimPts != null) { + SegmentString ss = new BasicSegmentString(trimPts, null); + segStrings.add(ss); + } } return segStrings; } + private static Coordinate[] trimRepeatedPoints(Coordinate[] pts) { + if (pts.length <= 2) + return pts; + + int len = pts.length; + boolean hasRepeatedStart = pts[0].equals2D(pts[1]); + boolean hasRepeatedEnd = pts[len - 1].equals2D(pts[len - 2]); + if (! hasRepeatedStart && ! hasRepeatedEnd) + return pts; + + //-- trim ends + int startIndex = 0; + Coordinate startPt = pts[0]; + while (startIndex < len - 1 && startPt.equals2D(pts[startIndex+1])) { + startIndex++; + } + int endIndex = len-1; + Coordinate endPt = pts[endIndex]; + while (endIndex > 0 && endPt.equals2D(pts[endIndex - 1])) { + endIndex--; + } + //-- are all points identical? + if (endIndex - startIndex < 1) { + return null; + } + Coordinate[] trimPts = CoordinateArrays.extract(pts, startIndex, endIndex); + return trimPts; + } + private static class NonSimpleIntersectionFinder implements SegmentIntersector { diff --git a/modules/core/src/test/java/org/locationtech/jts/operation/valid/IsSimpleTest.java b/modules/core/src/test/java/org/locationtech/jts/operation/valid/IsSimpleTest.java index 0d4c402809..0da9868c84 100644 --- a/modules/core/src/test/java/org/locationtech/jts/operation/valid/IsSimpleTest.java +++ b/modules/core/src/test/java/org/locationtech/jts/operation/valid/IsSimpleTest.java @@ -98,6 +98,38 @@ public void testRing() checkIsSimple(a, BoundaryNodeRule.ENDPOINT_BOUNDARY_RULE, true ); } + public void testLineRepeatedStart() { + String a = "LINESTRING (100 100, 100 100, 20 20, 200 20, 100 100)"; + + // rings are simple under all rules + checkIsSimple(a, BoundaryNodeRule.MOD2_BOUNDARY_RULE, true ); + checkIsSimple(a, BoundaryNodeRule.ENDPOINT_BOUNDARY_RULE, true ); + } + + public void testLineRepeatedEnd() { + String a = "LINESTRING (100 100, 20 20, 200 20, 100 100, 100 100)"; + + // rings are simple under all rules + checkIsSimple(a, BoundaryNodeRule.MOD2_BOUNDARY_RULE, true ); + checkIsSimple(a, BoundaryNodeRule.ENDPOINT_BOUNDARY_RULE, true ); + } + + public void testLineRepeatedBothEnds() { + String a = "LINESTRING (100 100, 100 100, 100 100, 20 20, 200 20, 100 100, 100 100)"; + + // rings are simple under all rules + checkIsSimple(a, BoundaryNodeRule.MOD2_BOUNDARY_RULE, true ); + checkIsSimple(a, BoundaryNodeRule.ENDPOINT_BOUNDARY_RULE, true ); + } + + public void testLineRepeatedAll() { + String a = "LINESTRING (100 100, 100 100, 100 100)"; + + // rings are simple under all rules + checkIsSimple(a, BoundaryNodeRule.MOD2_BOUNDARY_RULE, true ); + checkIsSimple(a, BoundaryNodeRule.ENDPOINT_BOUNDARY_RULE, true ); + } + public void testLinesAll() { checkIsSimpleAll("MULTILINESTRING ((10 20, 90 20), (10 30, 90 30), (50 40, 50 10))", BoundaryNodeRule.MOD2_BOUNDARY_RULE, diff --git a/modules/tests/src/test/resources/testxml/general/TestSimple.xml b/modules/tests/src/test/resources/testxml/general/TestSimple.xml index b5e8333124..6dcc56241e 100644 --- a/modules/tests/src/test/resources/testxml/general/TestSimple.xml +++ b/modules/tests/src/test/resources/testxml/general/TestSimple.xml @@ -61,6 +61,54 @@ + + L - simple line - repeated start point + + LINESTRING(10 10, 10 10, 20 20) + + + + true + + + + + + L - simple line - repeated end point + + LINESTRING(10 10, 20 20, 20 20) + + + + true + + + + + + L - simple line - repeated points at both ends + + LINESTRING(10 10, 10 10, 20 20, 20 20) + + + + true + + + + + + L - simple line - zerolength + + LINESTRING(10 10, 10 10, 10 10) + + + + true + + + + L - non-simple, proper interior intersection