1 /*
2  * Copyright (c) 2019 Martin Davis.
3  *
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
7  * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
8  * and the Eclipse Distribution License is available at
9  *
10  * http://www.eclipse.org/org/documents/edl-v10.php.
11  */
12 package org.locationtech.jts.operation.overlayng;
13 
14 import java.util.Collection;
15 import java.util.List;
16 
17 import org.locationtech.jts.geom.Envelope;
18 import org.locationtech.jts.geom.Geometry;
19 import org.locationtech.jts.geom.GeometryFactory;
20 import org.locationtech.jts.geom.LineString;
21 import org.locationtech.jts.geom.Location;
22 import org.locationtech.jts.geom.Point;
23 import org.locationtech.jts.geom.Polygon;
24 import org.locationtech.jts.geom.PrecisionModel;
25 import org.locationtech.jts.geom.TopologyException;
26 import org.locationtech.jts.geomgraph.Label;
27 import org.locationtech.jts.noding.MCIndexNoder;
28 import org.locationtech.jts.noding.Noder;
29 import org.locationtech.jts.noding.snap.SnappingNoder;
30 import org.locationtech.jts.noding.snapround.SnapRoundingNoder;
31 import org.locationtech.jts.operation.overlay.OverlayOp;
32 
33 /**
34  * Computes the geometric overlay of two {@link Geometry}s,
35  * using an explicit precision model to allow robust computation.
36  * The overlay can be used to determine any of the
37  * following set-theoretic operations (boolean combinations) of the geometries:
38  * <ul>
39  * <li>{@link INTERSECTION} - all points which lie in both geometries
40  * <li>{@link UNION} - all points which lie in at least one geometry
41  * <li>{@link DIFFERENCE} - all points which lie in the first geometry but not the second
42  * <li>{@link SYMDIFFERENCE} - all points which lie in one geometry but not both
43  * </ul>
44  * Input geometries may have different dimension.
45  * Input collections must be homogeneous (all elements must have the same dimension).
46  * <p>
47  * The precision model used for the computation can be supplied
48  * independent of the precision model of the input geometry.
49  * The main use for this is to allow using a fixed precision
50  * for geometry with a floating precision model.
51  * This does two things: ensures robust computation;
52  * and forces the output to be validly rounded to the precision model.
53  * <p>
54  * For fixed precision models noding is performed using a {@link SnapRoundingNoder}.
55  * This provides robust computation (as long as precision is limited to
56  * around 13 decimal digits).
57  * <p>
58  * For floating precision an {@link MCIndexNoder} is used.
59  * This is not fully robust, so can sometimes result in
60  * {@link TopologyException}s being thrown.
61  * For robust full-precision overlay see {@link OverlayNGRobust}.
62  * <p>
63  * A custom {@link Noder} can be supplied.
64  * This allows using a more performant noding strategy in specific cases,
65  * for instance in {@link CoverageUnion}.
66  * <p>
67  * <b>Note:</b If a {@link SnappingNoder} is used
68  * it is best to specify a fairly small snap tolerance,
69  * since the intersection clipping optimization can
70  * interact with the snapping to alter the result.
71  * <p>
72  * Optionally the overlay computation can process using strict mode
73  * (via {@link #setStrictMode(boolean)}.
74  * In strict mode result semantics are:
75  * <ul>
76  * <li>Lines and Points resulting from topology collapses are not included in the result</li>
77  * <li>Result geometry is homogeneous
78  *     for the {@link #INTERSECTION} and {@link #DIFFERENCE} operations.
79  * <li>Result geometry is homogeneous
80  *     for the {@link #UNION} and {@link #SYMDIFFERENCE} operations
81  *     if the inputs have the same dimension</li>
82  * </ul>
83  * Strict mode has the following benefits:
84  * <ul>
85  * <li>Results are simpler</li>
86  * <li>Overlay operations are chainable
87  *     without needing to remove lower-dimension elements</li>
88  * </ul>
89  * The original JTS overlay semantics corresponds to non-strict mode.
90  * <p>
91  * If a robustness error occurs, a {@link TopologyException} is thrown.
92  * These are usually caused by numerical rounding causing the noding output
93  * to not be fully noded.
94  * For robust computation with full-precision {@link OverlayNGRobust} can be used.
95  *
96  * @author mdavis
97  *
98  * @see OverlayNGRobust
99  *
100  */
101 public class OverlayNG
102 {
103   /**
104    * The code for the Intersection overlay operation.
105    */
106   public static final int INTERSECTION  = OverlayOp.INTERSECTION;
107 
108   /**
109    * The code for the Union overlay operation.
110    */
111   public static final int UNION         = OverlayOp.UNION;
112 
113   /**
114    *  The code for the Difference overlay operation.
115    */
116   public static final int DIFFERENCE    = OverlayOp.DIFFERENCE;
117 
118   /**
119    *  The code for the Symmetric Difference overlay operation.
120    */
121   public static final int SYMDIFFERENCE = OverlayOp.SYMDIFFERENCE;
122 
123   /**
124    * The default setting for Strict Mode.
125    *
126    * The original JTS overlay semantics used non-strict result
127    * semantics, including;
128    * - An Intersection result can be mixed-dimension,
129    *   due to inclusion of intersection components of all dimensions
130    * - Results can include lines caused by Area topology collapse
131    */
132   static final boolean STRICT_MODE_DEFAULT = false;
133 
134   /**
135    * Tests whether a point with a given topological {@link Label}
136    * relative to two geometries is contained in
137    * the result of overlaying the geometries using
138    * a given overlay operation.
139    * <p>
140    * The method handles arguments of {@link Location#NONE} correctly
141    *
142    * @param label the topological label of the point
143    * @param opCode the code for the overlay operation to test
144    * @return true if the label locations correspond to the overlayOpCode
145    */
isResultOfOpPoint(OverlayLabel label, int opCode)146   static boolean isResultOfOpPoint(OverlayLabel label, int opCode)
147   {
148     int loc0 = label.getLocation(0);
149     int loc1 = label.getLocation(1);
150     return isResultOfOp(opCode, loc0, loc1);
151   }
152 
153   /**
154    * Tests whether a point with given {@link Location}s
155    * relative to two geometries would be contained in
156    * the result of overlaying the geometries using
157    * a given overlay operation.
158    * This is used to determine whether components
159    * computed during the overlay process should be
160    * included in the result geometry.
161    * <p>
162    * The method handles arguments of {@link Location#NONE} correctly.
163    *
164    * @param overlayOpCode the code for the overlay operation to test
165    * @param loc0 the code for the location in the first geometry
166    * @param loc1 the code for the location in the second geometry
167    *
168    * @return true if a point with given locations is in the result of the overlay operation
169    */
isResultOfOp(int overlayOpCode, int loc0, int loc1)170   static boolean isResultOfOp(int overlayOpCode, int loc0, int loc1)
171   {
172     if (loc0 == Location.BOUNDARY) loc0 = Location.INTERIOR;
173     if (loc1 == Location.BOUNDARY) loc1 = Location.INTERIOR;
174     switch (overlayOpCode) {
175     case INTERSECTION:
176       return loc0 == Location.INTERIOR
177           && loc1 == Location.INTERIOR;
178     case UNION:
179       return loc0 == Location.INTERIOR
180           || loc1 == Location.INTERIOR;
181     case DIFFERENCE:
182       return loc0 == Location.INTERIOR
183           && loc1 != Location.INTERIOR;
184     case SYMDIFFERENCE:
185       return   (     loc0 == Location.INTERIOR &&  loc1 != Location.INTERIOR)
186             || (     loc0 != Location.INTERIOR &&  loc1 == Location.INTERIOR);
187     }
188     return false;
189   }
190 
191   /**
192    * Computes an overlay operation for
193    * the given geometry operands, with the
194    * noding strategy determined by the precision model.
195    *
196    * @param geom0 the first geometry argument
197    * @param geom1 the second geometry argument
198    * @param opCode the code for the desired overlay operation
199    * @param pm the precision model to use
200    * @return the result of the overlay operation
201    */
overlay(Geometry geom0, Geometry geom1, int opCode, PrecisionModel pm)202   public static Geometry overlay(Geometry geom0, Geometry geom1,
203       int opCode, PrecisionModel pm)
204   {
205     OverlayNG ov = new OverlayNG(geom0, geom1, pm, opCode);
206     Geometry geomOv = ov.getResult();
207     return geomOv;
208   }
209 
210   /**
211    * Computes an overlay operation on the given geometry operands,
212    * using a supplied {@link Noder}.
213    *
214    * @param geom0 the first geometry argument
215    * @param geom1 the second geometry argument
216    * @param opCode the code for the desired overlay operation
217    * @param pm the precision model to use (which may be null if the noder does not use one)
218    * @param noder the noder to use
219    * @return the result of the overlay operation
220    */
overlay(Geometry geom0, Geometry geom1, int opCode, PrecisionModel pm, Noder noder)221   public static Geometry overlay(Geometry geom0, Geometry geom1,
222       int opCode, PrecisionModel pm, Noder noder)
223   {
224     OverlayNG ov = new OverlayNG(geom0, geom1, pm, opCode);
225     ov.setNoder(noder);
226     Geometry geomOv = ov.getResult();
227     return geomOv;
228   }
229 
230   /**
231    * Computes an overlay operation on the given geometry operands,
232    * using a supplied {@link Noder}.
233    *
234    * @param geom0 the first geometry argument
235    * @param geom1 the second geometry argument
236    * @param opCode the code for the desired overlay operation
237    * @param noder the noder to use
238    * @return the result of the overlay operation
239    */
overlay(Geometry geom0, Geometry geom1, int opCode, Noder noder)240   public static Geometry overlay(Geometry geom0, Geometry geom1,
241       int opCode, Noder noder)
242   {
243     OverlayNG ov = new OverlayNG(geom0, geom1, null, opCode);
244     ov.setNoder(noder);
245     Geometry geomOv = ov.getResult();
246     return geomOv;
247   }
248 
249   /**
250    * Computes an overlay operation on
251    * the given geometry operands,
252    * using the precision model of the geometry.
253    * and an appropriate noder.
254    * <p>
255    * The noder is chosen according to the precision model specified.
256    * <ul>
257    * <li>For {@link PrecisionModel#FIXED}
258    * a snap-rounding noder is used, and the computation is robust.
259    * <li>For {@link PrecisionModel#FLOATING}
260    * a non-snapping noder is used,
261    * and this computation may not be robust.
262    * If errors occur a {@link TopologyException} is thrown.
263    * </ul>
264    *
265    *
266    * @param geom0 the first argument geometry
267    * @param geom1 the second argument geometry
268    * @param opCode the code for the desired overlay operation
269    * @return the result of the overlay operation
270    */
overlay(Geometry geom0, Geometry geom1, int opCode)271   public static Geometry overlay(Geometry geom0, Geometry geom1, int opCode)
272   {
273     OverlayNG ov = new OverlayNG(geom0, geom1, opCode);
274     return ov.getResult();
275   }
276 
277   /**
278    * Computes a union operation on
279    * the given geometry, with the supplied precision model.
280    * <p>
281    * The input must be a valid geometry.
282    * Collections must be homogeneous.
283    * <p>
284    * To union an overlapping set of polygons in a more performant way use {@link UnaryUnionNG}.
285    * To union a polyonal coverage or linear network in a more performant way,
286    * use {@link CoverageUnion}.
287    *
288    * @param geom0 the geometry
289    * @param pm the precision model to use
290    * @return the result of the union operation
291    *
292    * @see OverlayMixedPoints
293    */
union(Geometry geom, PrecisionModel pm)294   static Geometry union(Geometry geom, PrecisionModel pm)
295   {
296     OverlayNG ov = new OverlayNG(geom, pm);
297     Geometry geomOv = ov.getResult();
298     return geomOv;
299   }
300 
301   /**
302    * Computes a union of a single geometry using a custom noder.
303    * <p>
304    * The primary use of this is to support coverage union.
305    * Because of this the overlay is performed using strict mode.
306    *
307    * @param geom the geometry to union
308    * @param pm the precision model to use (maybe be null)
309    * @param noder the noder to use
310    * @return the result geometry
311    *
312    * @see CoverageUnion
313    */
union(Geometry geom, PrecisionModel pm, Noder noder)314   static Geometry union(Geometry geom, PrecisionModel pm, Noder noder)
315   {
316     OverlayNG ov = new OverlayNG(geom, pm);
317     ov.setNoder(noder);
318     ov.setStrictMode(true);
319     Geometry geomOv = ov.getResult();
320     return geomOv;
321   }
322 
323   private int opCode;
324   private InputGeometry inputGeom;
325   private GeometryFactory geomFact;
326   private PrecisionModel pm;
327   private Noder noder;
328   private boolean isStrictMode = STRICT_MODE_DEFAULT;
329   private boolean isOptimized = true;
330   private boolean isAreaResultOnly = false;
331   private boolean isOutputEdges = false;
332   private boolean isOutputResultEdges = false;
333   private boolean isOutputNodedEdges = false;
334 
335   /**
336    * Creates an overlay operation on the given geometries,
337    * with a defined precision model.
338    * The noding strategy is determined by the precision model.
339    *
340    * @param geom0 the A operand geometry
341    * @param geom1 the B operand geometry (may be null)
342    * @param pm the precision model to use
343    * @param opCode the overlay opcode
344    */
OverlayNG(Geometry geom0, Geometry geom1, PrecisionModel pm, int opCode)345   public OverlayNG(Geometry geom0, Geometry geom1, PrecisionModel pm, int opCode) {
346     this.pm = pm;
347     this.opCode = opCode;
348     geomFact = geom0.getFactory();
349     inputGeom = new InputGeometry( geom0, geom1 );
350   }
351 
352   /**
353    * Creates an overlay operation on the given geometries
354    * using the precision model of the geometries.
355    * <p>
356    * The noder is chosen according to the precision model specified.
357    * <ul>
358    * <li>For {@link PrecisionModel#FIXED}
359    * a snap-rounding noder is used, and the computation is robust.
360    * <li>For {@link PrecisionModel#FLOATING}
361    * a non-snapping noder is used,
362    * and this computation may not be robust.
363    * If errors occur a {@link TopologyException} is thrown.
364    * </ul>
365    *
366    * @param geom0 the A operand geometry
367    * @param geom1 the B operand geometry (may be null)
368    * @param opCode the overlay opcode
369    */
OverlayNG(Geometry geom0, Geometry geom1, int opCode)370   public OverlayNG(Geometry geom0, Geometry geom1, int opCode) {
371     this(geom0, geom1, geom0.getFactory().getPrecisionModel(), opCode);
372   }
373 
374   /**
375    * Creates a union of a single geometry with a given precision model.
376    *
377    * @param geom the geometry
378    * @param pm the precision model to use
379    */
OverlayNG(Geometry geom, PrecisionModel pm)380   OverlayNG(Geometry geom, PrecisionModel pm) {
381     this(geom, null, pm, UNION);
382   }
383 
384   /**
385    * Sets whether the overlay results are computed according to strict mode
386    * semantics.
387    * <ul>
388    * <li>Lines resulting from topology collapse are not included
389    * <li>Result geometry is homogeneous
390    *     for the {@link #INTERSECTION} and {@link #DIFFERENCE} operations.
391    * <li>Result geometry is homogeneous
392    *     for the {@link #UNION} and {@link #SYMDIFFERENCE} operations
393    *     if the inputs have the same dimension
394    * </ul>
395    *
396    * @param isStrictMode true if strict mode is to be used
397    */
setStrictMode(boolean isStrictMode)398   public void setStrictMode(boolean isStrictMode) {
399     this.isStrictMode = isStrictMode;
400   }
401 
402   /**
403    * Sets whether overlay processing optimizations are enabled.
404    * It may be useful to disable optimizations
405    * for testing purposes.
406    * Default is TRUE (optimization enabled).
407    *
408    * @param isOptimized whether to optimize processing
409    */
setOptimized(boolean isOptimized)410   public void setOptimized(boolean isOptimized) {
411     this.isOptimized = isOptimized;
412   }
413 
414   /**
415    * Sets whether the result can contain only {@link Polygon} components.
416    * This is used if it is known that the result must be an (possibly empty) area.
417    *
418    * @param isAreaResultOnly true if the result should contain only area components
419    */
setAreaResultOnly(boolean isAreaResultOnly)420   void setAreaResultOnly(boolean isAreaResultOnly) {
421     this.isAreaResultOnly = isAreaResultOnly;
422   }
423 
424   //------ Testing options -------
425 
426   /**
427    *
428    * @param isOutputEdges
429    */
setOutputEdges(boolean isOutputEdges )430   public void setOutputEdges(boolean isOutputEdges ) {
431     this.isOutputEdges = isOutputEdges;
432   }
433 
setOutputNodedEdges(boolean isOutputNodedEdges )434   public void setOutputNodedEdges(boolean isOutputNodedEdges ) {
435     this.isOutputEdges = true;
436     this.isOutputNodedEdges = isOutputNodedEdges;
437   }
438 
setOutputResultEdges(boolean isOutputResultEdges )439   public void setOutputResultEdges(boolean isOutputResultEdges ) {
440     this.isOutputResultEdges = isOutputResultEdges;
441   }
442   //---------------------------------
443 
setNoder(Noder noder)444   void setNoder(Noder noder) {
445     this.noder = noder;
446   }
447 
448   /**
449    * Gets the result of the overlay operation.
450    *
451    * @return the result of the overlay operation.
452    *
453    * @throws IllegalArgumentException if the input is not supported (e.g. a mixed-dimension geometry)
454    * @throws TopologyException if a robustness error occurs
455    */
getResult()456   public Geometry getResult() {
457     // handle empty inputs which determine result
458     if (OverlayUtil.isEmptyResult(opCode,
459         inputGeom.getGeometry(0),
460         inputGeom.getGeometry(1),
461         pm)) {
462       return createEmptyResult();
463     }
464 
465     /**
466      * The elevation model is only computed if the input geometries have Z values.
467      */
468     ElevationModel elevModel = ElevationModel.create(inputGeom.getGeometry(0), inputGeom.getGeometry(1));
469     Geometry result;
470     if (inputGeom.isAllPoints()) {
471       // handle Point-Point inputs
472       result = OverlayPoints.overlay(opCode, inputGeom.getGeometry(0), inputGeom.getGeometry(1), pm);
473     }
474     else if (! inputGeom.isSingle() &&  inputGeom.hasPoints()) {
475       // handle Point-nonPoint inputs
476       result = OverlayMixedPoints.overlay(opCode, inputGeom.getGeometry(0), inputGeom.getGeometry(1), pm);
477     }
478     else {
479       // handle case where both inputs are formed of edges (Lines and Polygons)
480       result = computeEdgeOverlay();
481     }
482     /**
483      * This is a no-op if the elevation model was not computed due to Z not present
484      */
485     elevModel.populateZ(result);
486     return result;
487   }
488 
computeEdgeOverlay()489   private Geometry computeEdgeOverlay() {
490 
491     List<Edge> edges = nodeEdges();
492 
493     OverlayGraph graph = buildGraph(edges);
494 
495     if (isOutputNodedEdges) {
496       return OverlayUtil.toLines(graph, isOutputEdges, geomFact);
497     }
498 
499     labelGraph(graph);
500     //for (OverlayEdge e : graph.getEdges()) {  Debug.println(e);  }
501 
502     if (isOutputEdges || isOutputResultEdges) {
503       return  OverlayUtil.toLines(graph, isOutputEdges, geomFact);
504     }
505 
506     return extractResult(opCode, graph);
507   }
508 
nodeEdges()509   private List<Edge> nodeEdges() {
510     /**
511      * Node the edges, using whatever noder is being used
512      */
513     EdgeNodingBuilder nodingBuilder = new EdgeNodingBuilder(pm, noder);
514 
515     /**
516      * Optimize Intersection and Difference by clipping to the
517      * result extent, if enabled.
518      */
519     if ( isOptimized ) {
520       Envelope clipEnv = OverlayUtil.clippingEnvelope(opCode, inputGeom, pm);
521       if (clipEnv != null)
522         nodingBuilder.setClipEnvelope( clipEnv );
523     }
524 
525     List<Edge> mergedEdges = nodingBuilder.build(
526         inputGeom.getGeometry(0),
527         inputGeom.getGeometry(1));
528 
529     /**
530      * Record if an input geometry has collapsed.
531      * This is used to avoid trying to locate disconnected edges
532      * against a geometry which has collapsed completely.
533      */
534     inputGeom.setCollapsed(0, ! nodingBuilder.hasEdgesFor(0) );
535     inputGeom.setCollapsed(1, ! nodingBuilder.hasEdgesFor(1) );
536 
537     return mergedEdges;
538   }
539 
buildGraph(Collection<Edge> edges)540   private OverlayGraph buildGraph(Collection<Edge> edges) {
541     OverlayGraph graph = new OverlayGraph();
542     for (Edge e : edges) {
543       graph.addEdge(e.getCoordinates(), e.createLabel());
544     }
545     return graph;
546   }
547 
labelGraph(OverlayGraph graph)548   private void labelGraph(OverlayGraph graph) {
549     OverlayLabeller labeller = new OverlayLabeller(graph, inputGeom);
550     labeller.computeLabelling();
551     labeller.markResultAreaEdges(opCode);
552     labeller.unmarkDuplicateEdgesFromResultArea();
553   }
554 
555   /**
556    * Extracts the result geometry components from the fully labelled topology graph.
557    * <p>
558    * This method implements the semantic that the result of an
559    * intersection operation is homogeneous with highest dimension.
560    * In other words,
561    * if an intersection has components of a given dimension
562    * no lower-dimension components are output.
563    * For example, if two polygons intersect in an area,
564    * no linestrings or points are included in the result,
565    * even if portions of the input do meet in lines or points.
566    * This semantic choice makes more sense for typical usage,
567    * in which only the highest dimension components are of interest.
568    *
569    * @param opCode the overlay operation
570    * @param graph the topology graph
571    * @return the result geometry
572    */
extractResult(int opCode, OverlayGraph graph)573   private Geometry extractResult(int opCode, OverlayGraph graph) {
574     boolean isAllowMixedIntResult = ! isStrictMode;
575 
576     //--- Build polygons
577     List<OverlayEdge> resultAreaEdges = graph.getResultAreaEdges();
578     PolygonBuilder polyBuilder = new PolygonBuilder(resultAreaEdges, geomFact);
579     List<Polygon> resultPolyList = polyBuilder.getPolygons();
580     boolean hasResultAreaComponents = resultPolyList.size() > 0;
581 
582     List<LineString> resultLineList = null;
583     List<Point> resultPointList = null;
584 
585     if (! isAreaResultOnly) {
586       //--- Build lines
587       boolean allowResultLines = ! hasResultAreaComponents
588           || isAllowMixedIntResult
589           || opCode == SYMDIFFERENCE
590           || opCode == UNION;
591       if ( allowResultLines ) {
592         LineBuilder lineBuilder = new LineBuilder(inputGeom, graph, hasResultAreaComponents, opCode, geomFact);
593         lineBuilder.setStrictMode(isStrictMode);
594         resultLineList = lineBuilder.getLines();
595       }
596       /**
597        * Operations with point inputs are handled elsewhere.
598        * Only an Intersection op can produce point results
599        * from non-point inputs.
600        */
601       boolean hasResultComponents = hasResultAreaComponents || resultLineList.size() > 0;
602       boolean allowResultPoints = ! hasResultComponents || isAllowMixedIntResult;
603       if ( opCode == INTERSECTION && allowResultPoints ) {
604         IntersectionPointBuilder pointBuilder = new IntersectionPointBuilder(graph, geomFact);
605         pointBuilder.setStrictMode(isStrictMode);
606         resultPointList = pointBuilder.getPoints();
607       }
608     }
609 
610     if (isEmpty(resultPolyList)
611         && isEmpty(resultLineList)
612         && isEmpty(resultPointList))
613       return createEmptyResult();
614 
615     Geometry resultGeom = OverlayUtil.createResultGeometry(resultPolyList, resultLineList, resultPointList, geomFact);
616     return resultGeom;
617   }
618 
isEmpty(List list)619   private static boolean isEmpty(List list) {
620     return list == null || list.size() == 0;
621   }
622 
createEmptyResult()623   private Geometry createEmptyResult() {
624     return OverlayUtil.createEmptyResult(
625         OverlayUtil.resultDimension(opCode,
626             inputGeom.getDimension(0),
627             inputGeom.getDimension(1)),
628           geomFact);
629   }
630 
631 }
632 
633 
634