• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2005 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.common.geometry;
17 
18 import com.google.common.base.Preconditions;
19 
20 import java.util.ArrayList;
21 import java.util.Comparator;
22 import java.util.HashSet;
23 import java.util.PriorityQueue;
24 
25 /**
26  * An S2RegionCoverer is a class that allows arbitrary regions to be
27  * approximated as unions of cells (S2CellUnion). This is useful for
28  * implementing various sorts of search and precomputation operations.
29  *
30  * Typical usage: {@code S2RegionCoverer coverer; coverer.setMaxCells(5); S2Cap
31  * cap = S2Cap.fromAxisAngle(...); S2CellUnion covering;
32  * coverer.getCovering(cap, covering); * }
33  *
34  * This yields a cell union of at most 5 cells that is guaranteed to cover the
35  * given cap (a disc-shaped region on the sphere).
36  *
37  *  The approximation algorithm is not optimal but does a pretty good job in
38  * practice. The output does not always use the maximum number of cells allowed,
39  * both because this would not always yield a better approximation, and because
40  * max_cells() is a limit on how much work is done exploring the possible
41  * covering as well as a limit on the final output size.
42  *
43  *  One can also generate interior coverings, which are sets of cells which are
44  * entirely contained within a region. Interior coverings can be empty, even for
45  * non-empty regions, if there are no cells that satisfy the provided
46  * constraints and are contained by the region. Note that for performance
47  * reasons, it is wise to specify a max_level when computing interior coverings
48  * - otherwise for regions with small or zero area, the algorithm may spend a
49  * lot of time subdividing cells all the way to leaf level to try to find
50  * contained cells.
51  *
52  *  This class is thread-unsafe. Simultaneous calls to any of the getCovering
53  * methods will conflict and produce unpredictable results.
54  *
55  */
56 public final strictfp class S2RegionCoverer {
57 
58   /**
59    * By default, the covering uses at most 8 cells at any level. This gives a
60    * reasonable tradeoff between the number of cells used and the accuracy of
61    * the approximation (see table below).
62    */
63   public static final int DEFAULT_MAX_CELLS = 8;
64 
65   private static final S2Cell[] FACE_CELLS = new S2Cell[6];
66   static {
67     for (int face = 0; face < 6; ++face) {
68       FACE_CELLS[face] = S2Cell.fromFacePosLevel(face, (byte) 0, 0);
69     }
70   }
71 
72 
73   private int minLevel;
74   private int maxLevel;
75   private int levelMod;
76   private int maxCells;
77 
78   // True if we're computing an interior covering.
79   private boolean interiorCovering;
80 
81   // Counter of number of candidates created, for performance evaluation.
82   private int candidatesCreatedCounter;
83 
84   /**
85    * We save a temporary copy of the pointer passed to GetCovering() in order to
86    * avoid passing this parameter around internally. It is only used (and only
87    * valid) for the duration of a single GetCovering() call.
88    */
89   S2Region region;
90 
91   /**
92    * A temporary variable used by GetCovering() that holds the cell ids that
93    * have been added to the covering so far.
94    */
95   ArrayList<S2CellId> result;
96 
97 
98   static class Candidate {
99     private S2Cell cell;
100     private boolean isTerminal; // Cell should not be expanded further.
101     private int numChildren; // Number of children that intersect the region.
102     private Candidate[] children; // Actual size may be 0, 4, 16, or 64
103     // elements.
104   }
105 
106   static class QueueEntry {
107     private int id;
108     private Candidate candidate;
109 
QueueEntry(int id, Candidate candidate)110     public QueueEntry(int id, Candidate candidate) {
111       this.id = id;
112       this.candidate = candidate;
113     }
114   }
115 
116   /**
117    * We define our own comparison function on QueueEntries in order to make the
118    * results deterministic. Using the default less<QueueEntry>, entries of equal
119    * priority would be sorted according to the memory address of the candidate.
120    */
121   static class QueueEntriesComparator implements Comparator<QueueEntry> {
122     @Override
compare(S2RegionCoverer.QueueEntry x, S2RegionCoverer.QueueEntry y)123     public int compare(S2RegionCoverer.QueueEntry x, S2RegionCoverer.QueueEntry y) {
124       return x.id < y.id ? 1 : (x.id > y.id ? -1 : 0);
125     }
126   }
127 
128 
129   /**
130    * We keep the candidates in a priority queue. We specify a vector to hold the
131    * queue entries since for some reason priority_queue<> uses a deque by
132    * default.
133    */
134   private PriorityQueue<QueueEntry> candidateQueue;
135 
136   /**
137    * Default constructor, sets all fields to default values.
138    */
S2RegionCoverer()139   public S2RegionCoverer() {
140     minLevel = 0;
141     maxLevel = S2CellId.MAX_LEVEL;
142     levelMod = 1;
143     maxCells = DEFAULT_MAX_CELLS;
144     this.region = null;
145     result = new ArrayList<S2CellId>();
146     // TODO(kirilll?): 10 is a completely random number, work out a better
147     // estimate
148     candidateQueue = new PriorityQueue<QueueEntry>(10, new QueueEntriesComparator());
149   }
150 
151   // Set the minimum and maximum cell level to be used. The default is to use
152   // all cell levels. Requires: max_level() >= min_level().
153   //
154   // To find the cell level corresponding to a given physical distance, use
155   // the S2Cell metrics defined in s2.h. For example, to find the cell
156   // level that corresponds to an average edge length of 10km, use:
157   //
158   // int level = S2::kAvgEdge.GetClosestLevel(
159   // geostore::S2Earth::KmToRadians(length_km));
160   //
161   // Note: min_level() takes priority over max_cells(), i.e. cells below the
162   // given level will never be used even if this causes a large number of
163   // cells to be returned.
164 
165   /**
166    * Sets the minimum level to be used.
167    */
setMinLevel(int minLevel)168   public void setMinLevel(int minLevel) {
169     // assert (minLevel >= 0 && minLevel <= S2CellId.MAX_LEVEL);
170     this.minLevel = Math.max(0, Math.min(S2CellId.MAX_LEVEL, minLevel));
171   }
172 
173   /**
174    * Sets the maximum level to be used.
175    */
setMaxLevel(int maxLevel)176   public void setMaxLevel(int maxLevel) {
177     // assert (maxLevel >= 0 && maxLevel <= S2CellId.MAX_LEVEL);
178     this.maxLevel = Math.max(0, Math.min(S2CellId.MAX_LEVEL, maxLevel));
179   }
180 
minLevel()181   public int minLevel() {
182     return minLevel;
183   }
184 
maxLevel()185   public int maxLevel() {
186     return maxLevel;
187   }
188 
maxCells()189   public int maxCells() {
190     return maxCells;
191   }
192 
193   /**
194    * If specified, then only cells where (level - min_level) is a multiple of
195    * "level_mod" will be used (default 1). This effectively allows the branching
196    * factor of the S2CellId hierarchy to be increased. Currently the only
197    * parameter values allowed are 1, 2, or 3, corresponding to branching factors
198    * of 4, 16, and 64 respectively.
199    */
setLevelMod(int levelMod)200   public void setLevelMod(int levelMod) {
201     // assert (levelMod >= 1 && levelMod <= 3);
202     this.levelMod = Math.max(1, Math.min(3, levelMod));
203   }
204 
levelMod()205   public int levelMod() {
206     return levelMod;
207   }
208 
209 
210   /**
211    * Sets the maximum desired number of cells in the approximation (defaults to
212    * kDefaultMaxCells). Note the following:
213    *
214    * <ul>
215    * <li>For any setting of max_cells(), up to 6 cells may be returned if that
216    * is the minimum number of cells required (e.g. if the region intersects all
217    * six face cells). Up to 3 cells may be returned even for very tiny convex
218    * regions if they happen to be located at the intersection of three cube
219    * faces.
220    *
221    * <li>For any setting of max_cells(), an arbitrary number of cells may be
222    * returned if min_level() is too high for the region being approximated.
223    *
224    * <li>If max_cells() is less than 4, the area of the covering may be
225    * arbitrarily large compared to the area of the original region even if the
226    * region is convex (e.g. an S2Cap or S2LatLngRect).
227    * </ul>
228    *
229    * Accuracy is measured by dividing the area of the covering by the area of
230    * the original region. The following table shows the median and worst case
231    * values for this area ratio on a test case consisting of 100,000 spherical
232    * caps of random size (generated using s2regioncoverer_unittest):
233    *
234    * <pre>
235    * max_cells: 3 4 5 6 8 12 20 100 1000
236    * median ratio: 5.33 3.32 2.73 2.34 1.98 1.66 1.42 1.11 1.01
237    * worst case: 215518 14.41 9.72 5.26 3.91 2.75 1.92 1.20 1.02
238    * </pre>
239    */
setMaxCells(int maxCells)240   public void setMaxCells(int maxCells) {
241     this.maxCells = maxCells;
242   }
243 
244   /**
245    * Computes a list of cell ids that covers the given region and satisfies the
246    * various restrictions specified above.
247    *
248    * @param region The region to cover
249    * @param covering The list filled in by this method
250    */
getCovering(S2Region region, ArrayList<S2CellId> covering)251   public void getCovering(S2Region region, ArrayList<S2CellId> covering) {
252     // Rather than just returning the raw list of cell ids generated by
253     // GetCoveringInternal(), we construct a cell union and then denormalize it.
254     // This has the effect of replacing four child cells with their parent
255     // whenever this does not violate the covering parameters specified
256     // (min_level, level_mod, etc). This strategy significantly reduces the
257     // number of cells returned in many cases, and it is cheap compared to
258     // computing the covering in the first place.
259 
260     S2CellUnion tmp = getCovering(region);
261     tmp.denormalize(minLevel(), levelMod(), covering);
262   }
263 
264   /**
265    * Computes a list of cell ids that is contained within the given region and
266    * satisfies the various restrictions specified above.
267    *
268    * @param region The region to fill
269    * @param interior The list filled in by this method
270    */
getInteriorCovering(S2Region region, ArrayList<S2CellId> interior)271   public void getInteriorCovering(S2Region region, ArrayList<S2CellId> interior) {
272     S2CellUnion tmp = getInteriorCovering(region);
273     tmp.denormalize(minLevel(), levelMod(), interior);
274   }
275 
276   /**
277    * Return a normalized cell union that covers the given region and satisfies
278    * the restrictions *EXCEPT* for min_level() and level_mod(). These criteria
279    * cannot be satisfied using a cell union because cell unions are
280    * automatically normalized by replacing four child cells with their parent
281    * whenever possible. (Note that the list of cell ids passed to the cell union
282    * constructor does in fact satisfy all the given restrictions.)
283    */
getCovering(S2Region region)284   public S2CellUnion getCovering(S2Region region) {
285     S2CellUnion covering = new S2CellUnion();
286     getCovering(region, covering);
287     return covering;
288   }
289 
getCovering(S2Region region, S2CellUnion covering)290   public void getCovering(S2Region region, S2CellUnion covering) {
291     interiorCovering = false;
292     getCoveringInternal(region);
293     covering.initSwap(result);
294   }
295 
296   /**
297    * Return a normalized cell union that is contained within the given region
298    * and satisfies the restrictions *EXCEPT* for min_level() and level_mod().
299    */
getInteriorCovering(S2Region region)300   public S2CellUnion getInteriorCovering(S2Region region) {
301     S2CellUnion covering = new S2CellUnion();
302     getInteriorCovering(region, covering);
303     return covering;
304   }
305 
getInteriorCovering(S2Region region, S2CellUnion covering)306   public void getInteriorCovering(S2Region region, S2CellUnion covering) {
307     interiorCovering = true;
308     getCoveringInternal(region);
309     covering.initSwap(result);
310   }
311 
312   /**
313    * Given a connected region and a starting point, return a set of cells at the
314    * given level that cover the region.
315    */
getSimpleCovering( S2Region region, S2Point start, int level, ArrayList<S2CellId> output)316   public static void getSimpleCovering(
317       S2Region region, S2Point start, int level, ArrayList<S2CellId> output) {
318     floodFill(region, S2CellId.fromPoint(start).parent(level), output);
319   }
320 
321   /**
322    * If the cell intersects the given region, return a new candidate with no
323    * children, otherwise return null. Also marks the candidate as "terminal" if
324    * it should not be expanded further.
325    */
newCandidate(S2Cell cell)326   private Candidate newCandidate(S2Cell cell) {
327     if (!region.mayIntersect(cell)) {
328       return null;
329     }
330 
331     boolean isTerminal = false;
332     if (cell.level() >= minLevel) {
333       if (interiorCovering) {
334         if (region.contains(cell)) {
335           isTerminal = true;
336         } else if (cell.level() + levelMod > maxLevel) {
337           return null;
338         }
339       } else {
340         if (cell.level() + levelMod > maxLevel || region.contains(cell)) {
341           isTerminal = true;
342         }
343       }
344     }
345     Candidate candidate = new Candidate();
346     candidate.cell = cell;
347     candidate.isTerminal = isTerminal;
348     if (!isTerminal) {
349       candidate.children = new Candidate[1 << maxChildrenShift()];
350     }
351     candidatesCreatedCounter++;
352     return candidate;
353   }
354 
355   /** Return the log base 2 of the maximum number of children of a candidate. */
maxChildrenShift()356   private int maxChildrenShift() {
357     return 2 * levelMod;
358   }
359 
360   /**
361    * Process a candidate by either adding it to the result list or expanding its
362    * children and inserting it into the priority queue. Passing an argument of
363    * NULL does nothing.
364    */
addCandidate(Candidate candidate)365   private void addCandidate(Candidate candidate) {
366     if (candidate == null) {
367       return;
368     }
369 
370     if (candidate.isTerminal) {
371       result.add(candidate.cell.id());
372       return;
373     }
374     // assert (candidate.numChildren == 0);
375 
376     // Expand one level at a time until we hit min_level_ to ensure that
377     // we don't skip over it.
378     int numLevels = (candidate.cell.level() < minLevel) ? 1 : levelMod;
379     int numTerminals = expandChildren(candidate, candidate.cell, numLevels);
380 
381     if (candidate.numChildren == 0) {
382       // Do nothing
383     } else if (!interiorCovering && numTerminals == 1 << maxChildrenShift()
384         && candidate.cell.level() >= minLevel) {
385       // Optimization: add the parent cell rather than all of its children.
386       // We can't do this for interior coverings, since the children just
387       // intersect the region, but may not be contained by it - we need to
388       // subdivide them further.
389       candidate.isTerminal = true;
390       addCandidate(candidate);
391 
392     } else {
393       // We negate the priority so that smaller absolute priorities are returned
394       // first. The heuristic is designed to refine the largest cells first,
395       // since those are where we have the largest potential gain. Among cells
396       // at the same level, we prefer the cells with the smallest number of
397       // intersecting children. Finally, we prefer cells that have the smallest
398       // number of children that cannot be refined any further.
399       int priority = -((((candidate.cell.level() << maxChildrenShift()) + candidate.numChildren)
400           << maxChildrenShift()) + numTerminals);
401       candidateQueue.add(new QueueEntry(priority, candidate));
402       // logger.info("Push: " + candidate.cell.id() + " (" + priority + ") ");
403     }
404   }
405 
406   /**
407    * Populate the children of "candidate" by expanding the given number of
408    * levels from the given cell. Returns the number of children that were marked
409    * "terminal".
410    */
expandChildren(Candidate candidate, S2Cell cell, int numLevels)411   private int expandChildren(Candidate candidate, S2Cell cell, int numLevels) {
412     numLevels--;
413     S2Cell[] childCells = new S2Cell[4];
414     for (int i = 0; i < 4; ++i) {
415       childCells[i] = new S2Cell();
416     }
417     cell.subdivide(childCells);
418     int numTerminals = 0;
419     for (int i = 0; i < 4; ++i) {
420       if (numLevels > 0) {
421         if (region.mayIntersect(childCells[i])) {
422           numTerminals += expandChildren(candidate, childCells[i], numLevels);
423         }
424         continue;
425       }
426       Candidate child = newCandidate(childCells[i]);
427       if (child != null) {
428         candidate.children[candidate.numChildren++] = child;
429         if (child.isTerminal) {
430           ++numTerminals;
431         }
432       }
433     }
434     return numTerminals;
435   }
436 
437   /** Computes a set of initial candidates that cover the given region. */
getInitialCandidates()438   private void getInitialCandidates() {
439     // Optimization: if at least 4 cells are desired (the normal case),
440     // start with a 4-cell covering of the region's bounding cap. This
441     // lets us skip quite a few levels of refinement when the region to
442     // be covered is relatively small.
443     if (maxCells >= 4) {
444       // Find the maximum level such that the bounding cap contains at most one
445       // cell vertex at that level.
446       S2Cap cap = region.getCapBound();
447       int level = Math.min(S2Projections.MIN_WIDTH.getMaxLevel(2 * cap.angle().radians()),
448           Math.min(maxLevel(), S2CellId.MAX_LEVEL - 1));
449       if (levelMod() > 1 && level > minLevel()) {
450         level -= (level - minLevel()) % levelMod();
451       }
452       // We don't bother trying to optimize the level == 0 case, since more than
453       // four face cells may be required.
454       if (level > 0) {
455         // Find the leaf cell containing the cap axis, and determine which
456         // subcell of the parent cell contains it.
457         ArrayList<S2CellId> base = new ArrayList<S2CellId>(4);
458         S2CellId id = S2CellId.fromPoint(cap.axis());
459         id.getVertexNeighbors(level, base);
460         for (int i = 0; i < base.size(); ++i) {
461           addCandidate(newCandidate(new S2Cell(base.get(i))));
462         }
463         return;
464       }
465     }
466     // Default: start with all six cube faces.
467     for (int face = 0; face < 6; ++face) {
468       addCandidate(newCandidate(FACE_CELLS[face]));
469     }
470   }
471 
472   /** Generates a covering and stores it in result. */
getCoveringInternal(S2Region region)473   private void getCoveringInternal(S2Region region) {
474     // Strategy: Start with the 6 faces of the cube. Discard any
475     // that do not intersect the shape. Then repeatedly choose the
476     // largest cell that intersects the shape and subdivide it.
477     //
478     // result contains the cells that will be part of the output, while the
479     // priority queue contains cells that we may still subdivide further. Cells
480     // that are entirely contained within the region are immediately added to
481     // the output, while cells that do not intersect the region are immediately
482     // discarded.
483     // Therefore pq_ only contains cells that partially intersect the region.
484     // Candidates are prioritized first according to cell size (larger cells
485     // first), then by the number of intersecting children they have (fewest
486     // children first), and then by the number of fully contained children
487     // (fewest children first).
488 
489     Preconditions.checkState(candidateQueue.isEmpty() && result.isEmpty());
490 
491     this.region = region;
492     candidatesCreatedCounter = 0;
493 
494     getInitialCandidates();
495     while (!candidateQueue.isEmpty() && (!interiorCovering || result.size() < maxCells)) {
496       Candidate candidate = candidateQueue.poll().candidate;
497       // logger.info("Pop: " + candidate.cell.id());
498       if (candidate.cell.level() < minLevel || candidate.numChildren == 1
499           || result.size() + (interiorCovering ? 0 : candidateQueue.size()) + candidate.numChildren
500               <= maxCells) {
501         // Expand this candidate into its children.
502         for (int i = 0; i < candidate.numChildren; ++i) {
503           addCandidate(candidate.children[i]);
504         }
505       } else if (interiorCovering) {
506         // Do nothing
507       } else {
508         candidate.isTerminal = true;
509         addCandidate(candidate);
510       }
511     }
512 
513     candidateQueue.clear();
514     this.region = null;
515   }
516 
517   /**
518    * Given a region and a starting cell, return the set of all the
519    * edge-connected cells at the same level that intersect "region". The output
520    * cells are returned in arbitrary order.
521    */
floodFill(S2Region region, S2CellId start, ArrayList<S2CellId> output)522   private static void floodFill(S2Region region, S2CellId start, ArrayList<S2CellId> output) {
523     HashSet<S2CellId> all = new HashSet<S2CellId>();
524     ArrayList<S2CellId> frontier = new ArrayList<S2CellId>();
525     output.clear();
526     all.add(start);
527     frontier.add(start);
528     while (!frontier.isEmpty()) {
529       S2CellId id = frontier.get(frontier.size() - 1);
530       frontier.remove(frontier.size() - 1);
531       if (!region.mayIntersect(new S2Cell(id))) {
532         continue;
533       }
534       output.add(id);
535 
536       S2CellId[] neighbors = new S2CellId[4];
537       id.getEdgeNeighbors(neighbors);
538       for (int edge = 0; edge < 4; ++edge) {
539         S2CellId nbr = neighbors[edge];
540         boolean hasNbr = all.contains(nbr);
541         if (!all.contains(nbr)) {
542           frontier.add(nbr);
543           all.add(nbr);
544         }
545       }
546     }
547   }
548 }
549