diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateGeometry.java b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateGeometry.java
index c932c6d66e..84d2d57f8e 100644
--- a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateGeometry.java
+++ b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateGeometry.java
@@ -213,11 +213,11 @@ public boolean isNodeInArea(Coordinate nodePt, Geometry parentPolygonal) {
int loc = getLocator().locateNodeWithDim(nodePt, parentPolygonal);
return loc == DimensionLocation.AREA_INTERIOR;
}
-
- public int locateLineEnd(Coordinate p) {
- return getLocator().locateLineEnd(p);
- }
+ public int locateLineEndWithDim(Coordinate p) {
+ return getLocator().locateLineEndWithDim(p);
+ }
+
/**
* Locates a vertex of a polygon.
* A vertex of a Polygon or MultiPolygon is on
diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateNG.java b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateNG.java
index 18c6ed3f38..97af365712 100644
--- a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateNG.java
+++ b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateNG.java
@@ -413,7 +413,6 @@ private boolean computeLineEnds(RelateGeometry geom, boolean isA, RelateGeometry
continue;
LineString line = (LineString) elem;
- //TODO: add optimzation to skip disjoint elements once exterior point found
Coordinate e0 = line.getCoordinateN(0);
hasExteriorIntersection |= computeLineEnd(geom, isA, e0, geomTarget, topoComputer);
if (topoComputer.isResultKnown()) {
@@ -433,9 +432,27 @@ private boolean computeLineEnds(RelateGeometry geom, boolean isA, RelateGeometry
return false;
}
+ /**
+ * Compute the topology of a line endpoint.
+ * Also reports if the line end is in the exterior of the target geometry,
+ * to optimize testing multiple exterior endpoints.
+ *
+ * @param geom
+ * @param isA
+ * @param pt
+ * @param geomTarget
+ * @param topoComputer
+ * @return true if the line endpoint is in the exterior of the target
+ */
private boolean computeLineEnd(RelateGeometry geom, boolean isA, Coordinate pt,
RelateGeometry geomTarget, TopologyComputer topoComputer) {
- int locLineEnd = geom.locateLineEnd(pt);
+ int locDimLineEnd = geom.locateLineEndWithDim(pt);
+ int dimLineEnd = DimensionLocation.dimension(locDimLineEnd, topoComputer.getDimension(isA));
+ //-- skip line ends which are in a GC area
+ if (dimLineEnd != Dimension.L)
+ return false;
+ int locLineEnd = DimensionLocation.location(locDimLineEnd);
+
int locDimTarget = geomTarget.locateWithDim(pt);
int locTarget = DimensionLocation.location(locDimTarget);
int dimTarget = DimensionLocation.dimension(locDimTarget, topoComputer.getDimension(! isA));
diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelatePointLocator.java b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelatePointLocator.java
index f4942e78c3..655e19d0ba 100644
--- a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelatePointLocator.java
+++ b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelatePointLocator.java
@@ -142,14 +142,33 @@ private void addPolygonal(Geometry polygonal) {
public int locate(Coordinate p) {
return DimensionLocation.location(locateWithDim(p));
}
-
- public int locateLineEnd(Coordinate p) {
- return lineBoundary.isBoundary(p) ? Location.BOUNDARY : Location.INTERIOR;
+
+ /**
+ * Locates a point which is a line endpoint,
+ * as a {@link DimensionLocation}.
+ * For a mixed-dim GC, the line end point may also lie in an area,
+ * in which case this location is reported.
+ * Otherwise, the dimLoc will be either LINE_BOUNDARY
+ * or LINE_INTERIOR, depending on the endpoint valence
+ * and the BoundaryNodeRule in place.
+ *
+ * @param p the line end point to locate
+ * @return the dimension and location of the point
+ */
+ public int locateLineEndWithDim(Coordinate p) {
+ if (polygons != null) {
+ int locPoly = locateOnPolygons(p, false, null);
+ if (locPoly != Location.EXTERIOR)
+ return DimensionLocation.locationArea(locPoly);
+ }
+ return lineBoundary.isBoundary(p)
+ ? DimensionLocation.LINE_BOUNDARY
+ : DimensionLocation.LINE_INTERIOR;
}
/**
* Locates a point which is known to be a node of the geometry
- * (i.e. a point or on an edge).
+ * (i.e. a vertex or on an edge).
*
* @param p the node point to locate
* @param parentPolygonal the polygon the point is a node of
diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyComputer.java b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyComputer.java
index 22c7e6858d..313d10abae 100644
--- a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyComputer.java
+++ b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyComputer.java
@@ -273,10 +273,25 @@ public void addPointOnGeometry(boolean isA, int locTarget, int dimTarget, Coordi
throw new IllegalStateException("Unknown target dimension: " + dimTarget);
}
+ /**
+ * Add topology for a line end.
+ * The line end point must be "significant";
+ * i.e. not contained in an area if the source is a mixed-dimension GC.
+ *
+ * @param isLineA the input containing the line end
+ * @param locLineEnd the location of the line end (Interior or Boundary)
+ * @param locTarget the location on the target geometry
+ * @param dimTarget the dimension of the interacting target geometry element,
+ * (if any), or the dimension of the target
+ * @param pt the line end coordinate
+ */
public void addLineEndOnGeometry(boolean isLineA, int locLineEnd, int locTarget, int dimTarget, Coordinate pt) {
+ //-- record topology at line end point
+ updateDim(isLineA, locLineEnd, locTarget, Dimension.P);
+
+ //-- Line and Area targets may have additional topology
switch (dimTarget) {
case Dimension.P:
- addLineEndOnPoint(isLineA, locLineEnd, locTarget, pt);
return;
case Dimension.L:
addLineEndOnLine(isLineA, locLineEnd, locTarget, pt);
@@ -287,32 +302,30 @@ public void addLineEndOnGeometry(boolean isLineA, int locLineEnd, int locTarget,
}
throw new IllegalStateException("Unknown target dimension: " + dimTarget);
}
-
- private void addLineEndOnPoint(boolean isLineA, int locLineEnd, int locPoint, Coordinate pt) {
- updateDim(isLineA, locLineEnd, locPoint, Dimension.P);
- }
private void addLineEndOnLine(boolean isLineA, int locLineEnd, int locLine, Coordinate pt) {
- updateDim(isLineA, locLineEnd, locLine, Dimension.P);
/**
- * When a line end is in the exterior, some length of the line interior
- * must also be in the exterior.
+ * When a line end is in the EXTERIOR of a Line,
+ * some length of the source Line INTERIOR
+ * is also in the target Line EXTERIOR.
* This works for zero-length lines as well.
*/
-
if (locLine == Location.EXTERIOR) {
updateDim(isLineA, Location.INTERIOR, Location.EXTERIOR, Dimension.L);
}
- }
+ }
private void addLineEndOnArea(boolean isLineA, int locLineEnd, int locArea, Coordinate pt) {
- if (locArea == Location.BOUNDARY) {
- updateDim(isLineA, locLineEnd, locArea, Dimension.P);
- }
- else {
+ if (locArea != Location.BOUNDARY) {
+ /**
+ * When a line end is in an Area INTERIOR or EXTERIOR
+ * some length of the source Line Interior
+ * AND the Exterior of the line
+ * is also in that location of the target.
+ * NOTE: this assumes the line end is NOT also in an Area of a mixed-dim GC
+ */
//TODO: handle zero-length lines?
updateDim(isLineA, Location.INTERIOR, locArea, Dimension.L);
- updateDim(isLineA, locLineEnd, locArea, Dimension.P);
updateDim(isLineA, Location.EXTERIOR, locArea, Dimension.A);
}
}
diff --git a/modules/core/src/test/java/org/locationtech/jts/operation/relateng/RelateNGGCTest.java b/modules/core/src/test/java/org/locationtech/jts/operation/relateng/RelateNGGCTest.java
index 18be3890f7..b17a098cae 100644
--- a/modules/core/src/test/java/org/locationtech/jts/operation/relateng/RelateNGGCTest.java
+++ b/modules/core/src/test/java/org/locationtech/jts/operation/relateng/RelateNGGCTest.java
@@ -226,5 +226,11 @@ public void testPolygonContainingLineInBoundary() {
checkEquals(a, b, true);
}
+ public void testPolygonContainingLineInBoundaryAndInterior() {
+ String a = "POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))";
+ String b = "GEOMETRYCOLLECTION (POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0)), LINESTRING (0 2, 0 5, 5 5))";
+ checkEquals(a, b, true);
+ }
+
}
diff --git a/modules/core/src/test/java/org/locationtech/jts/operation/relateng/RelatePointLocatorTest.java b/modules/core/src/test/java/org/locationtech/jts/operation/relateng/RelatePointLocatorTest.java
index 4035fb0b9e..73bb3b05c2 100644
--- a/modules/core/src/test/java/org/locationtech/jts/operation/relateng/RelatePointLocatorTest.java
+++ b/modules/core/src/test/java/org/locationtech/jts/operation/relateng/RelatePointLocatorTest.java
@@ -71,6 +71,13 @@ public void testLineNode() {
checkNodeLocation(gcPLA, 3, 1, Location.BOUNDARY);
}
+ public void testLineEndInGCLA() {
+ 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))";
+ checkLineEndDimLocation(wkt, 5, 5, DimensionLocation.AREA_INTERIOR);
+ checkLineEndDimLocation(wkt, 12, 2, DimensionLocation.LINE_INTERIOR);
+ checkLineEndDimLocation(wkt, 12, 10, DimensionLocation.LINE_BOUNDARY);
+ }
+
private void checkDimLocation(String wkt, double x, double y, int expectedDimLoc) {
Geometry geom = read(wkt);
RelatePointLocator locator = new RelatePointLocator(geom);
@@ -78,6 +85,13 @@ private void checkDimLocation(String wkt, double x, double y, int expectedDimLoc
assertEquals(expectedDimLoc, actual);
}
+ private void checkLineEndDimLocation(String wkt, double x, double y, int expectedDimLoc) {
+ Geometry geom = read(wkt);
+ RelatePointLocator locator = new RelatePointLocator(geom);
+ int actual = locator.locateLineEndWithDim(new Coordinate(x, y));
+ assertEquals(expectedDimLoc, actual);
+ }
+
private void checkNodeLocation(String wkt, double x, double y, int expectedLoc) {
Geometry geom = read(wkt);
RelatePointLocator locator = new RelatePointLocator(geom);
diff --git a/modules/tests/src/test/resources/testxml/misc/TestRelateGC.xml b/modules/tests/src/test/resources/testxml/misc/TestRelateGC.xml
index 3b23304a93..f67b789a0a 100644
--- a/modules/tests/src/test/resources/testxml/misc/TestRelateGC.xml
+++ b/modules/tests/src/test/resources/testxml/misc/TestRelateGC.xml
@@ -85,7 +85,7 @@
- GC:PL/mA
+ GC:PL/A
GEOMETRYCOLLECTION (POINT (7 1), LINESTRING (6 5, 6 4))
@@ -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
true
+
+ GC:AmP/A - polygon with overlapping points equal to polygon
+
+ GEOMETRYCOLLECTION (POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)),
+ MULTIPOINT(0 2, 0 5))
+
+
+ POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))
+
+ true
+ true
+ true
+ true
+ false
+ false
+ true
+ true
+ false
+ false
+ true
+
+
+
+ GC:AL/A - polygon with overlapping line equal to polygon
+
+ GEOMETRYCOLLECTION (POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0)),
+ LINESTRING (0 2, 0 5, 5 5))
+
+
+ POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))
+
+ true
+ true
+ true
+ true
+ false
+ false
+ true
+ true
+ false
+ false
+ true
+