Skip to main content

A clip round the ear

Inspired by a recent discussion on the JTS user list about decomposing simple polygons into triangles, here is a program that demonstrates the brute force, ear-clipping algorithm. To run this you will need the JTS library which you can get from the project web site or as a maven artifact (version 1.11) from the Maven central repository.


import com.vividsolutions.jts.algorithm.Angle;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.io.WKTReader;
import java.util.ArrayList;
import java.util.List;

/**
* Demonstrates brute force approach to the ear clipping algorithm
* to triangulate a polygon. This demo doesn't yet cope with
* polygons that have holes.
*
* @author Michael Bedward
*/
public class EarClipping {
private static final double EPS = 1.0E-4;

GeometryFactory gf = new GeometryFactory();

public static void main(String[] args) throws Exception {
new EarClipping().demo();
}

/**
* Demonstrate the ear-clipping algorithm
* @throws Exception
*/
private void demo() throws Exception {
WKTReader reader = new WKTReader(gf);
Polygon poly = (Polygon )reader.read("POLYGON((0 0, 5 0, 2 3, 5 5, 10 0, 10 -5, 5 -2, 0 -5, 0 0))");
System.out.println("Input polygon:");
System.out.println(poly);

Geometry ears = triangulate(poly);
final int n = ears.getNumGeometries();

System.out.println();
System.out.println("Found " + n + " ears:");
for (int i = 0; i < n; i++) {
System.out.println(ears.getGeometryN(i));
}
}

/**
* Brute force approach to ear clipping
*
* @param inputPoly input polygon
* @return GeometryCollection of triangular polygons
*/
private Geometry triangulate(Polygon inputPoly) {
if (inputPoly.getNumInteriorRing() > 0) {
throw new IllegalArgumentException("Can't deal with polgons that have holes yet");
}

List<Polygon> ears = new ArrayList<Polygon>();
Polygon workingPoly = (Polygon) inputPoly.clone();

Coordinate[] coords = workingPoly.getBoundary().getCoordinates();
coords = removeColinearVertices(coords);
int N = coords.length - 1;

boolean finished = false;
int k0 = 0;
do {
int k1 = (k0 + 1) % N;
int k2 = (k0 + 2) % N;
LineString ls = gf.createLineString(new Coordinate[] {coords[k0], coords[k2]});

if (workingPoly.covers(ls)) {
Polygon ear = gf.createPolygon(
gf.createLinearRing(new Coordinate[]{coords[k0], coords[k1], coords[k2], coords[k0]}),
null);
ears.add(ear);
workingPoly = (Polygon) workingPoly.difference(ear);
coords = workingPoly.getBoundary().getCoordinates();
coords = removeColinearVertices(coords);
N = coords.length - 1;
k0 = 0;
if (N == 3) { // triangle
ears.add(gf.createPolygon(gf.createLinearRing(coords), null));
finished = true;
}
} else {
k0++ ;
}
} while (!finished);

return gf.createGeometryCollection(ears.toArray(new Geometry[0]));
}

/**
* Remove co-linear vertices. TopologyPreservingSimplifier could be
* used for this but that seems like over-kill.
*
* @param coords polygon vertices
* @return coordinates with any co-linear vertices removed
*/
private Coordinate[] removeColinearVertices(Coordinate[] coords) {
final int N = coords.length - 1;
List<Coordinate> coordList = new ArrayList<Coordinate>();

for (int j = 1; j <= N; j++) {
int i = (j - 1) % N;
int k = (j + 1) % N;
if (Math.abs(Math.PI - Angle.angleBetween(coords[i], coords[j], coords[k])) > EPS) {
coordList.add(coords[j]);
}
}

coordList.add(new Coordinate(coordList.get(0)));
return coordList.toArray(new Coordinate[0]);
}

}

Comments

Popular posts from this blog

Fitting an ellipse to point data

Some time ago I wrote an R function to fit an ellipse to point data, using an algorithm developed by Radim Halíř and Jan Flusser 1 in Matlab, and posted it to the r-help list . The implementation was a bit hacky, returning odd results for some data. A couple of days ago, an email arrived from John Minter asking for a pointer to the original code. I replied with a link and mentioned that I'd be interested to know if John made any improvements to the code. About ten minutes later, John emailed again with a much improved version ! Not only is it more reliable, but also more efficient. So with many thanks to John, here is the improved code: fit.ellipse <- function (x, y = NULL) { # from: # http://r.789695.n4.nabble.com/Fitting-a-half-ellipse-curve-tp2719037p2720560.html # # Least squares fitting of an ellipse to point data # using the algorithm described in: # Radim Halir & Jan Flusser. 1998. # Numerically stable direct least squares fitting of ellipses

Circle packing in R (again)

Back in 2010 I posted some R code for circle packing . Now, just five years later, I've ported the code to Rcpp and created a little package which you can find at GitHub . The main function is circleLayout which takes a set of overlapping circles and tries to find a non-overlapping arrangement for them. Here's an example: And here's the code: # Create some random circles, positioned within the central portion # of a bounding square, with smaller circles being more common than # larger ones. ncircles <- 200 limits <- c(-50, 50) inset <- diff(limits) / 3 rmax <- 20 xyr <- data.frame( x = runif(ncircles, min(limits) + inset, max(limits) - inset), y = runif(ncircles, min(limits) + inset, max(limits) - inset), r = rbeta(ncircles, 1, 10) * rmax) # Next, we use the `circleLayout` function to try to find a non-overlapping # arrangement, allowing the circles to occupy any part of the bounding square. # The returned value is a list with elements for

Graph-based circle packing

The previous two posts showed examples of a simple circle packing algorithm using the packcircles package (available from CRAN and GitHub ). The algorithm involved iterative pair-repulsion to jiggle the circles until (hopefully) a non-overlapping arrangement emerged. In this post we'll look an alternative approach. An algorithm to find an arrangement of circles satisfying a prior specification of circle sizes and tangencies was described by Collins and Stephenson in their 2003 paper in Computation Geometry Theory and Applications. A version of their algorithm was implemented in Python by David Eppstein as part of his PADS library (see CirclePack.py ). I've now ported David's version to R/Rcpp and included it in the packcircles package. In the figure below, the graph on the left represents the desired pattern of circle tangencies: e.g. circle 7 should touch all of, and only, circles 1, 2, 6 and 8. Circles 5, 7, 8 and 9 are internal , while the remaining circles are exter