1 package org.locationtech.jts.operation.overlayng;
2 
3 import java.util.ArrayList;
4 import java.util.List;
5 
6 import org.locationtech.jts.geom.Coordinate;
7 import org.locationtech.jts.geom.CoordinateList;
8 import org.locationtech.jts.geom.GeometryFactory;
9 import org.locationtech.jts.geom.TopologyException;
10 import org.locationtech.jts.io.WKTWriter;
11 import org.locationtech.jts.util.Assert;
12 
13 
14 class MaximalEdgeRing {
15 
16   private static final int STATE_FIND_INCOMING = 1;
17   private static final int STATE_LINK_OUTGOING = 2;
18 
19   /**
20    * Traverses the star of edges originating at a node
21    * and links consecutive result edges together
22    * into <b>maximal</b> edge rings.
23    * To link two edges the <code>resultNextMax</code> pointer
24    * for an <b>incoming</b> result edge
25    * is set to the next <b>outgoing</b> result edge.
26    * <p>
27    * Edges are linked when:
28    * <ul>
29    * <li>they belong to an area (i.e. they have sides)
30    * <li>they are marked as being in the result
31    * </ul>
32    * <p>
33    * Edges are linked in CCW order
34    * (which is the order they are linked in the underlying graph).
35    * This means that rings have their face on the Right
36    * (in other words,
37    * the topological location of the face is given by the RHS label of the DirectedEdge).
38    * This produces rings with CW orientation.
39    * <p>
40    * PRECONDITIONS:
41    * - This edge is in the result
42    * - This edge is not yet linked
43    * - The edge and its sym are NOT both marked as being in the result
44    */
linkResultAreaMaxRingAtNode(OverlayEdge nodeEdge)45   public static void linkResultAreaMaxRingAtNode(OverlayEdge nodeEdge)
46   {
47     Assert.isTrue(nodeEdge.isInResultArea(), "Attempt to link non-result edge");
48     // assertion is only valid if building a polygonal geometry (ie not a coverage)
49     //Assert.isTrue(! nodeEdge.symOE().isInResultArea(), "Found both half-edges in result");
50 
51     /**
52      * Since the node edge is an out-edge,
53      * make it the last edge to be linked
54      * by starting at the next edge.
55      * The node edge cannot be an in-edge as well,
56      * but the next one may be the first in-edge.
57      */
58     OverlayEdge endOut = nodeEdge.oNextOE();
59     OverlayEdge currOut = endOut;
60 //Debug.println("\n------  Linking node MAX edges");
61 //Debug.println("BEFORE: " + toString(nodeEdge));
62     int state = STATE_FIND_INCOMING;
63     OverlayEdge currResultIn = null;
64     do {
65       /**
66        * If an edge is linked this node has already been processed
67        * so can skip further processing
68        */
69       if (currResultIn != null && currResultIn.isResultMaxLinked())
70         return;
71 
72       switch (state) {
73       case STATE_FIND_INCOMING:
74         OverlayEdge currIn = currOut.symOE();
75         if (! currIn.isInResultArea()) break;
76         currResultIn = currIn;
77         state = STATE_LINK_OUTGOING;
78         //Debug.println("Found result in-edge:  " + currResultIn);
79         break;
80       case STATE_LINK_OUTGOING:
81         if (! currOut.isInResultArea()) break;
82         // link the in edge to the out edge
83         currResultIn.setNextResultMax(currOut);
84         state = STATE_FIND_INCOMING;
85         //Debug.println("Linked Max edge:  " + currResultIn + " -> " + currOut);
86         break;
87       }
88       currOut = currOut.oNextOE();
89     } while (currOut != endOut);
90     //Debug.println("AFTER: " + toString(nodeEdge));
91     if (state == STATE_LINK_OUTGOING) {
92 //Debug.print(firstOut == null, this);
93       throw new TopologyException("no outgoing edge found", nodeEdge.getCoordinate());
94     }
95   }
96 
97   private OverlayEdge startEdge;
98 
MaximalEdgeRing(OverlayEdge e)99   public MaximalEdgeRing(OverlayEdge e) {
100     this.startEdge = e;
101     attachEdges(e);
102   }
103 
attachEdges(OverlayEdge startEdge)104   private void attachEdges(OverlayEdge startEdge) {
105     OverlayEdge edge = startEdge;
106     do {
107       if (edge == null)
108         throw new TopologyException("Ring edge is null");
109       if (edge.getEdgeRingMax() == this)
110         throw new TopologyException("Ring edge visited twice at " + edge.getCoordinate(), edge.getCoordinate());
111       if (edge.nextResultMax() == null) {
112         throw new TopologyException("Ring edge missing at", edge.dest());
113       }
114       edge.setEdgeRingMax(this);
115       edge = edge.nextResultMax();
116     } while (edge != startEdge);
117   }
118 
buildMinimalRings(GeometryFactory geometryFactory)119   public List<OverlayEdgeRing> buildMinimalRings(GeometryFactory geometryFactory)
120   {
121     linkMinimalRings();
122 
123     List<OverlayEdgeRing> minEdgeRings = new ArrayList<OverlayEdgeRing>();
124     OverlayEdge e = startEdge;
125     do {
126       if (e.getEdgeRing() == null) {
127         OverlayEdgeRing minEr = new OverlayEdgeRing(e, geometryFactory);
128         minEdgeRings.add(minEr);
129       }
130       e = e.nextResultMax();
131     } while (e != startEdge);
132     return minEdgeRings;
133   }
134 
linkMinimalRings()135   private void linkMinimalRings() {
136     OverlayEdge e = startEdge;
137     do {
138       linkMinRingEdgesAtNode(e, this);
139       e = e.nextResultMax();
140     } while (e != startEdge);
141   }
142 
143   /**
144    * Links the edges of a {@link MaximalEdgeRing} around this node
145    * into minimal edge rings ({@link OverlayEdgeRing}s).
146    * Minimal ring edges are linked in the opposite orientation (CW)
147    * to the maximal ring.
148    * This changes self-touching rings into a two or more separate rings,
149    * as per the OGC SFS polygon topology semantics.
150    * This relinking must be done to each max ring separately,
151    * rather than all the node result edges, since there may be
152    * more than one max ring incident at the node.
153    *
154    * @param nodeEdge an edge originating at this node
155    * @param maxRing the maximal ring to link
156    */
linkMinRingEdgesAtNode(OverlayEdge nodeEdge, MaximalEdgeRing maxRing)157   private static void linkMinRingEdgesAtNode(OverlayEdge nodeEdge, MaximalEdgeRing maxRing)
158   {
159     //Assert.isTrue(nodeEdge.isInResult(), "Attempt to link non-result edge");
160 
161     /**
162      * The node edge is an out-edge,
163      * so it is the first edge linked
164      * with the next CCW in-edge
165      */
166     OverlayEdge endOut = nodeEdge;
167     OverlayEdge currMaxRingOut = endOut;
168     OverlayEdge currOut = endOut.oNextOE();
169 //Debug.println("\n------  Linking node MIN ring edges");
170 //Debug.println("BEFORE: " + toString(nodeEdge));
171     do {
172       if (isAlreadyLinked(currOut.symOE(), maxRing))
173         return;
174 
175       if (currMaxRingOut == null) {
176         currMaxRingOut = selectMaxOutEdge(currOut, maxRing);
177       }
178       else {
179         currMaxRingOut = linkMaxInEdge(currOut, currMaxRingOut, maxRing);
180       }
181       currOut = currOut.oNextOE();
182     } while (currOut != endOut);
183     //Debug.println("AFTER: " + toString(nodeEdge));
184     if ( currMaxRingOut != null ) {
185       throw new TopologyException("Unmatched edge found during min-ring linking", nodeEdge.getCoordinate());
186     }
187   }
188 
189   /**
190    * Tests if an edge of the maximal edge ring is already linked into
191    * a minimal {@link OverlayEdgeRing}.  If so, this node has already been processed
192    * earlier in the maximal edgering linking scan.
193    *
194    * @param edge an edge of a maximal edgering
195    * @param maxRing the maximal edgering
196    * @return true if the edge has already been linked into a minimal edgering.
197    */
isAlreadyLinked(OverlayEdge edge, MaximalEdgeRing maxRing)198   private static boolean isAlreadyLinked(OverlayEdge edge, MaximalEdgeRing maxRing) {
199     boolean isLinked = edge.getEdgeRingMax() == maxRing
200         && edge.isResultLinked();
201     return isLinked;
202   }
203 
selectMaxOutEdge(OverlayEdge currOut, MaximalEdgeRing maxEdgeRing)204   private static OverlayEdge selectMaxOutEdge(OverlayEdge currOut, MaximalEdgeRing maxEdgeRing) {
205     // select if currOut edge is part of this max ring
206     if (currOut.getEdgeRingMax() ==  maxEdgeRing)
207       return currOut;
208     // otherwise skip this edge
209     return null;
210   }
211 
linkMaxInEdge(OverlayEdge currOut, OverlayEdge currMaxRingOut, MaximalEdgeRing maxEdgeRing)212   private static OverlayEdge linkMaxInEdge(OverlayEdge currOut,
213       OverlayEdge currMaxRingOut,
214       MaximalEdgeRing maxEdgeRing)
215   {
216     OverlayEdge currIn = currOut.symOE();
217     // currIn is not in this max-edgering, so keep looking
218     if (currIn.getEdgeRingMax() !=  maxEdgeRing)
219       return currMaxRingOut;
220 
221     //Debug.println("Found result in-edge:  " + currIn);
222 
223     currIn.setNextResult(currMaxRingOut);
224     //Debug.println("Linked Min Edge:  " + currIn + " -> " + currMaxRingOut);
225     // return null to indicate to scan for the next max-ring out-edge
226     return null;
227   }
228 
toString()229   public String toString() {
230     Coordinate[] pts = getCoordinates();
231     return WKTWriter.toLineString(pts);
232   }
233 
getCoordinates()234   private Coordinate[] getCoordinates() {
235     CoordinateList coords = new CoordinateList();
236     OverlayEdge edge = startEdge;
237     do {
238       coords.add(edge.orig());
239       if (edge.nextResultMax() == null) {
240         break;
241       }
242       edge = edge.nextResultMax();
243     } while (edge != startEdge);
244     // add last coordinate
245     coords.add(edge.dest());
246     return coords.toCoordinateArray();
247   }
248 }
249