• 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.collect.Lists;
19 
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.Iterator;
23 import java.util.List;
24 
25 /**
26  * An S2CellUnion is a region consisting of cells of various sizes. Typically a
27  * cell union is used to approximate some other shape. There is a tradeoff
28  * between the accuracy of the approximation and how many cells are used. Unlike
29  * polygons, cells have a fixed hierarchical structure. This makes them more
30  * suitable for optimizations based on preprocessing.
31  *
32  */
33 public strictfp class S2CellUnion implements S2Region, Iterable<S2CellId> {
34 
35   /** The CellIds that form the Union */
36   private ArrayList<S2CellId> cellIds = new ArrayList<S2CellId>();
37 
S2CellUnion()38   public S2CellUnion() {
39   }
40 
initFromCellIds(ArrayList<S2CellId> cellIds)41   public void initFromCellIds(ArrayList<S2CellId> cellIds) {
42     initRawCellIds(cellIds);
43     normalize();
44   }
45 
46   /**
47    * Populates a cell union with the given S2CellIds or 64-bit cells ids, and
48    * then calls Normalize(). The InitSwap() version takes ownership of the
49    * vector data without copying and clears the given vector. These methods may
50    * be called multiple times.
51    */
initFromIds(ArrayList<Long> cellIds)52   public void initFromIds(ArrayList<Long> cellIds) {
53     initRawIds(cellIds);
54     normalize();
55   }
56 
initSwap(ArrayList<S2CellId> cellIds)57   public void initSwap(ArrayList<S2CellId> cellIds) {
58     initRawSwap(cellIds);
59     normalize();
60   }
61 
initRawCellIds(ArrayList<S2CellId> cellIds)62   public void initRawCellIds(ArrayList<S2CellId> cellIds) {
63     this.cellIds = cellIds;
64   }
65 
initRawIds(ArrayList<Long> cellIds)66   public void initRawIds(ArrayList<Long> cellIds) {
67     int size = cellIds.size();
68     this.cellIds = new ArrayList<S2CellId>(size);
69     for (Long id : cellIds) {
70       this.cellIds.add(new S2CellId(id));
71     }
72   }
73 
74   /**
75    * Like Init(), but does not call Normalize(). The cell union *must* be
76    * normalized before doing any calculations with it, so it is the caller's
77    * responsibility to make sure that the input is normalized. This method is
78    * useful when converting cell unions to another representation and back.
79    * These methods may be called multiple times.
80    */
initRawSwap(ArrayList<S2CellId> cellIds)81   public void initRawSwap(ArrayList<S2CellId> cellIds) {
82     this.cellIds = new ArrayList<S2CellId>(cellIds);
83     cellIds.clear();
84   }
85 
size()86   public int size() {
87     return cellIds.size();
88   }
89 
90   /** Convenience methods for accessing the individual cell ids. */
cellId(int i)91   public S2CellId cellId(int i) {
92     return cellIds.get(i);
93   }
94 
95   /** Enable iteration over the union's cells. */
96   @Override
iterator()97   public Iterator<S2CellId> iterator() {
98     return cellIds.iterator();
99   }
100 
101   /** Direct access to the underlying vector for iteration . */
cellIds()102   public ArrayList<S2CellId> cellIds() {
103     return cellIds;
104   }
105 
106   /**
107    * Replaces "output" with an expanded version of the cell union where any
108    * cells whose level is less than "min_level" or where (level - min_level) is
109    * not a multiple of "level_mod" are replaced by their children, until either
110    * both of these conditions are satisfied or the maximum level is reached.
111    *
112    *  This method allows a covering generated by S2RegionCoverer using
113    * min_level() or level_mod() constraints to be stored as a normalized cell
114    * union (which allows various geometric computations to be done) and then
115    * converted back to the original list of cell ids that satisfies the desired
116    * constraints.
117    */
denormalize(int minLevel, int levelMod, ArrayList<S2CellId> output)118   public void denormalize(int minLevel, int levelMod, ArrayList<S2CellId> output) {
119     // assert (minLevel >= 0 && minLevel <= S2CellId.MAX_LEVEL);
120     // assert (levelMod >= 1 && levelMod <= 3);
121 
122     output.clear();
123     output.ensureCapacity(size());
124     for (S2CellId id : this) {
125       int level = id.level();
126       int newLevel = Math.max(minLevel, level);
127       if (levelMod > 1) {
128         // Round up so that (new_level - min_level) is a multiple of level_mod.
129         // (Note that S2CellId::kMaxLevel is a multiple of 1, 2, and 3.)
130         newLevel += (S2CellId.MAX_LEVEL - (newLevel - minLevel)) % levelMod;
131         newLevel = Math.min(S2CellId.MAX_LEVEL, newLevel);
132       }
133       if (newLevel == level) {
134         output.add(id);
135       } else {
136         S2CellId end = id.childEnd(newLevel);
137         for (id = id.childBegin(newLevel); !id.equals(end); id = id.next()) {
138           output.add(id);
139         }
140       }
141     }
142   }
143 
144   /**
145    * If there are more than "excess" elements of the cell_ids() vector that are
146    * allocated but unused, reallocate the array to eliminate the excess space.
147    * This reduces memory usage when many cell unions need to be held in memory
148    * at once.
149    */
pack()150   public void pack() {
151     cellIds.trimToSize();
152   }
153 
154 
155   /**
156    * Return true if the cell union contains the given cell id. Containment is
157    * defined with respect to regions, e.g. a cell contains its 4 children. This
158    * is a fast operation (logarithmic in the size of the cell union).
159    */
contains(S2CellId id)160   public boolean contains(S2CellId id) {
161     // This function requires that Normalize has been called first.
162     //
163     // This is an exact test. Each cell occupies a linear span of the S2
164     // space-filling curve, and the cell id is simply the position at the center
165     // of this span. The cell union ids are sorted in increasing order along
166     // the space-filling curve. So we simply find the pair of cell ids that
167     // surround the given cell id (using binary search). There is containment
168     // if and only if one of these two cell ids contains this cell.
169 
170     int pos = Collections.binarySearch(cellIds, id);
171     if (pos < 0) {
172       pos = -pos - 1;
173     }
174     if (pos < cellIds.size() && cellIds.get(pos).rangeMin().lessOrEquals(id)) {
175       return true;
176     }
177     return pos != 0 && cellIds.get(pos - 1).rangeMax().greaterOrEquals(id);
178   }
179 
180   /**
181    * Return true if the cell union intersects the given cell id. This is a fast
182    * operation (logarithmic in the size of the cell union).
183    */
intersects(S2CellId id)184   public boolean intersects(S2CellId id) {
185     // This function requires that Normalize has been called first.
186     // This is an exact test; see the comments for Contains() above.
187     int pos = Collections.binarySearch(cellIds, id);
188 
189     if (pos < 0) {
190       pos = -pos - 1;
191     }
192 
193 
194     if (pos < cellIds.size() && cellIds.get(pos).rangeMin().lessOrEquals(id.rangeMax())) {
195       return true;
196     }
197     return pos != 0 && cellIds.get(pos - 1).rangeMax().greaterOrEquals(id.rangeMin());
198   }
199 
contains(S2CellUnion that)200   public boolean contains(S2CellUnion that) {
201     // TODO(kirilll?): A divide-and-conquer or alternating-skip-search approach
202     // may be significantly faster in both the average and worst case.
203     for (S2CellId id : that) {
204       if (!this.contains(id)) {
205         return false;
206       }
207     }
208     return true;
209   }
210 
211   /** This is a fast operation (logarithmic in the size of the cell union). */
212   @Override
contains(S2Cell cell)213   public boolean contains(S2Cell cell) {
214     return contains(cell.id());
215   }
216 
217   /**
218    * Return true if this cell union contain/intersects the given other cell
219    * union.
220    */
intersects(S2CellUnion union)221   public boolean intersects(S2CellUnion union) {
222     // TODO(kirilll?): A divide-and-conquer or alternating-skip-search approach
223     // may be significantly faster in both the average and worst case.
224     for (S2CellId id : union) {
225       if (intersects(id)) {
226         return true;
227       }
228     }
229     return false;
230   }
231 
getUnion(S2CellUnion x, S2CellUnion y)232   public void getUnion(S2CellUnion x, S2CellUnion y) {
233     // assert (x != this && y != this);
234     cellIds.clear();
235     cellIds.ensureCapacity(x.size() + y.size());
236     cellIds.addAll(x.cellIds);
237     cellIds.addAll(y.cellIds);
238     normalize();
239   }
240 
241   /**
242    * Specialized version of GetIntersection() that gets the intersection of a
243    * cell union with the given cell id. This can be useful for "splitting" a
244    * cell union into chunks.
245    */
getIntersection(S2CellUnion x, S2CellId id)246   public void getIntersection(S2CellUnion x, S2CellId id) {
247     // assert (x != this);
248     cellIds.clear();
249     if (x.contains(id)) {
250       cellIds.add(id);
251     } else {
252       int pos = Collections.binarySearch(x.cellIds, id.rangeMin());
253 
254       if (pos < 0) {
255         pos = -pos - 1;
256       }
257 
258       S2CellId idmax = id.rangeMax();
259       int size = x.cellIds.size();
260       while (pos < size && x.cellIds.get(pos).lessOrEquals(idmax)) {
261         cellIds.add(x.cellIds.get(pos++));
262       }
263     }
264   }
265 
266   /**
267    * Initialize this cell union to the union or intersection of the two given
268    * cell unions. Requires: x != this and y != this.
269    */
getIntersection(S2CellUnion x, S2CellUnion y)270   public void getIntersection(S2CellUnion x, S2CellUnion y) {
271     // assert (x != this && y != this);
272 
273     // This is a fairly efficient calculation that uses binary search to skip
274     // over sections of both input vectors. It takes constant time if all the
275     // cells of "x" come before or after all the cells of "y" in S2CellId order.
276 
277     cellIds.clear();
278 
279     int i = 0;
280     int j = 0;
281 
282     while (i < x.cellIds.size() && j < y.cellIds.size()) {
283       S2CellId imin = x.cellId(i).rangeMin();
284       S2CellId jmin = y.cellId(j).rangeMin();
285       if (imin.greaterThan(jmin)) {
286         // Either j->contains(*i) or the two cells are disjoint.
287         if (x.cellId(i).lessOrEquals(y.cellId(j).rangeMax())) {
288           cellIds.add(x.cellId(i++));
289         } else {
290           // Advance "j" to the first cell possibly contained by *i.
291           j = indexedBinarySearch(y.cellIds, imin, j + 1);
292           // The previous cell *(j-1) may now contain *i.
293           if (x.cellId(i).lessOrEquals(y.cellId(j - 1).rangeMax())) {
294             --j;
295           }
296         }
297       } else if (jmin.greaterThan(imin)) {
298         // Identical to the code above with "i" and "j" reversed.
299         if (y.cellId(j).lessOrEquals(x.cellId(i).rangeMax())) {
300           cellIds.add(y.cellId(j++));
301         } else {
302           i = indexedBinarySearch(x.cellIds, jmin, i + 1);
303           if (y.cellId(j).lessOrEquals(x.cellId(i - 1).rangeMax())) {
304             --i;
305           }
306         }
307       } else {
308         // "i" and "j" have the same range_min(), so one contains the other.
309         if (x.cellId(i).lessThan(y.cellId(j))) {
310           cellIds.add(x.cellId(i++));
311         } else {
312           cellIds.add(y.cellId(j++));
313         }
314       }
315     }
316     // The output is generated in sorted order, and there should not be any
317     // cells that can be merged (provided that both inputs were normalized).
318     // assert (!normalize());
319   }
320 
321   /**
322    * Just as normal binary search, except that it allows specifying the starting
323    * value for the lower bound.
324    *
325    * @return The position of the searched element in the list (if found), or the
326    *         position where the element could be inserted without violating the
327    *         order.
328    */
indexedBinarySearch(List<S2CellId> l, S2CellId key, int low)329   private int indexedBinarySearch(List<S2CellId> l, S2CellId key, int low) {
330     int high = l.size() - 1;
331 
332     while (low <= high) {
333       int mid = (low + high) >> 1;
334       S2CellId midVal = l.get(mid);
335       int cmp = midVal.compareTo(key);
336 
337       if (cmp < 0) {
338         low = mid + 1;
339       } else if (cmp > 0) {
340         high = mid - 1;
341       } else {
342         return mid; // key found
343       }
344     }
345     return low; // key not found
346   }
347 
348   /**
349    * Expands the cell union such that it contains all cells of the given level
350    * that are adjacent to any cell of the original union. Two cells are defined
351    * as adjacent if their boundaries have any points in common, i.e. most cells
352    * have 8 adjacent cells (not counting the cell itself).
353    *
354    *  Note that the size of the output is exponential in "level". For example,
355    * if level == 20 and the input has a cell at level 10, there will be on the
356    * order of 4000 adjacent cells in the output. For most applications the
357    * Expand(min_fraction, min_distance) method below is easier to use.
358    */
expand(int level)359   public void expand(int level) {
360     ArrayList<S2CellId> output = new ArrayList<S2CellId>();
361     long levelLsb = S2CellId.lowestOnBitForLevel(level);
362     int i = size() - 1;
363     do {
364       S2CellId id = cellId(i);
365       if (id.lowestOnBit() < levelLsb) {
366         id = id.parent(level);
367         // Optimization: skip over any cells contained by this one. This is
368         // especially important when very small regions are being expanded.
369         while (i > 0 && id.contains(cellId(i - 1))) {
370           --i;
371         }
372       }
373       output.add(id);
374       id.getAllNeighbors(level, output);
375     } while (--i >= 0);
376     initSwap(output);
377   }
378 
379   /**
380    * Expand the cell union such that it contains all points whose distance to
381    * the cell union is at most minRadius, but do not use cells that are more
382    * than maxLevelDiff levels higher than the largest cell in the input. The
383    * second parameter controls the tradeoff between accuracy and output size
384    * when a large region is being expanded by a small amount (e.g. expanding
385    * Canada by 1km).
386    *
387    *  For example, if maxLevelDiff == 4, the region will always be expanded by
388    * approximately 1/16 the width of its largest cell. Note that in the worst
389    * case, the number of cells in the output can be up to 4 * (1 + 2 **
390    * maxLevelDiff) times larger than the number of cells in the input.
391    */
expand(S1Angle minRadius, int maxLevelDiff)392   public void expand(S1Angle minRadius, int maxLevelDiff) {
393     int minLevel = S2CellId.MAX_LEVEL;
394     for (S2CellId id : this) {
395       minLevel = Math.min(minLevel, id.level());
396     }
397     // Find the maximum level such that all cells are at least "min_radius"
398     // wide.
399     int radiusLevel = S2Projections.MIN_WIDTH.getMaxLevel(minRadius.radians());
400     if (radiusLevel == 0 && minRadius.radians() > S2Projections.MIN_WIDTH.getValue(0)) {
401       // The requested expansion is greater than the width of a face cell.
402       // The easiest way to handle this is to expand twice.
403       expand(0);
404     }
405     expand(Math.min(minLevel + maxLevelDiff, radiusLevel));
406   }
407 
408   @Override
clone()409   public S2Region clone() {
410     S2CellUnion copy = new S2CellUnion();
411     copy.initRawCellIds(Lists.newArrayList(cellIds));
412     return copy;
413   }
414 
415   @Override
getCapBound()416   public S2Cap getCapBound() {
417     // Compute the approximate centroid of the region. This won't produce the
418     // bounding cap of minimal area, but it should be close enough.
419     if (cellIds.isEmpty()) {
420       return S2Cap.empty();
421     }
422     S2Point centroid = new S2Point(0, 0, 0);
423     for (S2CellId id : this) {
424       double area = S2Cell.averageArea(id.level());
425       centroid = S2Point.add(centroid, S2Point.mul(id.toPoint(), area));
426     }
427     if (centroid.equals(new S2Point(0, 0, 0))) {
428       centroid = new S2Point(1, 0, 0);
429     } else {
430       centroid = S2Point.normalize(centroid);
431     }
432 
433     // Use the centroid as the cap axis, and expand the cap angle so that it
434     // contains the bounding caps of all the individual cells. Note that it is
435     // *not* sufficient to just bound all the cell vertices because the bounding
436     // cap may be concave (i.e. cover more than one hemisphere).
437     S2Cap cap = S2Cap.fromAxisHeight(centroid, 0);
438     for (S2CellId id : this) {
439       cap = cap.addCap(new S2Cell(id).getCapBound());
440     }
441     return cap;
442   }
443 
444   @Override
getRectBound()445   public S2LatLngRect getRectBound() {
446     S2LatLngRect bound = S2LatLngRect.empty();
447     for (S2CellId id : this) {
448       bound = bound.union(new S2Cell(id).getRectBound());
449     }
450     return bound;
451   }
452 
453 
454   /** This is a fast operation (logarithmic in the size of the cell union). */
455   @Override
mayIntersect(S2Cell cell)456   public boolean mayIntersect(S2Cell cell) {
457     return intersects(cell.id());
458   }
459 
460   /**
461    * The point 'p' does not need to be normalized. This is a fast operation
462    * (logarithmic in the size of the cell union).
463    */
contains(S2Point p)464   public boolean contains(S2Point p) {
465     return contains(S2CellId.fromPoint(p));
466 
467   }
468 
469   /**
470    * The number of leaf cells covered by the union.
471    * This will be no more than 6*2^60 for the whole sphere.
472    *
473    * @return the number of leaf cells covered by the union
474    */
leafCellsCovered()475   public long leafCellsCovered() {
476     long numLeaves = 0;
477     for (S2CellId cellId : cellIds) {
478       int invertedLevel = S2CellId.MAX_LEVEL - cellId.level();
479       numLeaves += (1L << (invertedLevel << 1));
480     }
481     return numLeaves;
482   }
483 
484 
485   /**
486    * Approximate this cell union's area by summing the average area of
487    * each contained cell's average area, using {@link S2Cell#averageArea()}.
488    * This is equivalent to the number of leaves covered, multiplied by
489    * the average area of a leaf.
490    * Note that {@link S2Cell#averageArea()} does not take into account
491    * distortion of cell, and thus may be off by up to a factor of 1.7.
492    * NOTE: Since this is proportional to LeafCellsCovered(), it is
493    * always better to use the other function if all you care about is
494    * the relative average area between objects.
495    *
496    * @return the sum of the average area of each contained cell's average area
497    */
averageBasedArea()498   public double averageBasedArea() {
499     return S2Cell.averageArea(S2CellId.MAX_LEVEL) * leafCellsCovered();
500   }
501 
502   /**
503    * Calculates this cell union's area by summing the approximate area for each
504    * contained cell, using {@link S2Cell#approxArea()}.
505    *
506    * @return approximate area of the cell union
507    */
approxArea()508   public double approxArea() {
509     double area = 0;
510     for (S2CellId cellId : cellIds) {
511       area += new S2Cell(cellId).approxArea();
512     }
513     return area;
514   }
515 
516   /**
517    * Calculates this cell union's area by summing the exact area for each
518    * contained cell, using the {@link S2Cell#exactArea()}.
519    *
520    * @return the exact area of the cell union
521    */
exactArea()522   public double exactArea() {
523     double area = 0;
524     for (S2CellId cellId : cellIds) {
525       area += new S2Cell(cellId).exactArea();
526     }
527     return area;
528   }
529 
530   /** Return true if two cell unions are identical. */
531   @Override
equals(Object that)532   public boolean equals(Object that) {
533     if (!(that instanceof S2CellUnion)) {
534       return false;
535     }
536     S2CellUnion union = (S2CellUnion) that;
537     return this.cellIds.equals(union.cellIds);
538   }
539 
540   @Override
hashCode()541   public int hashCode() {
542     int value = 17;
543     for (S2CellId id : this) {
544       value = 37 * value + id.hashCode();
545     }
546     return value;
547   }
548 
549   /**
550    * Normalizes the cell union by discarding cells that are contained by other
551    * cells, replacing groups of 4 child cells by their parent cell whenever
552    * possible, and sorting all the cell ids in increasing order. Returns true if
553    * the number of cells was reduced.
554    *
555    *  This method *must* be called before doing any calculations on the cell
556    * union, such as Intersects() or Contains().
557    *
558    * @return true if the normalize operation had any effect on the cell union,
559    *         false if the union was already normalized
560    */
normalize()561   public boolean normalize() {
562     // Optimize the representation by looking for cases where all subcells
563     // of a parent cell are present.
564 
565     ArrayList<S2CellId> output = new ArrayList<S2CellId>(cellIds.size());
566     output.ensureCapacity(cellIds.size());
567     Collections.sort(cellIds);
568 
569     for (S2CellId id : this) {
570       int size = output.size();
571       // Check whether this cell is contained by the previous cell.
572       if (!output.isEmpty() && output.get(size - 1).contains(id)) {
573         continue;
574       }
575 
576       // Discard any previous cells contained by this cell.
577       while (!output.isEmpty() && id.contains(output.get(output.size() - 1))) {
578         output.remove(output.size() - 1);
579       }
580 
581       // Check whether the last 3 elements of "output" plus "id" can be
582       // collapsed into a single parent cell.
583       while (output.size() >= 3) {
584         size = output.size();
585         // A necessary (but not sufficient) condition is that the XOR of the
586         // four cells must be zero. This is also very fast to test.
587         if ((output.get(size - 3).id() ^ output.get(size - 2).id() ^ output.get(size - 1).id())
588             != id.id()) {
589           break;
590         }
591 
592         // Now we do a slightly more expensive but exact test. First, compute a
593         // mask that blocks out the two bits that encode the child position of
594         // "id" with respect to its parent, then check that the other three
595         // children all agree with "mask.
596         long mask = id.lowestOnBit() << 1;
597         mask = ~(mask + (mask << 1));
598         long idMasked = (id.id() & mask);
599         if ((output.get(size - 3).id() & mask) != idMasked
600             || (output.get(size - 2).id() & mask) != idMasked
601             || (output.get(size - 1).id() & mask) != idMasked || id.isFace()) {
602           break;
603         }
604 
605         // Replace four children by their parent cell.
606         output.remove(size - 1);
607         output.remove(size - 2);
608         output.remove(size - 3);
609         id = id.parent();
610       }
611       output.add(id);
612     }
613     if (output.size() < size()) {
614       initRawSwap(output);
615       return true;
616     }
617     return false;
618   }
619 }
620