/// <summary> /// Traverses edges from edgeStart which /// lie in a single line (have degree = 2). /// <para/> /// The direction of the linework is preserved as far as possible. /// Specifically, the direction of the line is determined /// by the start edge direction. This implies /// that if all edges are reversed, the created line /// will be reversed to match. /// (Other more complex strategies would be possible. /// E.g. using the direction of the majority of segments, /// or preferring the direction of the A edges.) /// </summary> private LineString BuildLine(OverlayEdge node) { var pts = new CoordinateList(); pts.Add(node.Orig, false); bool isForward = node.IsForward; var e = node; do { e.MarkVisitedBoth(); e.AddCoordinates(pts); // end line if next vertex is a node if (DegreeOfLines(e.SymOE) != 2) { break; } e = NextLineEdgeUnvisited(e.SymOE); // e will be null if next edge has been visited, which indicates a ring }while (e != null); var ptsOut = pts.ToCoordinateArray(isForward); var line = _geometryFactory.CreateLineString(ptsOut); return(line); }
/// <summary> /// Determines the <see cref="Location"/> for an edge within an Area geometry /// via point-in-polygon location. /// <para/> /// NOTE this is only safe to use for disconnected edges, /// since the test is carried out against the original input geometry, /// and precision reduction may cause incorrect results for edges /// which are close enough to a boundary to become connected. /// </summary> /// <param name="geomIndex">The parent geometry index</param> /// <param name="edge">The edge to locate</param> /// <returns>The location of the edge.</returns> private Location LocateEdge(int geomIndex, OverlayEdge edge) { var loc = _inputGeometry.LocatePointInArea(geomIndex, edge.Orig); var edgeLoc = loc != Location.Exterior ? Location.Interior : Location.Exterior; return(edgeLoc); }
/// <summary> /// Tests if an edge of the maximal edge ring is already linked into /// a minimal <see cref="OverlayEdgeRing"/>. If so, this node has already been processed /// earlier in the maximal edgering linking scan. /// </summary> /// <param name="edge">An edge of a maximal edgering</param> /// <param name="maxRing">The maximal edgering</param> /// <returns><c>true</c> if the edge has already been linked into a minimal edgering.</returns> private static bool IsAlreadyLinked(OverlayEdge edge, MaximalEdgeRing maxRing) { bool isLinked = edge.MaxEdgeRing == maxRing && edge.IsResultLinked; return(isLinked); }
/// <summary> /// Determines the location of an edge relative to a target input geometry. /// The edge has no location information /// because it is disconnected from other /// edges that would provide that information. /// The location is determined by checking /// if the edge lies inside the target geometry area(if any). /// </summary> /// <param name="edge">The edge to label</param> /// <param name="geomIndex">The input geometry to label against</param> private void LabelDisconnectedEdge(OverlayEdge edge, int geomIndex) { var label = edge.Label; //Assert.isTrue(label.isNotPart(geomIndex)); /* * if target geom is not an area then * edge must be EXTERIOR, since to be * INTERIOR it would have been labelled * when it was created. */ if (!_inputGeometry.IsArea(geomIndex)) { label.SetLocationAll(geomIndex, Location.Exterior); return; } ; //Debug.println("\n------ labelDisconnectedEdge - geomIndex= " + geomIndex); //Debug.print("BEFORE: " + edge.toStringNode()); /* * Locate edge in input area using a Point-In-Poly check. * This should be safe even with precision reduction, * because since the edge has remained disconnected * its interior-exterior relationship * can be determined relative to the original input geometry. */ //int edgeLoc = locateEdge(geomIndex, edge); var edgeLoc = LocateEdgeBothEnds(geomIndex, edge); label.SetLocationAll(geomIndex, edgeLoc); //Debug.print("AFTER: " + edge.toStringNode()); }
private static OverlayEdge SelectMaxOutEdge(OverlayEdge currOut, MaximalEdgeRing maxEdgeRing) { // select if currOut edge is part of this max ring if (currOut.MaxEdgeRing == maxEdgeRing) { return(currOut); } // otherwise skip this edge return(null); }
private LineString ToLine(OverlayEdge edge) { bool isForward = edge.IsForward; var pts = new CoordinateList(); pts.Add(edge.Orig, false); edge.AddCoordinates(pts); var ptsOut = pts.ToCoordinateArray(isForward); var line = _geometryFactory.CreateLineString(ptsOut); return(line); }
/// <summary> /// Marks an edge which forms part of the boundary of the result area. /// This is determined by the overlay operation being executed, /// and the location of the edge. /// The relevant location is either the right side of a boundary edge, /// or the line location of a non-boundary edge. /// </summary> /// <param name="e">The edge to mark</param> /// <param name="overlayOpCode">The overlay operation</param> public void MarkInResultArea(OverlayEdge e, SpatialFunction overlayOpCode) { var label = e.Label; if (label.IsBoundaryEither && OverlayNG.IsResultOfOp( overlayOpCode, label.GetLocationBoundaryOrLine(0, Position.Right, e.IsForward), label.GetLocationBoundaryOrLine(1, Position.Right, e.IsForward))) { e.MarkInResultArea(); } //Debug.println("markInResultArea: " + e); }
/// <summary> /// Determines the {@link Location} for an edge within an Area geometry /// via point-in-polygon location, /// by checking that both endpoints are interior to the target geometry. /// Checking both endpoints ensures correct results in the presence of topology collapse. /// <para/> /// NOTE this is only safe to use for disconnected edges, /// since the test is carried out against the original input geometry, /// and precision reduction may cause incorrect results for edges /// which are close enough to a boundary to become connected. /// </summary> /// <param name="geomIndex">The parent geometry index</param> /// <param name="edge">The edge to locate</param> /// <returns>The location of the edge</returns> private Location LocateEdgeBothEnds(int geomIndex, OverlayEdge edge) { /* * To improve the robustness of the point location, * check both ends of the edge. * Edge is only labelled INTERIOR if both ends are. */ var locOrig = _inputGeometry.LocatePointInArea(geomIndex, edge.Orig); var locDest = _inputGeometry.LocatePointInArea(geomIndex, edge.Dest); bool isInt = locOrig != Location.Exterior && locDest != Location.Exterior; var edgeLoc = isInt ? Location.Interior : Location.Exterior; return(edgeLoc); }
/// <summary> /// Computes the degree of the line edges incident on a node /// </summary> /// <param name="node">Node to compute degree for</param> /// <returns>Degree of the node line edges</returns> private static int DegreeOfLines(OverlayEdge node) { int degree = 0; var e = node; do { if (e.IsInResultLine) { degree++; } e = e.ONextOE; } while (e != node); return(degree); }
/// <summary> /// Finds a boundary edge for this geom originating at the given /// node, if one exists. /// A boundary edge should exist if this is a node on the boundary /// of the parent area geometry. /// </summary> /// <param name="nodeEdge">An edge for this node</param> /// <param name="geomIndex">The parent geometry index</param> /// <returns>A boundary edge, or <c>null</c> if no boundary edge exists</returns> private static OverlayEdge FindPropagationStartEdge(OverlayEdge nodeEdge, int geomIndex) { var eStart = nodeEdge; do { var label = eStart.Label; if (label.IsBoundary(geomIndex)) { Assert.IsTrue(label.HasSides(geomIndex)); return(eStart); } eStart = eStart.ONextOE; } while (eStart != nodeEdge); return(null); }
/// <summary> /// Finds the next edge around a node which forms /// part of a result line. /// </summary> /// <param name="node">A line edge originating at the node to be scanned</param> /// <returns>The next line edge, or null if there is none /// </returns> private static OverlayEdge NextLineEdgeUnvisited(OverlayEdge node) { var e = node; do { e = e.ONextOE; if (e.IsVisited) { continue; } if (e.IsInResultLine) { return(e); } } while (e != node); return(null); }
private static OverlayEdge LinkMaxInEdge(OverlayEdge currOut, OverlayEdge currMaxRingOut, MaximalEdgeRing maxEdgeRing) { var currIn = currOut.SymOE; // currIn is not in this max-edgering, so keep looking if (currIn.MaxEdgeRing != maxEdgeRing) { return(currMaxRingOut); } //Debug.println("Found result in-edge: " + currIn); currIn.NextResult = currMaxRingOut; //Debug.println("Linked Min Edge: " + currIn + " -> " + currMaxRingOut); // return null to indicate to scan for the next max-ring out-edge return(null); }
private void LabelCollapsedEdge(OverlayEdge edge, int geomIndex) { //Debug.println("\n------ labelCollapsedEdge - geomIndex= " + geomIndex); //Debug.print("BEFORE: " + edge.toStringNode()); var label = edge.Label; if (!label.IsCollapse(geomIndex)) { return; } /* * This must be a collapsed edge which is disconnected * from any area edges (e.g. a fully collapsed shell or hole). * It can be labeled according to its parent source ring role. */ label.SetLocationCollapse(geomIndex); //Debug.print("AFTER: " + edge.toStringNode()); }
private static void PropagateLinearLocationAtNode(OverlayEdge eNode, int geomIndex, bool isInputLine, Stack <OverlayEdge> edgeStack) { // Note: edgeStack is a Deque<OverlayEdge> in JTS. It is used as a Stack. var lineLoc = eNode.Label.GetLineLocation(geomIndex); /* * If the parent geom is a Line * then only propagate EXTERIOR locations. */ if (isInputLine && lineLoc != Location.Exterior) { return; } //Debug.println("check " + geomIndex + " from: " + eNode); var e = eNode.ONextOE; do { var label = e.Label; //Debug.println(""*** setting "+ 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("propagateLineLocationAtNode - setting "+ index + ": " + e); /* * Add sym edge to stack for graph traversal * (Don't add e itself, since e origin node has now been scanned) */ edgeStack.Push(e.SymOE); } e = e.ONextOE; } while (e != eNode); }
private void AttachEdges(OverlayEdge startEdge) { var edge = startEdge; do { if (edge == null) { throw new TopologyException("Ring edge is null"); } if (edge.MaxEdgeRing == this) { throw new TopologyException($"Ring edge visited twice at {edge.Coordinate}", edge.Coordinate); } if (edge.NextResultMax == null) { throw new TopologyException($"Ring edge missing at {edge.Dest}", edge.Dest); } edge.MaxEdgeRing = this; edge = edge.NextResultMax; } while (edge != startEdge); }
public static string ToString(OverlayEdge nodeEdge) { var orig = nodeEdge.Orig; var sb = new StringBuilder(); sb.AppendFormat("Node( {0} )\n", WKTWriter.Format(orig)); var 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()); }
/// <summary> /// Links the edges of a <see cref="MaximalEdgeRing"/> around this node /// into minimal edge rings (<see cref="OverlayEdgeRing"/>s). /// Minimal ring edges are linked in the opposite orientation (CW) /// to the maximal ring. /// This changes self-touching rings into a two or more separate rings, /// as per the OGC SFS polygon topology semantics. /// This relinking must be done to each max ring separately, /// rather than all the node result edges, since there may be /// more than one max ring incident at the node. /// </summary> /// <param name="maxRing">The maximal ring to link</param> /// <param name="nodeEdge">An edge originating at this node</param> private static void LinkMinRingEdgesAtNode(OverlayEdge nodeEdge, MaximalEdgeRing maxRing) { //Assert.isTrue(nodeEdge.isInResult(), "Attempt to link non-result edge"); /* * The node edge is an out-edge, * so it is the first edge linked * with the next CCW in-edge */ var endOut = nodeEdge; var currMaxRingOut = endOut; var currOut = endOut.ONextOE; //Debug.println("\n------ Linking node MIN ring edges"); //Debug.println("BEFORE: " + toString(nodeEdge)); do { if (IsAlreadyLinked(currOut.SymOE, maxRing)) { return; } if (currMaxRingOut == null) { currMaxRingOut = SelectMaxOutEdge(currOut, maxRing); } else { currMaxRingOut = LinkMaxInEdge(currOut, currMaxRingOut, maxRing); } currOut = currOut.ONextOE; } while (currOut != endOut); //Debug.println("AFTER: " + toString(nodeEdge)); if (currMaxRingOut != null) { throw new TopologyException("Unmatched edge found during min-ring linking", nodeEdge.Coordinate); } }
private static string LabelForResult(OverlayEdge edge) { return(edge.Label.ToString(edge.IsForward) + (edge.IsInResultArea ? " Res" : "")); }
/// <summary> /// Scans around a node CCW, propagating the side labels /// for a given area geometry to all edges (and their sym) /// with unknown locations for that geometry. /// </summary> /// <param name="nodeEdge"></param> /// <param name="geomIndex">The geometry to propagate locations for</param> public void PropagateAreaLocations(OverlayEdge nodeEdge, int geomIndex) { /* * Only propagate for area geometries */ if (!_inputGeometry.IsArea(geomIndex)) { return; } /* * No need to propagate if node has only one edge. * This handles dangling edges created by overlap limiting. */ if (nodeEdge.Degree() == 1) { return; } var eStart = FindPropagationStartEdge(nodeEdge, geomIndex); // no labelled edge found, so nothing to propagate if (eStart == null) { return; } // initialize currLoc to location of L side var currLoc = eStart.GetLocation(geomIndex, Position.Left); var e = eStart.ONextOE; //Debug.println("\npropagateSideLabels geomIndex = " + geomIndex + " : " + eStart); //Debug.print("BEFORE: " + toString(eStart)); do { var label = e.Label; if (!label.IsBoundary(geomIndex)) { /* * If this is not a Boundary edge for this input area, * its location is now known relative to this input area */ label.SetLocationLine(geomIndex, currLoc); } else { // must be a boundary edge Assert.IsTrue(label.HasSides(geomIndex)); /* * This is a boundary edge for the input area geom. * Update the current location from its labels. * Also check for topological consistency. */ var locRight = e.GetLocation(geomIndex, Position.Right); if (locRight != currLoc) { /* * Debug.println("side location conflict: index= " + geomIndex + " R loc " + Location.toLocationSymbol(locRight) + " <> curr loc " + Location.toLocationSymbol(currLoc) + " for " + e); + //*/ throw new TopologyException("side location conflict: arg " + geomIndex, e.Coordinate); } var locLeft = e.GetLocation(geomIndex, Position.Left); if (locLeft == Location.Null) { Assert.ShouldNeverReachHere("found single null side at " + e); } currLoc = locLeft; } e = e.ONextOE; } while (e != eStart); //Debug.print("AFTER: " + toString(eStart)); }
/// <summary> /// Traverses the star of edges originating at a node /// and links consecutive result edges together /// into <b>maximal</b> edge rings. /// To link two edges the <c>resultNextMax</c> pointer /// for an <b>incoming</b> result edge /// is set to the next <b>outgoing</b> result edge. /// <para/> /// Edges are linked when: /// <list type="bullet"> /// <item><description>they belong to an area (i.e.they have sides)</description></item> /// <item><description>they are marked as being in the result</description></item> /// </list> /// <para/> /// Edges are linked in CCW order /// (which is the order they are linked in the underlying graph). /// This means that rings have their face on the Right /// (in other words, /// the topological location of the face is given by the RHS label of the DirectedEdge). /// This produces rings with CW orientation. /// <para/> /// PRECONDITIONS: <br/> /// - This edge is in the result<br/> /// - This edge is not yet linked<br/> /// - The edge and its sym are NOT both marked as being in the result /// </summary> public static void LinkResultAreaMaxRingAtNode(OverlayEdge nodeEdge) { Assert.IsTrue(nodeEdge.IsInResultArea, "Attempt to link non-result edge"); // assertion is only valid if building a polygonal geometry (ie not a coverage) //Assert.isTrue(! nodeEdge.symOE().isInResultArea(), "Found both half-edges in result"); /* * Since the node edge is an out-edge, * make it the last edge to be linked * by starting at the next edge. * The node edge cannot be an in-edge as well, * but the next one may be the first in-edge. */ var endOut = nodeEdge.ONextOE; var currOut = endOut; //Debug.println("\n------ Linking node MAX edges"); //Debug.println("BEFORE: " + toString(nodeEdge)); int state = STATE_FIND_INCOMING; OverlayEdge currResultIn = null; do { /** * If an edge is linked this node has already been processed * so can skip further processing */ if (currResultIn != null && currResultIn.IsResultMaxLinked) { return; } switch (state) { case STATE_FIND_INCOMING: var currIn = currOut.SymOE; if (!currIn.IsInResultArea) { break; } currResultIn = currIn; state = STATE_LINK_OUTGOING; //Debug.println("Found result in-edge: " + currResultIn); break; case STATE_LINK_OUTGOING: if (!currOut.IsInResultArea) { break; } // link the in edge to the out edge currResultIn.NextResultMax = currOut; state = STATE_FIND_INCOMING; //Debug.println("Linked Max edge: " + currResultIn + " -> " + currOut); break; } currOut = currOut.ONextOE; } while (currOut != endOut); //Debug.println("AFTER: " + toString(nodeEdge)); if (state == STATE_LINK_OUTGOING) { //Debug.print(firstOut == null, this); throw new TopologyException("no outgoing edge found", nodeEdge.Coordinate); } }
public MaximalEdgeRing(OverlayEdge e) { _startEdge = e; AttachEdges(e); }
private readonly List <OverlayEdgeRing> _holes = new List <OverlayEdgeRing>(); // a list of EdgeRings which are holes in this EdgeRing public OverlayEdgeRing(OverlayEdge start, GeometryFactory geometryFactory) { Edge = start; Coordinates = ComputeRingPts(start); ComputeRing(Coordinates, geometryFactory); }