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}