| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
|
| 14 |
package org.locationtech.jts.operation.overlay; |
| 15 |
|
| 16 |
import java.util.ArrayList; |
| 17 |
import java.util.Iterator; |
| 18 |
import java.util.List; |
| 19 |
|
| 20 |
import org.locationtech.jts.algorithm.PointLocator; |
| 21 |
import org.locationtech.jts.geom.Coordinate; |
| 22 |
import org.locationtech.jts.geom.Geometry; |
| 23 |
import org.locationtech.jts.geom.GeometryFactory; |
| 24 |
import org.locationtech.jts.geom.Location; |
| 25 |
import org.locationtech.jts.geom.TopologyException; |
| 26 |
import org.locationtech.jts.geomgraph.Depth; |
| 27 |
import org.locationtech.jts.geomgraph.DirectedEdge; |
| 28 |
import org.locationtech.jts.geomgraph.DirectedEdgeStar; |
| 29 |
import org.locationtech.jts.geomgraph.Edge; |
| 30 |
import org.locationtech.jts.geomgraph.EdgeList; |
| 31 |
import org.locationtech.jts.geomgraph.EdgeNodingValidator; |
| 32 |
import org.locationtech.jts.geomgraph.Label; |
| 33 |
import org.locationtech.jts.geomgraph.Node; |
| 34 |
import org.locationtech.jts.geomgraph.PlanarGraph; |
| 35 |
import org.locationtech.jts.geomgraph.Position; |
| 36 |
import org.locationtech.jts.operation.GeometryGraphOperation; |
| 37 |
import org.locationtech.jts.util.Assert; |
| 38 |
|
| 39 |
/** |
| 40 |
* Computes the geometric overlay of two {@link Geometry}s. The overlay |
| 41 |
* can be used to determine any boolean combination of the geometries. |
| 42 |
* |
| 43 |
* @version 1.7 |
| 44 |
*/ |
| 45 |
public class OverlayOp |
| 46 |
extends GeometryGraphOperation |
| 47 |
{ |
| 48 |
/** |
| 49 |
* The spatial functions supported by this class. |
| 50 |
* These operations implement various boolean combinations of the resultants of the overlay. |
| 51 |
*/ |
| 52 |
|
| 53 |
/** |
| 54 |
* The code for the Intersection overlay operation. |
| 55 |
*/ |
| 56 |
public static final int INTERSECTION = 1; |
| 57 |
|
| 58 |
/** |
| 59 |
* The code for the Union overlay operation. |
| 60 |
*/ |
| 61 |
public static final int UNION = 2; |
| 62 |
|
| 63 |
/** |
| 64 |
* The code for the Difference overlay operation. |
| 65 |
*/ |
| 66 |
public static final int DIFFERENCE = 3; |
| 67 |
|
| 68 |
/** |
| 69 |
* The code for the Symmetric Difference overlay operation. |
| 70 |
*/ |
| 71 |
public static final int SYMDIFFERENCE = 4; |
| 72 |
|
| 73 |
/** |
| 74 |
* Computes an overlay operation for |
| 75 |
* the given geometry arguments. |
| 76 |
* |
| 77 |
* @param geom0 the first geometry argument |
| 78 |
* @param geom1 the second geometry argument |
| 79 |
* @param opCode the code for the desired overlay operation |
| 80 |
* @return the result of the overlay operation |
| 81 |
* @throws TopologyException if a robustness problem is encountered |
| 82 |
*/ |
| 83 |
public static Geometry overlayOp(Geometry geom0, Geometry geom1, int opCode) |
| 84 |
{ |
| 85 |
OverlayOp gov = new OverlayOp(geom0, geom1); |
| 86 |
Geometry geomOv = gov.getResultGeometry(opCode); |
| 87 |
return geomOv; |
| 88 |
} |
| 89 |
|
| 90 |
/** |
| 91 |
* Tests whether a point with a given topological {@link Label} |
| 92 |
* relative to two geometries is contained in |
| 93 |
* the result of overlaying the geometries using |
| 94 |
* a given overlay operation. |
| 95 |
* <p> |
| 96 |
* The method handles arguments of {@link Location#NONE} correctly |
| 97 |
* |
| 98 |
* @param label the topological label of the point |
| 99 |
* @param opCode the code for the overlay operation to test |
| 100 |
* @return true if the label locations correspond to the overlayOpCode |
| 101 |
*/ |
| 102 |
public static boolean isResultOfOp(Label label, int opCode) |
| 103 |
{ |
| 104 |
int loc0 = label.getLocation(0); |
| 105 |
int loc1 = label.getLocation(1); |
| 106 |
return isResultOfOp(loc0, loc1, opCode); |
| 107 |
} |
| 108 |
|
| 109 |
/** |
| 110 |
* Tests whether a point with given {@link Location}s |
| 111 |
* relative to two geometries is contained in |
| 112 |
* the result of overlaying the geometries using |
| 113 |
* a given overlay operation. |
| 114 |
* <p> |
| 115 |
* The method handles arguments of {@link Location#NONE} correctly |
| 116 |
* |
| 117 |
* @param loc0 the code for the location in the first geometry |
| 118 |
* @param loc1 the code for the location in the second geometry |
| 119 |
* @param overlayOpCode the code for the overlay operation to test |
| 120 |
* @return true if the locations correspond to the overlayOpCode |
| 121 |
*/ |
| 122 |
public static boolean isResultOfOp(int loc0, int loc1, int overlayOpCode) |
| 123 |
{ |
| 124 |
if (loc0 == Location.BOUNDARY) loc0 = Location.INTERIOR; |
| 125 |
if (loc1 == Location.BOUNDARY) loc1 = Location.INTERIOR; |
| 126 |
switch (overlayOpCode) { |
| 127 |
case INTERSECTION: |
| 128 |
return loc0 == Location.INTERIOR |
| 129 |
&& loc1 == Location.INTERIOR; |
| 130 |
case UNION: |
| 131 |
return loc0 == Location.INTERIOR |
| 132 |
|| loc1 == Location.INTERIOR; |
| 133 |
case DIFFERENCE: |
| 134 |
return loc0 == Location.INTERIOR |
| 135 |
&& loc1 != Location.INTERIOR; |
| 136 |
case SYMDIFFERENCE: |
| 137 |
return ( loc0 == Location.INTERIOR && loc1 != Location.INTERIOR) |
| 138 |
|| ( loc0 != Location.INTERIOR && loc1 == Location.INTERIOR); |
| 139 |
} |
| 140 |
return false; |
| 141 |
} |
| 142 |
|
| 143 |
private final PointLocator ptLocator = new PointLocator(); |
| 144 |
private GeometryFactory geomFact; |
| 145 |
private Geometry resultGeom; |
| 146 |
|
| 147 |
private PlanarGraph graph; |
| 148 |
private EdgeList edgeList = new EdgeList(); |
| 149 |
|
| 150 |
private List resultPolyList = new ArrayList(); |
| 151 |
private List resultLineList = new ArrayList(); |
| 152 |
private List resultPointList = new ArrayList(); |
| 153 |
|
| 154 |
/** |
| 155 |
* Constructs an instance to compute a single overlay operation |
| 156 |
* for the given geometries. |
| 157 |
* |
| 158 |
* @param g0 the first geometry argument |
| 159 |
* @param g1 the second geometry argument |
| 160 |
*/ |
| 161 |
public OverlayOp(Geometry g0, Geometry g1) { |
| 162 |
super(g0, g1); |
| 163 |
graph = new PlanarGraph(new OverlayNodeFactory()); |
| 164 |
/** |
| 165 |
* Use factory of primary geometry. |
| 166 |
* Note that this does NOT handle mixed-precision arguments |
| 167 |
* where the second arg has greater precision than the first. |
| 168 |
*/ |
| 169 |
geomFact = g0.getFactory(); |
| 170 |
} |
| 171 |
|
| 172 |
/** |
| 173 |
* Gets the result of the overlay for a given overlay operation. |
| 174 |
* <p> |
| 175 |
* Note: this method can be called once only. |
| 176 |
* |
| 177 |
* @param overlayOpCode the overlay operation to perform |
| 178 |
* @return the compute result geometry |
| 179 |
* @throws TopologyException if a robustness problem is encountered |
| 180 |
*/ |
| 181 |
public Geometry getResultGeometry(int overlayOpCode) |
| 182 |
{ |
| 183 |
computeOverlay(overlayOpCode); |
| 184 |
return resultGeom; |
| 185 |
} |
| 186 |
|
| 187 |
/** |
| 188 |
* Gets the graph constructed to compute the overlay. |
| 189 |
* |
| 190 |
* @return the overlay graph |
| 191 |
*/ |
| 192 |
public PlanarGraph getGraph() { return graph; } |
| 193 |
|
| 194 |
private void computeOverlay(int opCode) |
| 195 |
{ |
| 196 |
|
| 197 |
|
| 198 |
|
| 199 |
copyPoints(0); |
| 200 |
copyPoints(1); |
| 201 |
|
| 202 |
|
| 203 |
arg[0].computeSelfNodes(li, false); |
| 204 |
arg[1].computeSelfNodes(li, false); |
| 205 |
|
| 206 |
|
| 207 |
arg[0].computeEdgeIntersections(arg[1], li, true); |
| 208 |
|
| 209 |
List baseSplitEdges = new ArrayList(); |
| 210 |
arg[0].computeSplitEdges(baseSplitEdges); |
| 211 |
arg[1].computeSplitEdges(baseSplitEdges); |
| 212 |
List splitEdges = baseSplitEdges; |
| 213 |
|
| 214 |
insertUniqueEdges(baseSplitEdges); |
| 215 |
|
| 216 |
computeLabelsFromDepths(); |
| 217 |
replaceCollapsedEdges(); |
| 218 |
|
| 219 |
|
| 220 |
|
| 221 |
/** |
| 222 |
* Check that the noding completed correctly. |
| 223 |
* |
| 224 |
* This test is slow, but necessary in order to catch robustness failure |
| 225 |
* situations. |
| 226 |
* If an exception is thrown because of a noding failure, |
| 227 |
* then snapping will be performed, which will hopefully avoid the problem. |
| 228 |
* In the future hopefully a faster check can be developed. |
| 229 |
* |
| 230 |
*/ |
| 231 |
EdgeNodingValidator.checkValid(edgeList.getEdges()); |
| 232 |
|
| 233 |
graph.addEdges(edgeList.getEdges()); |
| 234 |
computeLabelling(); |
| 235 |
|
| 236 |
labelIncompleteNodes(); |
| 237 |
|
| 238 |
|
| 239 |
|
| 240 |
/** |
| 241 |
* The ordering of building the result Geometries is important. |
| 242 |
* Areas must be built before lines, which must be built before points. |
| 243 |
* This is so that lines which are covered by areas are not included |
| 244 |
* explicitly, and similarly for points. |
| 245 |
*/ |
| 246 |
findResultAreaEdges(opCode); |
| 247 |
cancelDuplicateResultEdges(); |
| 248 |
|
| 249 |
PolygonBuilder polyBuilder = new PolygonBuilder(geomFact); |
| 250 |
polyBuilder.add(graph); |
| 251 |
resultPolyList = polyBuilder.getPolygons(); |
| 252 |
|
| 253 |
LineBuilder lineBuilder = new LineBuilder(this, geomFact, ptLocator); |
| 254 |
resultLineList = lineBuilder.build(opCode); |
| 255 |
|
| 256 |
PointBuilder pointBuilder = new PointBuilder(this, geomFact, ptLocator); |
| 257 |
resultPointList = pointBuilder.build(opCode); |
| 258 |
|
| 259 |
|
| 260 |
resultGeom = computeGeometry(resultPointList, resultLineList, resultPolyList, opCode); |
| 261 |
} |
| 262 |
|
| 263 |
private void insertUniqueEdges(List edges) |
| 264 |
{ |
| 265 |
for (Iterator i = edges.iterator(); i.hasNext(); ) { |
| 266 |
Edge e = (Edge) i.next(); |
| 267 |
insertUniqueEdge(e); |
| 268 |
} |
| 269 |
} |
| 270 |
/** |
| 271 |
* Insert an edge from one of the noded input graphs. |
| 272 |
* Checks edges that are inserted to see if an |
| 273 |
* identical edge already exists. |
| 274 |
* If so, the edge is not inserted, but its label is merged |
| 275 |
* with the existing edge. |
| 276 |
*/ |
| 277 |
protected void insertUniqueEdge(Edge e) |
| 278 |
{ |
| 279 |
|
| 280 |
|
| 281 |
Edge existingEdge = edgeList.findEqualEdge(e); |
| 282 |
|
| 283 |
|
| 284 |
if (existingEdge != null) { |
| 285 |
Label existingLabel = existingEdge.getLabel(); |
| 286 |
|
| 287 |
Label labelToMerge = e.getLabel(); |
| 288 |
|
| 289 |
|
| 290 |
if (! existingEdge.isPointwiseEqual(e)) { |
| 291 |
labelToMerge = new Label(e.getLabel()); |
| 292 |
labelToMerge.flip(); |
| 293 |
} |
| 294 |
Depth depth = existingEdge.getDepth(); |
| 295 |
|
| 296 |
|
| 297 |
if (depth.isNull()) { |
| 298 |
depth.add(existingLabel); |
| 299 |
} |
| 300 |
|
| 301 |
depth.add(labelToMerge); |
| 302 |
existingLabel.merge(labelToMerge); |
| 303 |
|
| 304 |
|
| 305 |
|
| 306 |
} |
| 307 |
else { |
| 308 |
|
| 309 |
|
| 310 |
|
| 311 |
edgeList.add(e); |
| 312 |
} |
| 313 |
} |
| 314 |
|
| 315 |
/** |
| 316 |
* If either of the GeometryLocations for the existing label is |
| 317 |
* exactly opposite to the one in the labelToMerge, |
| 318 |
* this indicates a dimensional collapse has happened. |
| 319 |
* In this case, convert the label for that Geometry to a Line label |
| 320 |
*/ |
| 321 |
|
| 322 |
|
| 323 |
|
| 324 |
|
| 325 |
|
| 326 |
|
| 327 |
|
| 328 |
|
| 329 |
|
| 330 |
|
| 331 |
|
| 332 |
|
| 333 |
|
| 334 |
|
| 335 |
|
| 336 |
/** |
| 337 |
* Update the labels for edges according to their depths. |
| 338 |
* For each edge, the depths are first normalized. |
| 339 |
* Then, if the depths for the edge are equal, |
| 340 |
* this edge must have collapsed into a line edge. |
| 341 |
* If the depths are not equal, update the label |
| 342 |
* with the locations corresponding to the depths |
| 343 |
* (i.e. a depth of 0 corresponds to a Location of EXTERIOR, |
| 344 |
* a depth of 1 corresponds to INTERIOR) |
| 345 |
*/ |
| 346 |
private void computeLabelsFromDepths() |
| 347 |
{ |
| 348 |
for (Iterator it = edgeList.iterator(); it.hasNext(); ) { |
| 349 |
Edge e = (Edge) it.next(); |
| 350 |
Label lbl = e.getLabel(); |
| 351 |
Depth depth = e.getDepth(); |
| 352 |
/** |
| 353 |
* Only check edges for which there were duplicates, |
| 354 |
* since these are the only ones which might |
| 355 |
* be the result of dimensional collapses. |
| 356 |
*/ |
| 357 |
if (! depth.isNull()) { |
| 358 |
depth.normalize(); |
| 359 |
for (int i = 0; i < 2; i++) { |
| 360 |
if (! lbl.isNull(i) && lbl.isArea() && ! depth.isNull(i)) { |
| 361 |
/** |
| 362 |
* if the depths are equal, this edge is the result of |
| 363 |
* the dimensional collapse of two or more edges. |
| 364 |
* It has the same location on both sides of the edge, |
| 365 |
* so it has collapsed to a line. |
| 366 |
*/ |
| 367 |
if (depth.getDelta(i) == 0) { |
| 368 |
lbl.toLine(i); |
| 369 |
} |
| 370 |
else { |
| 371 |
/** |
| 372 |
* This edge may be the result of a dimensional collapse, |
| 373 |
* but it still has different locations on both sides. The |
| 374 |
* label of the edge must be updated to reflect the resultant |
| 375 |
* side locations indicated by the depth values. |
| 376 |
*/ |
| 377 |
Assert.isTrue(! depth.isNull(i, Position.LEFT), "depth of LEFT side has not been initialized"); |
| 378 |
lbl.setLocation(i, Position.LEFT, depth.getLocation(i, Position.LEFT)); |
| 379 |
Assert.isTrue(! depth.isNull(i, Position.RIGHT), "depth of RIGHT side has not been initialized"); |
| 380 |
lbl.setLocation(i, Position.RIGHT, depth.getLocation(i, Position.RIGHT)); |
| 381 |
} |
| 382 |
} |
| 383 |
} |
| 384 |
} |
| 385 |
} |
| 386 |
} |
| 387 |
/** |
| 388 |
* If edges which have undergone dimensional collapse are found, |
| 389 |
* replace them with a new edge which is a L edge |
| 390 |
*/ |
| 391 |
private void replaceCollapsedEdges() |
| 392 |
{ |
| 393 |
List newEdges = new ArrayList(); |
| 394 |
for (Iterator it = edgeList.iterator(); it.hasNext(); ) { |
| 395 |
Edge e = (Edge) it.next(); |
| 396 |
if (e.isCollapsed()) { |
| 397 |
|
| 398 |
it.remove(); |
| 399 |
newEdges.add(e.getCollapsedEdge()); |
| 400 |
} |
| 401 |
} |
| 402 |
edgeList.addAll(newEdges); |
| 403 |
} |
| 404 |
/** |
| 405 |
* Copy all nodes from an arg geometry into this graph. |
| 406 |
* The node label in the arg geometry overrides any previously computed |
| 407 |
* label for that argIndex. |
| 408 |
* (E.g. a node may be an intersection node with |
| 409 |
* a previously computed label of BOUNDARY, |
| 410 |
* but in the original arg Geometry it is actually |
| 411 |
* in the interior due to the Boundary Determination Rule) |
| 412 |
*/ |
| 413 |
private void copyPoints(int argIndex) |
| 414 |
{ |
| 415 |
for (Iterator i = arg[argIndex].getNodeIterator(); i.hasNext(); ) { |
| 416 |
Node graphNode = (Node) i.next(); |
| 417 |
Node newNode = graph.addNode(graphNode.getCoordinate()); |
| 418 |
newNode.setLabel(argIndex, graphNode.getLabel().getLocation(argIndex)); |
| 419 |
} |
| 420 |
} |
| 421 |
|
| 422 |
/** |
| 423 |
* Compute initial labelling for all DirectedEdges at each node. |
| 424 |
* In this step, DirectedEdges will acquire a complete labelling |
| 425 |
* (i.e. one with labels for both Geometries) |
| 426 |
* only if they |
| 427 |
* are incident on a node which has edges for both Geometries |
| 428 |
*/ |
| 429 |
private void computeLabelling() |
| 430 |
{ |
| 431 |
for (Iterator nodeit = graph.getNodes().iterator(); nodeit.hasNext(); ) { |
| 432 |
Node node = (Node) nodeit.next(); |
| 433 |
|
| 434 |
node.getEdges().computeLabelling(arg); |
| 435 |
} |
| 436 |
mergeSymLabels(); |
| 437 |
updateNodeLabelling(); |
| 438 |
} |
| 439 |
/** |
| 440 |
* For nodes which have edges from only one Geometry incident on them, |
| 441 |
* the previous step will have left their dirEdges with no labelling for the other |
| 442 |
* Geometry. However, the sym dirEdge may have a labelling for the other |
| 443 |
* Geometry, so merge the two labels. |
| 444 |
*/ |
| 445 |
private void mergeSymLabels() |
| 446 |
{ |
| 447 |
for (Iterator nodeit = graph.getNodes().iterator(); nodeit.hasNext(); ) { |
| 448 |
Node node = (Node) nodeit.next(); |
| 449 |
((DirectedEdgeStar) node.getEdges()).mergeSymLabels(); |
| 450 |
|
| 451 |
} |
| 452 |
} |
| 453 |
private void updateNodeLabelling() |
| 454 |
{ |
| 455 |
|
| 456 |
|
| 457 |
|
| 458 |
|
| 459 |
for (Iterator nodeit = graph.getNodes().iterator(); nodeit.hasNext(); ) { |
| 460 |
Node node = (Node) nodeit.next(); |
| 461 |
Label lbl = ((DirectedEdgeStar) node.getEdges()).getLabel(); |
| 462 |
node.getLabel().merge(lbl); |
| 463 |
} |
| 464 |
} |
| 465 |
|
| 466 |
/** |
| 467 |
* Incomplete nodes are nodes whose labels are incomplete. |
| 468 |
* (e.g. the location for one Geometry is null). |
| 469 |
* These are either isolated nodes, |
| 470 |
* or nodes which have edges from only a single Geometry incident on them. |
| 471 |
* |
| 472 |
* Isolated nodes are found because nodes in one graph which don't intersect |
| 473 |
* nodes in the other are not completely labelled by the initial process |
| 474 |
* of adding nodes to the nodeList. |
| 475 |
* To complete the labelling we need to check for nodes that lie in the |
| 476 |
* interior of edges, and in the interior of areas. |
| 477 |
* <p> |
| 478 |
* When each node labelling is completed, the labelling of the incident |
| 479 |
* edges is updated, to complete their labelling as well. |
| 480 |
*/ |
| 481 |
private void labelIncompleteNodes() |
| 482 |
{ |
| 483 |
|
| 484 |
for (Iterator ni = graph.getNodes().iterator(); ni.hasNext(); ) { |
| 485 |
Node n = (Node) ni.next(); |
| 486 |
Label label = n.getLabel(); |
| 487 |
if (n.isIsolated()) { |
| 488 |
|
| 489 |
if (label.isNull(0)) |
| 490 |
labelIncompleteNode(n, 0); |
| 491 |
else |
| 492 |
labelIncompleteNode(n, 1); |
| 493 |
} |
| 494 |
|
| 495 |
((DirectedEdgeStar) n.getEdges()).updateLabelling(label); |
| 496 |
|
| 497 |
} |
| 498 |
|
| 499 |
|
| 500 |
|
| 501 |
|
| 502 |
|
| 503 |
|
| 504 |
|
| 505 |
} |
| 506 |
|
| 507 |
/** |
| 508 |
* Label an isolated node with its relationship to the target geometry. |
| 509 |
*/ |
| 510 |
private void labelIncompleteNode(Node n, int targetIndex) |
| 511 |
{ |
| 512 |
int loc = ptLocator.locate(n.getCoordinate(), arg[targetIndex].getGeometry()); |
| 513 |
|
| 514 |
|
| 515 |
|
| 516 |
n.getLabel().setLocation(targetIndex, loc); |
| 517 |
} |
| 518 |
|
| 519 |
/** |
| 520 |
* Find all edges whose label indicates that they are in the result area(s), |
| 521 |
* according to the operation being performed. Since we want polygon shells to be |
| 522 |
* oriented CW, choose dirEdges with the interior of the result on the RHS. |
| 523 |
* Mark them as being in the result. |
| 524 |
* Interior Area edges are the result of dimensional collapses. |
| 525 |
* They do not form part of the result area boundary. |
| 526 |
*/ |
| 527 |
private void findResultAreaEdges(int opCode) |
| 528 |
{ |
| 529 |
for (Iterator it = graph.getEdgeEnds().iterator(); it.hasNext(); ) { |
| 530 |
DirectedEdge de = (DirectedEdge) it.next(); |
| 531 |
|
| 532 |
Label label = de.getLabel(); |
| 533 |
if (label.isArea() |
| 534 |
&& ! de.isInteriorAreaEdge() |
| 535 |
&& isResultOfOp( |
| 536 |
label.getLocation(0, Position.RIGHT), |
| 537 |
label.getLocation(1, Position.RIGHT), |
| 538 |
opCode)) { |
| 539 |
de.setInResult(true); |
| 540 |
|
| 541 |
} |
| 542 |
} |
| 543 |
} |
| 544 |
/** |
| 545 |
* If both a dirEdge and its sym are marked as being in the result, cancel |
| 546 |
* them out. |
| 547 |
*/ |
| 548 |
private void cancelDuplicateResultEdges() |
| 549 |
{ |
| 550 |
|
| 551 |
|
| 552 |
for (Iterator it = graph.getEdgeEnds().iterator(); it.hasNext(); ) { |
| 553 |
DirectedEdge de = (DirectedEdge) it.next(); |
| 554 |
DirectedEdge sym = de.getSym(); |
| 555 |
if (de.isInResult() && sym.isInResult()) { |
| 556 |
de.setInResult(false); |
| 557 |
sym.setInResult(false); |
| 558 |
|
| 559 |
} |
| 560 |
} |
| 561 |
} |
| 562 |
/** |
| 563 |
* Tests if a point node should be included in the result or not. |
| 564 |
* |
| 565 |
* @param coord the point coordinate |
| 566 |
* @return true if the coordinate point is covered by a result Line or Area geometry |
| 567 |
*/ |
| 568 |
public boolean isCoveredByLA(Coordinate coord) |
| 569 |
{ |
| 570 |
if (isCovered(coord, resultLineList)) return true; |
| 571 |
if (isCovered(coord, resultPolyList)) return true; |
| 572 |
return false; |
| 573 |
} |
| 574 |
/** |
| 575 |
* Tests if an L edge should be included in the result or not. |
| 576 |
* |
| 577 |
* @param coord the point coordinate |
| 578 |
* @return true if the coordinate point is covered by a result Area geometry |
| 579 |
*/ |
| 580 |
public boolean isCoveredByA(Coordinate coord) |
| 581 |
{ |
| 582 |
if (isCovered(coord, resultPolyList)) return true; |
| 583 |
return false; |
| 584 |
} |
| 585 |
/** |
| 586 |
* @return true if the coord is located in the interior or boundary of |
| 587 |
* a geometry in the list. |
| 588 |
*/ |
| 589 |
private boolean isCovered(Coordinate coord, List geomList) |
| 590 |
{ |
| 591 |
for (Iterator it = geomList.iterator(); it.hasNext(); ) { |
| 592 |
Geometry geom = (Geometry) it.next(); |
| 593 |
int loc = ptLocator.locate(coord, geom); |
| 594 |
if (loc != Location.EXTERIOR) return true; |
| 595 |
} |
| 596 |
return false; |
| 597 |
} |
| 598 |
|
| 599 |
private Geometry computeGeometry( List resultPointList, |
| 600 |
List resultLineList, |
| 601 |
List resultPolyList, |
| 602 |
int opcode) |
| 603 |
{ |
| 604 |
List geomList = new ArrayList(); |
| 605 |
|
| 606 |
geomList.addAll(resultPointList); |
| 607 |
geomList.addAll(resultLineList); |
| 608 |
geomList.addAll(resultPolyList); |
| 609 |
|
| 610 |
|
| 611 |
if (geomList.isEmpty()) |
| 612 |
return createEmptyResult(opcode, arg[0].getGeometry(), arg[1].getGeometry(), geomFact); |
| 613 |
|
| 614 |
|
| 615 |
|
| 616 |
return geomFact.buildGeometry(geomList); |
| 617 |
} |
| 618 |
|
| 619 |
/** |
| 620 |
* Creates an empty result geometry of the appropriate dimension, |
| 621 |
* based on the given overlay operation and the dimensions of the inputs. |
| 622 |
* The created geometry is always an atomic geometry, |
| 623 |
* not a collection. |
| 624 |
* <p> |
| 625 |
* The empty result is constructed using the following rules: |
| 626 |
* <ul> |
| 627 |
* <li>{@link #INTERSECTION} - result has the dimension of the lowest input dimension |
| 628 |
* <li>{@link #UNION} - result has the dimension of the highest input dimension |
| 629 |
* <li>{@link #DIFFERENCE} - result has the dimension of the left-hand input |
| 630 |
* <li>{@link #SYMDIFFERENCE} - result has the dimension of the highest input dimension |
| 631 |
* (since the symmetric Difference is the union of the differences). |
| 632 |
* </ul> |
| 633 |
* |
| 634 |
* @param overlayOpCode the code for the overlay operation being performed |
| 635 |
* @param a an input geometry |
| 636 |
* @param b an input geometry |
| 637 |
* @param geomFact the geometry factory being used for the operation |
| 638 |
* @return an empty atomic geometry of the appropriate dimension |
| 639 |
*/ |
| 640 |
public static Geometry createEmptyResult(int overlayOpCode, Geometry a, Geometry b, GeometryFactory geomFact) |
| 641 |
{ |
| 642 |
Geometry result = null; |
| 643 |
int resultDim = resultDimension(overlayOpCode, a, b); |
| 644 |
|
| 645 |
/** |
| 646 |
* Handles resultSDim = -1, although should not happen |
| 647 |
*/ |
| 648 |
return result = geomFact.createEmpty(resultDim); |
| 649 |
} |
| 650 |
|
| 651 |
private static int resultDimension(int opCode, Geometry g0, Geometry g1) |
| 652 |
{ |
| 653 |
int dim0 = g0.getDimension(); |
| 654 |
int dim1 = g1.getDimension(); |
| 655 |
|
| 656 |
int resultDimension = -1; |
| 657 |
switch (opCode) { |
| 658 |
case INTERSECTION: |
| 659 |
resultDimension = Math.min(dim0, dim1); |
| 660 |
break; |
| 661 |
case UNION: |
| 662 |
resultDimension = Math.max(dim0, dim1); |
| 663 |
break; |
| 664 |
case DIFFERENCE: |
| 665 |
resultDimension = dim0; |
| 666 |
break; |
| 667 |
case SYMDIFFERENCE: |
| 668 |
/** |
| 669 |
* This result is chosen because |
| 670 |
* <pre> |
| 671 |
* SymDiff = Union(Diff(A, B), Diff(B, A) |
| 672 |
* </pre> |
| 673 |
* and Union has the dimension of the highest-dimension argument. |
| 674 |
*/ |
| 675 |
resultDimension = Math.max(dim0, dim1); |
| 676 |
break; |
| 677 |
} |
| 678 |
return resultDimension; |
| 679 |
} |
| 680 |
} |
| 681 |
|