java.org.locationtech.jts.triangulate.quadedge.QuadEdgeSubdivision 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.triangulate.quadedge;
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.CoordinateList;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineSegment;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.MultiLineString;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.Triangle;
import org.locationtech.jts.io.WKTWriter;
/**
* A class that contains the {@link QuadEdge}s representing a planar
* subdivision that models a triangulation.
* The subdivision is constructed using the
* quadedge algebra defined in the class {@link QuadEdge}.
* All metric calculations
* are done in the {@link Vertex} class.
* In addition to a triangulation, subdivisions
* support extraction of Voronoi diagrams.
* This is easily accomplished, since the Voronoi diagram is the dual
* of the Delaunay triangulation.
*
* Subdivisions can be provided with a tolerance value. Inserted vertices which
* are closer than this value to vertices already in the subdivision will be
* ignored. Using a suitable tolerance value can prevent robustness failures
* from happening during Delaunay triangulation.
*
* Subdivisions maintain a frame triangle around the client-created
* edges. The frame is used to provide a bounded "container" for all edges
* within a TIN. Normally the frame edges, frame connecting edges, and frame
* triangles are not included in client processing.
*
* @author David Skea
* @author Martin Davis
*/
public class QuadEdgeSubdivision {
/**
* Gets the edges for the triangle to the left of the given {@link QuadEdge}.
*
* @param startQE
* @param triEdge
*
* @throws IllegalArgumentException
* if the edges do not form a triangle
*/
public static void getTriangleEdges(QuadEdge startQE, QuadEdge[] triEdge) {
triEdge[0] = startQE;
triEdge[1] = triEdge[0].lNext();
triEdge[2] = triEdge[1].lNext();
if (triEdge[2].lNext() != triEdge[0])
throw new IllegalArgumentException("Edges do not form a triangle");
}
private final static double EDGE_COINCIDENCE_TOL_FACTOR = 1000;
// debugging only - preserve current subdiv statically
// private static QuadEdgeSubdivision currentSubdiv;
// used for edge extraction to ensure edge uniqueness
private int visitedKey = 0;
// private Set quadEdges = new HashSet();
private List quadEdges = new ArrayList();
private QuadEdge startingEdge;
private double tolerance;
private double edgeCoincidenceTolerance;
private Vertex[] frameVertex = new Vertex[3];
private Envelope frameEnv;
private QuadEdgeLocator locator = null;
/**
* Creates a new instance of a quad-edge subdivision based on a frame triangle
* that encloses a supplied bounding box. A new super-bounding box that
* contains the triangle is computed and stored.
*
* @param env
* the bounding box to surround
* @param tolerance
* the tolerance value for determining if two sites are equal
*/
public QuadEdgeSubdivision(Envelope env, double tolerance) {
// currentSubdiv = this;
this.tolerance = tolerance;
edgeCoincidenceTolerance = tolerance / EDGE_COINCIDENCE_TOL_FACTOR;
createFrame(env);
startingEdge = initSubdiv();
locator = new LastFoundQuadEdgeLocator(this);
}
private void createFrame(Envelope env)
{
double deltaX = env.getWidth();
double deltaY = env.getHeight();
double offset = 0.0;
if (deltaX > deltaY) {
offset = deltaX * 10.0;
} else {
offset = deltaY * 10.0;
}
frameVertex[0] = new Vertex((env.getMaxX() + env.getMinX()) / 2.0, env
.getMaxY()
+ offset);
frameVertex[1] = new Vertex(env.getMinX() - offset, env.getMinY() - offset);
frameVertex[2] = new Vertex(env.getMaxX() + offset, env.getMinY() - offset);
frameEnv = new Envelope(frameVertex[0].getCoordinate(), frameVertex[1]
.getCoordinate());
frameEnv.expandToInclude(frameVertex[2].getCoordinate());
}
private QuadEdge initSubdiv()
{
// build initial subdivision from frame
QuadEdge ea = makeEdge(frameVertex[0], frameVertex[1]);
QuadEdge eb = makeEdge(frameVertex[1], frameVertex[2]);
QuadEdge.splice(ea.sym(), eb);
QuadEdge ec = makeEdge(frameVertex[2], frameVertex[0]);
QuadEdge.splice(eb.sym(), ec);
QuadEdge.splice(ec.sym(), ea);
return ea;
}
/**
* Gets the vertex-equality tolerance value
* used in this subdivision
*
* @return the tolerance value
*/
public double getTolerance() {
return tolerance;
}
/**
* Gets the envelope of the Subdivision (including the frame).
*
* @return the envelope
*/
public Envelope getEnvelope() {
return new Envelope(frameEnv);
}
/**
* Gets the collection of base {@link QuadEdge}s (one for every pair of
* vertices which is connected).
*
* @return a collection of QuadEdges
*/
public Collection getEdges() {
return quadEdges;
}
/**
* Sets the {@link QuadEdgeLocator} to use for locating containing triangles
* in this subdivision.
*
* @param locator
* a QuadEdgeLocator
*/
public void setLocator(QuadEdgeLocator locator) {
this.locator = locator;
}
/**
* Creates a new quadedge, recording it in the edges list.
*
* @param o
* @param d
* @return a new quadedge
*/
public QuadEdge makeEdge(Vertex o, Vertex d) {
QuadEdge q = QuadEdge.makeEdge(o, d);
quadEdges.add(q);
return q;
}
/**
* Creates a new QuadEdge connecting the destination of a to the origin of b,
* in such a way that all three have the same left face after the connection
* is complete. The quadedge is recorded in the edges list.
*
* @param a
* @param b
* @return a quadedge
*/
public QuadEdge connect(QuadEdge a, QuadEdge b) {
QuadEdge q = QuadEdge.connect(a, b);
quadEdges.add(q);
return q;
}
/**
* Deletes a quadedge from the subdivision. Linked quadedges are updated to
* reflect the deletion.
*
* @param e
* the quadedge to delete
*/
public void delete(QuadEdge e) {
QuadEdge.splice(e, e.oPrev());
QuadEdge.splice(e.sym(), e.sym().oPrev());
QuadEdge eSym = e.sym();
QuadEdge eRot = e.rot();
QuadEdge eRotSym = e.rot().sym();
// this is inefficient on an ArrayList, but this method should be called infrequently
quadEdges.remove(e);
quadEdges.remove(eSym);
quadEdges.remove(eRot);
quadEdges.remove(eRotSym);
e.delete();
eSym.delete();
eRot.delete();
eRotSym.delete();
}
/**
* Locates an edge of a triangle which contains a location
* specified by a Vertex v.
* The edge returned has the
* property that either v is on e, or e is an edge of a triangle containing v.
* The search starts from startEdge amd proceeds on the general direction of v.
*
* This locate algorithm relies on the subdivision being Delaunay. For
* non-Delaunay subdivisions, this may loop for ever.
*
* @param v the location to search for
* @param startEdge an edge of the subdivision to start searching at
* @return a QuadEdge which contains v, or is on the edge of a triangle containing v
* @throws LocateFailureException
* if the location algorithm fails to converge in a reasonable
* number of iterations
*/
public QuadEdge locateFromEdge(Vertex v, QuadEdge startEdge) {
int iter = 0;
int maxIter = quadEdges.size();
QuadEdge e = startEdge;
while (true) {
iter++;
/**
* So far it has always been the case that failure to locate indicates an
* invalid subdivision. So just fail completely. (An alternative would be
* to perform an exhaustive search for the containing triangle, but this
* would mask errors in the subdivision topology)
*
* This can also happen if two vertices are located very close together,
* since the orientation predicates may experience precision failures.
*/
if (iter > maxIter) {
throw new LocateFailureException(e.toLineSegment());
// String msg = "Locate failed to converge (at edge: " + e + ").
// Possible causes include invalid Subdivision topology or very close
// sites";
// System.err.println(msg);
// dumpTriangles();
}
if ((v.equals(e.orig())) || (v.equals(e.dest()))) {
break;
} else if (v.rightOf(e)) {
e = e.sym();
} else if (!v.rightOf(e.oNext())) {
e = e.oNext();
} else if (!v.rightOf(e.dPrev())) {
e = e.dPrev();
} else {
// on edge or in triangle containing edge
break;
}
}
// System.out.println("Locate count: " + iter);
return e;
}
/**
* Finds a quadedge of a triangle containing a location
* specified by a {@link Vertex}, if one exists.
*
* @param v the vertex to locate
* @return a quadedge on the edge of a triangle which touches or contains the location
* or null if no such triangle exists
*/
public QuadEdge locate(Vertex v) {
return locator.locate(v);
}
/**
* Finds a quadedge of a triangle containing a location
* specified by a {@link Coordinate}, if one exists.
*
* @param p the Coordinate to locate
* @return a quadedge on the edge of a triangle which touches or contains the location
* or null if no such triangle exists
*/
public QuadEdge locate(Coordinate p) {
return locator.locate(new Vertex(p));
}
/**
* Locates the edge between the given vertices, if it exists in the
* subdivision.
*
* @param p0 a coordinate
* @param p1 another coordinate
* @return the edge joining the coordinates, if present
* or null if no such edge exists
*/
public QuadEdge locate(Coordinate p0, Coordinate p1) {
// find an edge containing one of the points
QuadEdge e = locator.locate(new Vertex(p0));
if (e == null)
return null;
// normalize so that p0 is origin of base edge
QuadEdge base = e;
if (e.dest().getCoordinate().equals2D(p0))
base = e.sym();
// check all edges around origin of base edge
QuadEdge locEdge = base;
do {
if (locEdge.dest().getCoordinate().equals2D(p1))
return locEdge;
locEdge = locEdge.oNext();
} while (locEdge != base);
return null;
}
/**
* Inserts a new site into the Subdivision, connecting it to the vertices of
* the containing triangle (or quadrilateral, if the split point falls on an
* existing edge).
*
* This method does NOT maintain the Delaunay condition. If desired, this must
* be checked and enforced by the caller.
*
* This method does NOT check if the inserted vertex falls on an edge. This
* must be checked by the caller, since this situation may cause erroneous
* triangulation
*
* @param v
* the vertex to insert
* @return a new quad edge terminating in v
*/
public QuadEdge insertSite(Vertex v) {
QuadEdge e = locate(v);
if ((v.equals(e.orig(), tolerance)) || (v.equals(e.dest(), tolerance))) {
return e; // point already in subdivision.
}
// Connect the new point to the vertices of the containing
// triangle (or quadrilateral, if the new point fell on an
// existing edge.)
QuadEdge base = makeEdge(e.orig(), v);
QuadEdge.splice(base, e);
QuadEdge startEdge = base;
do {
base = connect(e, base.sym());
e = base.oPrev();
} while (e.lNext() != startEdge);
return startEdge;
}
/**
* Tests whether a QuadEdge is an edge incident on a frame triangle vertex.
*
* @param e
* the edge to test
* @return true if the edge is connected to the frame triangle
*/
public boolean isFrameEdge(QuadEdge e) {
if (isFrameVertex(e.orig()) || isFrameVertex(e.dest()))
return true;
return false;
}
/**
* Tests whether a QuadEdge is an edge on the border of the frame facets and
* the internal facets. E.g. an edge which does not itself touch a frame
* vertex, but which touches an edge which does.
*
* @param e
* the edge to test
* @return true if the edge is on the border of the frame
*/
public boolean isFrameBorderEdge(QuadEdge e) {
// MD debugging
QuadEdge[] leftTri = new QuadEdge[3];
getTriangleEdges(e, leftTri);
// System.out.println(new QuadEdgeTriangle(leftTri).toString());
QuadEdge[] rightTri = new QuadEdge[3];
getTriangleEdges(e.sym(), rightTri);
// System.out.println(new QuadEdgeTriangle(rightTri).toString());
// check other vertex of triangle to left of edge
Vertex vLeftTriOther = e.lNext().dest();
if (isFrameVertex(vLeftTriOther))
return true;
// check other vertex of triangle to right of edge
Vertex vRightTriOther = e.sym().lNext().dest();
if (isFrameVertex(vRightTriOther))
return true;
return false;
}
/**
* Tests whether a vertex is a vertex of the outer triangle.
*
* @param v
* the vertex to test
* @return true if the vertex is an outer triangle vertex
*/
public boolean isFrameVertex(Vertex v) {
if (v.equals(frameVertex[0]))
return true;
if (v.equals(frameVertex[1]))
return true;
if (v.equals(frameVertex[2]))
return true;
return false;
}
private LineSegment seg = new LineSegment();
/**
* Tests whether a {@link Coordinate} lies on a {@link QuadEdge}, up to a
* tolerance determined by the subdivision tolerance.
*
* @param e
* a QuadEdge
* @param p
* a point
* @return true if the vertex lies on the edge
*/
public boolean isOnEdge(QuadEdge e, Coordinate p) {
seg.setCoordinates(e.orig().getCoordinate(), e.dest().getCoordinate());
double dist = seg.distance(p);
// heuristic (hack?)
return dist < edgeCoincidenceTolerance;
}
/**
* Tests whether a {@link Vertex} is the start or end vertex of a
* {@link QuadEdge}, up to the subdivision tolerance distance.
*
* @param e
* @param v
* @return true if the vertex is a endpoint of the edge
*/
public boolean isVertexOfEdge(QuadEdge e, Vertex v) {
if ((v.equals(e.orig(), tolerance)) || (v.equals(e.dest(), tolerance))) {
return true;
}
return false;
}
/**
* Gets the unique {@link Vertex}es in the subdivision,
* including the frame vertices if desired.
*
* @param includeFrame
* true if the frame vertices should be included
* @return a collection of the subdivision vertices
*
* @see #getVertexUniqueEdges
*/
public Collection getVertices(boolean includeFrame)
{
Set vertices = new HashSet();
for (Iterator i = quadEdges.iterator(); i.hasNext();) {
QuadEdge qe = (QuadEdge) i.next();
Vertex v = qe.orig();
//System.out.println(v);
if (includeFrame || ! isFrameVertex(v))
vertices.add(v);
/**
* Inspect the sym edge as well, since it is
* possible that a vertex is only at the
* dest of all tracked quadedges.
*/
Vertex vd = qe.dest();
//System.out.println(vd);
if (includeFrame || ! isFrameVertex(vd))
vertices.add(vd);
}
return vertices;
}
/**
* Gets a collection of {@link QuadEdge}s whose origin
* vertices are a unique set which includes
* all vertices in the subdivision.
* The frame vertices can be included if required.
*
* This is useful for algorithms which require traversing the
* subdivision starting at all vertices.
* Returning a quadedge for each vertex
* is more efficient than
* the alternative of finding the actual vertices
* using {@link #getVertices} and then locating
* quadedges attached to them.
*
* @param includeFrame true if the frame vertices should be included
* @return a collection of QuadEdge with the vertices of the subdivision as their origins
*/
public List getVertexUniqueEdges(boolean includeFrame)
{
List edges = new ArrayList();
Set visitedVertices = new HashSet();
for (Iterator i = quadEdges.iterator(); i.hasNext();) {
QuadEdge qe = (QuadEdge) i.next();
Vertex v = qe.orig();
//System.out.println(v);
if (! visitedVertices.contains(v)) {
visitedVertices.add(v);
if (includeFrame || ! isFrameVertex(v)) {
edges.add(qe);
}
}
/**
* Inspect the sym edge as well, since it is
* possible that a vertex is only at the
* dest of all tracked quadedges.
*/
QuadEdge qd = qe.sym();
Vertex vd = qd.orig();
//System.out.println(vd);
if (! visitedVertices.contains(vd)) {
visitedVertices.add(vd);
if (includeFrame || ! isFrameVertex(vd)) {
edges.add(qd);
}
}
}
return edges;
}
/**
* Gets all primary quadedges in the subdivision.
* A primary edge is a {@link QuadEdge}
* which occupies the 0'th position in its array of associated quadedges.
* These provide the unique geometric edges of the triangulation.
*
* @param includeFrame true if the frame edges are to be included
* @return a List of QuadEdges
*/
public List getPrimaryEdges(boolean includeFrame) {
visitedKey++;
List edges = new ArrayList();
Stack edgeStack = new Stack();
edgeStack.push(startingEdge);
Set visitedEdges = new HashSet();
while (!edgeStack.empty()) {
QuadEdge edge = (QuadEdge) edgeStack.pop();
if (! visitedEdges.contains(edge)) {
QuadEdge priQE = edge.getPrimary();
if (includeFrame || ! isFrameEdge(priQE))
edges.add(priQE);
edgeStack.push(edge.oNext());
edgeStack.push(edge.sym().oNext());
visitedEdges.add(edge);
visitedEdges.add(edge.sym());
}
}
return edges;
}
/**
* A TriangleVisitor which computes and sets the
* circumcentre as the origin of the dual
* edges originating in each triangle.
*
* @author mbdavis
*
*/
private static class TriangleCircumcentreVisitor implements TriangleVisitor
{
public TriangleCircumcentreVisitor() {
}
public void visit(QuadEdge[] triEdges)
{
Coordinate a = triEdges[0].orig().getCoordinate();
Coordinate b = triEdges[1].orig().getCoordinate();
Coordinate c = triEdges[2].orig().getCoordinate();
// TODO: choose the most accurate circumcentre based on the edges
Coordinate cc = Triangle.circumcentre(a, b, c);
Vertex ccVertex = new Vertex(cc);
// save the circumcentre as the origin for the dual edges originating in this triangle
for (int i = 0; i < 3; i++) {
triEdges[i].rot().setOrig(ccVertex);
}
}
}
/*****************************************************************************
* Visitors
****************************************************************************/
public void visitTriangles(TriangleVisitor triVisitor,
boolean includeFrame) {
visitedKey++;
// visited flag is used to record visited edges of triangles
// setVisitedAll(false);
Stack edgeStack = new Stack();
edgeStack.push(startingEdge);
Set visitedEdges = new HashSet();
while (!edgeStack.empty()) {
QuadEdge edge = (QuadEdge) edgeStack.pop();
if (! visitedEdges.contains(edge)) {
QuadEdge[] triEdges = fetchTriangleToVisit(edge, edgeStack,
includeFrame, visitedEdges);
if (triEdges != null)
triVisitor.visit(triEdges);
}
}
}
/**
* The quadedges forming a single triangle.
* Only one visitor is allowed to be active at a
* time, so this is safe.
*/
private QuadEdge[] triEdges = new QuadEdge[3];
/**
* Stores the edges for a visited triangle. Also pushes sym (neighbour) edges
* on stack to visit later.
*
* @param edge
* @param edgeStack
* @param includeFrame
* @return the visited triangle edges
* or null if the triangle should not be visited (for instance, if it is
* outer)
*/
private QuadEdge[] fetchTriangleToVisit(QuadEdge edge, Stack edgeStack,
boolean includeFrame, Set visitedEdges) {
QuadEdge curr = edge;
int edgeCount = 0;
boolean isFrame = false;
do {
triEdges[edgeCount] = curr;
if (isFrameEdge(curr))
isFrame = true;
// push sym edges to visit next
QuadEdge sym = curr.sym();
if (! visitedEdges.contains(sym))
edgeStack.push(sym);
// mark this edge as visited
visitedEdges.add(curr);
edgeCount++;
curr = curr.lNext();
} while (curr != edge);
if (isFrame && !includeFrame)
return null;
return triEdges;
}
/**
* Gets a list of the triangles
* in the subdivision, specified as
* an array of the primary quadedges around the triangle.
*
* @param includeFrame
* true if the frame triangles should be included
* @return a List of QuadEdge[3] arrays
*/
public List getTriangleEdges(boolean includeFrame) {
TriangleEdgesListVisitor visitor = new TriangleEdgesListVisitor();
visitTriangles(visitor, includeFrame);
return visitor.getTriangleEdges();
}
private static class TriangleEdgesListVisitor implements TriangleVisitor {
private List triList = new ArrayList();
public void visit(QuadEdge[] triEdges) {
triList.add(triEdges);
}
public List getTriangleEdges() {
return triList;
}
}
/**
* Gets a list of the triangles in the subdivision,
* specified as an array of the triangle {@link Vertex}es.
*
* @param includeFrame
* true if the frame triangles should be included
* @return a List of Vertex[3] arrays
*/
public List getTriangleVertices(boolean includeFrame) {
TriangleVertexListVisitor visitor = new TriangleVertexListVisitor();
visitTriangles(visitor, includeFrame);
return visitor.getTriangleVertices();
}
private static class TriangleVertexListVisitor implements TriangleVisitor {
private List triList = new ArrayList();
public void visit(QuadEdge[] triEdges) {
triList.add(new Vertex[] { triEdges[0].orig(), triEdges[1].orig(),
triEdges[2].orig() });
}
public List getTriangleVertices() {
return triList;
}
}
/**
* Gets the coordinates for each triangle in the subdivision as an array.
*
* @param includeFrame
* true if the frame triangles should be included
* @return a list of Coordinate[4] representing each triangle
*/
public List getTriangleCoordinates(boolean includeFrame) {
TriangleCoordinatesVisitor visitor = new TriangleCoordinatesVisitor();
visitTriangles(visitor, includeFrame);
return visitor.getTriangles();
}
private static class TriangleCoordinatesVisitor implements TriangleVisitor {
private CoordinateList coordList = new CoordinateList();
private List triCoords = new ArrayList();
public TriangleCoordinatesVisitor() {
}
public void visit(QuadEdge[] triEdges) {
coordList.clear();
for (int i = 0; i < 3; i++) {
Vertex v = triEdges[i].orig();
coordList.add(v.getCoordinate());
}
if (coordList.size() > 0) {
coordList.closeRing();
Coordinate[] pts = coordList.toCoordinateArray();
if (pts.length != 4) {
//checkTriangleSize(pts);
return;
}
triCoords.add(pts);
}
}
private void checkTriangleSize(Coordinate[] pts)
{
String loc = "";
if (pts.length >= 2)
loc = WKTWriter.toLineString(pts[0], pts[1]);
else {
if (pts.length >= 1)
loc = WKTWriter.toPoint(pts[0]);
}
// Assert.isTrue(pts.length == 4, "Too few points for visited triangle at " + loc);
//com.vividsolutions.jts.util.Debug.println("too few points for triangle at " + loc);
}
public List getTriangles() {
return triCoords;
}
}
/**
* Gets the geometry for the edges in the subdivision as a {@link MultiLineString}
* containing 2-point lines.
*
* @param geomFact the GeometryFactory to use
* @return a MultiLineString
*/
public Geometry getEdges(GeometryFactory geomFact) {
List quadEdges = getPrimaryEdges(false);
LineString[] edges = new LineString[quadEdges.size()];
int i = 0;
for (Iterator it = quadEdges.iterator(); it.hasNext();) {
QuadEdge qe = (QuadEdge) it.next();
edges[i++] = geomFact.createLineString(new Coordinate[] {
qe.orig().getCoordinate(), qe.dest().getCoordinate() });
}
return geomFact.createMultiLineString(edges);
}
/**
* Gets the geometry for the triangles in a triangulated subdivision as a {@link GeometryCollection}
* of triangular {@link Polygon}s.
*
* @param geomFact the GeometryFactory to use
* @return a GeometryCollection of triangular Polygons
*/
public Geometry getTriangles(GeometryFactory geomFact) {
List triPtsList = getTriangleCoordinates(false);
Polygon[] tris = new Polygon[triPtsList.size()];
int i = 0;
for (Iterator it = triPtsList.iterator(); it.hasNext();) {
Coordinate[] triPt = (Coordinate[]) it.next();
tris[i++] = geomFact
.createPolygon(geomFact.createLinearRing(triPt));
}
return geomFact.createGeometryCollection(tris);
}
/**
* Gets the cells in the Voronoi diagram for this triangulation.
* The cells are returned as a {@link GeometryCollection} of {@link Polygon}s
*
* The userData of each polygon is set to be the {@link Coordinate}
* of the cell site. This allows easily associating external
* data associated with the sites to the cells.
*
* @param geomFact a geometry factory
* @return a GeometryCollection of Polygons
*/
public Geometry getVoronoiDiagram(GeometryFactory geomFact)
{
List vorCells = getVoronoiCellPolygons(geomFact);
return geomFact.createGeometryCollection(GeometryFactory.toGeometryArray(vorCells));
}
/**
* Gets a List of {@link Polygon}s for the Voronoi cells
* of this triangulation.
*
* The userData of each polygon is set to be the {@link Coordinate}
* of the cell site. This allows easily associating external
* data associated with the sites to the cells.
*
* @param geomFact a geometry factory
* @return a List of Polygons
*/
public List getVoronoiCellPolygons(GeometryFactory geomFact)
{
/*
* Compute circumcentres of triangles as vertices for dual edges.
* Precomputing the circumcentres is more efficient,
* and more importantly ensures that the computed centres
* are consistent across the Voronoi cells.
*/
visitTriangles(new TriangleCircumcentreVisitor(), true);
List cells = new ArrayList();
Collection edges = getVertexUniqueEdges(false);
for (Iterator i = edges.iterator(); i.hasNext(); ) {
QuadEdge qe = (QuadEdge) i.next();
cells.add(getVoronoiCellPolygon(qe, geomFact));
}
return cells;
}
/**
* Gets the Voronoi cell around a site specified
* by the origin of a QuadEdge.
*
* The userData of the polygon is set to be the {@link Coordinate}
* of the site. This allows attaching external
* data associated with the site to this cell polygon.
*
* @param qe a quadedge originating at the cell site
* @param geomFact a factory for building the polygon
* @return a polygon indicating the cell extent
*/
public Polygon getVoronoiCellPolygon(QuadEdge qe, GeometryFactory geomFact)
{
List cellPts = new ArrayList();
QuadEdge startQE = qe;
do {
// Coordinate cc = circumcentre(qe);
// use previously computed circumcentre
Coordinate cc = qe.rot().orig().getCoordinate();
cellPts.add(cc);
// move to next triangle CW around vertex
qe = qe.oPrev();
} while (qe != startQE);
CoordinateList coordList = new CoordinateList();
coordList.addAll(cellPts, false);
coordList.closeRing();
if (coordList.size() < 4) {
System.out.println(coordList);
coordList.add(coordList.get(coordList.size()-1), true);
}
Coordinate[] pts = coordList.toCoordinateArray();
Polygon cellPoly = geomFact.createPolygon(geomFact.createLinearRing(pts));
Vertex v = startQE.orig();
cellPoly.setUserData(v.getCoordinate());
return cellPoly;
}
}