/* * The JTS Topology Suite is a collection of Java classes that * implement the fundamental operations required to validate a given * geo-spatial data set to a known topological specification. * * Copyright (C) 2001 Vivid Solutions * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ package com.revolsys.geometry.operation.polygonize; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.Stack; import com.revolsys.geometry.model.GeometryFactory; import com.revolsys.geometry.model.LineString; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.planargraph.DirectedEdge; import com.revolsys.geometry.planargraph.DirectedEdgeStar; import com.revolsys.geometry.planargraph.Edge; import com.revolsys.geometry.planargraph.Node; import com.revolsys.geometry.planargraph.PlanarGraph; import com.revolsys.geometry.util.Assert; /** * Represents a planar graph of edges that can be used to compute a * polygonization, and implements the algorithms to compute the * {@link EdgeRings} formed by the graph. * <p> * The marked flag on {@link DirectedEdge}s is used to indicate that a directed edge * has be logically deleted from the graph. * * @version 1.7 */ class PolygonizeGraph extends PlanarGraph { /** * Computes the next edge pointers going CCW around the given node, for the * given edgering label. * This algorithm has the effect of converting maximal edgerings into minimal edgerings */ private static void computeNextCCWEdges(final Node node, final long label) { final DirectedEdgeStar deStar = node.getOutEdges(); // PolyDirectedEdge lastInDE = null; PolygonizeDirectedEdge firstOutDE = null; PolygonizeDirectedEdge prevInDE = null; // the edges are stored in CCW order around the star final List edges = deStar.getEdges(); // for (Iterator i = deStar.getEdges().iterator(); i.hasNext(); ) { for (int i = edges.size() - 1; i >= 0; i--) { final PolygonizeDirectedEdge de = (PolygonizeDirectedEdge)edges.get(i); final PolygonizeDirectedEdge sym = (PolygonizeDirectedEdge)de.getSym(); PolygonizeDirectedEdge outDE = null; if (de.getLabel() == label) { outDE = de; } PolygonizeDirectedEdge inDE = null; if (sym.getLabel() == label) { inDE = sym; } if (outDE == null && inDE == null) { continue; // this edge is not in edgering } if (inDE != null) { prevInDE = inDE; } if (outDE != null) { if (prevInDE != null) { prevInDE.setNext(outDE); prevInDE = null; } if (firstOutDE == null) { firstOutDE = outDE; } } } if (prevInDE != null) { Assert.isTrue(firstOutDE != null); prevInDE.setNext(firstOutDE); } } private static void computeNextCWEdges(final Node node) { final DirectedEdgeStar deStar = node.getOutEdges(); PolygonizeDirectedEdge startDE = null; PolygonizeDirectedEdge prevDE = null; // the edges are stored in CCW order around the star for (final Object element : deStar.getEdges()) { final PolygonizeDirectedEdge outDE = (PolygonizeDirectedEdge)element; if (outDE.isMarked()) { continue; } if (startDE == null) { startDE = outDE; } if (prevDE != null) { final PolygonizeDirectedEdge sym = (PolygonizeDirectedEdge)prevDE.getSym(); sym.setNext(outDE); } prevDE = outDE; } if (prevDE != null) { final PolygonizeDirectedEdge sym = (PolygonizeDirectedEdge)prevDE.getSym(); sym.setNext(startDE); } } /** * Deletes all edges at a node */ public static void deleteAllEdges(final Node node) { final List edges = node.getOutEdges().getEdges(); for (final Iterator i = edges.iterator(); i.hasNext();) { final PolygonizeDirectedEdge de = (PolygonizeDirectedEdge)i.next(); de.setMarked(true); final PolygonizeDirectedEdge sym = (PolygonizeDirectedEdge)de.getSym(); if (sym != null) { sym.setMarked(true); } } } /** * Traverses a ring of DirectedEdges, accumulating them into a list. * This assumes that all dangling directed edges have been removed * from the graph, so that there is always a next dirEdge. * * @param startDE the DirectedEdge to start traversing at * @return a List of DirectedEdges that form a ring */ private static List findDirEdgesInRing(final PolygonizeDirectedEdge startDE) { PolygonizeDirectedEdge de = startDE; final List edges = new ArrayList(); do { edges.add(de); de = de.getNext(); Assert.isTrue(de != null, "found null DE in ring"); Assert.isTrue(de == startDE || !de.isInRing(), "found DE already in ring"); } while (de != startDE); return edges; } // private List labelledRings; /** * Finds all nodes in a maximal edgering which are self-intersection nodes * @param startDE * @param label * @return the list of intersection nodes found, * or <code>null</code> if no intersection nodes were found */ private static List findIntersectionNodes(final PolygonizeDirectedEdge startDE, final long label) { PolygonizeDirectedEdge de = startDE; List intNodes = null; do { final Node node = de.getFromNode(); if (getDegree(node, label) > 1) { if (intNodes == null) { intNodes = new ArrayList(); } intNodes.add(node); } de = de.getNext(); Assert.isTrue(de != null, "found null DE in ring"); Assert.isTrue(de == startDE || !de.isInRing(), "found DE already in ring"); } while (de != startDE); return intNodes; } /** * Finds and labels all edgerings in the graph. * The edge rings are labelling with unique integers. * The labelling allows detecting cut edges. * * @param dirEdges a List of the DirectedEdges in the graph * @return a List of DirectedEdges, one for each edge ring found */ private static List findLabeledEdgeRings(final Collection dirEdges) { final List edgeRingStarts = new ArrayList(); // label the edge rings formed long currLabel = 1; for (final Iterator i = dirEdges.iterator(); i.hasNext();) { final PolygonizeDirectedEdge de = (PolygonizeDirectedEdge)i.next(); if (de.isMarked()) { continue; } if (de.getLabel() >= 0) { continue; } edgeRingStarts.add(de); final List edges = findDirEdgesInRing(de); label(edges, currLabel); currLabel++; } return edgeRingStarts; } private static int getDegree(final Node node, final long label) { final List edges = node.getOutEdges().getEdges(); int degree = 0; for (final Iterator i = edges.iterator(); i.hasNext();) { final PolygonizeDirectedEdge de = (PolygonizeDirectedEdge)i.next(); if (de.getLabel() == label) { degree++; } } return degree; } private static int getDegreeNonDeleted(final Node node) { final List edges = node.getOutEdges().getEdges(); int degree = 0; for (final Iterator i = edges.iterator(); i.hasNext();) { final PolygonizeDirectedEdge de = (PolygonizeDirectedEdge)i.next(); if (!de.isMarked()) { degree++; } } return degree; } private static void label(final Collection dirEdges, final long label) { for (final Iterator i = dirEdges.iterator(); i.hasNext();) { final PolygonizeDirectedEdge de = (PolygonizeDirectedEdge)i.next(); de.setLabel(label); } } private final GeometryFactory factory; /** * Construct a new new polygonization graph. */ public PolygonizeGraph(final GeometryFactory factory) { this.factory = factory; } /** * Add a {@link LineString} forming an edge of the polygon graph. * @param line the line to add */ public void addEdge(final LineString line) { final LineString cleanLine = line.removeDuplicatePoints(); if (cleanLine.isEmpty()) { return; } if (cleanLine.getVertexCount() < 2) { return; } final Point startPt = cleanLine.getVertex(0).newPoint2D(); final Point endPt = cleanLine.getVertex(-1).newPoint2D(); final Node nStart = getNode(startPt); final Node nEnd = getNode(endPt); final DirectedEdge de0 = new PolygonizeDirectedEdge(nStart, nEnd, cleanLine.getVertex(1).newPoint2D(), true); final DirectedEdge de1 = new PolygonizeDirectedEdge(nEnd, nStart, cleanLine.getVertex(-2).newPoint2D(), false); final Edge edge = new PolygonizeEdge(line); edge.setDirectedEdges(de0, de1); add(edge); } /** * Traverses the polygonized edge rings in the graph * and computes the depth parity (odd or even) * relative to the exterior of the graph. * If the client has requested that the output * be polygonally valid, only odd polygons will be constructed. * */ public void computeDepthParity() { while (true) { final PolygonizeDirectedEdge de = null; // findLowestDirEdge(); if (de == null) { return; } computeDepthParity(de); } } /** * Traverses all connected edges, computing the depth parity * of the associated polygons. * * @param de */ private void computeDepthParity(final PolygonizeDirectedEdge de) { } private void computeNextCWEdges() { // set the next pointers for the edges around each node for (final Iterator iNode = nodeIterator(); iNode.hasNext();) { final Node node = (Node)iNode.next(); computeNextCWEdges(node); } } /** * Convert the maximal edge rings found by the initial graph traversal * into the minimal edge rings required by JTS polygon topology rules. * * @param ringEdges the list of start edges for the edgeRings to convert. */ private void convertMaximalToMinimalEdgeRings(final List ringEdges) { for (final Iterator i = ringEdges.iterator(); i.hasNext();) { final PolygonizeDirectedEdge de = (PolygonizeDirectedEdge)i.next(); final long label = de.getLabel(); final List intNodes = findIntersectionNodes(de, label); if (intNodes == null) { continue; } // flip the next pointers on the intersection nodes to create minimal edge // rings for (final Iterator iNode = intNodes.iterator(); iNode.hasNext();) { final Node node = (Node)iNode.next(); computeNextCCWEdges(node, label); } } } /** * Finds and removes all cut edges from the graph. * @return a list of the {@link LineString}s forming the removed cut edges */ public List<LineString> deleteCutEdges() { computeNextCWEdges(); // label the current set of edgerings findLabeledEdgeRings(this.dirEdges); /** * Cut Edges are edges where both dirEdges have the same label. * Delete them, and record them */ final List<LineString> cutLines = new ArrayList<>(); for (final Object element : this.dirEdges) { final PolygonizeDirectedEdge de = (PolygonizeDirectedEdge)element; if (de.isMarked()) { continue; } final PolygonizeDirectedEdge sym = (PolygonizeDirectedEdge)de.getSym(); if (de.getLabel() == sym.getLabel()) { de.setMarked(true); sym.setMarked(true); // save the line as a cut edge final PolygonizeEdge e = (PolygonizeEdge)de.getEdge(); cutLines.add(e.getLine()); } } return cutLines; } /** * Marks all edges from the graph which are "dangles". * Dangles are which are incident on a node with degree 1. * This process is recursive, since removing a dangling edge * may result in another edge becoming a dangle. * In order to handle large recursion depths efficiently, * an explicit recursion stack is used * * @return a List containing the {@link LineString}s that formed dangles */ public Collection<LineString> deleteDangles() { final List nodesToRemove = findNodesOfDegree(1); final Set<LineString> dangleLines = new HashSet<>(); final Stack nodeStack = new Stack(); for (final Iterator i = nodesToRemove.iterator(); i.hasNext();) { nodeStack.push(i.next()); } while (!nodeStack.isEmpty()) { final Node node = (Node)nodeStack.pop(); deleteAllEdges(node); final List nodeOutEdges = node.getOutEdges().getEdges(); for (final Iterator i = nodeOutEdges.iterator(); i.hasNext();) { final PolygonizeDirectedEdge de = (PolygonizeDirectedEdge)i.next(); // delete this edge and its sym de.setMarked(true); final PolygonizeDirectedEdge sym = (PolygonizeDirectedEdge)de.getSym(); if (sym != null) { sym.setMarked(true); } // save the line as a dangle final PolygonizeEdge e = (PolygonizeEdge)de.getEdge(); dangleLines.add(e.getLine()); final Node toNode = de.getToNode(); // add the toNode to the list to be processed, if it is now a dangle if (getDegreeNonDeleted(toNode) == 1) { nodeStack.push(toNode); } } } return dangleLines; } private EdgeRing findEdgeRing(final PolygonizeDirectedEdge startDE) { PolygonizeDirectedEdge de = startDE; final EdgeRing er = new EdgeRing(this.factory); do { er.add(de); de.setRing(er); de = de.getNext(); Assert.isTrue(de != null, "found null DE in ring"); Assert.isTrue(de == startDE || !de.isInRing(), "found DE already in ring"); } while (de != startDE); return er; } /** * Computes the minimal EdgeRings formed by the edges in this graph. * @return a list of the {@link EdgeRing}s found by the polygonization process. */ public List<EdgeRing> getEdgeRings() { // maybe could optimize this, since most of these pointers should be set // correctly already // by deleteCutEdges() computeNextCWEdges(); // clear labels of all edges in graph label(this.dirEdges, -1); final List maximalRings = findLabeledEdgeRings(this.dirEdges); convertMaximalToMinimalEdgeRings(maximalRings); // find all edgerings (which will now be minimal ones, as required) final List<EdgeRing> edgeRingList = new ArrayList<>(); for (final Object element : this.dirEdges) { final PolygonizeDirectedEdge de = (PolygonizeDirectedEdge)element; if (de.isMarked()) { continue; } if (de.isInRing()) { continue; } final EdgeRing er = findEdgeRing(de); edgeRingList.add(er); } return edgeRingList; } public GeometryFactory getGeometryFactory() { return this.factory; } private Node getNode(final Point point) { Node node = findNode(point); if (node == null) { final double x = point.getX(); final double y = point.getY(); node = new Node(x, y); // ensure node is only added once to graph add(node); } return node; } }