java.org.locationtech.jts.operation.polygonize.PolygonizeGraph Maven / Gradle / Ivy
Show all versions of jts Show documentation
/*
* Copyright (c) 2016 Vivid Solutions.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v. 1.0 which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
*
* http://www.eclipse.org/org/documents/edl-v10.php.
*/
package org.locationtech.jts.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 org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateArrays;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.planargraph.DirectedEdge;
import org.locationtech.jts.planargraph.DirectedEdgeStar;
import org.locationtech.jts.planargraph.Edge;
import org.locationtech.jts.planargraph.Node;
import org.locationtech.jts.planargraph.PlanarGraph;
import org.locationtech.jts.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.
*
* 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
{
private static int getDegreeNonDeleted(Node node)
{
List edges = node.getOutEdges().getEdges();
int degree = 0;
for (Iterator i = edges.iterator(); i.hasNext(); ) {
PolygonizeDirectedEdge de = (PolygonizeDirectedEdge) i.next();
if (! de.isMarked()) degree++;
}
return degree;
}
private static int getDegree(Node node, long label)
{
List edges = node.getOutEdges().getEdges();
int degree = 0;
for (Iterator i = edges.iterator(); i.hasNext(); ) {
PolygonizeDirectedEdge de = (PolygonizeDirectedEdge) i.next();
if (de.getLabel() == label) degree++;
}
return degree;
}
/**
* Deletes all edges at a node
*/
public static void deleteAllEdges(Node node)
{
List edges = node.getOutEdges().getEdges();
for (Iterator i = edges.iterator(); i.hasNext(); ) {
PolygonizeDirectedEdge de = (PolygonizeDirectedEdge) i.next();
de.setMarked(true);
PolygonizeDirectedEdge sym = (PolygonizeDirectedEdge) de.getSym();
if (sym != null)
sym.setMarked(true);
}
}
private GeometryFactory factory;
//private List labelledRings;
/**
* Create a new polygonization graph.
*/
public PolygonizeGraph(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(LineString line)
{
if (line.isEmpty()) { return; }
Coordinate[] linePts = CoordinateArrays.removeRepeatedPoints(line.getCoordinates());
if (linePts.length < 2) { return; }
Coordinate startPt = linePts[0];
Coordinate endPt = linePts[linePts.length - 1];
Node nStart = getNode(startPt);
Node nEnd = getNode(endPt);
DirectedEdge de0 = new PolygonizeDirectedEdge(nStart, nEnd, linePts[1], true);
DirectedEdge de1 = new PolygonizeDirectedEdge(nEnd, nStart, linePts[linePts.length - 2], false);
Edge edge = new PolygonizeEdge(line);
edge.setDirectedEdges(de0, de1);
add(edge);
}
private Node getNode(Coordinate pt)
{
Node node = findNode(pt);
if (node == null) {
node = new Node(pt);
// ensure node is only added once to graph
add(node);
}
return node;
}
private void computeNextCWEdges()
{
// set the next pointers for the edges around each node
for (Iterator iNode = nodeIterator(); iNode.hasNext(); ) {
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(List ringEdges)
{
for (Iterator i = ringEdges.iterator(); i.hasNext(); ) {
PolygonizeDirectedEdge de = (PolygonizeDirectedEdge) i.next();
long label = de.getLabel();
List intNodes = findIntersectionNodes(de, label);
if (intNodes == null) continue;
// flip the next pointers on the intersection nodes to create minimal edge rings
for (Iterator iNode = intNodes.iterator(); iNode.hasNext(); ) {
Node node = (Node) iNode.next();
computeNextCCWEdges(node, label);
}
}
}
/**
* Finds all nodes in a maximal edgering which are self-intersection nodes
* @param startDE
* @param label
* @return the list of intersection nodes found,
* or null
if no intersection nodes were found
*/
private static List findIntersectionNodes(PolygonizeDirectedEdge startDE, long label)
{
PolygonizeDirectedEdge de = startDE;
List intNodes = null;
do {
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;
}
/**
* 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 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(dirEdges, -1);
List maximalRings = findLabeledEdgeRings(dirEdges);
convertMaximalToMinimalEdgeRings(maximalRings);
// find all edgerings (which will now be minimal ones, as required)
List edgeRingList = new ArrayList();
for (Iterator i = dirEdges.iterator(); i.hasNext(); ) {
PolygonizeDirectedEdge de = (PolygonizeDirectedEdge) i.next();
if (de.isMarked()) continue;
if (de.isInRing()) continue;
EdgeRing er = findEdgeRing(de);
edgeRingList.add(er);
}
return edgeRingList;
}
/**
* Finds and labels all edgerings in the graph.
* The edge rings are labeling with unique integers.
* The labeling 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(Collection dirEdges)
{
List edgeRingStarts = new ArrayList();
// label the edge rings formed
long currLabel = 1;
for (Iterator i = dirEdges.iterator(); i.hasNext(); ) {
PolygonizeDirectedEdge de = (PolygonizeDirectedEdge) i.next();
if (de.isMarked()) continue;
if (de.getLabel() >= 0) continue;
edgeRingStarts.add(de);
List edges = EdgeRing.findDirEdgesInRing(de);
label(edges, currLabel);
currLabel++;
}
return edgeRingStarts;
}
/**
* Finds and removes all cut edges from the graph.
* @return a list of the {@link LineString}s forming the removed cut edges
*/
public List deleteCutEdges()
{
computeNextCWEdges();
// label the current set of edgerings
findLabeledEdgeRings(dirEdges);
/**
* Cut Edges are edges where both dirEdges have the same label.
* Delete them, and record them
*/
List cutLines = new ArrayList();
for (Iterator i = dirEdges.iterator(); i.hasNext(); ) {
PolygonizeDirectedEdge de = (PolygonizeDirectedEdge) i.next();
if (de.isMarked()) continue;
PolygonizeDirectedEdge sym = (PolygonizeDirectedEdge) de.getSym();
if (de.getLabel() == sym.getLabel()) {
de.setMarked(true);
sym.setMarked(true);
// save the line as a cut edge
PolygonizeEdge e = (PolygonizeEdge) de.getEdge();
cutLines.add(e.getLine());
}
}
return cutLines;
}
private static void label(Collection dirEdges, long label)
{
for (Iterator i = dirEdges.iterator(); i.hasNext(); ) {
PolygonizeDirectedEdge de = (PolygonizeDirectedEdge) i.next();
de.setLabel(label);
}
}
private static void computeNextCWEdges(Node node)
{
DirectedEdgeStar deStar = node.getOutEdges();
PolygonizeDirectedEdge startDE = null;
PolygonizeDirectedEdge prevDE = null;
// the edges are stored in CCW order around the star
for (Iterator i = deStar.getEdges().iterator(); i.hasNext(); ) {
PolygonizeDirectedEdge outDE = (PolygonizeDirectedEdge) i.next();
if (outDE.isMarked()) continue;
if (startDE == null)
startDE = outDE;
if (prevDE != null) {
PolygonizeDirectedEdge sym = (PolygonizeDirectedEdge) prevDE.getSym();
sym.setNext(outDE);
}
prevDE = outDE;
}
if (prevDE != null) {
PolygonizeDirectedEdge sym = (PolygonizeDirectedEdge) prevDE.getSym();
sym.setNext(startDE);
}
}
/**
* 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(Node node, long label)
{
DirectedEdgeStar deStar = node.getOutEdges();
//PolyDirectedEdge lastInDE = null;
PolygonizeDirectedEdge firstOutDE = null;
PolygonizeDirectedEdge prevInDE = null;
// the edges are stored in CCW order around the star
List edges = deStar.getEdges();
//for (Iterator i = deStar.getEdges().iterator(); i.hasNext(); ) {
for (int i = edges.size() - 1; i >= 0; i--) {
PolygonizeDirectedEdge de = (PolygonizeDirectedEdge) edges.get(i);
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 EdgeRing findEdgeRing(PolygonizeDirectedEdge startDE)
{
EdgeRing er = new EdgeRing(factory);
er.build(startDE);
return er;
}
/**
* 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 deleteDangles()
{
List nodesToRemove = findNodesOfDegree(1);
Set dangleLines = new HashSet();
Stack nodeStack = new Stack();
for (Iterator i = nodesToRemove.iterator(); i.hasNext(); ) {
nodeStack.push(i.next());
}
while (! nodeStack.isEmpty()) {
Node node = (Node) nodeStack.pop();
deleteAllEdges(node);
List nodeOutEdges = node.getOutEdges().getEdges();
for (Iterator i = nodeOutEdges.iterator(); i.hasNext(); ) {
PolygonizeDirectedEdge de = (PolygonizeDirectedEdge) i.next();
// delete this edge and its sym
de.setMarked(true);
PolygonizeDirectedEdge sym = (PolygonizeDirectedEdge) de.getSym();
if (sym != null)
sym.setMarked(true);
// save the line as a dangle
PolygonizeEdge e = (PolygonizeEdge) de.getEdge();
dangleLines.add(e.getLine());
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;
}
/**
* 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) {
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(PolygonizeDirectedEdge de)
{
}
}