View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.geometry.enclosing;
18  
19  import java.util.ArrayList;
20  import java.util.List;
21  
22  import org.apache.commons.geometry.core.Point;
23  import org.apache.commons.geometry.core.internal.GeometryInternalError;
24  import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
25  
26  /** Class implementing Emo Welzl's algorithm to find the smallest enclosing ball in linear time.
27   * <p>
28   * The class implements the algorithm described in paper <a
29   * href="http://www.inf.ethz.ch/personal/emo/PublFiles/SmallEnclDisk_LNCS555_91.pdf">Smallest
30   * Enclosing Disks (Balls and Ellipsoids)</a> by Emo Welzl, Lecture Notes in Computer Science
31   * 555 (1991) 359-370. The pivoting improvement published in the paper <a
32   * href="http://www.inf.ethz.ch/personal/gaertner/texts/own_work/esa99_final.pdf">Fast and
33   * Robust Smallest Enclosing Balls</a>, by Bernd Gärtner and further modified in
34   * paper <a
35   * href="http://www.idt.mdh.se/kurser/ct3340/ht12/MINICONFERENCE/FinalPapers/ircse12_submission_30.pdf">
36   * Efficient Computation of Smallest Enclosing Balls in Three Dimensions</a> by Linus Källberg
37   * to avoid performing local copies of data have been included.
38   * </p>
39   * @param <P> Point type.
40   */
41  public class WelzlEncloser<P extends Point<P>> implements Encloser<P> {
42  
43      /** Precision context used to compare floating point numbers. */
44      private final DoublePrecisionContext precision;
45  
46      /** Object used to generate balls from support points. */
47      private final SupportBallGenerator<P> generator;
48  
49      /** Simple constructor.
50       * @param generator generator for balls on support
51       * @param precision precision context used to compare floating point values
52       */
53      public WelzlEncloser(final SupportBallGenerator<P> generator, final DoublePrecisionContext precision) {
54          this.generator = generator;
55          this.precision = precision;
56      }
57  
58      /** {@inheritDoc} */
59      @Override
60      public EnclosingBall<P> enclose(final Iterable<P> points) {
61  
62          if (points == null || !points.iterator().hasNext()) {
63              throw new IllegalArgumentException("Unable to generate enclosing ball: no points given");
64          }
65  
66          // Emo Welzl algorithm with Bernd Gärtner and Linus Källberg improvements
67          return pivotingBall(points);
68      }
69  
70      /** Compute enclosing ball using Gärtner's pivoting heuristic.
71       * @param points points to be enclosed
72       * @return enclosing ball
73       */
74      private EnclosingBall<P> pivotingBall(final Iterable<P> points) {
75  
76          final P first = points.iterator().next();
77          final List<P> extreme = new ArrayList<>(first.getDimension() + 1);
78          final List<P> support = new ArrayList<>(first.getDimension() + 1);
79  
80          // start with only first point selected as a candidate support
81          extreme.add(first);
82          EnclosingBall<P> ball = moveToFrontBall(extreme, extreme.size(), support);
83  
84          while (true) {
85  
86              // select the point farthest to current ball
87              final P farthest = selectFarthest(points, ball);
88  
89              if (ball.contains(farthest, precision)) {
90                  // we have found a ball containing all points
91                  return ball;
92              }
93  
94              // recurse search, restricted to the small subset containing support and farthest point
95              support.clear();
96              support.add(farthest);
97              final EnclosingBall<P> savedBall = ball;
98              ball = moveToFrontBall(extreme, extreme.size(), support);
99              if (precision.lt(ball.getRadius(), savedBall.getRadius())) {
100                 // this should never happen
101                 throw new GeometryInternalError();
102             }
103 
104             // it was an interesting point, move it to the front
105             // according to Gärtner's heuristic
106             extreme.add(0, farthest);
107 
108             // prune the least interesting points
109             extreme.subList(ball.getSupportSize(), extreme.size()).clear();
110         }
111     }
112 
113     /** Compute enclosing ball using Welzl's move to front heuristic.
114      * @param extreme subset of extreme points
115      * @param nbExtreme number of extreme points to consider
116      * @param support points that must belong to the ball support
117      * @return enclosing ball, for the extreme subset only
118      */
119     private EnclosingBall<P> moveToFrontBall(final List<P> extreme, final int nbExtreme,
120                                                 final List<P> support) {
121         // create a new ball on the prescribed support
122         EnclosingBall<P> ball = generator.ballOnSupport(support);
123 
124         if (ball.getSupportSize() <= ball.getCenter().getDimension()) {
125 
126             for (int i = 0; i < nbExtreme; ++i) {
127                 final P pi = extreme.get(i);
128                 if (!ball.contains(pi, precision)) {
129 
130                     // we have found an outside point,
131                     // enlarge the ball by adding it to the support
132                     support.add(pi);
133                     ball = moveToFrontBall(extreme, i, support);
134                     support.remove(support.size() - 1);
135 
136                     // it was an interesting point, move it to the front
137                     // according to Welzl's heuristic
138                     for (int j = i; j > 0; --j) {
139                         extreme.set(j, extreme.get(j - 1));
140                     }
141                     extreme.set(0, pi);
142                 }
143             }
144         }
145 
146         return ball;
147     }
148 
149     /** Select the point farthest to the current ball.
150      * @param points points to be enclosed
151      * @param ball current ball
152      * @return farthest point
153      */
154     private P selectFarthest(final Iterable<P> points, final EnclosingBall<P> ball) {
155 
156         final P center = ball.getCenter();
157         P farthest   = null;
158         double dMax  = -1.0;
159 
160         for (final P point : points) {
161             final double d = point.distance(center);
162             if (d > dMax) {
163                 farthest = point;
164                 dMax     = d;
165             }
166         }
167 
168         return farthest;
169     }
170 }