using System; using System.Collections; using GeoAPI.Geometries; using GisSharpBlog.NetTopologySuite.GeometriesGraph; using GisSharpBlog.NetTopologySuite.Utilities; using Wintellect.PowerCollections; namespace GisSharpBlog.NetTopologySuite.Operation.Buffer { /// /// A connected subset of the graph of /// DirectedEdges and Nodes. /// Its edges will generate either /// a single polygon in the complete buffer, with zero or more holes, or /// one or more connected holes. /// public class BufferSubgraph : IComparable { private RightmostEdgeFinder finder; private IList dirEdgeList = new ArrayList(); private IList nodes = new ArrayList(); private ICoordinate rightMostCoord = null; /// /// /// public BufferSubgraph() { finder = new RightmostEdgeFinder(); } /// /// /// public IList DirectedEdges { get { return dirEdgeList; } } /// /// /// public IList Nodes { get { return nodes; } } /// /// Gets the rightmost coordinate in the edges of the subgraph. /// public ICoordinate RightMostCoordinate { get { return rightMostCoord; } } /// /// Creates the subgraph consisting of all edges reachable from this node. /// Finds the edges in the graph and the rightmost coordinate. /// /// A node to start the graph traversal from. public void Create(Node node) { AddReachable(node); finder.FindEdge(dirEdgeList); rightMostCoord = finder.Coordinate; } /// /// Adds all nodes and edges reachable from this node to the subgraph. /// Uses an explicit stack to avoid a large depth of recursion. /// /// A node known to be in the subgraph. private void AddReachable(Node startNode) { Stack nodeStack = new Stack(); nodeStack.Push(startNode); while (nodeStack.Count != 0) { Node node = (Node) nodeStack.Pop(); Add(node, nodeStack); } } /// /// Adds the argument node and all its out edges to the subgraph /// /// The node to add. /// The current set of nodes being traversed. private void Add(Node node, Stack nodeStack) { node.Visited = true; nodes.Add(node); for (IEnumerator i = ((DirectedEdgeStar) node.Edges).GetEnumerator(); i.MoveNext(); ) { DirectedEdge de = (DirectedEdge) i.Current; dirEdgeList.Add(de); DirectedEdge sym = de.Sym; Node symNode = sym.Node; /* * NOTE: this is a depth-first traversal of the graph. * This will cause a large depth of recursion. * It might be better to do a breadth-first traversal. */ if (! symNode.IsVisited) nodeStack.Push(symNode); } } /// /// /// private void ClearVisitedEdges() { for (IEnumerator it = dirEdgeList.GetEnumerator(); it.MoveNext(); ) { DirectedEdge de = (DirectedEdge) it.Current; de.Visited = false; } } /// /// /// /// public void ComputeDepth(int outsideDepth) { ClearVisitedEdges(); // find an outside edge to assign depth to DirectedEdge de = finder.Edge; // right side of line returned by finder is on the outside de.SetEdgeDepths(Positions.Right, outsideDepth); CopySymDepths(de); ComputeDepths(de); } /// /// Compute depths for all dirEdges via breadth-first traversal of nodes in graph. /// /// Edge to start processing with. // MD - use iteration & queue rather than recursion, for speed and robustness private void ComputeDepths(DirectedEdge startEdge) { Set nodesVisited = new Set(); Queue nodeQueue = new Queue(); Node startNode = startEdge.Node; nodeQueue.Enqueue(startNode); nodesVisited.Add(startNode); startEdge.Visited = true; while (nodeQueue.Count != 0) { Node n = (Node) nodeQueue.Dequeue(); nodesVisited.Add(n); // compute depths around node, starting at this edge since it has depths assigned ComputeNodeDepth(n); // add all adjacent nodes to process queue, unless the node has been visited already IEnumerator i = ((DirectedEdgeStar)n.Edges).GetEnumerator(); while (i.MoveNext()) { DirectedEdge de = (DirectedEdge) i.Current; DirectedEdge sym = de.Sym; if (sym.IsVisited) continue; Node adjNode = sym.Node; if (!(nodesVisited.Contains(adjNode))) { nodeQueue.Enqueue(adjNode); nodesVisited.Add(adjNode); } } } } /// /// /// /// private void ComputeNodeDepth(Node n) { // find a visited dirEdge to start at DirectedEdge startEdge = null; IEnumerator i = ((DirectedEdgeStar) n.Edges).GetEnumerator(); while (i.MoveNext()) { DirectedEdge de = (DirectedEdge) i.Current; if (de.IsVisited || de.Sym.IsVisited) { startEdge = de; break; } } // MD - testing Result: breaks algorithm Assert.IsTrue(startEdge != null, "unable to find edge to compute depths at " + n.Coordinate); ((DirectedEdgeStar) n.Edges).ComputeDepths(startEdge); // copy depths to sym edges IEnumerator j = ((DirectedEdgeStar) n.Edges).GetEnumerator(); while (j.MoveNext()) { DirectedEdge de = (DirectedEdge) j.Current; de.Visited = true; CopySymDepths(de); } } /// /// /// /// private void CopySymDepths(DirectedEdge de) { DirectedEdge sym = de.Sym; sym.SetDepth(Positions.Left, de.GetDepth(Positions.Right)); sym.SetDepth(Positions.Right, de.GetDepth(Positions.Left)); } /// /// Find all edges whose depths indicates that they are in the result area(s). /// Since we want polygon shells to be /// oriented CW, choose dirEdges with the interior of the result on the RHS. /// Mark them as being in the result. /// Interior Area edges are the result of dimensional collapses. /// They do not form part of the result area boundary. /// public void FindResultEdges() { for (IEnumerator it = dirEdgeList.GetEnumerator(); it.MoveNext(); ) { DirectedEdge de = (DirectedEdge) it.Current; /* * Select edges which have an interior depth on the RHS * and an exterior depth on the LHS. * Note that because of weird rounding effects there may be * edges which have negative depths! Negative depths * count as "outside". */ // - handle negative depths if (de.GetDepth(Positions.Right) >= 1 && de.GetDepth(Positions.Left) <= 0 && !de.IsInteriorAreaEdge) de.InResult = true; } } /// /// BufferSubgraphs are compared on the x-value of their rightmost Coordinate. /// This defines a partial ordering on the graphs such that: /// g1 >= g2 - Ring(g2) does not contain Ring(g1) /// where Polygon(g) is the buffer polygon that is built from g. /// This relationship is used to sort the BufferSubgraphs so that shells are guaranteed to /// be built before holes. /// public int CompareTo(Object o) { BufferSubgraph graph = (BufferSubgraph) o; if (this.RightMostCoordinate.X < graph.RightMostCoordinate.X) return -1; if (this.RightMostCoordinate.X > graph.RightMostCoordinate.X) return 1; return 0; } } }