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 +