<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-3727209628133289053</id><updated>2012-02-16T17:53:34.836-08:00</updated><category term='geometry'/><category term='polygon'/><category term='black arts'/><category term='triangulate'/><category term='jts'/><category term='splines'/><category term='jsr 275'/><category term='bezier'/><category term='pragprowrimo'/><category term='curve fitting'/><category term='R'/><title type='text'>Last Resort Software</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://lastresortsoftware.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://lastresortsoftware.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Michael Bedward</name><uri>http://www.blogger.com/profile/17736007204837985875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_uGUChmI9xjo/SEjuW8gefdI/AAAAAAAAAFI/gU20EaCKr30/S220/snail.png'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>14</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-3727209628133289053.post-2982190725665933139</id><published>2010-12-06T18:21:00.000-08:00</published><updated>2010-12-06T20:51:13.055-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='curve fitting'/><category scheme='http://www.blogger.com/atom/ns#' term='polygon'/><category scheme='http://www.blogger.com/atom/ns#' term='jts'/><category scheme='http://www.blogger.com/atom/ns#' term='bezier'/><title type='text'>Smooth as...</title><content type='html'>For a current project I needed a polygon smoothing algorithm in Java that:&lt;ol type="1"&gt;&lt;br /&gt;&lt;li&gt; generated a curve passing through all polygon vertices&lt;/li&gt;&lt;br /&gt;&lt;li&gt; had some way of controlling tightness of fit to the polygon&lt;/li&gt;&lt;br /&gt;&lt;li&gt; fell within the domain of my very limited mathematical grasp&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;Luckily for me, Maxim Shemanarev had not only worked out a Bezier curve algorithm that satisfied all of the above, but he also published a beautifully clear explanation of how it works&lt;a href="smooth-as.html#note1"&gt;&lt;sup&gt;note 1&lt;/sup&gt;&lt;/a&gt;. Maxim also provided some illustrative C code, but my Java implementation below is an independent effort (ie. all mistakes are my fault). It uses geometry objects from the &lt;a href="http://sourceforge.net/projects/jts-topo-suite/"&gt;JTS library&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Below is an example of the algorithm in action. The original polygon is in black. The smoothed polygon composed of fitted cubic Bezier segments is in blue. The red points and lines indicate positions of the Bezier control points calculated with Maxim's algorithm. There are a pair of control points for each vertex of the input polygon.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_uGUChmI9xjo/TP2qMoN2CHI/AAAAAAAAAQs/YFcxTpCp--c/s1600/Screen%2Bshot%2B2010-12-07%2Bat%2B2.28.14%2BPM.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 385px; height: 400px;" src="http://4.bp.blogspot.com/_uGUChmI9xjo/TP2qMoN2CHI/AAAAAAAAAQs/YFcxTpCp--c/s400/Screen%2Bshot%2B2010-12-07%2Bat%2B2.28.14%2BPM.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5547777449920956530" /&gt;&lt;/a&gt;&lt;br /&gt;The tightness of fit of the smoothed boundary to the original boundary is controlled by a single parameter which varies between 0 and 1. In the figure above the tightness was set to 0. Below is the same polygon smoothed with tightness set to 0.6.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_uGUChmI9xjo/TP2rVq898lI/AAAAAAAAAQ0/vavY9icSoC0/s1600/Screen%2Bshot%2B2010-12-07%2Bat%2B2.33.42%2BPM.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 385px; height: 400px;" src="http://3.bp.blogspot.com/_uGUChmI9xjo/TP2rVq898lI/AAAAAAAAAQ0/vavY9icSoC0/s400/Screen%2Bshot%2B2010-12-07%2Bat%2B2.33.42%2BPM.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5547778704785928786" /&gt;&lt;/a&gt;&lt;br /&gt;Notice that the higher tightness parameter has shifted each pair of control points closer to their respective polygon vertices.&lt;br /&gt;&lt;br /&gt;Finally, here's the Java code. Share and enjoy...&lt;br /&gt;&lt;pre class="code"&gt;/*&lt;br /&gt;* Copyright 2010 Michael Bedward&lt;br /&gt;*&lt;br /&gt;* This file is part of jai-tools.&lt;br /&gt;*&lt;br /&gt;* jai-tools is free software: you can redistribute it and/or modify&lt;br /&gt;* it under the terms of the GNU Lesser General Public License as&lt;br /&gt;* published by the Free Software Foundation, either version 3 of the&lt;br /&gt;* License, or (at your option) any later version.&lt;br /&gt;*&lt;br /&gt;* jai-tools is distributed in the hope that it will be useful,&lt;br /&gt;* but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br /&gt;* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;br /&gt;* GNU Lesser General Public License for more details.&lt;br /&gt;*&lt;br /&gt;* You should have received a copy of the GNU Lesser General Public&lt;br /&gt;* License along with jai-tools.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;package mxb.interpolation;&lt;br /&gt;&lt;br /&gt;import com.vividsolutions.jts.geom.Coordinate;&lt;br /&gt;import com.vividsolutions.jts.geom.GeometryFactory;&lt;br /&gt;import com.vividsolutions.jts.geom.LinearRing;&lt;br /&gt;import com.vividsolutions.jts.geom.Polygon;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;* Polygon smoothing by interpolation with cubic Bazier curves.&lt;br /&gt;* This code implements an algorithm which was devised by Maxim Shemanarev&lt;br /&gt;* and described by him at:&lt;br /&gt;* &amp;lt;a href="http://www.antigrain.com/research/bezier_interpolation/index.html"&amp;gt;&lt;br /&gt;* http://www.antigrain.com/research/bezier_interpolation/index.html&amp;lt;/a&amp;gt;.&lt;br /&gt;*&lt;br /&gt;* Note: the code here is &amp;lt;b&amp;gt;not&amp;lt;/b&amp;gt; that written by Maxim to accompany his&lt;br /&gt;* algorithm description. Rather, it is an original implementation and any&lt;br /&gt;* errors are my fault.&lt;br /&gt;*/&lt;br /&gt;public class PolygonSmoother {&lt;br /&gt;  private GeometryFactory geomFactory;&lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   * Calculates a pair of Bezier control points for each vertex in an&lt;br /&gt;   * array of {@code Coordinates}.&lt;br /&gt;   *&lt;br /&gt;   * @param coords vertex coordinates&lt;br /&gt;   * @param N number of coordinates in {@coords} to use&lt;br /&gt;   * @param alpha tightness of fit&lt;br /&gt;   *&lt;br /&gt;   * @return array of {@code Coordinates} for positions of control points&lt;br /&gt;   */&lt;br /&gt;  private Coordinate[][] getControlPoints(Coordinate[] coords, int N, double alpha) {&lt;br /&gt;      if (alpha &amp;lt; 0.0 || alpha &amp;gt; 1.0) {&lt;br /&gt;          throw new IllegalArgumentException("alpha must be a value between 0 and 1 inclusive");&lt;br /&gt;      }&lt;br /&gt;&lt;br /&gt;      Coordinate[][] ctrl = new Coordinate[N][2];&lt;br /&gt;&lt;br /&gt;      Coordinate[] v = new Coordinate[3];&lt;br /&gt;&lt;br /&gt;      Coordinate[] mid = new Coordinate[2];&lt;br /&gt;      mid[0] = new Coordinate();&lt;br /&gt;      mid[1] = new Coordinate();&lt;br /&gt;&lt;br /&gt;      Coordinate anchor = new Coordinate();&lt;br /&gt;      double[] vdist = new double[2];&lt;br /&gt;      double mdist;&lt;br /&gt;&lt;br /&gt;      v[1] = coords[N - 1];&lt;br /&gt;      v[2] = coords[0];&lt;br /&gt;      mid[1].x = (v[1].x + v[2].x) / 2.0;&lt;br /&gt;      mid[1].y = (v[1].y + v[2].y) / 2.0;&lt;br /&gt;      vdist[1] = v[1].distance(v[2]);&lt;br /&gt;&lt;br /&gt;      for (int i = 0; i &amp;lt; N; i++) {&lt;br /&gt;          v[0] = v[1];&lt;br /&gt;          v[1] = v[2];&lt;br /&gt;          v[2] = coords[(i + 1) % N];&lt;br /&gt;&lt;br /&gt;          mid[0].x = mid[1].x;&lt;br /&gt;          mid[0].y = mid[1].y;&lt;br /&gt;          mid[1].x = (v[1].x + v[2].x) / 2.0;&lt;br /&gt;          mid[1].y = (v[1].y + v[2].y) / 2.0;&lt;br /&gt;&lt;br /&gt;          vdist[0] = vdist[1];&lt;br /&gt;          vdist[1] = v[1].distance(v[2]);&lt;br /&gt;&lt;br /&gt;          double p = vdist[0] / (vdist[0] + vdist[1]);&lt;br /&gt;          anchor.x = mid[0].x + p * (mid[1].x - mid[0].x);&lt;br /&gt;          anchor.y = mid[0].y + p * (mid[1].y - mid[0].y);&lt;br /&gt;&lt;br /&gt;          double xdelta = anchor.x - v[1].x;&lt;br /&gt;          double ydelta = anchor.y - v[1].y;&lt;br /&gt;&lt;br /&gt;          ctrl[i][0] = new Coordinate(&lt;br /&gt;                  alpha*(v[1].x - mid[0].x + xdelta) + mid[0].x - xdelta,&lt;br /&gt;                  alpha*(v[1].y - mid[0].y + ydelta) + mid[0].y - ydelta);&lt;br /&gt;&lt;br /&gt;          ctrl[i][1] = new Coordinate(&lt;br /&gt;                  alpha*(v[1].x - mid[1].x + xdelta) + mid[1].x - xdelta,&lt;br /&gt;                  alpha*(v[1].y - mid[1].y + ydelta) + mid[1].y - ydelta);&lt;br /&gt;      }&lt;br /&gt;&lt;br /&gt;      return ctrl;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   * Calculates vertices along a cubic Bazier curve given start point, end point&lt;br /&gt;   * and two control points.&lt;br /&gt;   *&lt;br /&gt;   * @param start start position&lt;br /&gt;   * @param end end position&lt;br /&gt;   * @param ctrl1 first control point&lt;br /&gt;   * @param ctrl2 second control point&lt;br /&gt;   * @param nv number of vertices including the start and end points&lt;br /&gt;   *&lt;br /&gt;   * @return vertices along the Bezier curve&lt;br /&gt;   */&lt;br /&gt;  private Coordinate[] cubicBezier(final Coordinate start, final Coordinate end,&lt;br /&gt;          final Coordinate ctrl1, final Coordinate ctrl2, final int nv) {&lt;br /&gt;            &lt;br /&gt;      final Coordinate[] curve = new Coordinate[nv];&lt;br /&gt;    &lt;br /&gt;      final Coordinate[] buf = new Coordinate[3];&lt;br /&gt;      for (int i = 0; i &amp;lt; buf.length; i++) {&lt;br /&gt;          buf[i] = new Coordinate();&lt;br /&gt;      }&lt;br /&gt;    &lt;br /&gt;      curve[0] = new Coordinate(start);&lt;br /&gt;      curve[nv - 1] = new Coordinate(end);&lt;br /&gt;&lt;br /&gt;      for (int i = 1; i &amp;lt; nv-1; i++) {&lt;br /&gt;          double t = (double) i / (nv - 1);&lt;br /&gt;          double tc = 1.0 - t;&lt;br /&gt;&lt;br /&gt;          double t0 = tc*tc*tc;&lt;br /&gt;          double t1 = 3.0*tc*tc*t;&lt;br /&gt;          double t2 = 3.0*tc*t*t;&lt;br /&gt;          double t3 = t*t*t;&lt;br /&gt;          double tsum = t0 + t1 + t2 + t3;&lt;br /&gt;&lt;br /&gt;          Coordinate c = new Coordinate();&lt;br /&gt;&lt;br /&gt;          c.x = t0*start.x + t1*ctrl1.x + t2*ctrl2.x + t3*end.x;&lt;br /&gt;          c.x /= tsum;&lt;br /&gt;          c.y = t0*start.y + t1*ctrl1.y + t2*ctrl2.y + t3*end.y;&lt;br /&gt;          c.y /= tsum;&lt;br /&gt;&lt;br /&gt;          curve[i] = c;&lt;br /&gt;      }&lt;br /&gt;&lt;br /&gt;      return curve;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   * Creates a new {@code Polygon} whose exterior shell is a smoothed&lt;br /&gt;   * version of the input {@code Polygon}.&lt;br /&gt;   * &amp;lt;p&amp;gt;&lt;br /&gt;   * Note: this method presently ignores holes.&lt;br /&gt;   *&lt;br /&gt;   * @param p the input {@code Polygon}&lt;br /&gt;   *&lt;br /&gt;   * @param alpha a value between 0 and 1 (inclusive) specifying the tightness&lt;br /&gt;   *        of fit of the smoothed boundary (0 is loose)&lt;br /&gt;   *&lt;br /&gt;   * @param pointsPerSegment the number of vertices to use to represent each&lt;br /&gt;   *        Bezier curve derived from an edge of the input {@code Polygon}&lt;br /&gt;   *&lt;br /&gt;   * @return the smoothed {@code Polygon}&lt;br /&gt;   */&lt;br /&gt;  public Polygon smooth(Polygon p, double alpha, int pointsPerSegment) {&lt;br /&gt;    &lt;br /&gt;      if (geomFactory == null) {&lt;br /&gt;          geomFactory = new GeometryFactory();&lt;br /&gt;      }&lt;br /&gt;    &lt;br /&gt;      Coordinate[] coords = p.getExteriorRing().getCoordinates();&lt;br /&gt;      int Nvertices = coords.length - 1;  // first coord == last coord&lt;br /&gt;    &lt;br /&gt;      Coordinate[][] controlPoints = getControlPoints(coords, Nvertices, alpha);&lt;br /&gt;    &lt;br /&gt;      Coordinate[] smoothCoords = new Coordinate[Nvertices * pointsPerSegment];&lt;br /&gt;      for (int i = 0, k = 0; i &amp;lt; Nvertices; i++) {&lt;br /&gt;          int next = (i + 1) % Nvertices;&lt;br /&gt;        &lt;br /&gt;          Coordinate[] segment = cubicBezier(&lt;br /&gt;                  coords[i], coords[next],&lt;br /&gt;                  controlPoints[i][1], controlPoints[next][0],&lt;br /&gt;                  pointsPerSegment);&lt;br /&gt;        &lt;br /&gt;          for (int j = 0; j &amp;lt; pointsPerSegment; j++, k++) {&lt;br /&gt;              smoothCoords[k] = segment[j];&lt;br /&gt;          }&lt;br /&gt;      }&lt;br /&gt;    &lt;br /&gt;      LinearRing shell = geomFactory.createLinearRing(smoothCoords);&lt;br /&gt;      return geomFactory.createPolygon(shell, null);&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;a name="note1"&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt; Maxim Shemanarev's algorithm &lt;a href="http://www.antigrain.com/research/bezier_interpolation/index.html"&gt;http://www.antigrain.com/research/bezier_interpolation/index.html&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3727209628133289053-2982190725665933139?l=lastresortsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lastresortsoftware.blogspot.com/feeds/2982190725665933139/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://lastresortsoftware.blogspot.com/2010/12/smooth-as.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/2982190725665933139'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/2982190725665933139'/><link rel='alternate' type='text/html' href='http://lastresortsoftware.blogspot.com/2010/12/smooth-as.html' title='Smooth as...'/><author><name>Michael Bedward</name><uri>http://www.blogger.com/profile/17736007204837985875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_uGUChmI9xjo/SEjuW8gefdI/AAAAAAAAAFI/gU20EaCKr30/S220/snail.png'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_uGUChmI9xjo/TP2qMoN2CHI/AAAAAAAAAQs/YFcxTpCp--c/s72-c/Screen%2Bshot%2B2010-12-07%2Bat%2B2.28.14%2BPM.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3727209628133289053.post-5664348965131170872</id><published>2010-09-01T00:23:00.000-07:00</published><updated>2010-09-01T00:28:42.095-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='R'/><title type='text'>Monte Carlo testing of classification groups</title><content type='html'>This is another article on the theme of defining groups in a hierarchical classification. A previous article described &lt;a href="http://lastresortsoftware.blogspot.com/2010/08/homogeneity-analysis-of-hierarchical.html"&gt;homogeneity analysis&lt;/a&gt; to visualize how any well any number of groups, defined at the same level accounts for the variability in the dataset, as measured by within-group pairwise distances. Here we will look at testing whether splitting a particular group into two daughter groups is supported by the data.&lt;br /&gt;&lt;br /&gt;Suppose we have a group G containing N objects which we are considering splitting into two groups G1 and G2 consisting of N1 and N2 objects respectively. A Monte Carlo approach to testing whether this is worth doing is as follows:&lt;br /&gt;&lt;br /&gt;1. Calculate the mean within-group pairwise distance that would result from defining G1 and G2.&lt;br /&gt;&lt;br /&gt;2. Randomly allocate objects to two groups of sizes N1 and N2 and calculate the resulting mean within-group pairwise distance.&lt;br /&gt;&lt;br /&gt;3. Repeat step 2 a large number of times (e.g. 999) to create a vector of Monte Carlo means. Add the actual mean from step 1 to this vector.&lt;br /&gt;&lt;br /&gt;4. Calculate an empirical probability from the position of the actual mean in the sorted vector from step 3.&lt;br /&gt;&lt;br /&gt;If the groups G1 and G2 are meaningful, the probability from step 4 should be low, ie. the actual mean should be lower than most or all of the Monte Carlo means.&lt;br /&gt;&lt;br /&gt;Below is the R code for this algorithm. It takes as input a distance matrix; a vector of group IDs for all objects; the IDs of the two groups being tested and the number of Monte Carlo means to sample.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;&lt;br /&gt;testGroups &lt;- function (d, grp, g1=1, g2=2, nsamples=999)&lt;br /&gt;{&lt;br /&gt;# Performs a Monte-Carlo test of the informativeness of two groups of&lt;br /&gt;# objects by comparing the actual mean of within-group pairwise distances&lt;br /&gt;# to a vector of means derived by randomly allocating objects to the&lt;br /&gt;# two groups. If the two groups are informative, the actual mean should&lt;br /&gt;# be less than most or all of the Monte-Carlo means.&lt;br /&gt;#&lt;br /&gt;# Arguments:&lt;br /&gt;# d - distance matrix&lt;br /&gt;# grp - vector of group ids for all objects in the distance matrix&lt;br /&gt;#       (ids of objects outside the two groups being considered can&lt;br /&gt;#        be set to arbitrary values)&lt;br /&gt;# g1 - id of first group to be tested&lt;br /&gt;# g2 - id of second group to be tested&lt;br /&gt;# nsamples - number of random groupings to generate (if possible)&lt;br /&gt;#&lt;br /&gt;# The function attempts to generate unique allocations using the following rules:&lt;br /&gt;#&lt;br /&gt;# 1. If nsamples is greater than choose(N1+N2, N1) it is reduced accordingly &lt;br /&gt;#    and a warning message issued. All unique allocations are then &lt;br /&gt;#    generated systematically.&lt;br /&gt;#&lt;br /&gt;# 2. If nsamples is greater than choose(N1+N2, N1) / 20 allocations are generated&lt;br /&gt;#    systematically to guarantee uniqueness. &lt;br /&gt;#&lt;br /&gt;# 3. If nsamples is less than choose(N1+N2, N1) / 20, allocations are&lt;br /&gt;#    generated randomly with no check for uniqueness to reduce running&lt;br /&gt;#    time.&lt;br /&gt;#&lt;br /&gt;# The function invisibly returns a list with the following elements:&lt;br /&gt;# actual.mean - the mean within-group pairwise distance for actual groupings&lt;br /&gt;# prob - probability of observing a mean less than or equal to the actual&lt;br /&gt;#        mean calculated by comparing the actual value to a vector of &lt;br /&gt;#        means from random allocations plus the actual mean&lt;br /&gt;# mc.mean - mean within-group pairwise distance for each random allocation&lt;br /&gt;#&lt;br /&gt;# Michael Bedward, August 2010&lt;br /&gt;&lt;br /&gt;  # Constants&lt;br /&gt;  SYSTEMATIC &lt;- 1&lt;br /&gt;  RANDOM &lt;- 2&lt;br /&gt;&lt;br /&gt;  Nobj &lt;- length(grp)&lt;br /&gt;  if (length(d) != Nobj*(Nobj-1)/2) {&lt;br /&gt;    stop("Number of objects differs between dist object and group vector")&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  N1 &lt;- sum(grp == g1)&lt;br /&gt;  N2 &lt;- sum(grp == g2)&lt;br /&gt;  if (N1 == 1 &amp; N2 == 1) {&lt;br /&gt;    stop("Can't test split between two singleton groups")&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  objects &lt;- which(grp == g1 | grp == g2)&lt;br /&gt;  &lt;br /&gt;  Nc &lt;- choose(N1+N2, N1)&lt;br /&gt;  &lt;br /&gt;  if (nsamples &gt;= Nc/20) {&lt;br /&gt;    if (nsamples &gt; Nc) {&lt;br /&gt;      warning(paste("Requested number of samples (", nsamples, ") exceeds possible rearrangements (", Nc, ")", sep=""))&lt;br /&gt;      nsamples &lt;- Nc&lt;br /&gt;    }&lt;br /&gt;    sample.method &lt;- SYSTEMATIC&lt;br /&gt;  } else {&lt;br /&gt;    sample.method &lt;- RANDOM&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  # Helper function: calculates total pairwise distance between&lt;br /&gt;  # objects within a group. Returns total and number of &lt;br /&gt;  # pairwise distances&lt;br /&gt;  calc.grp.dist &lt;- function(grp.obj) {&lt;br /&gt;    if (length(grp.obj) == 1) {&lt;br /&gt;      return( c(0, 0) )&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    pairwise &lt;- combn(sort(grp.obj), 2)&lt;br /&gt;    di &lt;- Nobj * (pairwise[1,] - 1) - pairwise[1,]*(pairwise[1,]-1)/2 + pairwise[2,]-pairwise[1,]&lt;br /&gt;    c(sum(d[di]), length(di)) &lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  # Helper function: calculates mean within-group distance.&lt;br /&gt;  # Arg indices holds the indices of elements in the objects vector for&lt;br /&gt;  # one of the groups.&lt;br /&gt;  calc.mean &lt;- function(indices) {&lt;br /&gt;    g1dist &lt;- calc.grp.dist(objects[indices])&lt;br /&gt;    g2dist &lt;- calc.grp.dist(objects[-indices])&lt;br /&gt;    (g1dist[1] + g2dist[1]) / (g1dist[2] + g2dist[2])&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  samples &lt;- numeric(nsamples+1)&lt;br /&gt;&lt;br /&gt;  # actual within-group mean distance&lt;br /&gt;  samples[1] &lt;- calc.mean(which(grp == g1))&lt;br /&gt;&lt;br /&gt;  # randomly allocate objects to groups and calculate means&lt;br /&gt;  Nmin &lt;- min(N1, N2)&lt;br /&gt;  k &lt;- 2&lt;br /&gt;  if (sample.method == SYSTEMATIC) {&lt;br /&gt;    combns &lt;- combn(N1+N2, Nmin)[, sample(Nc, nsamples)]&lt;br /&gt;  } else {  # sample.method == RANDOM&lt;br /&gt;    combns &lt;- replicate(nsamples, sample(N1+N2, Nmin))&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  apply( combns, 2, function(x){samples[k] &lt;&lt;- calc.mean(x); k &lt;&lt;- k+1} )&lt;br /&gt;  prob &lt;- ecdf(samples)(samples[1])&lt;br /&gt;  print(paste("Prob =", prob, "from", nsamples, "samples plus actual value"))&lt;br /&gt;  invisible(list(actual.mean=samples[1], prob=prob, mc.means=samples[-1]))&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3727209628133289053-5664348965131170872?l=lastresortsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lastresortsoftware.blogspot.com/feeds/5664348965131170872/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://lastresortsoftware.blogspot.com/2010/09/monte-carlo-testing-of-classification.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/5664348965131170872'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/5664348965131170872'/><link rel='alternate' type='text/html' href='http://lastresortsoftware.blogspot.com/2010/09/monte-carlo-testing-of-classification.html' title='Monte Carlo testing of classification groups'/><author><name>Michael Bedward</name><uri>http://www.blogger.com/profile/17736007204837985875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_uGUChmI9xjo/SEjuW8gefdI/AAAAAAAAAFI/gU20EaCKr30/S220/snail.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3727209628133289053.post-7420930707921264819</id><published>2010-08-12T18:35:00.000-07:00</published><updated>2010-08-13T01:04:02.716-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='R'/><title type='text'>Fun with the proto package: building an MCMC sampler for Bayesian regression</title><content type='html'>The &lt;a href="http://code.google.com/p/r-proto/"&gt;proto&lt;/a&gt; package is my latest favourite R goodie. It brings &lt;a href="http://en.wikipedia.org/wiki/Prototype-based_programming"&gt;prototype-based programming&lt;/a&gt; to the R language - a style of programming that lets you do many of the things you can do with classes, but with a lot less up-front work. Louis Kates and Thomas Petzoldt provide an excellent introduction to using proto in the &lt;a href="http://cran.r-project.org/web/packages/proto/vignettes/proto.pdf"&gt;package vignette&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;As a learning exercise I concocted the example below involving Bayesian logistic regression. It was inspired by an &lt;a href="http://biostatmatt.com/archives/663"&gt;article&lt;/a&gt; on Matt Shotwell's blog about using R environments rather than lists to store the state of a Markov Chain Monte Carlo sampler. Here I use proto to create a parent class-like object (or &lt;i&gt;trait&lt;/i&gt; in proto-ese) to contain the regression functions and create child objects to hold both data and results for individual analyses.&lt;br /&gt;&lt;br /&gt;First here's an example session...&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;# Make up some data with a continuous predictor and binary response&lt;br /&gt;nrec &lt;- 500&lt;br /&gt;x &lt;- rnorm(nrec)&lt;br /&gt;y &lt;- rbinom(nrec, 1, plogis(2 - 4*x))&lt;br /&gt;&lt;br /&gt;# Predictor matrix with a col of 1s for intercept&lt;br /&gt;pred &lt;- matrix(c(rep(1, nrec), x), ncol=2)&lt;br /&gt;colnames(pred) &lt;- c("intercept", "X")&lt;br /&gt;&lt;br /&gt;# Load the proto package&lt;br /&gt;library(proto)&lt;br /&gt;&lt;br /&gt;# Use the Logistic parent object to create a child object which will &lt;br /&gt;# hold the data and run the regression (the $ operator references &lt;br /&gt;# functions and data within a proto object)&lt;br /&gt;lr &lt;- Logistic$new(pred, y)&lt;br /&gt;lr$run(5000, 1000)&lt;br /&gt;&lt;br /&gt;# lr now contains both data and results&lt;br /&gt;str(lr)&lt;br /&gt;&lt;br /&gt;proto object &lt;br /&gt; $ cov      : num [1:2, 1:2] 0.05 -0.0667 -0.0667 0.1621 &lt;br /&gt;  ..- attr(*, "dimnames")=List of 2 &lt;br /&gt; $ prior.cov: num [1:2, 1:2] 100 0 0 100 &lt;br /&gt; $ prior.mu : num [1:2] 0 0 &lt;br /&gt; $ beta     : num [1:5000, 1:2] 2.09 2.09 2.09 2.21 2.21 ... &lt;br /&gt;  ..- attr(*, "dimnames")=List of 2 &lt;br /&gt; $ adapt    : num 1000 &lt;br /&gt; $ y        : num [1:500] 0 1 1 1 1 1 1 1 1 1 ... &lt;br /&gt; $ x        : num [1:500, 1:2] 1 1 1 1 1 1 1 1 1 1 ... &lt;br /&gt;  ..- attr(*, "dimnames")=List of 2 &lt;br /&gt; parent: proto object &lt;br /&gt;&lt;br /&gt;# Use the Logistic summary function to tabulate and plot results&lt;br /&gt;lr$summary()&lt;br /&gt;&lt;br /&gt;From 5000 samples after 1000 iterations burning in&lt;br /&gt;   intercept           X         &lt;br /&gt; Min.   :1.420   Min.   :-5.296  &lt;br /&gt; 1st Qu.:1.840   1st Qu.:-3.915  &lt;br /&gt; Median :2.000   Median :-3.668  &lt;br /&gt; Mean   :1.994   Mean   :-3.693  &lt;br /&gt; 3rd Qu.:2.128   3rd Qu.:-3.455  &lt;br /&gt; Max.   :2.744   Max.   :-2.437  &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_uGUChmI9xjo/TGTW0Mf9uAI/AAAAAAAAAP8/imsDmZPwmjA/s1600/Rplot.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 242px;" src="http://4.bp.blogspot.com/_uGUChmI9xjo/TGTW0Mf9uAI/AAAAAAAAAP8/imsDmZPwmjA/s400/Rplot.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5504760836750424066" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;And here's the code for the Logistic trait...&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;Logistic &lt;- proto()&lt;br /&gt;&lt;br /&gt;Logistic$new &lt;- function(., x, y) {&lt;br /&gt;  # Creates a child object to hold data and results&lt;br /&gt;  #&lt;br /&gt;  # x - a design matrix (ie. predictors&lt;br /&gt;  # y - a binary reponse vector&lt;br /&gt;&lt;br /&gt;  proto(., x=x, y=y)&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;Logistic$run &lt;- function(., niter, adapt=1000) {&lt;br /&gt;  # Perform the regression by running the MCMC&lt;br /&gt;  # sampler&lt;br /&gt;  #&lt;br /&gt;  # niter - number of iterations to sample&lt;br /&gt;  # adapt - number of prior iterations to run&lt;br /&gt;  #         for the 'burning in' period&lt;br /&gt;&lt;br /&gt;  require(mvtnorm)&lt;br /&gt;&lt;br /&gt;  # Set up variables used by the sampler&lt;br /&gt;  .$adapt &lt;- adapt&lt;br /&gt;  total.iter &lt;- niter + adapt  &lt;br /&gt;  .$beta &lt;- matrix(0, nrow=total.iter, ncol=ncol(.$x))&lt;br /&gt;  .$prior.mu &lt;- rep(0, ncol(.$x))&lt;br /&gt;  .$prior.cov &lt;- diag(100, ncol(.$x))&lt;br /&gt;  .$cov &lt;- diag(ncol(.$x))&lt;br /&gt;&lt;br /&gt;  # Run the sampler&lt;br /&gt;  b &lt;- rep(0, ncol(.$x))&lt;br /&gt;  for (i in 1:total.iter) {&lt;br /&gt;    b &lt;- .$update(i, b)&lt;br /&gt;    .$beta[i,] &lt;- b&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  # Trim the results matrix to remove the burn-in&lt;br /&gt;  # period&lt;br /&gt;  if (.$adapt &gt; 0) {&lt;br /&gt;    .$beta &lt;- .$beta[(.$adapt + 1):total.iter,]&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;Logistic$update &lt;- function(., it, beta.old) {&lt;br /&gt;  # Perform a single iteration of the MCMC sampler using&lt;br /&gt;  # Metropolis-Hastings algorithm.&lt;br /&gt;  # Adapted from code by Brian Neelon published at:&lt;br /&gt;  # http://www.duke.edu/~neelo003/r/&lt;br /&gt;  #&lt;br /&gt;  # it -       iteration number&lt;br /&gt;  # beta.old - vector of coefficient values from &lt;br /&gt;  #            the previous iteration&lt;br /&gt;&lt;br /&gt;  # Update the coefficient covariance if we are far&lt;br /&gt;  # enough through the sampling&lt;br /&gt;  if (.$adapt &gt; 0 &amp; it &gt; 2 * .$adapt) {&lt;br /&gt;    .$cov &lt;- cov(.$beta[(it - .$adapt):(it - 1),])&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  # generate proposed new coefficient values&lt;br /&gt;  beta.new &lt;- c(beta.old + rmvnorm(1, sigma=.$cov))&lt;br /&gt;  &lt;br /&gt;  # calculate prior and current probabilities and log-likelihood&lt;br /&gt;  if (it == 1) {&lt;br /&gt;    .$..log.prior.old &lt;- dmvnorm(beta.old, .$prior.mu, .$prior.cov, log=TRUE)&lt;br /&gt;    .$..probs.old &lt;- plogis(.$x %*% beta.old)&lt;br /&gt;    .$..LL.old &lt;- sum(log(ifelse(.$y, .$..probs.old, 1 - .$..probs.old)))&lt;br /&gt;  }&lt;br /&gt;  log.prior.new &lt;- dmvnorm(beta.new, .$prior.mu, .$prior.cov, log=TRUE)&lt;br /&gt;  probs.new &lt;- plogis(.$x %*% beta.new)  &lt;br /&gt;  LL.new &lt;- sum(log(ifelse(.$y, probs.new, 1-probs.new)))&lt;br /&gt;  &lt;br /&gt;  # Metropolis-Hastings acceptance ratio (log scale)&lt;br /&gt;  ratio &lt;- LL.new + log.prior.new - .$..LL.old - .$..log.prior.old&lt;br /&gt;  &lt;br /&gt;  if (log(runif(1)) &lt; ratio) {&lt;br /&gt;   .$..log.prior.old &lt;- log.prior.new&lt;br /&gt;   .$..probs.old &lt;- probs.new&lt;br /&gt;   .$..LL.old &lt;- LL.new&lt;br /&gt;    return(beta.new)&lt;br /&gt;  } else {&lt;br /&gt;    return(beta.old)&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;Logistic$summary &lt;- function(., show.plot=TRUE) {&lt;br /&gt;  # Summarize the results&lt;br /&gt;&lt;br /&gt;  cat("From", nrow(.$beta), "samples after", .$adapt, "iterations burning in\n")&lt;br /&gt;  base::print(base::summary(.$beta))&lt;br /&gt;  &lt;br /&gt;  if (show.plot) {&lt;br /&gt;    par(mfrow=c(1, ncol(.$beta)))&lt;br /&gt;    for (i in 1:ncol(.$beta)) {&lt;br /&gt;      plot(density(.$beta[,i]), main=colnames(.$beta)[i])&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now that's probably not the greatest design in the world, but it only took me a few minutes to put it together and it's incredibly easy to modify or extend. Try it !&lt;br /&gt;&lt;br /&gt;Thanks to Brian Neelon for making his MCMC logistic regression code available (&lt;a href="http://www.duke.edu/~neelo003/r/"&gt;http://www.duke.edu/~neelo003/r/&lt;/a&gt;).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3727209628133289053-7420930707921264819?l=lastresortsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lastresortsoftware.blogspot.com/feeds/7420930707921264819/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://lastresortsoftware.blogspot.com/2010/08/fun-with-proto-package-building-mcmc.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/7420930707921264819'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/7420930707921264819'/><link rel='alternate' type='text/html' href='http://lastresortsoftware.blogspot.com/2010/08/fun-with-proto-package-building-mcmc.html' title='Fun with the proto package: building an MCMC sampler for Bayesian regression'/><author><name>Michael Bedward</name><uri>http://www.blogger.com/profile/17736007204837985875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_uGUChmI9xjo/SEjuW8gefdI/AAAAAAAAAFI/gU20EaCKr30/S220/snail.png'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_uGUChmI9xjo/TGTW0Mf9uAI/AAAAAAAAAP8/imsDmZPwmjA/s72-c/Rplot.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3727209628133289053.post-2422877168527126378</id><published>2010-08-10T00:16:00.000-07:00</published><updated>2010-08-10T02:23:03.876-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='R'/><title type='text'>Homogeneity analysis of hierarchical classifications</title><content type='html'>I've spent more years than I care to remember analysing vegetation survey data (typically species abundances in plots) using a variety of software including my own algorithms coded in FORTRAN and C++. A recent query on the r-help list, about how to determine the number of groups to define in a hierarchical classification produced with the hclust function, prompted me to unearth one of these algorithms, homogeneity analysis&lt;sup&gt;1&lt;/sup&gt;, which can help to visualize how different levels of grouping partition the variability in a distance matrix.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_uGUChmI9xjo/TGEGb24vzwI/AAAAAAAAAPc/OA07_e7WF3k/s1600/Rplot.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 210px;" src="http://2.bp.blogspot.com/_uGUChmI9xjo/TGEGb24vzwI/AAAAAAAAAPc/OA07_e7WF3k/s400/Rplot.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5503687295283220226" /&gt;&lt;/a&gt;&lt;br /&gt;This algorithm is extremely simple. The classification is progressively divided into groups, with all groups being defined at the same dendrogram height. At each level of grouping, the average of within-group pairwise distances is calculated. Homogeneity is then defined as:&lt;code&gt;&lt;pre&gt;&lt;br /&gt;      H = 1 - Dav&lt;sub&gt;within-group&lt;/sub&gt; - Dav&lt;sub&gt;total&lt;/sub&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;where Dav&lt;sub&gt;total&lt;/sub&gt; is the average pairwise distance in the dataset as a whole.&lt;br /&gt;&lt;br /&gt;For data were there exist well-defined clusters of values, a plot of homogeneity against number of groups will display an 'elbow' where the initial rapid increase in homogeneity turns to a more gradual increase. The example above shows a classification of the USArrests dataset and corresponding homogeneity plot which suggests defining 7 groups. It was generated as follows:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;&gt; d &lt;- dist(USArrests)&lt;br /&gt;&gt; hc &lt;- hclust(d, method="average")&lt;br /&gt;&gt; h &lt;- hmg(d, hc)&lt;br /&gt;&gt; plot(h, type="s", main="USArrests homogeneity")&lt;br /&gt;&gt; abline(v=7, lty=2)&lt;br /&gt;&gt; plot(hc, labels=FALSE, main="USArrests dendrogram")&lt;br /&gt;&gt; rect.hclust(hc, k=7)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Here is the code for the hmg function:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;function(d, hc) {&lt;br /&gt;&lt;br /&gt;# R version of homogeneity analysis as described in:&lt;br /&gt;#   Bedward, Pressey and Keith. 1992. Homogeneity analysis: assessing the &lt;br /&gt;#   utility of classifications and maps of natural resources&lt;br /&gt;#   Australian Journal of Ecology 17:133-139.&lt;br /&gt;#&lt;br /&gt;# Arguments:&lt;br /&gt;# d  - either an object produced by dist() or a vector of&lt;br /&gt;#      pairwise dissimilarity values ordered in the manner of&lt;br /&gt;#      a dist result&lt;br /&gt;#&lt;br /&gt;# hc - classification produced by hclust()&lt;br /&gt;#&lt;br /&gt;# Value:&lt;br /&gt;# A two column matrix of number of groups and corresponding homogeneity value&lt;br /&gt;#&lt;br /&gt;# This code by Michael Bedward, 2010&lt;br /&gt;&lt;br /&gt;  m &lt;- hc$merge&lt;br /&gt;&lt;br /&gt;  # check args for consistency&lt;br /&gt;  Nobj &lt;- nrow(m) + 1&lt;br /&gt;  if (length(d) != Nobj * (Nobj-1) / 2) {&lt;br /&gt;    dname &lt;- dparse(substitute(d))&lt;br /&gt;    hcname &lt;- dparse(substitute(hc))&lt;br /&gt;    stop(paste(dname, "and", hcname, "refer to different numbers of objects"))&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  distMean &lt;- mean(d)&lt;br /&gt;  grp &lt;- matrix(c(1:Nobj, rep(0, Nobj)), ncol=2)&lt;br /&gt;&lt;br /&gt;  # helper function - decodes values in the merge matrix &lt;br /&gt;  # from hclust&lt;br /&gt;  getGrp &lt;- function( idx ) {&lt;br /&gt;    ifelse( idx &lt; 0, grp[-idx], getGrp( m[idx, 1] ) )&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  grpDistTotal &lt;- numeric(Nobj)&lt;br /&gt;  grpDistCount  &lt;- numeric(Nobj)&lt;br /&gt;  hmg &lt;- numeric(Nobj)&lt;br /&gt;  hmg[1] &lt;- 0&lt;br /&gt;  hmg[Nobj] &lt;- 1&lt;br /&gt;&lt;br /&gt;  distTotal &lt;- 0&lt;br /&gt;  distCount &lt;- 0&lt;br /&gt;  for (step in 1:(Nobj-2)) {&lt;br /&gt;    mrec &lt;- m[step, ]&lt;br /&gt;    grp1 &lt;- getGrp(mrec[1])&lt;br /&gt;    grp2 &lt;- getGrp(mrec[2])&lt;br /&gt;&lt;br /&gt;    distTotal &lt;- distTotal - grpDistTotal[grp1] - grpDistTotal[grp2]&lt;br /&gt;    distCount &lt;- distCount - grpDistCount[grp1] - grpDistCount[grp2]&lt;br /&gt;&lt;br /&gt;    grpDistTotal[grp1] &lt;- grpDistTotal[grp1] + grpDistTotal[grp2]&lt;br /&gt;    grpDistCount[grp1] &lt;- grpDistCount[grp1] + grpDistCount[grp2]&lt;br /&gt;&lt;br /&gt;    grp1Obj &lt;- which(grp == grp1)&lt;br /&gt;    grp2Obj &lt;- which(grp == grp2)&lt;br /&gt;&lt;br /&gt;    for (i in grp1Obj) {&lt;br /&gt;      for (j in grp2Obj) {&lt;br /&gt;        ii &lt;- min(i,j)&lt;br /&gt;        jj &lt;- max(i,j)&lt;br /&gt;        distIdx &lt;- Nobj*(ii-1) - ii*(ii-1)/2 + jj-ii&lt;br /&gt;        grpDistTotal[grp1] &lt;- grpDistTotal[grp1] + d[distIdx]&lt;br /&gt;        grpDistCount[grp1] &lt;- grpDistCount[grp1] + 1&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    # update group vector&lt;br /&gt;    grp[grp == grp2] &lt;- grp1&lt;br /&gt;&lt;br /&gt;    distTotal &lt;- distTotal + grpDistTotal[grp1]&lt;br /&gt;    distCount &lt;- distCount + grpDistCount[grp1]&lt;br /&gt;&lt;br /&gt;    hmg[Nobj - step] &lt;- 1 - distTotal / ( distCount * distMean )&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  out &lt;- matrix(c(1:Nobj, hmg), ncol=2)&lt;br /&gt;  colnames(out) &lt;- c("ngroups", "homogeneity")&lt;br /&gt;  out&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Reference&lt;br /&gt;&lt;sup&gt;1&lt;/sup&gt;M. Bedward, D. A. Keith, R. L. Pressey. 1992. Homogeneity analysis: Assessing the utility of classifications and maps of natural resources. Australian Journal of Ecology 17(2) 133-139.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3727209628133289053-2422877168527126378?l=lastresortsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lastresortsoftware.blogspot.com/feeds/2422877168527126378/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://lastresortsoftware.blogspot.com/2010/08/homogeneity-analysis-of-hierarchical.html#comment-form' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/2422877168527126378'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/2422877168527126378'/><link rel='alternate' type='text/html' href='http://lastresortsoftware.blogspot.com/2010/08/homogeneity-analysis-of-hierarchical.html' title='Homogeneity analysis of hierarchical classifications'/><author><name>Michael Bedward</name><uri>http://www.blogger.com/profile/17736007204837985875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_uGUChmI9xjo/SEjuW8gefdI/AAAAAAAAAFI/gU20EaCKr30/S220/snail.png'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_uGUChmI9xjo/TGEGb24vzwI/AAAAAAAAAPc/OA07_e7WF3k/s72-c/Rplot.png' height='72' width='72'/><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3727209628133289053.post-4708054432277799248</id><published>2010-08-03T21:15:00.000-07:00</published><updated>2010-08-05T05:04:16.630-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='R'/><title type='text'>Meeting in the middle; or fudging model II regression with nls</title><content type='html'>My colleague Karen needed an equation to predict trunk diameter given tree height, which she hoped to base on measurements of trees in semi-arid Australian woodlands. This is the dark art of &lt;a href="http://en.wikipedia.org/wiki/Allometry"&gt;allometry&lt;/a&gt; and a quick google found a large number of formulae that have been used in different studies of tree dimensions. No problem: I started to play with a few of them and eventually settled on this one:&lt;blockquote&gt;&lt;br /&gt;dbh = exp( b0 + b1 / (b2 + h) )&lt;br /&gt;where: dbh is trunk diameter at breast height; h is tree height.&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;Karen also needed to do reverse predictions, ie. predict a tree's height given its trunk diameter. Again no problem, the inverse equation is simply:&lt;blockquote&gt;&lt;br /&gt;height = b1 / (log( dbh ) - b0) - b2&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;But then, the pièce de résistance: the forward predictions had to agree with the reverse predictions, ie. if plugging height h into the forward equation gave trunk diameter d, then plugging d into the reverse equation should get you back to h. Karen pointed out that this seemed to be a Model II regression problem because as well as needing symmetric predictions, both variables were subject to measurement error.&lt;br /&gt;&lt;br /&gt;R has at least two packages with Model II regression functions: &lt;a href="http://cran.r-project.org/web/packages/lmodel2/index.html"&gt;lmodel2&lt;/a&gt; and &lt;a href="http://cran.r-project.org/web/packages/smatr/index.html"&gt;smatr&lt;/a&gt;, but both seemed to be restricted to a single predictor (I'd be interested to hear if I'm wrong about that). Meanwhile, the trusty nls function seemed to do a good job of fitting the forward and reverse equations while dealing with the pattern of variance displayed by the tree data (mu^3).&lt;br /&gt;&lt;br /&gt;A solution arrived in the form of &lt;a href="http://tolstoy.newcastle.edu.au/R/help/05/06/5992.html"&gt;this post in the r-help list archive&lt;/a&gt; explaining how the coefficients from the forward and reverse fits could be combined by taking their geometric mean to arrive at a form of Model II regression. We tried this and it appeared to work nicely.&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_uGUChmI9xjo/TFqlUiV2GLI/AAAAAAAAAPQ/BNpD0OQE4tY/s1600/Rplot.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 400px;" src="http://1.bp.blogspot.com/_uGUChmI9xjo/TFqlUiV2GLI/AAAAAAAAAPQ/BNpD0OQE4tY/s400/Rplot.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5501891667020880050" /&gt;&lt;/a&gt;&lt;br /&gt;The plot above shows the separate fits, plus the combined fit which does indeed give symmetric predictions, for one of the tree species.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3727209628133289053-4708054432277799248?l=lastresortsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lastresortsoftware.blogspot.com/feeds/4708054432277799248/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://lastresortsoftware.blogspot.com/2010/08/meeting-in-middle-or-fudging-model-ii.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/4708054432277799248'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/4708054432277799248'/><link rel='alternate' type='text/html' href='http://lastresortsoftware.blogspot.com/2010/08/meeting-in-middle-or-fudging-model-ii.html' title='Meeting in the middle; or fudging model II regression with nls'/><author><name>Michael Bedward</name><uri>http://www.blogger.com/profile/17736007204837985875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_uGUChmI9xjo/SEjuW8gefdI/AAAAAAAAAFI/gU20EaCKr30/S220/snail.png'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_uGUChmI9xjo/TFqlUiV2GLI/AAAAAAAAAPQ/BNpD0OQE4tY/s72-c/Rplot.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3727209628133289053.post-7411800353685212912</id><published>2010-07-26T00:54:00.000-07:00</published><updated>2010-07-27T20:11:56.469-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='R'/><title type='text'>Circle packing with R</title><content type='html'>To visualize the results of a simulation model of woodland trees within R, I needed an algorithm that could arrange a large number of circles within a rectangle such that no two circles overlapped by more than a specified amount. A colleague had approached this problem earlier by sorting the circles in order of descending size, then randomly dropping each one into the rectangle repeatedly until it landed in a position with acceptable overlap.&lt;br /&gt;&lt;br /&gt;I suspected a faster and more robust algorithm could be constructed using some kind of "jiggling the circles" approach. Luckily for me, I discovered that Sean McCullough had written &lt;a href="http://www.cricketschirping.com/processing/CirclePacking1"&gt;a really nice example&lt;/a&gt; of circles packing into a cluster using the &lt;a href="http://processing.org/"&gt;Processing&lt;/a&gt; language. Sean's program is based on an iterative pair-repulsion algorithm in which overlapping circles move away from each other. Based on this, and modifying the algorithm a little, I came up with an R function to produce constrained random layouts of a given set of circles. Here are two layouts for the same input set...&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_uGUChmI9xjo/TE6jVEfwBxI/AAAAAAAAAPI/D-fsdy1SNLs/s1600/Rplot.gif"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 228px;" src="http://1.bp.blogspot.com/_uGUChmI9xjo/TE6jVEfwBxI/AAAAAAAAAPI/D-fsdy1SNLs/s400/Rplot.gif" border="0" alt="" id="BLOGGER_PHOTO_ID_5498511777445447442" /&gt;&lt;/a&gt;&lt;br /&gt;The algorithm is wonderfully simple. Each circle in the input list is compared to those following it. If two circles overlap by more than the allowed amount, they are moved apart until the overlap is acceptable. The distance moved by each circle is proportional to the radius of the other to approximate inertia (very loosely); thus when a small circle is overlapped by a large circle, the small circle moves furthest. This process is repeated iteratively until no more movement takes place (acceptable layout) or a maximum number of iterations is reached (layout failure). To avoid edge effects, the bounding rectangle is treated as a toroid. For my purposes, I only require a circle's centre to be within the plotted rectangle.&lt;br /&gt;&lt;br /&gt;You can see in the plots that the algorithm tends to produce clusters of small circles around big ones. For the woodland simulation model this is a nice property (saplings clustering around parent trees) but for other applications the algorithm could be further modified to lessen or avoid this effect.&lt;br /&gt;&lt;br /&gt;The code for the basic function is below. The set of input circles are described by the config matrix argument. Although this function produces good results, it takes a long time to run when the number of circles is large. However, a second version of the function, with most of the calculations moved into C code, runs much faster (code not posted here but available on request).&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;pack.circles &lt;- function(config, size=c(100, 100), max.iter=1000, overlap=0 ) {&lt;br /&gt;  #&lt;br /&gt;  # Simple circle packing algorithm based on inverse size weighted repulsion&lt;br /&gt;  #&lt;br /&gt;  # config   - matrix with two cols: radius, N&lt;br /&gt;  # size     - width and height of bounding rectangle&lt;br /&gt;  # max.iter - maximum number of iterations to try&lt;br /&gt;  # overlap  - allowable overlap expressed as proportion of joint radii&lt;br /&gt;&lt;br /&gt;  # ============================================================================&lt;br /&gt;  #  Global constants&lt;br /&gt;  # ============================================================================&lt;br /&gt;  # round-off tolerance&lt;br /&gt;  TOL &lt;- 0.0001&lt;br /&gt;&lt;br /&gt;  # convert overlap to proportion of radius&lt;br /&gt;  if (overlap &lt; 0 | overlap &gt;= 1) {&lt;br /&gt;    stop("overlap should be in the range [0, 1)")&lt;br /&gt;  }&lt;br /&gt;  PRADIUS &lt;- 1 - overlap&lt;br /&gt;&lt;br /&gt;  NCIRCLES &lt;- sum(config[,2])&lt;br /&gt;&lt;br /&gt;  # ============================================================================&lt;br /&gt;  #  Helper function - Draw a circle&lt;br /&gt;  # ============================================================================&lt;br /&gt;  draw.circle &lt;- function(x, y, r, col) { &lt;br /&gt;    lines( cos(seq(0, 2*pi, pi/180)) * r + x, sin(seq(0, 2*pi, pi/180)) * r + y , col=col )&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;  # ============================================================================&lt;br /&gt;  #  Helper function - Move two circles apart. The proportion of the required&lt;br /&gt;  #  distance moved by each circle is proportional to the size of the other &lt;br /&gt;  #  circle. For example, If a c1 with radius r1 overlaps c2 with radius r2,&lt;br /&gt;  #  and the movement distance required to separate them is ds, then c1 will&lt;br /&gt;  #  move ds * r2 / (r1 + r2) while c2 will move ds * r1 / (r1 + r2). Thus,&lt;br /&gt;  #  when a big circle overlaps a little one, the little one moves a lot while&lt;br /&gt;  #  the big one moves a little.&lt;br /&gt;  # ============================================================================&lt;br /&gt;  repel &lt;- function(xyr, c0, c1) {&lt;br /&gt;    dx &lt;- xyr[c1, 1] - xyr[c0, 1]&lt;br /&gt;    dy &lt;- xyr[c1, 2] - xyr[c0, 2]&lt;br /&gt;    d &lt;- sqrt(dx*dx + dy*dy)&lt;br /&gt;    r &lt;- xyr[c1, 3] + xyr[c0, 3]&lt;br /&gt;    w0 &lt;- xyr[c1, 3] / r&lt;br /&gt;    w1 &lt;- xyr[c0, 3] / r&lt;br /&gt;&lt;br /&gt;    if (d &lt; r - TOL) {&lt;br /&gt;      p &lt;- (r - d) / d&lt;br /&gt;      xyr[c1, 1] &lt;&lt;- toroid(xyr[c1, 1] + p*dx*w1, 1)&lt;br /&gt;      xyr[c1, 2] &lt;&lt;- toroid(xyr[c1, 2] + p*dy*w1, 2)&lt;br /&gt;      xyr[c0, 1] &lt;&lt;- toroid(xyr[c0, 1] - p*dx*w0, 1)&lt;br /&gt;      xyr[c0, 2] &lt;&lt;- toroid(xyr[c0, 2] - p*dy*w0, 2)&lt;br /&gt;&lt;br /&gt;      return(TRUE)&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    return(FALSE)&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;  # ============================================================================&lt;br /&gt;  #  Helper function - Adjust a coordinate such that if it is distance d beyond&lt;br /&gt;  #  an edge (ie. outside the area) it is moved to be distance d inside the &lt;br /&gt;  #  opposite edge. This has the effect of treating the area as a toroid.&lt;br /&gt;  # ============================================================================&lt;br /&gt;  toroid &lt;- function(coord, axis) {&lt;br /&gt;    tcoord &lt;- coord&lt;br /&gt;&lt;br /&gt;    if (coord &lt; 0) {&lt;br /&gt;      tcoord &lt;- coord + size[axis]&lt;br /&gt;    } else if (coord &gt;= size[axis]) {&lt;br /&gt;      tcoord &lt;- coord - size[axis]&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    tcoord&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;  # ============================================================================&lt;br /&gt;  #  Main program&lt;br /&gt;  # ============================================================================&lt;br /&gt;&lt;br /&gt;  # ------------------------------------------&lt;br /&gt;  # create a random initial layout&lt;br /&gt;  # ------------------------------------------&lt;br /&gt;  xyr &lt;- matrix(0, NCIRCLES, 3)&lt;br /&gt;&lt;br /&gt;  pos0 &lt;- 1&lt;br /&gt;  for (i in 1:nrow(config)) {&lt;br /&gt;    pos1 &lt;- pos0 + config[i,2] - 1&lt;br /&gt;    xyr[pos0:pos1, 1] &lt;- runif(config[i, 2], 0, size[1])&lt;br /&gt;    xyr[pos0:pos1, 2] &lt;- runif(config[i, 2], 0, size[2])&lt;br /&gt;    xyr[pos0:pos1, 3] &lt;- config[i, 1] * PRADIUS&lt;br /&gt;    pos0 &lt;- pos1 + 1&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  # ------------------------------------------&lt;br /&gt;  # iteratively adjust the layout&lt;br /&gt;  # ------------------------------------------&lt;br /&gt;  for (iter in 1:max.iter) {&lt;br /&gt;    moved &lt;- FALSE&lt;br /&gt;    for (i in 1:(NCIRCLES-1)) {&lt;br /&gt;      for (j in (i+1):NCIRCLES) {&lt;br /&gt;        if (repel(xyr, i, j)) {&lt;br /&gt;          moved &lt;- TRUE&lt;br /&gt;        }&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;    if (!moved) break&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  cat(paste(iter, "iterations\n"));&lt;br /&gt;  &lt;br /&gt;  # ------------------------------------------&lt;br /&gt;  # draw the layout&lt;br /&gt;  # ------------------------------------------&lt;br /&gt;  plot(0, type="n", xlab="x", xlim=c(0,size[1]), ylab="y", ylim=c(0, size[2]))&lt;br /&gt;&lt;br /&gt;  xyr[, 3] &lt;- xyr[, 3] / PRADIUS&lt;br /&gt;  for (i in 1:nrow(xyr)) {&lt;br /&gt;    draw.circle(xyr[i, 1], xyr[i, 2], xyr[i, 3], "gray")&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  # ------------------------------------------&lt;br /&gt;  # return the layout&lt;br /&gt;  # ------------------------------------------&lt;br /&gt;  colnames(xyr) &lt;- c("x", "y", "radius")&lt;br /&gt;  invisible(xyr) &lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3727209628133289053-7411800353685212912?l=lastresortsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lastresortsoftware.blogspot.com/feeds/7411800353685212912/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://lastresortsoftware.blogspot.com/2010/07/circle-packing-with-r.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/7411800353685212912'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/7411800353685212912'/><link rel='alternate' type='text/html' href='http://lastresortsoftware.blogspot.com/2010/07/circle-packing-with-r.html' title='Circle packing with R'/><author><name>Michael Bedward</name><uri>http://www.blogger.com/profile/17736007204837985875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_uGUChmI9xjo/SEjuW8gefdI/AAAAAAAAAFI/gU20EaCKr30/S220/snail.png'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_uGUChmI9xjo/TE6jVEfwBxI/AAAAAAAAAPI/D-fsdy1SNLs/s72-c/Rplot.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3727209628133289053.post-4703762613951109561</id><published>2010-04-27T06:59:00.000-07:00</published><updated>2010-04-27T07:14:01.013-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='triangulate'/><category scheme='http://www.blogger.com/atom/ns#' term='polygon'/><category scheme='http://www.blogger.com/atom/ns#' term='jts'/><category scheme='http://www.blogger.com/atom/ns#' term='geometry'/><title type='text'>Another clip round the ear</title><content type='html'>In the &lt;a href="http://lastresortsoftware.blogspot.com/2010/04/clip-round-ear.html"&gt;last episode&lt;/a&gt; I presented some code to triangulate a polygon using the ear-clipping algorithm. In this instalment the code has been extended to deal with polygons that have holes.  Here are two example results...&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_uGUChmI9xjo/S9bunsACOtI/AAAAAAAAAOg/yt_K7Ypw5Wo/s1600/Screen+shot+2010-04-27+at+7.28.45+PM.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 320px;" src="http://1.bp.blogspot.com/_uGUChmI9xjo/S9bunsACOtI/AAAAAAAAAOg/yt_K7Ypw5Wo/s320/Screen+shot+2010-04-27+at+7.28.45+PM.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5464817563454290642" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_uGUChmI9xjo/S9bunTDh9dI/AAAAAAAAAOY/_PkngTx_CRY/s1600/Screen+shot+2010-04-27+at+6.09.10+PM.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 320px;" src="http://3.bp.blogspot.com/_uGUChmI9xjo/S9bunTDh9dI/AAAAAAAAAOY/_PkngTx_CRY/s320/Screen+shot+2010-04-27+at+6.09.10+PM.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5464817556758066642" /&gt;&lt;/a&gt;&lt;br /&gt;No, they're not the prettiest triangulations ever seen, but they could be improved with some post-processing.&lt;br /&gt;&lt;br /&gt;Here is the Java code used to generate them. Note, you'll need the &lt;a href="http://sourceforge.net/projects/jts-topo-suite/"&gt;JTS library&lt;/a&gt; if you want to run this. Warning: this code isn't pretty, even by my standards, but it might be useful if you want to play with the algorithm.&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;import com.vividsolutions.jts.algorithm.CGAlgorithms;&lt;br /&gt;import com.vividsolutions.jts.geom.Coordinate;&lt;br /&gt;import com.vividsolutions.jts.geom.Envelope;&lt;br /&gt;import com.vividsolutions.jts.geom.Geometry;&lt;br /&gt;import com.vividsolutions.jts.geom.GeometryFactory;&lt;br /&gt;import com.vividsolutions.jts.geom.LineString;&lt;br /&gt;import com.vividsolutions.jts.geom.Polygon;&lt;br /&gt;import com.vividsolutions.jts.io.WKTReader;&lt;br /&gt;import java.awt.Color;&lt;br /&gt;import java.util.ArrayList;&lt;br /&gt;import java.util.Arrays;&lt;br /&gt;import java.util.Collections;&lt;br /&gt;import java.util.Comparator;&lt;br /&gt;import java.util.List;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; * Demonstrates brute force approach to the ear clipping algorithm&lt;br /&gt; * to triangulate a polygon. &lt;br /&gt; * &lt;br /&gt; * This version attempts a general approach to holes.&lt;br /&gt; *&lt;br /&gt; * @author Michael Bedward&lt;br /&gt; */&lt;br /&gt;public class EarClipping4 {&lt;br /&gt;    private static final double EPS = 1.0E-4;&lt;br /&gt;&lt;br /&gt;    private final GeometryFactory gf;&lt;br /&gt;    private final Polygon inputPolygon;&lt;br /&gt;    private Geometry ears;&lt;br /&gt;&lt;br /&gt;    private int curRingIndex;&lt;br /&gt;    private List&amp;lt;Coordinate&amp;gt; shellCoords;&lt;br /&gt;    private List&amp;lt;Integer&amp;gt; ringIndices;&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Demonstrate the ear-clipping algorithm&lt;br /&gt;     * @throws Exception&lt;br /&gt;     */&lt;br /&gt;    public static void main(String[] args) throws Exception {&lt;br /&gt;        WKTReader reader = new WKTReader();&lt;br /&gt;        Polygon poly = null;&lt;br /&gt;&lt;br /&gt;        /*&lt;br /&gt;         * Square with one hole that can't reach any exterior vertex&lt;br /&gt;         *&lt;br /&gt;        poly = (Polygon) reader.read(&lt;br /&gt;                "POLYGON ((0 0, 0 200, 200 200, 200 0, 0 0), " +&lt;br /&gt;                "(20 20, 20 40, 40 40, 40 20, 20 20), " +&lt;br /&gt;                "(20 160, 20 180, 40 180, 40 160, 20 160), " +&lt;br /&gt;                "(160 160, 160 180, 180 180, 180 160, 160 160), " +&lt;br /&gt;                "(160 20, 160 40, 180 40, 180 20, 160 20), " +&lt;br /&gt;                "(90 90, 90 110, 110 110, 110 90, 90 90) )");&lt;br /&gt;         * &lt;br /&gt;         */&lt;br /&gt;&lt;br /&gt;        /*&lt;br /&gt;         * Interlinked U-shaped holes&lt;br /&gt;         *&lt;br /&gt;        poly = (Polygon) reader.read(&lt;br /&gt;                "POLYGON ((0 0, 0 200, 250 200, 250 0, 0 0), " +&lt;br /&gt;                "(40 40, 40 170, 60 170, 60 60, 140 60, 140 120, 160 120, 160 40, 40 40), " +&lt;br /&gt;                "(90 80, 90 170, 210 170, 210 40, 190 40, 190 150, 110 150, 110 80, 90 80))" );&lt;br /&gt;&lt;br /&gt;        EarClipping4 clipper = new EarClipping4(poly);&lt;br /&gt;        Geometry ears  = clipper.getResult();&lt;br /&gt;&lt;br /&gt;        for (int i = 0; i &lt; ears.getNumGeometries(); i++) {&lt;br /&gt;            System.out.println(ears.getGeometryN(i));&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Constructor&lt;br /&gt;     *&lt;br /&gt;     * @param inputPolygon the input polygon&lt;br /&gt;     */&lt;br /&gt;    public EarClipping4(Polygon inputPolygon) {&lt;br /&gt;        gf = new GeometryFactory();&lt;br /&gt;        this.inputPolygon = inputPolygon;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Get the result triangular polygons.&lt;br /&gt;     *&lt;br /&gt;     * @return triangles as a GeometryCollection&lt;br /&gt;     */&lt;br /&gt;    public Geometry getResult() {&lt;br /&gt;        if (ears == null) {&lt;br /&gt;            ears = triangulate();&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        return ears;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Perform the triangulation&lt;br /&gt;     *&lt;br /&gt;     * @return GeometryCollection of triangular polygons&lt;br /&gt;     */&lt;br /&gt;    private Geometry triangulate() {&lt;br /&gt;        shellCoords = new ArrayList&amp;lt;Coordinate&amp;gt;();&lt;br /&gt;        ringIndices = new ArrayList&amp;lt;Integer&amp;gt;();&lt;br /&gt;        List&amp;lt;Polygon&amp;gt; earList = new ArrayList&amp;lt;Polygon&amp;gt;();&lt;br /&gt;        connectHoles();&lt;br /&gt;&lt;br /&gt;        removeColinearVertices();&lt;br /&gt;        int N = shellCoords.size() - 1;&lt;br /&gt;&lt;br /&gt;        boolean finished = false;&lt;br /&gt;        boolean found = false;&lt;br /&gt;        int k0 = 0;&lt;br /&gt;        do {&lt;br /&gt;            int k1 = (k0 + 1) % N;&lt;br /&gt;            int k2 = (k1 + 1) % N;&lt;br /&gt;            LineString ls = gf.createLineString(new Coordinate[] {shellCoords.get(k0), shellCoords.get(k2)});&lt;br /&gt;            &lt;br /&gt;            found = false;&lt;br /&gt;            if (inputPolygon.covers(ls)) {&lt;br /&gt;                Polygon ear = gf.createPolygon(gf.createLinearRing(&lt;br /&gt;                        new Coordinate[]{&lt;br /&gt;                            shellCoords.get(k0),&lt;br /&gt;                            shellCoords.get(k1),&lt;br /&gt;                            shellCoords.get(k2),&lt;br /&gt;                            shellCoords.get(k0)}),&lt;br /&gt;                        null);&lt;br /&gt;&lt;br /&gt;                if (inputPolygon.covers(ear)) {&lt;br /&gt;                    found = true;&lt;br /&gt;                    for (Polygon p : earList) {&lt;br /&gt;                        if (ear.overlaps(p)) {&lt;br /&gt;                            // overlap between candidate ear and previous ear&lt;br /&gt;                            found = false;&lt;br /&gt;                            break;&lt;br /&gt;                        }&lt;br /&gt;                    }&lt;br /&gt;&lt;br /&gt;                    if (found) {&lt;br /&gt;                        earList.add(ear);&lt;br /&gt;                        shellCoords.remove(k1);&lt;br /&gt;                        ringIndices.remove(k1);&lt;br /&gt;                        removeColinearVertices();&lt;br /&gt;                        N = shellCoords.size() - 1;&lt;br /&gt;                        k0 = 0;&lt;br /&gt;                        if (N &lt; 3) {&lt;br /&gt;                            finished = true;&lt;br /&gt;                        }&lt;br /&gt;                    }&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            if (!found) {&lt;br /&gt;                k0++;&lt;br /&gt;&lt;br /&gt;                if (k0 == N) {&lt;br /&gt;                    finished = true;&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;        } while (!finished);&lt;br /&gt;&lt;br /&gt;        return gf.createGeometryCollection(earList.toArray(new Geometry[0]));&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Remove co-linear vertices. TopologyPreservingSimplifier could be&lt;br /&gt;     * used for this but that seems like over-kill.&lt;br /&gt;     */&lt;br /&gt;    private void removeColinearVertices() {&lt;br /&gt;        int N = shellCoords.size() - 1;&lt;br /&gt;        List&amp;lt;Coordinate&amp;gt; coordList = new ArrayList&amp;lt;Coordinate&amp;gt;();&lt;br /&gt;&lt;br /&gt;        for (int j = 1; j &lt;= N; ) {&lt;br /&gt;            int i = (j - 1) % N;&lt;br /&gt;            int k = (j + 1) % N;&lt;br /&gt;            if (ringIndices.get(i) == ringIndices.get(j) &amp;&amp;&lt;br /&gt;                ringIndices.get(k) == ringIndices.get(j) &amp;&amp;&lt;br /&gt;                CGAlgorithms.isOnLine(shellCoords.get(j),&lt;br /&gt;                    new Coordinate[]{shellCoords.get(i), shellCoords.get(k)})) {&lt;br /&gt;                Coordinate c = shellCoords.remove(j);&lt;br /&gt;                ringIndices.remove(j);&lt;br /&gt;                N-- ;&lt;br /&gt;            } else {&lt;br /&gt;                j++ ;&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Connect any holes in the input polygon to the exterior ring to&lt;br /&gt;     * form a self-intersecting shell. Holes are added from the lowest&lt;br /&gt;     * upwards. As the resulting shell develops, a hole might be added&lt;br /&gt;     * to what was originally another hole.&lt;br /&gt;     *&lt;br /&gt;     * @return a new polygon with holes (if any) connected to the exterior&lt;br /&gt;     *         ring&lt;br /&gt;     */&lt;br /&gt;    private List&amp;lt;Coordinate&amp;gt; connectHoles() {&lt;br /&gt;        // defensively copy the input polygon&lt;br /&gt;        Polygon poly = (Polygon) inputPolygon.clone();&lt;br /&gt;        poly.normalize();&lt;br /&gt;&lt;br /&gt;        List&amp;lt;Geometry&amp;gt; orderedHoles = getOrderedHoles(poly);&lt;br /&gt;&lt;br /&gt;        Coordinate[] coords = poly.getExteriorRing().getCoordinates();&lt;br /&gt;        shellCoords.addAll(Arrays.asList(coords));&lt;br /&gt;&lt;br /&gt;        curRingIndex = 0;&lt;br /&gt;        for (int i = 0; i &lt; shellCoords.size(); i++) {&lt;br /&gt;            ringIndices.add(curRingIndex);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        for (int i = 0; i &lt; orderedHoles.size(); i++) {&lt;br /&gt;            curRingIndex++ ;&lt;br /&gt;            joinHoleToShell(orderedHoles.get(i));&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        return shellCoords;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Returns a list of holes in the input polygon (if any) ordered&lt;br /&gt;     * by y coordinate with ties broken using x coordinate.&lt;br /&gt;     *&lt;br /&gt;     * @param poly input polygon&lt;br /&gt;     * @return a list of Geometry objects representing the ordered holes&lt;br /&gt;     *         (may be empty)&lt;br /&gt;     */&lt;br /&gt;    private List&amp;lt;Geometry&amp;gt; getOrderedHoles(final Polygon poly) {&lt;br /&gt;        List&amp;lt;Geometry&amp;gt; holes = new ArrayList&amp;lt;Geometry&amp;gt;();&lt;br /&gt;        List&amp;lt;IndexedEnvelope&amp;gt; bounds = new ArrayList&amp;lt;IndexedEnvelope&amp;gt;();&lt;br /&gt;&lt;br /&gt;        if (poly.getNumInteriorRing() &gt; 0) {&lt;br /&gt;            for (int i = 0; i &lt; poly.getNumInteriorRing(); i++) {&lt;br /&gt;                bounds.add( new IndexedEnvelope(i, poly.getInteriorRingN(i).getEnvelopeInternal()) );&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            Collections.sort(bounds, new IndexedEnvelopeComparator());&lt;br /&gt;&lt;br /&gt;            for (int i = 0; i &lt; bounds.size(); i++) {&lt;br /&gt;                holes.add(poly.getInteriorRingN(bounds.get(i).index));&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        return holes;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Join a given hole to the current shell. The hole coordinates are&lt;br /&gt;     * inserted into the list of shell coordinates.&lt;br /&gt;     *&lt;br /&gt;     * @param hole the hole to join&lt;br /&gt;     */&lt;br /&gt;    private void joinHoleToShell(Geometry hole) {&lt;br /&gt;        double minD2 = Double.MAX_VALUE;&lt;br /&gt;        int shellVertexIndex = -1;&lt;br /&gt;&lt;br /&gt;        final int Ns = shellCoords.size() - 1;&lt;br /&gt;&lt;br /&gt;        final int holeVertexIndex = getLowestVertex(hole);&lt;br /&gt;        final Coordinate[] holeCoords = hole.getCoordinates();&lt;br /&gt;&lt;br /&gt;        final Coordinate ch = holeCoords[holeVertexIndex];&lt;br /&gt;        List&amp;lt;IndexedDouble&amp;gt; distanceList = new ArrayList&amp;lt;IndexedDouble&amp;gt;();&lt;br /&gt;&lt;br /&gt;        /*&lt;br /&gt;         * Note: it's important to scan the shell vertices in reverse so&lt;br /&gt;         * that if a hole ends up being joined to what was originally&lt;br /&gt;         * another hole, the previous hole's coordinates appear in the shell&lt;br /&gt;         * before the new hole's coordinates (otherwise the triangulation&lt;br /&gt;         * algorithm tends to get stuck).&lt;br /&gt;         */&lt;br /&gt;        for (int i = Ns - 1; i &gt;= 0; i--) {&lt;br /&gt;            Coordinate cs = shellCoords.get(i);&lt;br /&gt;            double d2 = (ch.x - cs.x) * (ch.x - cs.x) + (ch.y - cs.y) * (ch.y - cs.y);&lt;br /&gt;            if (d2 &lt; minD2) {&lt;br /&gt;                minD2 = d2;&lt;br /&gt;                shellVertexIndex = i;&lt;br /&gt;            }&lt;br /&gt;            distanceList.add(new IndexedDouble(i, d2));&lt;br /&gt;        }&lt;br /&gt;        &lt;br /&gt;        /*&lt;br /&gt;         * Try a quick join: if the closest shell vertex is reachable without&lt;br /&gt;         * crossing any holes.&lt;br /&gt;         */&lt;br /&gt;        LineString join = gf.createLineString(new Coordinate[]{ch, shellCoords.get(shellVertexIndex)});&lt;br /&gt;        if (inputPolygon.covers(join)) {&lt;br /&gt;            doJoinHole(shellVertexIndex, holeCoords, holeVertexIndex);&lt;br /&gt;            return;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        /*&lt;br /&gt;         * Quick join didn't work. Sort the shell coords on distance to the&lt;br /&gt;         * hole vertex nnd choose the closest reachable one.&lt;br /&gt;         */&lt;br /&gt;        Collections.sort(distanceList, new IndexedDoubleComparator());&lt;br /&gt;        for (int i = 1; i &lt; distanceList.size(); i++) {&lt;br /&gt;            join = gf.createLineString(new Coordinate[] {ch, shellCoords.get(distanceList.get(i).index)});&lt;br /&gt;            if (inputPolygon.covers(join)) {&lt;br /&gt;                shellVertexIndex = distanceList.get(i).index;&lt;br /&gt;                doJoinHole(shellVertexIndex, holeCoords, holeVertexIndex);&lt;br /&gt;                return;&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        throw new IllegalStateException("Failed to join hole to shell");&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Helper method for joinHoleToShell. Insert the hole coordinates into&lt;br /&gt;     * the shell coordinate list.&lt;br /&gt;     *&lt;br /&gt;     * @param shellCoords list of current shell coordinates&lt;br /&gt;     * @param shellVertexIndex insertion point in the shell coordinate list&lt;br /&gt;     * @param holeCoords array of hole coordinates&lt;br /&gt;     * @param holeVertexIndex attachment point of hole&lt;br /&gt;     */&lt;br /&gt;    private void doJoinHole(int shellVertexIndex, Coordinate[] holeCoords, int holeVertexIndex) {&lt;br /&gt;        List&amp;lt;Coordinate&amp;gt; newCoords = new ArrayList&amp;lt;Coordinate&amp;gt;();&lt;br /&gt;        List&amp;lt;Integer&amp;gt; newRingIndices = new ArrayList&amp;lt;Integer&amp;gt;();&lt;br /&gt;&lt;br /&gt;        newCoords.add(new Coordinate(shellCoords.get(shellVertexIndex)));&lt;br /&gt;        newRingIndices.add(ringIndices.get(shellVertexIndex));&lt;br /&gt;&lt;br /&gt;        final int N = holeCoords.length - 1;&lt;br /&gt;        int i = holeVertexIndex;&lt;br /&gt;        do {&lt;br /&gt;            newCoords.add(new Coordinate(holeCoords[i]));&lt;br /&gt;            newRingIndices.add(curRingIndex);&lt;br /&gt;            i = (i + 1) % N;&lt;br /&gt;        } while (i != holeVertexIndex);&lt;br /&gt;&lt;br /&gt;        newCoords.add(new Coordinate(holeCoords[holeVertexIndex]));&lt;br /&gt;        newRingIndices.add(curRingIndex);&lt;br /&gt;&lt;br /&gt;        shellCoords.addAll(shellVertexIndex, newCoords);&lt;br /&gt;        ringIndices.addAll(shellVertexIndex, newRingIndices);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Return the index of the lowest vertex&lt;br /&gt;     *&lt;br /&gt;     * @param geom input geometry&lt;br /&gt;     * @return index of the first vertex found at lowest point&lt;br /&gt;     *         of the geometry&lt;br /&gt;     */&lt;br /&gt;    private int getLowestVertex(Geometry geom) {&lt;br /&gt;        Coordinate[] coords = geom.getCoordinates();&lt;br /&gt;        double minY = geom.getEnvelopeInternal().getMinY();&lt;br /&gt;        for (int i = 0; i &lt; coords.length; i++) {&lt;br /&gt;            if (Math.abs(coords[i].y - minY) &lt; EPS) {&lt;br /&gt;                return i;&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        throw new IllegalStateException("Failed to find lowest vertex");&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private static class IndexedEnvelope {&lt;br /&gt;        int index;&lt;br /&gt;        Envelope envelope;&lt;br /&gt;&lt;br /&gt;        public IndexedEnvelope(int i, Envelope env) { index = i; envelope = env; }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private static class IndexedEnvelopeComparator implements Comparator&amp;lt;IndexedEnvelope&amp;gt; {&lt;br /&gt;        public int compare(IndexedEnvelope o1, IndexedEnvelope o2) {&lt;br /&gt;            double delta = o1.envelope.getMinY() - o2.envelope.getMinY();&lt;br /&gt;            if (Math.abs(delta) &lt; EPS) {&lt;br /&gt;                delta = o1.envelope.getMinX() - o2.envelope.getMinX();&lt;br /&gt;                if (Math.abs(delta) &lt; EPS) {&lt;br /&gt;                    return 0;&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;            return (delta &gt; 0 ? 1 : -1);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private static class IndexedDouble {&lt;br /&gt;        int index;&lt;br /&gt;        double value;&lt;br /&gt;&lt;br /&gt;        public IndexedDouble(int i, double v) { index = i; value = v; }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private static class IndexedDoubleComparator implements Comparator&amp;lt;IndexedDouble&amp;gt; {&lt;br /&gt;        public int compare(IndexedDouble o1, IndexedDouble o2) {&lt;br /&gt;            double delta = o1.value - o2.value;&lt;br /&gt;            if (Math.abs(delta) &lt; EPS) {&lt;br /&gt;                    return 0;&lt;br /&gt;            }&lt;br /&gt;            return (delta &gt; 0 ? 1 : -1);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3727209628133289053-4703762613951109561?l=lastresortsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lastresortsoftware.blogspot.com/feeds/4703762613951109561/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://lastresortsoftware.blogspot.com/2010/04/another-clip-round-ear.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/4703762613951109561'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/4703762613951109561'/><link rel='alternate' type='text/html' href='http://lastresortsoftware.blogspot.com/2010/04/another-clip-round-ear.html' title='Another clip round the ear'/><author><name>Michael Bedward</name><uri>http://www.blogger.com/profile/17736007204837985875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_uGUChmI9xjo/SEjuW8gefdI/AAAAAAAAAFI/gU20EaCKr30/S220/snail.png'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_uGUChmI9xjo/S9bunsACOtI/AAAAAAAAAOg/yt_K7Ypw5Wo/s72-c/Screen+shot+2010-04-27+at+7.28.45+PM.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3727209628133289053.post-1054383592435353763</id><published>2010-04-23T02:24:00.001-07:00</published><updated>2010-04-27T07:15:51.510-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='triangulate'/><category scheme='http://www.blogger.com/atom/ns#' term='polygon'/><category scheme='http://www.blogger.com/atom/ns#' term='jts'/><category scheme='http://www.blogger.com/atom/ns#' term='geometry'/><title type='text'>A clip round the ear</title><content type='html'>Inspired by a recent discussion on the &lt;a href="https://lists.sourceforge.net/lists/listinfo/jts-topo-suite-user"&gt;JTS user list&lt;/a&gt; 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 &lt;a href="http://sourceforge.net/projects/jts-topo-suite/"&gt;project web site&lt;/a&gt; or as a maven artifact (version 1.11) from the &lt;a href="http://repo1.maven.org/maven2/com/vividsolutions/jts/1.11/"&gt;Maven central repository&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;import com.vividsolutions.jts.algorithm.Angle;&lt;br /&gt;import com.vividsolutions.jts.geom.Coordinate;&lt;br /&gt;import com.vividsolutions.jts.geom.Geometry;&lt;br /&gt;import com.vividsolutions.jts.geom.GeometryFactory;&lt;br /&gt;import com.vividsolutions.jts.geom.LineString;&lt;br /&gt;import com.vividsolutions.jts.geom.Polygon;&lt;br /&gt;import com.vividsolutions.jts.io.WKTReader;&lt;br /&gt;import java.util.ArrayList;&lt;br /&gt;import java.util.List;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; * Demonstrates brute force approach to the ear clipping algorithm&lt;br /&gt; * to triangulate a polygon. This demo doesn't yet cope with&lt;br /&gt; * polygons that have holes.&lt;br /&gt; *&lt;br /&gt; * @author Michael Bedward&lt;br /&gt; */&lt;br /&gt;public class EarClipping {&lt;br /&gt;    private static final double EPS = 1.0E-4;&lt;br /&gt;&lt;br /&gt;    GeometryFactory gf = new GeometryFactory();&lt;br /&gt;&lt;br /&gt;    public static void main(String[] args) throws Exception {&lt;br /&gt;        new EarClipping().demo();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Demonstrate the ear-clipping algorithm&lt;br /&gt;     * @throws Exception&lt;br /&gt;     */&lt;br /&gt;    private void demo() throws Exception {&lt;br /&gt;        WKTReader reader = new WKTReader(gf);&lt;br /&gt;        Polygon poly = (Polygon )reader.read("POLYGON((0 0, 5 0, 2 3, 5 5, 10 0, 10 -5, 5 -2, 0 -5, 0 0))");&lt;br /&gt;        System.out.println("Input polygon:");&lt;br /&gt;        System.out.println(poly);&lt;br /&gt;&lt;br /&gt;        Geometry ears = triangulate(poly);&lt;br /&gt;        final int n = ears.getNumGeometries();&lt;br /&gt;&lt;br /&gt;        System.out.println();&lt;br /&gt;        System.out.println("Found " + n + " ears:");&lt;br /&gt;        for (int i = 0; i &lt; n; i++) {&lt;br /&gt;            System.out.println(ears.getGeometryN(i));&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Brute force approach to ear clipping&lt;br /&gt;     *&lt;br /&gt;     * @param inputPoly input polygon&lt;br /&gt;     * @return GeometryCollection of triangular polygons&lt;br /&gt;     */&lt;br /&gt;    private Geometry triangulate(Polygon inputPoly) {&lt;br /&gt;        if (inputPoly.getNumInteriorRing() &gt; 0) {&lt;br /&gt;            throw new IllegalArgumentException("Can't deal with polgons that have holes yet");&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        List&amp;lt;Polygon&amp;gt; ears = new ArrayList&amp;lt;Polygon&amp;gt;();&lt;br /&gt;        Polygon workingPoly = (Polygon) inputPoly.clone();&lt;br /&gt;&lt;br /&gt;        Coordinate[] coords = workingPoly.getBoundary().getCoordinates();&lt;br /&gt;        coords = removeColinearVertices(coords);&lt;br /&gt;        int N = coords.length - 1;&lt;br /&gt;&lt;br /&gt;        boolean finished = false;&lt;br /&gt;        int k0 = 0;&lt;br /&gt;        do {&lt;br /&gt;            int k1 = (k0 + 1) % N;&lt;br /&gt;            int k2 = (k0 + 2) % N;&lt;br /&gt;            LineString ls = gf.createLineString(new Coordinate[] {coords[k0], coords[k2]});&lt;br /&gt;            &lt;br /&gt;            if (workingPoly.covers(ls)) {&lt;br /&gt;                Polygon ear = gf.createPolygon(&lt;br /&gt;                        gf.createLinearRing(new Coordinate[]{coords[k0], coords[k1], coords[k2], coords[k0]}),&lt;br /&gt;                        null);&lt;br /&gt;                ears.add(ear);&lt;br /&gt;                workingPoly = (Polygon) workingPoly.difference(ear);&lt;br /&gt;                coords = workingPoly.getBoundary().getCoordinates();&lt;br /&gt;                coords = removeColinearVertices(coords);&lt;br /&gt;                N = coords.length - 1;&lt;br /&gt;                k0 = 0;&lt;br /&gt;                if (N == 3) {  // triangle&lt;br /&gt;                    ears.add(gf.createPolygon(gf.createLinearRing(coords), null));&lt;br /&gt;                    finished = true;&lt;br /&gt;                }&lt;br /&gt;            } else {&lt;br /&gt;                k0++ ;&lt;br /&gt;            }&lt;br /&gt;        } while (!finished);&lt;br /&gt;&lt;br /&gt;        return gf.createGeometryCollection(ears.toArray(new Geometry[0]));&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Remove co-linear vertices. TopologyPreservingSimplifier could be&lt;br /&gt;     * used for this but that seems like over-kill.&lt;br /&gt;     *&lt;br /&gt;     * @param coords polygon vertices&lt;br /&gt;     * @return coordinates with any co-linear vertices removed&lt;br /&gt;     */&lt;br /&gt;    private Coordinate[] removeColinearVertices(Coordinate[] coords) {&lt;br /&gt;        final int N = coords.length - 1;&lt;br /&gt;        List&amp;lt;Coordinate&amp;gt; coordList = new ArrayList&amp;lt;Coordinate&amp;gt;();&lt;br /&gt;&lt;br /&gt;        for (int j = 1; j &lt;= N; j++) {&lt;br /&gt;            int i = (j - 1) % N;&lt;br /&gt;            int k = (j + 1) % N;&lt;br /&gt;            if (Math.abs(Math.PI - Angle.angleBetween(coords[i], coords[j], coords[k])) &gt; EPS) {&lt;br /&gt;                coordList.add(coords[j]);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        coordList.add(new Coordinate(coordList.get(0)));&lt;br /&gt;        return coordList.toArray(new Coordinate[0]);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3727209628133289053-1054383592435353763?l=lastresortsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lastresortsoftware.blogspot.com/feeds/1054383592435353763/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://lastresortsoftware.blogspot.com/2010/04/clip-round-ear.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/1054383592435353763'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/1054383592435353763'/><link rel='alternate' type='text/html' href='http://lastresortsoftware.blogspot.com/2010/04/clip-round-ear.html' title='A clip round the ear'/><author><name>Michael Bedward</name><uri>http://www.blogger.com/profile/17736007204837985875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_uGUChmI9xjo/SEjuW8gefdI/AAAAAAAAAFI/gU20EaCKr30/S220/snail.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3727209628133289053.post-4905203331093069463</id><published>2010-03-20T23:15:00.000-07:00</published><updated>2010-03-21T21:00:13.208-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='curve fitting'/><category scheme='http://www.blogger.com/atom/ns#' term='splines'/><title type='text'>Cubic spline interpolation</title><content type='html'>I spent a while today casting around for a simple, fast and just-good-enough cubic interpolation algorithm to use in the GeoTools library where it is to serve as a 'filter function' that can be used when rendering maps.&lt;br /&gt;&lt;br /&gt;The &lt;a href="http://portal.acm.org/portal.cfm"&gt;ACM digitial library&lt;/a&gt; has a mind-boggling collection of references on interpolation methods, most of which were fascinating but also over the top for this application.&lt;br /&gt;&lt;br /&gt;In the end I put together the following code based on a Wikipedia article about &lt;a href="http://en.wikipedia.org/wiki/Cubic_Hermite_spline"&gt;cubic Hermite splines&lt;/a&gt;. Here it is...&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; * Cubic hermite spline interpolation. This is adapted from the description of the&lt;br /&gt; * algorithm at: http://en.wikipedia.org/wiki/Cubic_Hermite_spline.&lt;br /&gt; * Tangent caculations are done with simple finite differencing in the interests&lt;br /&gt; * of speed.&lt;br /&gt; * &amp;lt;p&amp;gt;&lt;br /&gt; * The input arrays xi and yi contain the coordinates of four interpolation&lt;br /&gt; * points defining three segments with the middle segment containing the point&lt;br /&gt; * for which we seek an interpolated value.&lt;br /&gt; *&lt;br /&gt; * @param x x ordinate of the point for which we seek an interpolated value&lt;br /&gt; *          and which lies between xi[1] and xi[2]&lt;br /&gt; * @param xi interpolation point x ordinates&lt;br /&gt; * @param yi interpolation point y ordinates&lt;br /&gt; *&lt;br /&gt; * @return interpolated value for x&lt;br /&gt; */&lt;br /&gt;public double cubic(double x, double[] xi, double[] yi) {&lt;br /&gt;    double span01 = xi[1] - xi[0];&lt;br /&gt;    double span12 = xi[2] - xi[1];&lt;br /&gt;    double span23 = xi[3] - xi[2];&lt;br /&gt;&lt;br /&gt;    double t = (x - xi[1]) / span12;&lt;br /&gt;    double t2 = t*t;&lt;br /&gt;    double t3 = t2 * t;&lt;br /&gt;&lt;br /&gt;    double m1 = 0.5 * ((yi[2] - yi[1]) / span12 + (yi[1] - yi[0]) / span01);&lt;br /&gt;    double m2 = 0.5 * ((yi[3] - yi[2]) / span23 + (yi[2] - yi[1]) / span12);&lt;br /&gt;&lt;br /&gt;    double y = (2*t3 - 3*t2 + 1) * yi[1] +&lt;br /&gt;               (t3 - 2*t2 + t) * span12 * m1 +&lt;br /&gt;               (-2*t3 + 3*t2) * yi[2] +&lt;br /&gt;               (t3 - t2) * span12 * m2;&lt;br /&gt;&lt;br /&gt;    return y;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;I make no claims about the elegance or efficiency of this code other than suspecting that it rates badly in both areas, but it does a perfectly acceptable job when I throw some test data at it...&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_uGUChmI9xjo/S6W9hxyVqYI/AAAAAAAAAOQ/9rdztTn5teE/s1600-h/Screen+shot+2010-03-21+at+5.31.52+PM.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 364px;" src="http://4.bp.blogspot.com/_uGUChmI9xjo/S6W9hxyVqYI/AAAAAAAAAOQ/9rdztTn5teE/s400/Screen+shot+2010-03-21+at+5.31.52+PM.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5450971311999658370" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3727209628133289053-4905203331093069463?l=lastresortsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lastresortsoftware.blogspot.com/feeds/4905203331093069463/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://lastresortsoftware.blogspot.com/2010/03/cubic-spline-interpolation.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/4905203331093069463'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/4905203331093069463'/><link rel='alternate' type='text/html' href='http://lastresortsoftware.blogspot.com/2010/03/cubic-spline-interpolation.html' title='Cubic spline interpolation'/><author><name>Michael Bedward</name><uri>http://www.blogger.com/profile/17736007204837985875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_uGUChmI9xjo/SEjuW8gefdI/AAAAAAAAAFI/gU20EaCKr30/S220/snail.png'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_uGUChmI9xjo/S6W9hxyVqYI/AAAAAAAAAOQ/9rdztTn5teE/s72-c/Screen+shot+2010-03-21+at+5.31.52+PM.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3727209628133289053.post-948513853817800105</id><published>2009-11-26T23:23:00.000-08:00</published><updated>2009-11-27T00:28:46.201-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='black arts'/><title type='text'>K ?</title><content type='html'>Whisper the terrible words &lt;i&gt;type erasure&lt;/i&gt; to any moderately experienced Java programmer and you're likely to see:&lt;ol type="a"&gt;&lt;li&gt; fear&lt;/li&gt;&lt;li&gt; loathing&lt;/li&gt;&lt;li&gt; soul-crushing sense of defeat&lt;/li&gt;&lt;li&gt; all of the above&lt;/li&gt;&lt;/ol&gt;Now if you &lt;i&gt;are&lt;/i&gt; a moderately experienced Java programmer, but you're thinking to yourself "what's type erasure ?" my advice is this: &lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;&lt;span class="Apple-style-span" style="font-size: x-large;"&gt;Stop reading now ! &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Sometimes ignorance really is bliss.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;My own rule of thumb, when confronted by the need to query the identity of K, or V, or T or any of their evil friends, in the innards of a class with generic type parameters, is to run away. Sadly, there are circumstances when that's just not an option. Although I deeply empathise with those who equate Java reflection with incest and folk dancing there are times when it's black magic or nothing. But where to find the magic ?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Enter Richard Gomes and his article: &lt;a href="http://www.jquantlib.org/index.php/Using_TypeTokens_to_retrieve_generic_parameters"&gt;Using TypeTokens to retrieve generic parameters&lt;/a&gt;. This demonstrates the seemingly impossible feat of pulling erased rabbits out of Java's hat. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I'm off to try it now... "Nothing up my sleeve..."&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3727209628133289053-948513853817800105?l=lastresortsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lastresortsoftware.blogspot.com/feeds/948513853817800105/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://lastresortsoftware.blogspot.com/2009/11/k.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/948513853817800105'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/948513853817800105'/><link rel='alternate' type='text/html' href='http://lastresortsoftware.blogspot.com/2009/11/k.html' title='K ?'/><author><name>Michael Bedward</name><uri>http://www.blogger.com/profile/17736007204837985875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_uGUChmI9xjo/SEjuW8gefdI/AAAAAAAAAFI/gU20EaCKr30/S220/snail.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3727209628133289053.post-8247981603422221184</id><published>2009-11-17T16:33:00.000-08:00</published><updated>2009-11-17T17:00:39.743-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pragprowrimo'/><title type='text'>PragProWriBlockMo</title><content type='html'>&lt;a href="http://praglife.typepad.com/pragmatic_life/2009/10/prag-pro-wri-mo.html"&gt;PragProWriMo&lt;/a&gt; has been even more challenging than I'd feared thanks to my sub-gnat concentration span and memory like a... you know, round thing with holes.  While others have been trotting out chapter outlines, finding their voice and defining their readership, I've been shuffling along the beach of irrelevancy and gazing out at the ocean of unfinishedness.&lt;br /&gt;&lt;br /&gt;It's amazing / pathetic / pathological (select all that apply) just how intimidating this simple daily writing exercise has become. The main problem has been the notion of a book hovering over the writing. No matter how many times I've told myself to treat the writing as a pump-priming exercise rather than an examination, I haven't been able to shake the anxiety of not "seeing the book" in my mind's eye.&lt;br /&gt;&lt;br /&gt;But I will not surrender ! Well, that's not quite true because I did a few days ago, giving up on the whole thing as too hard. Now though, I'm putting the white flag back in the cupboard and making a new start. Rather than imagining the goal as a book I'm going to structure it as a set of short web tutorials.&lt;br /&gt;&lt;br /&gt;I wonder if I can count this post ?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3727209628133289053-8247981603422221184?l=lastresortsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lastresortsoftware.blogspot.com/feeds/8247981603422221184/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://lastresortsoftware.blogspot.com/2009/11/pragprowriblockmo.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/8247981603422221184'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/8247981603422221184'/><link rel='alternate' type='text/html' href='http://lastresortsoftware.blogspot.com/2009/11/pragprowriblockmo.html' title='PragProWriBlockMo'/><author><name>Michael Bedward</name><uri>http://www.blogger.com/profile/17736007204837985875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_uGUChmI9xjo/SEjuW8gefdI/AAAAAAAAAFI/gU20EaCKr30/S220/snail.png'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3727209628133289053.post-5271540872818406890</id><published>2009-11-01T18:00:00.000-08:00</published><updated>2009-11-01T18:57:34.134-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pragprowrimo'/><title type='text'>PragProWriMo begins</title><content type='html'>November is here and this not-so-young-anymore geek's heart has turned to writing. I'm taking up the challenge of &lt;a href="http://praglife.typepad.com/pragmatic_life/2009/10/prag-pro-wri-mo.html"&gt;PragProWriMo&lt;/a&gt; by committing (forcing, tricking...) myself to write every day. The target is &lt;span style="font-weight:bold;"&gt;eighty&lt;/span&gt; pages by the end of the month.&lt;br /&gt;&lt;br /&gt;As someone who has routinely taken &lt;span style="font-style:italic;"&gt;years&lt;/span&gt; to write an &lt;span style="font-weight:bold;"&gt;eight&lt;/span&gt; page scientific paper (only four pages really if you subtract the tables, figures and compulsory, gratuitous citations of the publications of those who you suspect might review your paper) eighty pages seems an imposing target.&lt;br /&gt;&lt;br /&gt;Ah well grasshopper, the longest journey etc. etc.&lt;br /&gt;&lt;br /&gt;My chosen geek book working title is &lt;span style="font-style:italic;"&gt;Biological Models in Java&lt;/span&gt;. That's likely to change as the writing progresses of course... I can already see it wandering towards &lt;span style="font-style:italic;"&gt;Mathematical Recreations in Java, Ruby, Groovy, Clojure, R and that other language (you know, the one with the clicking sounds)&lt;/span&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3727209628133289053-5271540872818406890?l=lastresortsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lastresortsoftware.blogspot.com/feeds/5271540872818406890/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://lastresortsoftware.blogspot.com/2009/11/pragprowrimo-begins.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/5271540872818406890'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/5271540872818406890'/><link rel='alternate' type='text/html' href='http://lastresortsoftware.blogspot.com/2009/11/pragprowrimo-begins.html' title='PragProWriMo begins'/><author><name>Michael Bedward</name><uri>http://www.blogger.com/profile/17736007204837985875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_uGUChmI9xjo/SEjuW8gefdI/AAAAAAAAAFI/gU20EaCKr30/S220/snail.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3727209628133289053.post-5675909481613998100</id><published>2009-10-28T20:41:00.000-07:00</published><updated>2009-11-01T18:19:57.943-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pragprowrimo'/><title type='text'>Unleash your inner literary geek</title><content type='html'>Those good fellows at Pragmatic Programmers have come up with the geeky equivalent of &lt;a href="http://www.nanowrimo.org/"&gt;NaNoWriMo&lt;/a&gt; - the annual event that encourages people to try their hand at writing - which they have christened &lt;a href="http://praglife.typepad.com/pragmatic_life/2009/10/prag-pro-wri-mo.html"&gt;PragProWriMo&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;This is the chance for you to get started on that brilliant computing book that's been festering in the back of your mind for the last few years. All you have to do is commit to writing a few pages every day throughout November. And yes, every day means &lt;span style="font-weight:bold;"&gt;every day&lt;span style="font-style:italic;"&gt;&lt;/span&gt;&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;I'm planning a tome on displacement behaviour myself...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3727209628133289053-5675909481613998100?l=lastresortsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lastresortsoftware.blogspot.com/feeds/5675909481613998100/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://lastresortsoftware.blogspot.com/2009/10/unleash-your-inner-literary-geek.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/5675909481613998100'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/5675909481613998100'/><link rel='alternate' type='text/html' href='http://lastresortsoftware.blogspot.com/2009/10/unleash-your-inner-literary-geek.html' title='Unleash your inner literary geek'/><author><name>Michael Bedward</name><uri>http://www.blogger.com/profile/17736007204837985875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_uGUChmI9xjo/SEjuW8gefdI/AAAAAAAAAFI/gU20EaCKr30/S220/snail.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3727209628133289053.post-2734859571794305523</id><published>2009-08-10T22:35:00.000-07:00</published><updated>2009-08-11T00:28:05.664-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='jsr 275'/><title type='text'>Measures of confusion with JSR-275</title><content type='html'>I love the idea behind JSR-275, a units of measurement API for Java (see &lt;a href="http://www.javaworld.com/javaworld/jw-10-2007/jw-10-jsr275.html"&gt;here&lt;/a&gt; for a background article by Jean-Marie Dautelle, the lead author) but I wish there were more examples out there about how to use it properly.&lt;br /&gt;&lt;br /&gt;I'm presently writing some code for a spatial simulation. I want the user to be able to specify constraints on spatial entities such as their maximum area, width, movement over time and so forth. Bringing JSR-275 to bear on this means that I can use a consistent set of units within the code while leaving the user free to express their input data in whatever units are convenient for them.&lt;br /&gt;&lt;br /&gt;The theory is beautiful but my, alas, my code is ugly. &lt;br /&gt;&lt;br /&gt;Take this snippet for example...&lt;br /&gt;&lt;pre class="prettyprint"&gt;// get a constraint from the user&lt;br /&gt;Constraint constraint = ...&lt;br /&gt;&lt;br /&gt;// get the unit used internally for this constraint (e.g. &amp;quot;m&amp;quot;)&lt;br /&gt;Unit&amp;lt;?&amp;gt; stdUnit = ... &lt;br /&gt;&lt;br /&gt;// the value for the constraint (in the user's units)&lt;br /&gt;double value = constraint.getValue();&lt;br /&gt;&lt;br /&gt;// get the label for the user's units (e.g. &amp;quot;ft&amp;quot;)&lt;br /&gt;String unitName = constraint.getUnitLabel(); &lt;br /&gt;&lt;br /&gt;// match the label to a jsr-275 class&lt;br /&gt;Unit&amp;lt;?&amp;gt; unit = Unit.valueOf(unitName);&lt;br /&gt;&lt;br /&gt;if ( !unit.isCompatible(stdUnit) ) { &lt;br /&gt; // e.g. mixing up length and area units&lt;br /&gt; // throw an exception&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;if (!unit.equals(stdUnit)) {&lt;br /&gt; /*&lt;br /&gt;    * The user is exercising their freedom to use a different unit&lt;br /&gt;    * to us (curse them) so we convert their value to one&lt;br /&gt;    * expressed in our units.&lt;br /&gt;    *&lt;br /&gt;    * (This is where it gets ugly)&lt;br /&gt;    */&lt;br /&gt;   UnitConverter converter = null;  &lt;br /&gt;&lt;br /&gt;   // is it an area constraint ?&lt;br /&gt;   if (unit.isCompatible(Area.UNIT)) {&lt;br /&gt;       converter = unit.asType(Area.class).getConverterTo( &lt;br /&gt;               stdUnit.asType(Area.class) );&lt;br /&gt;&lt;br /&gt;   // is it a length constraint ?&lt;br /&gt;   } else if (unit.isCompatible(Length.UNIT)) {&lt;br /&gt;       converter = unit.asType(Length.class).getConverterTo( &lt;br /&gt;               stdUnit.asType(Length.class) );&lt;br /&gt;&lt;br /&gt;   // bummer - it was something else I forgot to allow for&lt;br /&gt;   } else {&lt;br /&gt;       throw new RuntimeException(&amp;quot;Constraint system doesn't allow for &amp;quot;&lt;br /&gt;               + unit.toString());&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   value = converter.convert(value);&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now I'd like to avoid the if-else block that says &lt;span style="font-style:italic;"&gt;Is it an area ?  Is it a length ?&lt;/span&gt; and just say...&lt;br /&gt;&lt;pre class="prettyprint"&gt;   converter = unit.getConverterTo(stdUnit);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;...but the compiler isn't happy with this. It wants the Unit references to be fully parameterized.&lt;br /&gt;&lt;br /&gt;If I was only working with length, for instance, things would be simple...&lt;br /&gt;&lt;pre class="prettyprint"&gt;   Unit&amp;lt;Length&amp;gt; stdUnit = ... // our standard unit (e.g. m)&lt;br /&gt;   Unit&amp;lt;Length&amp;gt; unit = ...  // the user's unit (e.g. cubits)&lt;br /&gt;   UnitConverter converter = unit.getConverterTo( stdUnit );&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;That's concise and elegant. But since I want to allow constraints to be applied to different quantities (length and area at the moment; later possibly others) I seem stuck with the if-else alternative parameterizations above.&lt;br /&gt;&lt;br /&gt;I'm sure I must be missing something here. So, dear reader, if you are a JSR 275 guru please enlighten me !&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3727209628133289053-2734859571794305523?l=lastresortsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lastresortsoftware.blogspot.com/feeds/2734859571794305523/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://lastresortsoftware.blogspot.com/2009/08/measures-of-confusion-with-jsr-275.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/2734859571794305523'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3727209628133289053/posts/default/2734859571794305523'/><link rel='alternate' type='text/html' href='http://lastresortsoftware.blogspot.com/2009/08/measures-of-confusion-with-jsr-275.html' title='Measures of confusion with JSR-275'/><author><name>Michael Bedward</name><uri>http://www.blogger.com/profile/17736007204837985875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_uGUChmI9xjo/SEjuW8gefdI/AAAAAAAAAFI/gU20EaCKr30/S220/snail.png'/></author><thr:total>0</thr:total></entry></feed>
