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