Skip to content

Commit 1318924

Browse files
committed
Fix to handle self-touch nodes
1 parent e0bddb5 commit 1318924

File tree

4 files changed

+118
-6
lines changed

4 files changed

+118
-6
lines changed

modules/core/src/main/java/org/locationtech/jts/noding/BasicSegmentString.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@
2929
public class BasicSegmentString
3030
implements SegmentString
3131
{
32+
public static BasicSegmentString substring(SegmentString segString, int start, int end) {
33+
Coordinate[] pts = new Coordinate[end - start + 1];
34+
int ipts = 0;
35+
for (int i = start; i < end + 1; i++) {
36+
pts[ipts++] = segString.getCoordinate(i).copy();
37+
}
38+
return new BasicSegmentString(pts, segString.getData());
39+
}
40+
3241
private Coordinate[] pts;
3342
private Object data;
3443

modules/core/src/main/java/org/locationtech/jts/noding/BoundaryChainNoder.java

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.util.Collection;
1616
import java.util.HashSet;
1717
import java.util.List;
18+
import java.util.Set;
1819

1920
import org.locationtech.jts.geom.Coordinate;
2021
import org.locationtech.jts.geom.LineSegment;
@@ -50,11 +51,17 @@ public BoundaryChainNoder() {
5051

5152
@Override
5253
public void computeNodes(Collection segStrings) {
53-
HashSet<Segment> segSet = new HashSet<Segment>();
54+
HashSet<Segment> boundarySegSet = new HashSet<Segment>();
5455
BoundaryChainMap[] boundaryChains = new BoundaryChainMap[segStrings.size()];
55-
addSegments(segStrings, segSet, boundaryChains);
56-
markBoundarySegments(segSet);
56+
addSegments(segStrings, boundarySegSet, boundaryChains);
57+
markBoundarySegments(boundarySegSet);
5758
chainList = extractChains(boundaryChains);
59+
60+
//-- check for self-touching nodes and split chains at those nodes
61+
Set<Coordinate> nodePts = findNodePts(chainList);
62+
if (nodePts.size() > 0) {
63+
chainList = nodeChains(chainList, nodePts);
64+
}
5865
}
5966

6067
private static void addSegments(Collection<SegmentString> segStrings, HashSet<Segment> segSet,
@@ -95,6 +102,56 @@ private static List<SegmentString> extractChains(BoundaryChainMap[] boundaryChai
95102
return chainList;
96103
}
97104

105+
private Set<Coordinate> findNodePts(List<SegmentString> segStrings) {
106+
Set<Coordinate> interorVertices = new HashSet<Coordinate>();
107+
Set<Coordinate> nodes = new HashSet<Coordinate>();
108+
for (SegmentString ss : segStrings) {
109+
//-- endpoints are nodes
110+
nodes.add(ss.getCoordinate(0));
111+
nodes.add(ss.getCoordinate(ss.size() - 1));
112+
113+
//-- check for duplicate interior points
114+
for (int i = 1; i < ss.size() - 1; i++) {
115+
Coordinate p = ss.getCoordinate(i);
116+
if (interorVertices.contains(p)) {
117+
nodes.add(p);
118+
}
119+
interorVertices.add(p);
120+
}
121+
}
122+
return nodes;
123+
}
124+
125+
private List<SegmentString> nodeChains(List<SegmentString> chains, Set<Coordinate> nodePts) {
126+
List<SegmentString> nodedChains = new ArrayList<SegmentString>();
127+
for (SegmentString chain : chains) {
128+
nodeChain(chain, nodePts, nodedChains);
129+
}
130+
return nodedChains;
131+
}
132+
133+
private void nodeChain(SegmentString chain, Set<Coordinate> nodePts, List<SegmentString> nodedChains) {
134+
int start = 0;
135+
while (start < chain.size() - 1) {
136+
int end = findNodeIndex(chain, start, nodePts);
137+
//-- if no interior nodes found, keep original chain
138+
if (start == 0 && end == chain.size() - 1) {
139+
nodedChains.add(chain);
140+
return;
141+
}
142+
nodedChains.add(BasicSegmentString.substring(chain, start, end));
143+
start = end;
144+
}
145+
}
146+
147+
private int findNodeIndex(SegmentString chain, int start, Set<Coordinate> nodePts) {
148+
for (int i = start + 1; i < chain.size(); i++) {
149+
if (nodePts.contains(chain.getCoordinate(i)))
150+
return i;
151+
}
152+
return chain.size() - 1;
153+
}
154+
98155
@Override
99156
public Collection getNodedSubstrings() {
100157
return chainList;

modules/core/src/test/java/org/locationtech/jts/coverage/CoverageUnionTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,27 @@ public void testEmpty() {
4040
);
4141
}
4242

43+
public void testHoleTouchingSide() {
44+
checkUnion(
45+
"GEOMETRYCOLLECTION (POLYGON ((1 9, 9 9, 9 6, 2 6, 1 9)), POLYGON ((1 1, 1 9, 2 6, 5 3, 9 6, 9 1, 1 1)))",
46+
"POLYGON ((9 6, 9 1, 1 1, 1 9, 9 9, 9 6), (9 6, 2 6, 5 3, 9 6))"
47+
);
48+
}
49+
50+
public void testHolesTouchingSide() {
51+
checkUnion(
52+
"GEOMETRYCOLLECTION (POLYGON ((1 9, 9 9, 9 6, 5 7, 2 6, 1 9)), POLYGON ((1 1, 1 9, 2 6, 4 3, 5 7, 7 3, 9 6, 9 1, 1 1)))",
53+
"POLYGON ((9 9, 9 6, 9 1, 1 1, 1 9, 9 9), (5 7, 7 3, 9 6, 5 7), (2 6, 4 3, 5 7, 2 6))"
54+
);
55+
}
56+
57+
public void testHolesTouching() {
58+
checkUnion(
59+
"GEOMETRYCOLLECTION (POLYGON ((1 9, 9 9, 9 6, 7 7, 5 7, 2 6, 1 9)), POLYGON ((1 1, 1 9, 2 6, 4 3, 5 7, 7 3, 7 7, 9 6, 9 1, 1 1)))",
60+
"POLYGON ((9 9, 9 6, 9 1, 1 1, 1 9, 9 9), (5 7, 7 3, 7 7, 5 7), (2 6, 4 3, 5 7, 2 6))"
61+
);
62+
}
63+
4364
private void checkUnion(String wktCoverage, String wktExpected) {
4465
Geometry covGeom = read(wktCoverage);
4566
Geometry[] coverage = toArray(covGeom);

modules/core/src/test/java/org/locationtech/jts/operation/overlayng/CoverageUnionTest.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,25 @@ public void testPolygonsConcentricHalfDonuts( ) {
3030
"MULTIPOLYGON (((1 9, 6 9, 9 9, 9 1, 6 1, 1 1, 1 9), (2 8, 2 2, 6 2, 8 2, 8 8, 6 8, 2 8)), ((5 3, 3 3, 3 7, 5 7, 7 7, 7 3, 5 3), (5 4, 6 4, 6 6, 5 6, 4 6, 4 4, 5 4)))");
3131
}
3232

33-
public void testPolygonsNested( ) {
34-
checkUnion("GEOMETRYCOLLECTION (POLYGON ((1 9, 9 9, 9 1, 1 1, 1 9), (3 7, 3 3, 7 3, 7 7, 3 7)), POLYGON ((3 7, 7 7, 7 3, 3 3, 3 7)))",
35-
"POLYGON ((1 1, 1 9, 9 9, 9 1, 1 1))");
33+
public void testHoleTouchingSide() {
34+
checkUnion(
35+
"GEOMETRYCOLLECTION (POLYGON ((1 9, 9 9, 9 6, 2 6, 1 9)), POLYGON ((1 1, 1 9, 2 6, 5 3, 9 6, 9 1, 1 1)))",
36+
"POLYGON ((9 6, 9 1, 1 1, 1 9, 9 9, 9 6), (9 6, 2 6, 5 3, 9 6))"
37+
);
38+
}
39+
40+
public void testHolesTouchingSide() {
41+
checkUnion(
42+
"GEOMETRYCOLLECTION (POLYGON ((1 9, 9 9, 9 6, 5 7, 2 6, 1 9)), POLYGON ((1 1, 1 9, 2 6, 4 3, 5 7, 7 3, 9 6, 9 1, 1 1)))",
43+
"POLYGON ((9 9, 9 6, 9 1, 1 1, 1 9, 9 9), (5 7, 7 3, 9 6, 5 7), (2 6, 4 3, 5 7, 2 6))"
44+
);
45+
}
46+
47+
public void testHolesTouching() {
48+
checkUnion(
49+
"GEOMETRYCOLLECTION (POLYGON ((1 9, 9 9, 9 6, 7 7, 5 7, 2 6, 1 9)), POLYGON ((1 1, 1 9, 2 6, 4 3, 5 7, 7 3, 7 7, 9 6, 9 1, 1 1)))",
50+
"POLYGON ((9 9, 9 6, 9 1, 1 1, 1 9, 9 9), (5 7, 7 3, 7 7, 5 7), (2 6, 4 3, 5 7, 2 6))"
51+
);
3652
}
3753

3854
public void testPolygonsFormingHole( ) {
@@ -45,6 +61,13 @@ public void testPolygonsSquareGrid( ) {
4561
"POLYGON ((0 25, 0 50, 0 75, 0 100, 25 100, 50 100, 75 100, 100 100, 100 75, 100 50, 100 25, 100 0, 75 0, 50 0, 25 0, 0 0, 0 25))");
4662
}
4763

64+
public void testPolygonsNested( ) {
65+
checkUnion("GEOMETRYCOLLECTION (POLYGON ((1 9, 9 9, 9 1, 1 1, 1 9), (3 7, 3 3, 7 3, 7 7, 3 7)), POLYGON ((3 7, 7 7, 7 3, 3 3, 3 7)))",
66+
"POLYGON ((1 1, 1 9, 9 9, 9 1, 1 1))");
67+
}
68+
69+
//------------------------------------------------------------
70+
4871
/**
4972
* Sequential lines are still noded
5073
*/
@@ -69,6 +92,8 @@ public void testLinesNetwork( ) {
6992
"MULTILINESTRING ((1 9, 3.1 8), (2 3, 4 3), (3.1 8, 5 7), (4 3, 5 3), (5 3, 5 7), (5 3, 7 4), (5 3, 8 1), (5 7, 7 8), (7 4, 9 5), (7 8, 9 9))");
7093
}
7194

95+
//=======================================================
96+
7297
private void checkUnion(String wkt, String wktExpected) {
7398
Geometry coverage = read(wkt);
7499
Geometry expected = read(wktExpected);

0 commit comments

Comments
 (0)