Skip to content

Commit

Permalink
Switch to outputting all components for intersection op
Browse files Browse the repository at this point in the history
Signed-off-by: Martin Davis <mtnclimb@gmail.com>
  • Loading branch information
dr-jts committed Aug 7, 2020
1 parent 874bacf commit ea13dc3
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,13 @@ public List<LineString> getLines() {
private void markResultLines() {
Collection<OverlayEdge> edges = graph.getEdges();
for (OverlayEdge edge : edges) {
if (isInResult(edge))
/**
* If the edge linework is already marked as in the result,
* it is not included as a line.
* This occurs when an edge either is in a result area
* or has already been included as a line.
*/
if (edge.isInResultEither())
continue;
if (isResultLine(edge.getLabel())) {
edge.markInResultLine();
Expand All @@ -94,17 +100,6 @@ private void markResultLines() {
}
}

/**
* If the edge linework is already in the result,
* this edge does not need to be included as a line.
*
* @param edge an edge of the topology graph
* @return true if the edge linework is already in the result
*/
private static boolean isInResult(OverlayEdge edge) {
return edge.isInResult() || edge.symOE().isInResult();
}

/**
* Checks if the topology indicated by an edge label
* determines that this edge should be part of a result line.
Expand All @@ -118,13 +113,17 @@ private static boolean isInResult(OverlayEdge edge) {
*/
private boolean isResultLine(OverlayLabel lbl) {
/**
* Edges which are just collapses along boundaries
* are not output.
* In other words, an edge must be from a source line
* or two (coincident) area boundaries.
* Edges which are collapses along boundaries are not output.
* I.e a result line edge must be from a input line
* or two coincident area boundaries.
*/
if (lbl.isBoundaryCollapse()) return false;

if (OverlayNG.ALLOW_INT_MIXED_INT_RESULT
&& opCode == OverlayNG.INTERSECTION && lbl.isBoundaryTouch()) {
return true;
}

/**
* Skip edges that are inside result area, if there is one.
* It is sufficient to check against an input area rather
Expand All @@ -138,8 +137,8 @@ private boolean isResultLine(OverlayLabel lbl) {
if (hasResultArea && lbl.isLineInArea(inputAreaIndex))
return false;

int aLoc = effectiveLocation(0, lbl);
int bLoc = effectiveLocation(1, lbl);
int aLoc = effectiveLocation(lbl, 0);
int bLoc = effectiveLocation(lbl, 1);

boolean isInResult = OverlayNG.isResultOfOp(opCode, aLoc, bLoc);
return isInResult;
Expand All @@ -150,16 +149,16 @@ private boolean isResultLine(OverlayLabel lbl) {
* for the purpose of overlay operation evaluation.
* Line edges and Collapses are reported as INTERIOR
* so they may be included in the result
* if warranted by the effect of the operation
* on the two edges.
* (For instance, the intersection of line edge and a collapsed boundary
* if warranted by the effect of the operation on the two edges.
* (For instance, the intersection of a line edge and a collapsed boundary
* is included in the result).
*
* @param geomIndex index of parent geometry
* @param lbl label of line
* @param geomIndex index of input geometry
*
* @return the effective location of the line
*/
private static int effectiveLocation(int geomIndex, OverlayLabel lbl) {
private static int effectiveLocation(OverlayLabel lbl, int geomIndex) {
if (lbl.isCollapse(geomIndex))
return Location.INTERIOR;
if (lbl.isLine(geomIndex))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@ public boolean isInResult() {
return isInResultArea || isInResultLine;
}

public boolean isInResultEither() {
return isInResult() || symOE().isInResult();
}

void setNextResult(OverlayEdge e) {
// Assert: e.orig() == this.dest();
nextResultEdge = e;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,34 +36,33 @@
* <p>
* For each input geometry, the label records
* that an edge is in one of the following states
* (denoted by the <code>dim</code> field).
* Each state has some additional information about the edge.
* (identified by the <code>dim</code> field).
* Each state has additional information about the edge topology.
* <ul>
* <li>A <b>Boundary</b> edge of an input Area (polygon)
* <li>A <b>Boundary</b> edge of an Area (polygon)
* <ul>
* <li><code>dim</code> = DIM_BOUNDARY</li>
* <li><code>locLeft, locRight</code> : the locations of the edge sides for the input Area</li>
* <li><code>locLeft, locRight</code> : the locations of the edge sides for the Area</li>
* <li><code>locLine</code> : INTERIOR</li>
* <li><code>isHole</code> : whether the
* edge was in a shell or a hole (the ring role)</li>
* </ul>
* </li>
* <li>A <b>Collapsed</b> edge of an input Area
* (which had two or more parent edges)
* (formed by merging two or more parent edges)
* <ul>
* <li><code>dim</code> = DIM_COLLAPSE</li>
* <li><code>locLine</code> : the location of the
* edge relative to the input Area</li>
* <li><code>locLine</code> : the location of the edge relative to the Area</li>
* <li><code>isHole</code> : whether some
* contributing edge was in a shell (<code>false</code>),
* or otherwise that all were in holes</li> (<code>true</code>)
* contributing edge was in a shell (<code>false</code>).
* Otherwise all parent edges were in holes</li> (<code>true</code>)
* </ul>
* </li>
* <li>A <b>Line</b> edge from an input line
* <ul>
* <li><code>dim</code> = DIM_LINE</li>
* <li><code>locLine</code> : the location of the
* edge relative to the input Line.
* Initialized to LOC_UNKNOWN to simplify logic.</li>
* <li><code>locLine</code> : the location of the edge relative to the Line.
* Initialized to LOC_UNKNOWN to simplify logic.</li>
* </ul>
* </li>
* <li>An edge which is <b>Not Part</b> of an input geometry
Expand Down Expand Up @@ -413,9 +412,8 @@ public boolean isBoundaryBoth() {
}

/**
* Tests if the label is for a collapsed
* edge of an area
* which is coincident with the boundary of the other area.
* Tests if the label is for a collapsed area edge
* and is a (non-collapsed) boundary edge of the other area.
*
* @return true if the label is for a collapse coincident with a boundary
*/
Expand All @@ -424,6 +422,18 @@ public boolean isBoundaryCollapse() {
return ! isBoundaryBoth();
}

/**
* Tests if a label is for an edge where two
* area touch along their boundary.
*
* @return true if the edge is a boundary touch
*/
public boolean isBoundaryTouch() {
return isBoundaryBoth()
&& getLocation(0, Position.RIGHT, true) != getLocation(1, Position.RIGHT, true);
}


/**
* Tests if a label is for an edge which is in the boundary of a source geometry.
*
Expand Down Expand Up @@ -601,27 +611,6 @@ public OverlayLabel copy() {
return new OverlayLabel(this);
}

/**
* Creates a flipped copy of this label.
*
* @return a flipped copy of the label
*/
public OverlayLabel copyFlip() {
OverlayLabel lbl = new OverlayLabel();

lbl.aLocLeft = this.aLocRight;
lbl.aLocRight = this.aLocLeft;
lbl.aLocLine = this.aLocLine;
lbl.aDim = this.aDim;

lbl.bLocLeft = this.bLocRight;
lbl.bLocRight = this.bLocLeft;
lbl.bLocLine = this.bLocLine;
lbl.bDim = this.bDim;

return lbl;
}

public String toString()
{
return toString(true);
Expand Down Expand Up @@ -667,5 +656,4 @@ public static char dimensionSymbol(int dim) {
return SYM_UNKNOWN;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
import java.util.Deque;
import java.util.List;

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Location;
import org.locationtech.jts.geom.Position;
import org.locationtech.jts.geom.TopologyException;
import org.locationtech.jts.io.WKTWriter;
import org.locationtech.jts.util.Assert;
import org.locationtech.jts.util.Debug;

/**
* Implements the logic to compute the full labeling
Expand Down Expand Up @@ -257,17 +260,18 @@ private static void propagateLinearLocationAtNode(OverlayEdge eNode,
*/
if (isInputLine && lineLoc != Location.EXTERIOR) return;

//Debug.println("propagateLinearLocationAtNode ----- using location for " + geomIndex + " from: " + eNode);
OverlayEdge e = eNode.oNextOE();
do {
OverlayLabel label = e.getLabel();
//Debug.println("propagateLinearLocationAtNode - checking " + index + ": " + e);
//Debug.println("check " + geomIndex + ": " + e);
if ( label.isLineLocationUnknown(geomIndex) ) {
/**
* If edge is not a boundary edge,
* its location is now known for this area
*/
label.setLocationLine(geomIndex, lineLoc);
//Debug.println("propagateLinearLocationAtNode - setting "+ index + ": " + e);
//Debug.println("*** setting "+ geomIndex + ": " + e);

/**
* Add sym edge to stack for graph traversal
Expand Down Expand Up @@ -457,4 +461,20 @@ public void unmarkDuplicateEdgesFromResultArea() {
}
}

public static String toString(OverlayEdge nodeEdge) {
Coordinate orig = nodeEdge.orig();
StringBuilder sb = new StringBuilder();
sb.append("Node( "+ WKTWriter.format(orig) + " )" + "\n");
OverlayEdge e = nodeEdge;
do {
sb.append(" -> " + e);
if (e.isResultLinked()) {
sb.append(" Link: ");
sb.append(e.nextResult());
}
sb.append("\n");
e = e.oNextOE();
} while (e != nodeEdge);
return sb.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,15 @@ public class OverlayNG
*/
public static final int SYMDIFFERENCE = OverlayOp.SYMDIFFERENCE;

/**
* Indicates whether intersections are allowed to produce
* heterogeneous results.
* True provides the classic JTS semantics
* (for proper boundary touches only -
* touching along collapses are not output).
*/
static final boolean ALLOW_INT_MIXED_INT_RESULT = true;

/**
* Tests whether a point with a given topological {@link Label}
* relative to two geometries is contained in
Expand Down Expand Up @@ -432,6 +441,7 @@ private Geometry computeEdgeOverlay() {
}

labelGraph(graph);
//for (OverlayEdge e : graph.getEdges()) { Debug.println(e); }

if (isOutputEdges || isOutputResultEdges) {
return OverlayUtil.toLines(graph, isOutputEdges, geomFact);
Expand Down Expand Up @@ -510,31 +520,38 @@ private Geometry extractResult(int opCode, OverlayGraph graph) {

//--- Build lines
List<LineString> resultLineList = null;
if (opCode != INTERSECTION || ! hasResultComponents) {
boolean allowMixedIntResult = ! hasResultComponents || ALLOW_INT_MIXED_INT_RESULT;
if (opCode != INTERSECTION || allowMixedIntResult) {
LineBuilder lineBuilder = new LineBuilder(inputGeom, graph, hasResultComponents, opCode, geomFact);
resultLineList = lineBuilder.getLines();
hasResultComponents = resultLineList.size() > 0;
}
hasResultComponents = hasResultComponents || resultLineList.size() > 0;
/**
* Since operations with point inputs are handled elsewhere,
* this only handles the case where non-point inputs
* intersect in points ONLY.
* intersect in points.
*/
List<Point> resultPointList = null;
if (opCode == INTERSECTION && ! hasResultComponents) {
allowMixedIntResult = ! hasResultComponents || ALLOW_INT_MIXED_INT_RESULT;
if (opCode == INTERSECTION && allowMixedIntResult) {
//if (opCode == INTERSECTION) {
IntersectionPointBuilder pointBuilder = new IntersectionPointBuilder(graph, geomFact);
resultPointList = pointBuilder.getPoints();
}

if ((resultPolyList == null || resultPolyList.size() == 0)
&& (resultLineList == null || resultLineList.size() == 0)
&& (resultPointList == null || resultPointList.size() == 0) )
if (isEmpty(resultPolyList)
&& isEmpty(resultLineList)
&& isEmpty(resultPointList))
return createEmptyResult();

Geometry resultGeom = OverlayUtil.createResultGeometry(resultPolyList, resultLineList, resultPointList, geomFact);
return resultGeom;
}

private static boolean isEmpty(List list) {
return list == null || list.size() == 0;
}

private Geometry createEmptyResult() {
return OverlayUtil.createEmptyResult(
OverlayUtil.resultDimension(opCode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,8 @@ <h2>Semantics</h2>
In some cases more nodes will be present.
(If merged lines are required see {@link LineMerger}.)</li>
<li>Polygon edges which collapse completely due to rounding are not output.</li>
<li>The <code>intersection</code> operation produces a homogeneous result.
The result contains the components of highest dimension in the intersection.
(For instance, the intersection of a <code>Polygon</code>
and a <code>LineString</code> might produce a <code>Point</code> result.)</li>
<li>The <code>intersection</code> operation may produce a heterogenous result.
The result contains all the dimensional components of the intersection.</li>
<li>The <code>difference</code> operation produces a homogeneous result.
The result dimension is that of the left-hand operand.</li>
<li>The <code>union</code> and <code>symmetric difference</code> operations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,18 @@ public void testDisjointIntersectionNoOpt() {
checkEqual(expected, actual);
}

public void testPolygoPolygonWithLineTouchIntersection() {
public void testAreaLineIntersection() {
Geometry a = read("POLYGON ((360 200, 220 200, 220 180, 300 180, 300 160, 300 140, 360 200))");
Geometry b = read("MULTIPOLYGON (((280 180, 280 160, 300 160, 300 180, 280 180)), ((220 230, 240 230, 240 180, 220 180, 220 230)))");
Geometry expected = read("POLYGON ((220 200, 240 200, 240 180, 220 180, 220 200))");
Geometry expected = read("GEOMETRYCOLLECTION (LINESTRING (280 180, 300 180), LINESTRING (300 160, 300 180), POLYGON ((220 180, 220 200, 240 200, 240 180, 220 180)))");
Geometry actual = intersection(a, b, 1);
checkEqual(expected, actual);
}

public void testAreaLinePointIntersection() {
Geometry a = read("POLYGON ((100 100, 200 100, 200 150, 250 100, 300 100, 300 150, 350 100, 350 200, 100 200, 100 100))");
Geometry b = read("POLYGON ((100 140, 170 140, 200 100, 400 100, 400 30, 100 30, 100 140))");
Geometry expected = read("GEOMETRYCOLLECTION (POINT (350 100), LINESTRING (250 100, 300 100), POLYGON ((100 100, 100 140, 170 140, 200 100, 100 100)))");
Geometry actual = intersection(a, b, 1);
checkEqual(expected, actual);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,27 @@ public static void main(String args[]) {

public OverlayNGTestOne(String name) { super(name); }

public void testBoxHoleCollapseAlongBEdgeDifference() {
public void xtestParallelSpikes() {
Geometry a = read("POLYGON ((1 3.3, 1.3 1.4, 3.1 1.4, 3.1 0.9, 1.3 0.9, 1 -0.2, 0.8 1.3, 1 3.3))");
Geometry b = read("POLYGON ((1 2.9, 2.9 2.9, 2.9 1.3, 1.7 1, 1.3 0.9, 1 0.4, 1 2.9))");
Geometry expected = read("POLYGON EMPTY");
checkEqual(expected, OverlayNGTest.intersection(a, b, 1));
}

public void xtestBoxHoleCollapseAlongBEdgeDifference() {
Geometry a = read("POLYGON ((0 3, 3 3, 3 0, 0 0, 0 3), (1 1.2, 1 1.1, 2.3 1.1, 1 1.2))");
Geometry b = read("POLYGON ((1 1, 2 1, 2 0, 1 0, 1 1))");
Geometry expected = read("POLYGON EMPTY");
checkEqual(expected, OverlayNGTest.difference(b, a, 1));
}

public void testPolyPolyTouchIntersection() {
Geometry a = read("POLYGON ((300 0, 100 0, 100 100, 300 100, 300 0))");
Geometry b = read("POLYGON ((100 200, 300 200, 300 100, 200 100, 200 0, 100 0, 100 200))");
Geometry expected = read("GEOMETRYCOLLECTION (LINESTRING (200 100, 300 100), POLYGON ((200 0, 100 0, 100 100, 200 100, 200 0)))");
checkEqual(expected, OverlayNGTest.intersection(a, b, 1));
}

public void xtestBoxHoleCollapseAlongBEdgeUnion() {
Geometry a = read("POLYGON ((0 3, 3 3, 3 0, 0 0, 0 3), (1 1.2, 1 1.1, 2.3 1.1, 1 1.2))");
Geometry b = read("POLYGON ((1 1, 2 1, 2 0, 1 0, 1 1))");
Expand Down
Loading

0 comments on commit ea13dc3

Please sign in to comment.