Skip to content

Commit

Permalink
Fix RelateNG for Line Ends in mixed-dim GCs (#1069)
Browse files Browse the repository at this point in the history
  • Loading branch information
dr-jts authored Aug 22, 2024
1 parent 7c26270 commit e98dcac
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand All @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,27 @@ 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);
int actual = locator.locateWithDim(new Coordinate(x, y));
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);
Expand Down
45 changes: 44 additions & 1 deletion modules/tests/src/test/resources/testxml/misc/TestRelateGC.xml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
</case>

<case>
<desc>GC:PL/mA</desc>
<desc>GC:PL/A</desc>
<a>
GEOMETRYCOLLECTION (POINT (7 1), LINESTRING (6 5, 6 4))
</a>
Expand Down Expand Up @@ -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
<test><op name="within" arg1="A" arg2="B"> true </op></test>
</case>

<case>
<desc>GC:AmP/A - polygon with overlapping points equal to polygon </desc>
<a>
GEOMETRYCOLLECTION (POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)),
MULTIPOINT(0 2, 0 5))
</a>
<b>
POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))
</b>
<test><op name="relate" arg3="2FFF1FFF2" arg1="A" arg2="B"> true </op></test>
<test><op name="contains" arg1="A" arg2="B"> true </op></test>
<test><op name="coveredBy" arg1="A" arg2="B"> true </op></test>
<test><op name="covers" arg1="A" arg2="B"> true </op></test>
<test><op name="crosses" arg1="A" arg2="B"> false </op></test>
<test><op name="disjoint" arg1="A" arg2="B"> false </op></test>
<test><op name="equalsTopo" arg1="A" arg2="B"> true </op></test>
<test><op name="intersects" arg1="A" arg2="B"> true </op></test>
<test><op name="overlaps" arg1="A" arg2="B"> false </op></test>
<test><op name="touches" arg1="A" arg2="B"> false </op></test>
<test><op name="within" arg1="A" arg2="B"> true </op></test>
</case>

<case>
<desc>GC:AL/A - polygon with overlapping line equal to polygon </desc>
<a>
GEOMETRYCOLLECTION (POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0)),
LINESTRING (0 2, 0 5, 5 5))
</a>
<b>
POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))
</b>
<test><op name="relate" arg3="2FFF1FFF2" arg1="A" arg2="B"> true </op></test>
<test><op name="contains" arg1="A" arg2="B"> true </op></test>
<test><op name="coveredBy" arg1="A" arg2="B"> true </op></test>
<test><op name="covers" arg1="A" arg2="B"> true </op></test>
<test><op name="crosses" arg1="A" arg2="B"> false </op></test>
<test><op name="disjoint" arg1="A" arg2="B"> false </op></test>
<test><op name="equalsTopo" arg1="A" arg2="B"> true </op></test>
<test><op name="intersects" arg1="A" arg2="B"> true </op></test>
<test><op name="overlaps" arg1="A" arg2="B"> false </op></test>
<test><op name="touches" arg1="A" arg2="B"> false </op></test>
<test><op name="within" arg1="A" arg2="B"> true </op></test>
</case>

</run>

0 comments on commit e98dcac

Please sign in to comment.