001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.math.geometry.triangulation; 031 032import java.util.ArrayList; 033import java.util.List; 034import java.util.PriorityQueue; 035 036import org.jgrapht.Graph; 037import org.jgrapht.graph.DefaultEdge; 038import org.jgrapht.graph.SimpleGraph; 039import org.openimaj.math.geometry.line.Line2d; 040import org.openimaj.math.geometry.point.Point2d; 041import org.openimaj.math.geometry.point.Point2dImpl; 042 043/** 044 * Static methods for the computation of a Voronoi diagram (aka Dirichlet 045 * tessellation) from a set of points. Internally, these use Fortune's algorithm 046 * to do the work. 047 * 048 * @see "http://en.wikipedia.org/wiki/Fortune%27s_algorithm" 049 * 050 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 051 * 052 */ 053public class Voronoi { 054 private Voronoi() { 055 } 056 057 /** 058 * Compute the Voronoi diagram as a graph of its vertices 059 * 060 * @param points 061 * the vertices 062 * @return the graph 063 */ 064 public static Graph<Point2d, DefaultEdge> computeVoronoiGraph(List<? extends Point2d> points) { 065 final double[] wh = computeWidthHeight(points); 066 return computeVoronoiGraph(points, wh[0], wh[1]); 067 } 068 069 /** 070 * Compute the Voronoi diagram as a graph of its vertices 071 * 072 * @param points 073 * the vertices 074 * @param width 075 * the width of the diagram 076 * @param height 077 * the height of the diagram 078 * @return the graph 079 */ 080 public static Graph<Point2d, DefaultEdge> computeVoronoiGraph(List<? extends Point2d> points, double width, 081 double height) 082 { 083 final FortunesAlgorithm f = new FortunesAlgorithm(width, height); 084 final List<Line2d> edges = f.runFortune(points); 085 086 final Graph<Point2d, DefaultEdge> graph = new SimpleGraph<Point2d, DefaultEdge>(DefaultEdge.class); 087 for (final Line2d l : edges) { 088 graph.addEdge(l.begin, l.end); 089 } 090 091 return graph; 092 } 093 094 /** 095 * Compute the Voronoi diagram as a list of its edges 096 * 097 * @param points 098 * the vertices 099 * @return the graph 100 */ 101 public static List<Line2d> computeVoronoiEdges(List<? extends Point2d> points) { 102 final double[] wh = computeWidthHeight(points); 103 104 return computeVoronoiEdges(points, wh[0], wh[1]); 105 } 106 107 /** 108 * Compute the Voronoi diagram as a list of its edges 109 * 110 * @param points 111 * the vertices 112 * @param width 113 * the width of the diagram 114 * @param height 115 * the height of the diagram 116 * @return the graph 117 */ 118 public static List<Line2d> computeVoronoiEdges(List<? extends Point2d> points, double width, 119 double height) 120 { 121 final FortunesAlgorithm f = new FortunesAlgorithm(width, height); 122 return f.runFortune(points); 123 } 124 125 private static double[] computeWidthHeight(List<? extends Point2d> points) { 126 float maxx = -Float.MAX_VALUE; 127 float minx = Float.MAX_VALUE; 128 float maxy = -Float.MAX_VALUE; 129 float miny = Float.MAX_VALUE; 130 131 for (final Point2d pt : points) { 132 final float x = pt.getX(); 133 final float y = pt.getY(); 134 135 if (maxx < x) 136 maxx = x; 137 if (minx > x) 138 minx = x; 139 if (maxy < y) 140 maxy = y; 141 if (miny > y) 142 miny = y; 143 } 144 145 final float w = maxx - minx; 146 final float h = maxy - miny; 147 return new double[] { maxx + 0.1 * w, maxy + 0.1 * h }; 148 } 149 150 /** 151 * Implementation of fortune's algorithm. 152 * 153 * @see "http://stackoverflow.com/questions/2346148/fastest-way-to-get-the-set-of-convex-polygons-formed-by-voronoi-line-segments" 154 */ 155 private static class FortunesAlgorithm { 156 // A list of line segments that defines where the cells are divided 157 private List<Line2d> output = new ArrayList<Line2d>(); 158 // The sites that have not yet been processed, in acending order of X 159 // coordinate 160 private PriorityQueue<Point2d> sites = new PriorityQueue<Point2d>(); 161 // Possible upcoming cirlce events in acending order of X coordinate 162 private PriorityQueue<CircleEvent> events = new PriorityQueue<CircleEvent>(); 163 // The root of the binary search tree of the parabolic wave front 164 private Arc root; 165 private double height; 166 private double width; 167 168 FortunesAlgorithm(double width, double height) { 169 this.width = width; 170 this.height = height; 171 } 172 173 List<Line2d> runFortune(List<? extends Point2d> points) { 174 for (final Point2d pt : points) 175 sites.add(new ComparablePoint(pt)); 176 177 // Process the queues; select the top element with smaller x 178 // coordinate. 179 while (sites.size() > 0) { 180 if ((events.size() > 0) && ((events.peek().xpos) <= (((ComparablePoint) sites.peek()).x))) { 181 processCircleEvent(events.poll()); 182 } else { 183 // process a site event by adding a curve to the parabolic 184 // front 185 frontInsert((ComparablePoint) sites.poll()); 186 } 187 } 188 189 // After all points are processed, do the remaining circle events. 190 while (events.size() > 0) { 191 processCircleEvent(events.poll()); 192 } 193 194 // Clean up dangling edges. 195 finishEdges(); 196 197 return output; 198 } 199 200 private void processCircleEvent(CircleEvent event) { 201 if (event.valid) { 202 // start a new edge 203 final Edge edgy = new Edge(event.p); 204 205 // Remove the associated arc from the front. 206 final Arc parc = event.a; 207 if (parc.prev != null) { 208 parc.prev.next = parc.next; 209 parc.prev.edge1 = edgy; 210 } 211 if (parc.next != null) { 212 parc.next.prev = parc.prev; 213 parc.next.edge0 = edgy; 214 } 215 216 // Finish the edges before and after this arc. 217 if (parc.edge0 != null) { 218 parc.edge0.finish(event.p); 219 } 220 if (parc.edge1 != null) { 221 parc.edge1.finish(event.p); 222 } 223 224 // Recheck circle events on either side of p: 225 if (parc.prev != null) { 226 checkCircleEvent(parc.prev, event.xpos); 227 } 228 if (parc.next != null) { 229 checkCircleEvent(parc.next, event.xpos); 230 } 231 232 } 233 } 234 235 void frontInsert(ComparablePoint focus) { 236 if (root == null) { 237 root = new Arc(focus); 238 return; 239 } 240 241 Arc parc = root; 242 while (parc != null) { 243 final CircleResultPack rez = intersect(focus, parc); 244 if (rez.valid) { 245 // New parabola intersects parc. If necessary, duplicate 246 // parc. 247 248 if (parc.next != null) { 249 final CircleResultPack rezz = intersect(focus, parc.next); 250 if (!rezz.valid) { 251 final Arc bla = new Arc(parc.focus); 252 bla.prev = parc; 253 bla.next = parc.next; 254 parc.next.prev = bla; 255 parc.next = bla; 256 } 257 } else { 258 parc.next = new Arc(parc.focus); 259 parc.next.prev = parc; 260 } 261 parc.next.edge1 = parc.edge1; 262 263 // Add new arc between parc and parc.next. 264 final Arc bla = new Arc(focus); 265 bla.prev = parc; 266 bla.next = parc.next; 267 parc.next.prev = bla; 268 parc.next = bla; 269 270 parc = parc.next; // Now parc points to the new arc. 271 272 // Add new half-edges connected to parc's endpoints. 273 parc.edge0 = new Edge(rez.center); 274 parc.prev.edge1 = parc.edge0; 275 parc.edge1 = new Edge(rez.center); 276 parc.next.edge0 = parc.edge1; 277 278 // Check for new circle events around the new arc: 279 checkCircleEvent(parc, focus.x); 280 checkCircleEvent(parc.prev, focus.x); 281 checkCircleEvent(parc.next, focus.x); 282 283 return; 284 } 285 286 // proceed to next arc 287 parc = parc.next; 288 } 289 290 // Special case: If p never intersects an arc, append it to the 291 // list. 292 parc = root; 293 while (parc.next != null) { 294 parc = parc.next; // Find the last node. 295 } 296 parc.next = new Arc(focus); 297 parc.next.prev = parc; 298 final ComparablePoint start = new ComparablePoint(0, (parc.next.focus.y + parc.focus.y) / 2); 299 parc.next.edge0 = new Edge(start); 300 parc.edge1 = parc.next.edge0; 301 302 } 303 304 void checkCircleEvent(Arc parc, double xpos) { 305 // Invalidate any old event. 306 if ((parc.event != null) && (parc.event.xpos != xpos)) { 307 parc.event.valid = false; 308 } 309 parc.event = null; 310 311 if ((parc.prev == null) || (parc.next == null)) { 312 return; 313 } 314 315 final CircleResultPack result = circle(parc.prev.focus, parc.focus, parc.next.focus); 316 if (result.valid && result.rightmostX > xpos) { 317 // Create new event. 318 parc.event = new CircleEvent(result.rightmostX, result.center, parc); 319 events.offer(parc.event); 320 } 321 322 } 323 324 // Find the rightmost point on the circle through a,b,c. 325 CircleResultPack circle(ComparablePoint a, ComparablePoint b, ComparablePoint c) { 326 final CircleResultPack result = new CircleResultPack(); 327 328 // Check that bc is a "right turn" from ab. 329 if ((b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y) > 0) { 330 result.valid = false; 331 return result; 332 } 333 334 // Algorithm from O'Rourke 2ed p. 189. 335 final double A = b.x - a.x; 336 final double B = b.y - a.y; 337 final double C = c.x - a.x; 338 final double D = c.y - a.y; 339 final double E = A * (a.x + b.x) + B * (a.y + b.y); 340 final double F = C * (a.x + c.x) + D * (a.y + c.y); 341 final double G = 2 * (A * (c.y - b.y) - B * (c.x - b.x)); 342 343 if (G == 0) { // Points are co-linear. 344 result.valid = false; 345 return result; 346 } 347 348 // centerpoint of the circle. 349 final ComparablePoint o = new ComparablePoint((D * E - B * F) / G, (A * F - C * E) / G); 350 result.center = o; 351 352 // o.x plus radius equals max x coordinate. 353 result.rightmostX = o.x + Math.sqrt(Math.pow(a.x - o.x, 2.0) + Math.pow(a.y - o.y, 2.0)); 354 355 result.valid = true; 356 return result; 357 } 358 359 // Will a new parabola at point p intersect with arc i? 360 CircleResultPack intersect(ComparablePoint p, Arc i) { 361 final CircleResultPack res = new CircleResultPack(); 362 res.valid = false; 363 if (i.focus.x == p.x) { 364 return res; 365 } 366 367 double a = 0.0; 368 double b = 0.0; 369 if (i.prev != null) // Get the intersection of i->prev, i. 370 { 371 a = intersection(i.prev.focus, i.focus, p.x).y; 372 } 373 if (i.next != null) // Get the intersection of i->next, i. 374 { 375 b = intersection(i.focus, i.next.focus, p.x).y; 376 } 377 378 if ((i.prev == null || a <= p.y) && (i.next == null || p.y <= b)) { 379 res.center = new ComparablePoint(0, p.y); 380 381 // Plug it back into the parabola equation to get the x 382 // coordinate 383 res.center.x = (i.focus.x * i.focus.x + (i.focus.y - res.center.y) * (i.focus.y - res.center.y) - p.x 384 * p.x) 385 / (2 * i.focus.x - 2 * p.x); 386 387 res.valid = true; 388 return res; 389 } 390 return res; 391 } 392 393 // Where do two parabolas intersect? 394 ComparablePoint intersection(ComparablePoint p0, ComparablePoint p1, double l) { 395 final ComparablePoint res = new ComparablePoint(0, 0); 396 ComparablePoint p = p0; 397 398 if (p0.x == p1.x) { 399 res.y = (p0.y + p1.y) / 2; 400 } else if (p1.x == l) { 401 res.y = p1.y; 402 } else if (p0.x == l) { 403 res.y = p0.y; 404 p = p1; 405 } else { 406 // Use the quadratic formula. 407 final double z0 = 2 * (p0.x - l); 408 final double z1 = 2 * (p1.x - l); 409 410 final double a = 1 / z0 - 1 / z1; 411 final double b = -2 * (p0.y / z0 - p1.y / z1); 412 final double c = (p0.y * p0.y + p0.x * p0.x - l * l) / z0 - (p1.y * p1.y + p1.x * p1.x - l * l) / z1; 413 414 res.y = (float) ((-b - Math.sqrt((b * b - 4 * a * c))) / (2 * a)); 415 } 416 // Plug back into one of the parabola equations. 417 res.x = (float) ((p.x * p.x + (p.y - res.y) * (p.y - res.y) - l * l) / (2 * p.x - 2 * l)); 418 return res; 419 } 420 421 void finishEdges() { 422 // Advance the sweep line so no parabolas can cross the bounding 423 // box. 424 final double l = width * 2 + height; 425 426 // Extend each remaining segment to the new parabola intersections. 427 Arc i = root; 428 while (i != null) { 429 if (i.edge1 != null) { 430 i.edge1.finish(intersection(i.focus, i.next.focus, l * 2)); 431 } 432 i = i.next; 433 } 434 } 435 436 class ComparablePoint extends Point2dImpl implements Comparable<ComparablePoint> { 437 public ComparablePoint(double X, double Y) { 438 x = (float) X; 439 y = (float) Y; 440 } 441 442 public ComparablePoint(Point2d pt) { 443 this.x = pt.getX(); 444 this.y = pt.getY(); 445 } 446 447 @Override 448 public int compareTo(ComparablePoint foo) { 449 return ((Float) this.x).compareTo(foo.x); 450 } 451 } 452 453 private static class CircleEvent implements Comparable<CircleEvent> { 454 double xpos; 455 ComparablePoint p; 456 Arc a; 457 boolean valid; 458 459 CircleEvent(double X, ComparablePoint P, Arc A) { 460 xpos = X; 461 a = A; 462 p = P; 463 valid = true; 464 } 465 466 @Override 467 public int compareTo(CircleEvent foo) { 468 return ((Double) this.xpos).compareTo(foo.xpos); 469 } 470 } 471 472 private class Edge extends Line2d { 473 boolean done; 474 475 Edge(ComparablePoint p) { 476 begin = p; 477 end = new ComparablePoint(0, 0); 478 done = false; 479 output.add(this); 480 } 481 482 void finish(ComparablePoint p) { 483 if (done) { 484 return; 485 } 486 end = p; 487 done = true; 488 } 489 } 490 491 /** 492 * Parabolic arc: the set of points equidistant from a focus point and 493 * the beach line 494 */ 495 private static class Arc { 496 ComparablePoint focus; 497 498 // Arcs exist in a linked list 499 Arc next, prev; 500 501 CircleEvent event; 502 503 Edge edge0, edge1; 504 505 Arc(ComparablePoint p) { 506 focus = p; 507 next = null; 508 prev = null; 509 event = null; 510 edge0 = null; 511 edge1 = null; 512 } 513 } 514 515 private static class CircleResultPack { 516 boolean valid; 517 ComparablePoint center; 518 double rightmostX; 519 } 520 } 521}