Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix IsSimpleOp for closed LineStrings with repeated endpoints #851

Merged
merged 1 commit into from
Mar 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -43,6 +44,8 @@
* <li><b>MultiPoint</b> geometries are simple if every point is unique
* <li><b>LineString</b> 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 <b>LinearRings</b>s.
* <li><b>MultiLineString</b> geometries are simple if
* their elements are simple and they intersect only at points
* which are boundary points of both elements.
Expand All @@ -63,7 +66,8 @@
* <p>
* Note that under the <tt>Mod-2</tt> rule, closed <tt>LineString</tt>s (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 <code>LineString</code>s touch
* only at their endpoints, use {@link BoundaryNodeRule#ENDPOINT_BOUNDARY_RULE}.
* For example, this can be used to validate that a collection of lines
Expand Down Expand Up @@ -281,12 +285,44 @@ private static List<SegmentString> extractSegmentStrings(Geometry geom) {
List<SegmentString> segStrings = new ArrayList<SegmentString>();
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
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
48 changes: 48 additions & 0 deletions modules/tests/src/test/resources/testxml/general/TestSimple.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,54 @@
</test>
</case>

<case>
<desc>L - simple line - repeated start point</desc>
<a>
LINESTRING(10 10, 10 10, 20 20)
</a>
<test>
<op name="isSimple" arg1="A">
true
</op>
</test>
</case>

<case>
<desc>L - simple line - repeated end point</desc>
<a>
LINESTRING(10 10, 20 20, 20 20)
</a>
<test>
<op name="isSimple" arg1="A">
true
</op>
</test>
</case>

<case>
<desc>L - simple line - repeated points at both ends</desc>
<a>
LINESTRING(10 10, 10 10, 20 20, 20 20)
</a>
<test>
<op name="isSimple" arg1="A">
true
</op>
</test>
</case>

<case>
<desc>L - simple line - zerolength</desc>
<a>
LINESTRING(10 10, 10 10, 10 10)
</a>
<test>
<op name="isSimple" arg1="A">
true
</op>
</test>
</case>

<case>
<desc>L - non-simple, proper interior intersection</desc>
<a>
Expand Down