Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix RelateNG for Line Ends in mixed-dim GCs #1069

Merged
merged 1 commit into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
Loading