Skip to content

Commit 22d8e08

Browse files
committed
Fix RelateNG line-end handling
1 parent 7c26270 commit 22d8e08

File tree

7 files changed

+138
-26
lines changed

7 files changed

+138
-26
lines changed

modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateGeometry.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -213,11 +213,11 @@ public boolean isNodeInArea(Coordinate nodePt, Geometry parentPolygonal) {
213213
int loc = getLocator().locateNodeWithDim(nodePt, parentPolygonal);
214214
return loc == DimensionLocation.AREA_INTERIOR;
215215
}
216-
217-
public int locateLineEnd(Coordinate p) {
218-
return getLocator().locateLineEnd(p);
219-
}
220216

217+
public int locateLineEndWithDim(Coordinate p) {
218+
return getLocator().locateLineEndWithDim(p);
219+
}
220+
221221
/**
222222
* Locates a vertex of a polygon.
223223
* A vertex of a Polygon or MultiPolygon is on

modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateNG.java

+19-2
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,6 @@ private boolean computeLineEnds(RelateGeometry geom, boolean isA, RelateGeometry
413413
continue;
414414

415415
LineString line = (LineString) elem;
416-
//TODO: add optimzation to skip disjoint elements once exterior point found
417416
Coordinate e0 = line.getCoordinateN(0);
418417
hasExteriorIntersection |= computeLineEnd(geom, isA, e0, geomTarget, topoComputer);
419418
if (topoComputer.isResultKnown()) {
@@ -433,9 +432,27 @@ private boolean computeLineEnds(RelateGeometry geom, boolean isA, RelateGeometry
433432
return false;
434433
}
435434

435+
/**
436+
* Compute the topology of a line endpoint.
437+
* Also reports if the line end is in the exterior of the target geometry,
438+
* to optimize testing multiple exterior endpoints.
439+
*
440+
* @param geom
441+
* @param isA
442+
* @param pt
443+
* @param geomTarget
444+
* @param topoComputer
445+
* @return true if the line endpoint is in the exterior of the target
446+
*/
436447
private boolean computeLineEnd(RelateGeometry geom, boolean isA, Coordinate pt,
437448
RelateGeometry geomTarget, TopologyComputer topoComputer) {
438-
int locLineEnd = geom.locateLineEnd(pt);
449+
int locDimLineEnd = geom.locateLineEndWithDim(pt);
450+
int dimLineEnd = DimensionLocation.dimension(locDimLineEnd, topoComputer.getDimension(isA));
451+
//-- skip line ends which are in a GC area
452+
if (dimLineEnd != Dimension.L)
453+
return false;
454+
int locLineEnd = DimensionLocation.location(locDimLineEnd);
455+
439456
int locDimTarget = geomTarget.locateWithDim(pt);
440457
int locTarget = DimensionLocation.location(locDimTarget);
441458
int dimTarget = DimensionLocation.dimension(locDimTarget, topoComputer.getDimension(! isA));

modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelatePointLocator.java

+23-4
Original file line numberDiff line numberDiff line change
@@ -142,14 +142,33 @@ private void addPolygonal(Geometry polygonal) {
142142
public int locate(Coordinate p) {
143143
return DimensionLocation.location(locateWithDim(p));
144144
}
145-
146-
public int locateLineEnd(Coordinate p) {
147-
return lineBoundary.isBoundary(p) ? Location.BOUNDARY : Location.INTERIOR;
145+
146+
/**
147+
* Locates a point which is a line endpoint,
148+
* as a {@link DimensionLocation}.
149+
* For a mixed-dim GC, the line end point may also lie in an area,
150+
* in which case this location is reported.
151+
* Otherwise, the dimLoc will be either LINE_BOUNDARY
152+
* or LINE_INTERIOR, depending on the endpoint valence
153+
* and the BoundaryNodeRule in place.
154+
*
155+
* @param p the line end point to locate
156+
* @return the dimension and location of the point
157+
*/
158+
public int locateLineEndWithDim(Coordinate p) {
159+
if (polygons != null) {
160+
int locPoly = locateOnPolygons(p, false, null);
161+
if (locPoly != Location.EXTERIOR)
162+
return DimensionLocation.locationArea(locPoly);
163+
}
164+
return lineBoundary.isBoundary(p)
165+
? DimensionLocation.LINE_BOUNDARY
166+
: DimensionLocation.LINE_INTERIOR;
148167
}
149168

150169
/**
151170
* Locates a point which is known to be a node of the geometry
152-
* (i.e. a point or on an edge).
171+
* (i.e. a vertex or on an edge).
153172
*
154173
* @param p the node point to locate
155174
* @param parentPolygonal the polygon the point is a node of

modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyComputer.java

+28-15
Original file line numberDiff line numberDiff line change
@@ -273,10 +273,25 @@ public void addPointOnGeometry(boolean isA, int locTarget, int dimTarget, Coordi
273273
throw new IllegalStateException("Unknown target dimension: " + dimTarget);
274274
}
275275

276+
/**
277+
* Add topology for a line end.
278+
* The line end point must be "significant";
279+
* i.e. not contained in an area if the source is a mixed-dimension GC.
280+
*
281+
* @param isLineA the input containing the line end
282+
* @param locLineEnd the location of the line end (Interior or Boundary)
283+
* @param locTarget the location on the target geometry
284+
* @param dimTarget the dimension of the interacting target geometry element,
285+
* (if any), or the dimension of the target
286+
* @param pt the line end coordinate
287+
*/
276288
public void addLineEndOnGeometry(boolean isLineA, int locLineEnd, int locTarget, int dimTarget, Coordinate pt) {
289+
//-- record topology at line end point
290+
updateDim(isLineA, locLineEnd, locTarget, Dimension.P);
291+
292+
//-- Line and Area targets may have additional topology
277293
switch (dimTarget) {
278294
case Dimension.P:
279-
addLineEndOnPoint(isLineA, locLineEnd, locTarget, pt);
280295
return;
281296
case Dimension.L:
282297
addLineEndOnLine(isLineA, locLineEnd, locTarget, pt);
@@ -287,32 +302,30 @@ public void addLineEndOnGeometry(boolean isLineA, int locLineEnd, int locTarget,
287302
}
288303
throw new IllegalStateException("Unknown target dimension: " + dimTarget);
289304
}
290-
291-
private void addLineEndOnPoint(boolean isLineA, int locLineEnd, int locPoint, Coordinate pt) {
292-
updateDim(isLineA, locLineEnd, locPoint, Dimension.P);
293-
}
294305

295306
private void addLineEndOnLine(boolean isLineA, int locLineEnd, int locLine, Coordinate pt) {
296-
updateDim(isLineA, locLineEnd, locLine, Dimension.P);
297307
/**
298-
* When a line end is in the exterior, some length of the line interior
299-
* must also be in the exterior.
308+
* When a line end is in the EXTERIOR of a Line,
309+
* some length of the source Line INTERIOR
310+
* is also in the target Line EXTERIOR.
300311
* This works for zero-length lines as well.
301312
*/
302-
303313
if (locLine == Location.EXTERIOR) {
304314
updateDim(isLineA, Location.INTERIOR, Location.EXTERIOR, Dimension.L);
305315
}
306-
}
316+
}
307317

308318
private void addLineEndOnArea(boolean isLineA, int locLineEnd, int locArea, Coordinate pt) {
309-
if (locArea == Location.BOUNDARY) {
310-
updateDim(isLineA, locLineEnd, locArea, Dimension.P);
311-
}
312-
else {
319+
if (locArea != Location.BOUNDARY) {
320+
/**
321+
* When a line end is in an Area INTERIOR or EXTERIOR
322+
* some length of the source Line Interior
323+
* AND the Exterior of the line
324+
* is also in that location of the target.
325+
* NOTE: this assumes the line end is NOT also in an Area of a mixed-dim GC
326+
*/
313327
//TODO: handle zero-length lines?
314328
updateDim(isLineA, Location.INTERIOR, locArea, Dimension.L);
315-
updateDim(isLineA, locLineEnd, locArea, Dimension.P);
316329
updateDim(isLineA, Location.EXTERIOR, locArea, Dimension.A);
317330
}
318331
}

modules/core/src/test/java/org/locationtech/jts/operation/relateng/RelateNGGCTest.java

+6
Original file line numberDiff line numberDiff line change
@@ -226,5 +226,11 @@ public void testPolygonContainingLineInBoundary() {
226226
checkEquals(a, b, true);
227227
}
228228

229+
public void testPolygonContainingLineInBoundaryAndInterior() {
230+
String a = "POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))";
231+
String b = "GEOMETRYCOLLECTION (POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0)), LINESTRING (0 2, 0 5, 5 5))";
232+
checkEquals(a, b, true);
233+
}
234+
229235

230236
}

modules/core/src/test/java/org/locationtech/jts/operation/relateng/RelatePointLocatorTest.java

+14
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,27 @@ public void testLineNode() {
7171
checkNodeLocation(gcPLA, 3, 1, Location.BOUNDARY);
7272
}
7373

74+
public void testLineEndInGCLA() {
75+
String wkt = "GEOMETRYCOLLECTION (POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0)), LINESTRING (12 2, 0 2, 0 5, 5 5), LINESTRING (12 10, 12 2))";
76+
checkLineEndDimLocation(wkt, 5, 5, DimensionLocation.AREA_INTERIOR);
77+
checkLineEndDimLocation(wkt, 12, 2, DimensionLocation.LINE_INTERIOR);
78+
checkLineEndDimLocation(wkt, 12, 10, DimensionLocation.LINE_BOUNDARY);
79+
}
80+
7481
private void checkDimLocation(String wkt, double x, double y, int expectedDimLoc) {
7582
Geometry geom = read(wkt);
7683
RelatePointLocator locator = new RelatePointLocator(geom);
7784
int actual = locator.locateWithDim(new Coordinate(x, y));
7885
assertEquals(expectedDimLoc, actual);
7986
}
8087

88+
private void checkLineEndDimLocation(String wkt, double x, double y, int expectedDimLoc) {
89+
Geometry geom = read(wkt);
90+
RelatePointLocator locator = new RelatePointLocator(geom);
91+
int actual = locator.locateLineEndWithDim(new Coordinate(x, y));
92+
assertEquals(expectedDimLoc, actual);
93+
}
94+
8195
private void checkNodeLocation(String wkt, double x, double y, int expectedLoc) {
8296
Geometry geom = read(wkt);
8397
RelatePointLocator locator = new RelatePointLocator(geom);

modules/tests/src/test/resources/testxml/misc/TestRelateGC.xml

+44-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585
</case>
8686

8787
<case>
88-
<desc>GC:PL/mA</desc>
88+
<desc>GC:PL/A</desc>
8989
<a>
9090
GEOMETRYCOLLECTION (POINT (7 1), LINESTRING (6 5, 6 4))
9191
</a>
@@ -554,5 +554,48 @@ GEOMETRYCOLLECTION (POLYGON ((1 9, 5 9, 6 6, 1 5, 1 9), (2 6, 4 8, 6 6, 2 6)), P
554554
<test><op name="within" arg1="A" arg2="B"> true </op></test>
555555
</case>
556556

557+
<case>
558+
<desc>GC:AmP/A - polygon with overlapping points equal to polygon </desc>
559+
<a>
560+
GEOMETRYCOLLECTION (POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)),
561+
MULTIPOINT(0 2, 0 5))
562+
</a>
563+
<b>
564+
POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))
565+
</b>
566+
<test><op name="relate" arg3="2FFF1FFF2" arg1="A" arg2="B"> true </op></test>
567+
<test><op name="contains" arg1="A" arg2="B"> true </op></test>
568+
<test><op name="coveredBy" arg1="A" arg2="B"> true </op></test>
569+
<test><op name="covers" arg1="A" arg2="B"> true </op></test>
570+
<test><op name="crosses" arg1="A" arg2="B"> false </op></test>
571+
<test><op name="disjoint" arg1="A" arg2="B"> false </op></test>
572+
<test><op name="equalsTopo" arg1="A" arg2="B"> true </op></test>
573+
<test><op name="intersects" arg1="A" arg2="B"> true </op></test>
574+
<test><op name="overlaps" arg1="A" arg2="B"> false </op></test>
575+
<test><op name="touches" arg1="A" arg2="B"> false </op></test>
576+
<test><op name="within" arg1="A" arg2="B"> true </op></test>
577+
</case>
578+
579+
<case>
580+
<desc>GC:AL/A - polygon with overlapping line equal to polygon </desc>
581+
<a>
582+
GEOMETRYCOLLECTION (POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0)),
583+
LINESTRING (0 2, 0 5, 5 5))
584+
</a>
585+
<b>
586+
POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))
587+
</b>
588+
<test><op name="relate" arg3="2FFF1FFF2" arg1="A" arg2="B"> true </op></test>
589+
<test><op name="contains" arg1="A" arg2="B"> true </op></test>
590+
<test><op name="coveredBy" arg1="A" arg2="B"> true </op></test>
591+
<test><op name="covers" arg1="A" arg2="B"> true </op></test>
592+
<test><op name="crosses" arg1="A" arg2="B"> false </op></test>
593+
<test><op name="disjoint" arg1="A" arg2="B"> false </op></test>
594+
<test><op name="equalsTopo" arg1="A" arg2="B"> true </op></test>
595+
<test><op name="intersects" arg1="A" arg2="B"> true </op></test>
596+
<test><op name="overlaps" arg1="A" arg2="B"> false </op></test>
597+
<test><op name="touches" arg1="A" arg2="B"> false </op></test>
598+
<test><op name="within" arg1="A" arg2="B"> true </op></test>
599+
</case>
557600

558601
</run>

0 commit comments

Comments
 (0)