diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/buffer/BufferBuilder.java b/modules/core/src/main/java/org/locationtech/jts/operation/buffer/BufferBuilder.java index 580de1b413..9fa34ba197 100644 --- a/modules/core/src/main/java/org/locationtech/jts/operation/buffer/BufferBuilder.java +++ b/modules/core/src/main/java/org/locationtech/jts/operation/buffer/BufferBuilder.java @@ -40,10 +40,6 @@ import org.locationtech.jts.noding.MCIndexNoder; import org.locationtech.jts.noding.Noder; import org.locationtech.jts.noding.SegmentString; -import org.locationtech.jts.operation.overlay.OverlayNodeFactory; -import org.locationtech.jts.operation.overlay.PolygonBuilder; - - /** * Builds the buffer geometry for a given input geometry and precision model. @@ -167,7 +163,7 @@ public Geometry buffer(Geometry g, double distance) boolean isNodingValidated = distance == 0.0; computeNodedEdges(bufferSegStrList, precisionModel, isNodingValidated); - graph = new PlanarGraph(new OverlayNodeFactory()); + graph = new PlanarGraph(new BufferNodeFactory()); graph.addEdges(edgeList.getEdges()); List subgraphList = createSubgraphs(graph); diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/buffer/BufferNodeFactory.java b/modules/core/src/main/java/org/locationtech/jts/operation/buffer/BufferNodeFactory.java new file mode 100644 index 0000000000..4cf3d48318 --- /dev/null +++ b/modules/core/src/main/java/org/locationtech/jts/operation/buffer/BufferNodeFactory.java @@ -0,0 +1,36 @@ +/* + * 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 2.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-v20.html + * and the Eclipse Distribution License is available at + * + * http://www.eclipse.org/org/documents/edl-v10.php. + */ +package org.locationtech.jts.operation.buffer; + +/** + * @version 1.7 + */ +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geomgraph.DirectedEdgeStar; +import org.locationtech.jts.geomgraph.Node; +import org.locationtech.jts.geomgraph.NodeFactory; +import org.locationtech.jts.geomgraph.PlanarGraph; + +/** + * Creates nodes for use in the {@link PlanarGraph}s constructed during + * buffer operations. + * + * @version 1.7 + */ +class BufferNodeFactory + extends NodeFactory +{ + public Node createNode(Coordinate coord) + { + return new Node(coord, new DirectedEdgeStar()); + } +} diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/buffer/MaximalEdgeRing.java b/modules/core/src/main/java/org/locationtech/jts/operation/buffer/MaximalEdgeRing.java new file mode 100644 index 0000000000..c4c7caf80c --- /dev/null +++ b/modules/core/src/main/java/org/locationtech/jts/operation/buffer/MaximalEdgeRing.java @@ -0,0 +1,86 @@ +/* + * 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 2.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-v20.html + * and the Eclipse Distribution License is available at + * + * http://www.eclipse.org/org/documents/edl-v10.php. + */ +package org.locationtech.jts.operation.buffer; + +import java.util.ArrayList; +import java.util.List; + +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geomgraph.DirectedEdge; +import org.locationtech.jts.geomgraph.DirectedEdgeStar; +import org.locationtech.jts.geomgraph.EdgeRing; +import org.locationtech.jts.geomgraph.Node; + +/** + * A ring of {@link DirectedEdge}s which may contain nodes of degree > 2. + * A MaximalEdgeRing may represent two different spatial entities: + *
+ * These are the form of rings used to define polygons under some spatial data models. + * However, under the OGC SFS model, {@link MinimalEdgeRing}s are required. + * A MaximalEdgeRing can be converted to a list of MinimalEdgeRings using the + * {@link #buildMinimalRings() } method. + * + * @version 1.7 + * @see org.locationtech.jts.operation.buffer.MinimalEdgeRing + */ +class MaximalEdgeRing + extends EdgeRing +{ + + public MaximalEdgeRing(DirectedEdge start, GeometryFactory geometryFactory) { + super(start, geometryFactory); + } + + public DirectedEdge getNext(DirectedEdge de) + { + return de.getNext(); + } + public void setEdgeRing(DirectedEdge de, EdgeRing er) + { + de.setEdgeRing(er); + } + + /** + * For all nodes in this EdgeRing, + * link the DirectedEdges at the node to form minimalEdgeRings + */ + public void linkDirectedEdgesForMinimalEdgeRings() + { + DirectedEdge de = startDe; + do { + Node node = de.getNode(); + ((DirectedEdgeStar) node.getEdges()).linkMinimalDirectedEdges(this); + de = de.getNext(); + } while (de != startDe); + } + + public List buildMinimalRings() + { + List minEdgeRings = new ArrayList(); + DirectedEdge de = startDe; + do { + if (de.getMinEdgeRing() == null) { + EdgeRing minEr = new MinimalEdgeRing(de, geometryFactory); + minEdgeRings.add(minEr); + } + de = de.getNext(); + } while (de != startDe); + return minEdgeRings; + } + +} diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/buffer/MinimalEdgeRing.java b/modules/core/src/main/java/org/locationtech/jts/operation/buffer/MinimalEdgeRing.java new file mode 100644 index 0000000000..bc2799bda5 --- /dev/null +++ b/modules/core/src/main/java/org/locationtech/jts/operation/buffer/MinimalEdgeRing.java @@ -0,0 +1,44 @@ +/* + * 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 2.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-v20.html + * and the Eclipse Distribution License is available at + * + * http://www.eclipse.org/org/documents/edl-v10.php. + */ +package org.locationtech.jts.operation.buffer; + +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geomgraph.DirectedEdge; +import org.locationtech.jts.geomgraph.Edge; +import org.locationtech.jts.geomgraph.EdgeRing; + +/** + * A ring of {@link Edge}s with the property that no node + * has degree greater than 2. These are the form of rings required + * to represent polygons under the OGC SFS spatial data model. + * + * @version 1.7 + * @see org.locationtech.jts.operation.buffer.MaximalEdgeRing + */ +class MinimalEdgeRing + extends EdgeRing +{ + + public MinimalEdgeRing(DirectedEdge start, GeometryFactory geometryFactory) { + super(start, geometryFactory); + } + + public DirectedEdge getNext(DirectedEdge de) + { + return de.getNextMin(); + } + public void setEdgeRing(DirectedEdge de, EdgeRing er) + { + de.setMinEdgeRing(er); + } + +} diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/buffer/PolygonBuilder.java b/modules/core/src/main/java/org/locationtech/jts/operation/buffer/PolygonBuilder.java new file mode 100644 index 0000000000..8deadd87a6 --- /dev/null +++ b/modules/core/src/main/java/org/locationtech/jts/operation/buffer/PolygonBuilder.java @@ -0,0 +1,276 @@ +/* + * 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 2.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-v20.html + * and the Eclipse Distribution License is available at + * + * http://www.eclipse.org/org/documents/edl-v10.php. + */ +package org.locationtech.jts.operation.buffer; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import org.locationtech.jts.algorithm.PointLocation; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.CoordinateArrays; +import org.locationtech.jts.geom.Envelope; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.LinearRing; +import org.locationtech.jts.geom.Polygon; +import org.locationtech.jts.geomgraph.DirectedEdge; +import org.locationtech.jts.geomgraph.EdgeRing; +import org.locationtech.jts.geomgraph.PlanarGraph; +import org.locationtech.jts.util.Assert; + +/** + * Forms {@link Polygon}s out of a graph of {@link DirectedEdge}s. + * The edges to use are marked as being in the result Area. + *
+ * + * @version 1.7 + */ +class PolygonBuilder { + + private GeometryFactory geometryFactory; + private List shellList = new ArrayList(); + + public PolygonBuilder(GeometryFactory geometryFactory) + { + this.geometryFactory = geometryFactory; + } + + /** + * Add a complete graph. + * The graph is assumed to contain one or more polygons, + * possibly with holes. + */ + public void add(PlanarGraph graph) + { + add(graph.getEdgeEnds(), graph.getNodes()); + } + + /** + * Add a set of edges and nodes, which form a graph. + * The graph is assumed to contain one or more polygons, + * possibly with holes. + */ + public void add(Collection dirEdges, Collection nodes) + { + PlanarGraph.linkResultDirectedEdges(nodes); + List maxEdgeRings = buildMaximalEdgeRings(dirEdges); + List freeHoleList = new ArrayList(); + List edgeRings = buildMinimalEdgeRings(maxEdgeRings, shellList, freeHoleList); + sortShellsAndHoles(edgeRings, shellList, freeHoleList); + placeFreeHoles(shellList, freeHoleList); + //Assert: every hole on freeHoleList has a shell assigned to it + } + + public List getPolygons() + { + List resultPolyList = computePolygons(shellList); + return resultPolyList; + } + + + /** + * for all DirectedEdges in result, form them into MaximalEdgeRings + */ + private List buildMaximalEdgeRings(Collection dirEdges) + { + List maxEdgeRings = new ArrayList(); + for (Iterator it = dirEdges.iterator(); it.hasNext(); ) { + DirectedEdge de = (DirectedEdge) it.next(); + if (de.isInResult() && de.getLabel().isArea() ) { + // if this edge has not yet been processed + if (de.getEdgeRing() == null) { + MaximalEdgeRing er = new MaximalEdgeRing(de, geometryFactory); + maxEdgeRings.add(er); + er.setInResult(); +//System.out.println("max node degree = " + er.getMaxDegree()); + } + } + } + return maxEdgeRings; + } + + private List buildMinimalEdgeRings(List maxEdgeRings, List shellList, List freeHoleList) + { + List edgeRings = new ArrayList(); + for (Iterator it = maxEdgeRings.iterator(); it.hasNext(); ) { + MaximalEdgeRing er = (MaximalEdgeRing) it.next(); + if (er.getMaxNodeDegree() > 2) { + er.linkDirectedEdgesForMinimalEdgeRings(); + List minEdgeRings = er.buildMinimalRings(); + // at this point we can go ahead and attempt to place holes, if this EdgeRing is a polygon + EdgeRing shell = findShell(minEdgeRings); + if (shell != null) { + placePolygonHoles(shell, minEdgeRings); + shellList.add(shell); + } + else { + freeHoleList.addAll(minEdgeRings); + } + } + else { + edgeRings.add(er); + } + } + return edgeRings; + } + + /** + * This method takes a list of MinimalEdgeRings derived from a MaximalEdgeRing, + * and tests whether they form a Polygon. This is the case if there is a single shell + * in the list. In this case the shell is returned. + * The other possibility is that they are a series of connected holes, in which case + * no shell is returned. + * + * @return the shell EdgeRing, if there is one + * or null, if all the rings are holes + */ + private EdgeRing findShell(List minEdgeRings) + { + int shellCount = 0; + EdgeRing shell = null; + for (Iterator it = minEdgeRings.iterator(); it.hasNext(); ) { + EdgeRing er = (MinimalEdgeRing) it.next(); + if (! er.isHole()) { + shell = er; + shellCount++; + } + } + Assert.isTrue(shellCount <= 1, "found two shells in MinimalEdgeRing list"); + return shell; + } + /** + * This method assigns the holes for a Polygon (formed from a list of + * MinimalEdgeRings) to its shell. + * Determining the holes for a MinimalEdgeRing polygon serves two purposes: + *