• 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.HashSet;
22 import java.util.List;
23 import java.util.Set;
24 import java.util.logging.Logger;
25 
26 public strictfp class S2CellUnionTest extends GeometryTestCase {
27   public static Logger logger = Logger.getLogger(S2CellUnionTest.class.getName());
28 
testBasic()29   public void testBasic() {
30     logger.info("TestBasic");
31 
32     S2CellUnion empty = new S2CellUnion();
33     ArrayList<S2CellId> ids = Lists.newArrayList();
34     empty.initFromCellIds(ids);
35     assertEquals(0, empty.size());
36 
37     S2CellId face1Id = S2CellId.fromFacePosLevel(1, 0, 0);
38     S2CellUnion face1Union = new S2CellUnion();
39     ids.add(face1Id);
40     face1Union.initFromCellIds(ids);
41     assertEquals(1, face1Union.size());
42     assertEquals(face1Id, face1Union.cellId(0));
43 
44     S2CellId face2Id = S2CellId.fromFacePosLevel(2, 0, 0);
45     S2CellUnion face2Union = new S2CellUnion();
46     ArrayList<Long> cellids = Lists.newArrayList();
47     cellids.add(face2Id.id());
48     face2Union.initFromIds(cellids);
49     assertEquals(1, face2Union.size());
50     assertEquals(face2Id, face2Union.cellId(0));
51 
52     S2Cell face1Cell = new S2Cell(face1Id);
53     S2Cell face2Cell = new S2Cell(face2Id);
54     assertTrue(face1Union.contains(face1Cell));
55     assertTrue(!face1Union.contains(face2Cell));
56   }
57 
testContainsCellUnion()58   public void testContainsCellUnion() {
59     logger.info("TestContainsCellUnion");
60 
61     Set<S2CellId> randomCells = new HashSet<S2CellId>();
62     for (int i = 0; i < 100; i++) {
63       randomCells.add(getRandomCellId(S2CellId.MAX_LEVEL));
64     }
65 
66     S2CellUnion union = new S2CellUnion();
67     union.initFromCellIds(Lists.newArrayList(randomCells));
68 
69     // Add one more
70     while (!randomCells.add(getRandomCellId(S2CellId.MAX_LEVEL))) {
71     }
72 
73     S2CellUnion unionPlusOne = new S2CellUnion();
74     unionPlusOne.initFromCellIds(Lists.newArrayList(randomCells));
75 
76     assertTrue(unionPlusOne.contains(union));
77     assertFalse(union.contains(unionPlusOne));
78 
79     // Build the set of parent cells and check containment
80     Set<S2CellId> parents = new HashSet<S2CellId>();
81     for (S2CellId cellId : union) {
82       parents.add(cellId.parent());
83     }
84 
85     S2CellUnion parentUnion = new S2CellUnion();
86     parentUnion.initFromCellIds(Lists.newArrayList(parents));
87 
88     assertTrue(parentUnion.contains(union));
89     assertFalse(union.contains(parentUnion));
90   }
91 
addCells(S2CellId id, boolean selected, List<S2CellId> input, ArrayList<S2CellId> expected)92   private void addCells(S2CellId id, boolean selected, List<S2CellId> input,
93       ArrayList<S2CellId> expected) {
94     // Decides whether to add "id" and/or some of its descendants to the
95     // test case. If "selected" is true, then the region covered by "id"
96     // *must* be added to the test case (either by adding "id" itself, or
97     // some combination of its descendants, or both). If cell ids are to
98     // the test case "input", then the corresponding expected result after
99     // simplification is added to "expected".
100 
101     if (id.equals(S2CellId.none())) {
102       // Initial call: decide whether to add cell(s) from each face.
103       for (int face = 0; face < 6; ++face) {
104         addCells(S2CellId.fromFacePosLevel(face, 0, 0), false, input, expected);
105       }
106       return;
107     }
108     if (id.isLeaf()) {
109       // The rnd.OneIn() call below ensures that the parent of a leaf cell
110       // will always be selected (if we make it that far down the hierarchy).
111       assertTrue(selected);
112       input.add(id);
113       return;
114     }
115     // The following code ensures that the probability of selecting a cell
116     // at each level is approximately the same, i.e. we test normalization
117     // of cells at all levels.
118     if (!selected && random(S2CellId.MAX_LEVEL - id.level()) != 0) {
119       // Once a cell has been selected, the expected output is predetermined.
120       // We then make sure that cells are selected that will normalize to
121       // the desired output.
122       expected.add(id);
123       selected = true;
124     }
125 
126     // With the rnd.OneIn() constants below, this function adds an average
127     // of 5/6 * (kMaxLevel - level) cells to "input" where "level" is the
128     // level at which the cell was first selected (level 15 on average).
129     // Therefore the average number of input cells in a test case is about
130     // (5/6 * 15 * 6) = 75. The average number of output cells is about 6.
131 
132     // If a cell is selected, we add it to "input" with probability 5/6.
133     boolean added = false;
134     if (selected && random(6) != 0) {
135       input.add(id);
136       added = true;
137     }
138     int numChildren = 0;
139     S2CellId child = id.childBegin();
140     for (int pos = 0; pos < 4; ++pos, child = child.next()) {
141       // If the cell is selected, on average we recurse on 4/12 = 1/3 child.
142       // This intentionally may result in a cell and some of its children
143       // being included in the test case.
144       //
145       // If the cell is not selected, on average we recurse on one child.
146       // We also make sure that we do not recurse on all 4 children, since
147       // then we might include all 4 children in the input case by accident
148       // (in which case the expected output would not be correct).
149       if (random(selected ? 12 : 4) == 0 && numChildren < 3) {
150         addCells(child, selected, input, expected);
151         ++numChildren;
152       }
153       // If this cell was selected but the cell itself was not added, we
154       // must ensure that all 4 children (or some combination of their
155       // descendents) are added.
156       if (selected && !added) {
157         addCells(child, selected, input, expected);
158       }
159     }
160   }
161 
testNormalize()162   public void testNormalize() {
163     logger.info("TestNormalize");
164 
165     // Try a bunch of random test cases, and keep track of average
166     // statistics for normalization (to see if they agree with the
167     // analysis above).
168     S2CellUnion cellunion = new S2CellUnion();
169     double inSum = 0, outSum = 0;
170     final int kIters = 2000;
171     for (int i = 0; i < kIters; ++i) {
172       ArrayList<S2CellId> input = Lists.newArrayList();
173       ArrayList<S2CellId> expected = Lists.newArrayList();
174       addCells(S2CellId.none(), false, input, expected);
175       inSum += input.size();
176       outSum += expected.size();
177       cellunion.initFromCellIds(input);
178       assertEquals(cellunion.size(), expected.size());
179 
180       assertEquals(expected, cellunion.cellIds());
181 
182       // Test GetCapBound().
183       S2Cap cap = cellunion.getCapBound();
184       for (int  k = 0; k < cellunion.size(); ++k) {
185         assertTrue(cap.contains(new S2Cell(cellunion.cellId(k))));
186       }
187 
188       // Test Contains(S2CellId) and Intersects(S2CellId).
189       for (int j = 0; j < input.size(); ++j) {
190         assertTrue(cellunion.contains(input.get(j)));
191         assertTrue(cellunion.intersects(input.get(j)));
192         if (!input.get(j).isFace()) {
193           assertTrue(cellunion.intersects(input.get(j).parent()));
194           if (input.get(j).level() > 1) {
195             assertTrue(cellunion.intersects(input.get(j).parent().parent()));
196             assertTrue(cellunion.intersects(input.get(j).parent(0)));
197           }
198         }
199         if (!input.get(j).isLeaf()) {
200           assertTrue(cellunion.contains(input.get(j).childBegin()));
201           assertTrue(cellunion.intersects(input.get(j).childBegin()));
202           assertTrue(cellunion.contains(input.get(j).childEnd().prev()));
203           assertTrue(cellunion.intersects(input.get(j).childEnd().prev()));
204           assertTrue(cellunion.contains(input.get(j).childBegin(S2CellId.MAX_LEVEL)));
205           assertTrue(cellunion.intersects(input.get(j).childBegin(S2CellId.MAX_LEVEL)));
206         }
207       }
208       for (int j = 0; j < expected.size(); ++j) {
209         if (!expected.get(j).isFace()) {
210           assertTrue(!cellunion.contains(expected.get(j).parent()));
211           assertTrue(!cellunion.contains(expected.get(j).parent(0)));
212         }
213       }
214 
215       // Test contains(S2CellUnion) and intersects(S2CellUnion)
216       ArrayList<S2CellId> x = Lists.newArrayList();
217       ArrayList<S2CellId> y = Lists.newArrayList();
218       ArrayList<S2CellId> xOrY = Lists.newArrayList();
219       ArrayList<S2CellId> xAndY = Lists.newArrayList();
220       for (int j = 0; j < input.size(); ++j) {
221         boolean inX = random(2) == 0;
222         boolean inY = random(2) == 0;
223         if (inX) {
224           x.add(input.get(j));
225         }
226         if (inY) {
227           y.add(input.get(j));
228         }
229         if (inX || inY) {
230           xOrY.add(input.get(j));
231         }
232       }
233       S2CellUnion xCells = new S2CellUnion();
234       S2CellUnion yCells = new S2CellUnion();
235       S2CellUnion xOrYExpected = new S2CellUnion();
236       S2CellUnion xAndYExpected = new S2CellUnion();
237       xCells.initFromCellIds(x);
238       yCells.initFromCellIds(y);
239       xOrYExpected.initFromCellIds(xOrY);
240 
241       S2CellUnion xOrYCells = new S2CellUnion();
242       xOrYCells.getUnion(xCells, yCells);
243       assertEquals(xOrYExpected, xOrYCells);
244 
245       // Compute the intersection of "x" with each cell of "y",
246       // check that this intersection is correct, and append the
247       // results to xAndYExpected.
248       for (int j = 0; j < yCells.size(); ++j) {
249         S2CellId yId = yCells.cellId(j);
250         S2CellUnion u = new S2CellUnion();
251         u.getIntersection(xCells, yId);
252         for (int k = 0; k < xCells.size(); ++k) {
253           S2CellId xId = xCells.cellId(k);
254           if (xId.contains(yId)) {
255             assertEquals(1, u.size());
256             assertEquals(yId, u.cellId(0));
257           } else if (yId.contains(xId)) {
258             if (!u.contains(xId)) {
259               u.getIntersection(xCells, yId);
260             }
261             assertTrue(u.contains(xId));
262           }
263         }
264         for (int k = 0; k < u.size(); ++k) {
265           assertTrue(xCells.contains(u.cellId(k)));
266           assertTrue(yId.contains(u.cellId(k)));
267         }
268         xAndY.addAll(u.cellIds());
269       }
270       xAndYExpected.initFromCellIds(xAndY);
271 
272       S2CellUnion xAndYCells = new S2CellUnion();
273       xAndYCells.getIntersection(xCells, yCells);
274       assertEquals(xAndYExpected, xAndYCells);
275 
276       ArrayList<S2CellId> test = Lists.newArrayList();
277       ArrayList<S2CellId> dummy = Lists.newArrayList();
278 
279       addCells(S2CellId.none(), false, test, dummy);
280       for (int j = 0; j < test.size(); ++j) {
281         boolean contains = false, intersects = false;
282         for (int k = 0; k < expected.size(); ++k) {
283           if (expected.get(k).contains(test.get(j))) {
284             contains = true;
285           }
286           if (expected.get(k).intersects(test.get(j))) {
287             intersects = true;
288           }
289         }
290         assertEquals(cellunion.contains(test.get(j)), contains);
291         assertEquals(cellunion.intersects(test.get(j)), intersects);
292       }
293 
294     }
295   }
296 
getMaxAngle(S2CellUnion covering, S2Point axis)297   double getMaxAngle(S2CellUnion covering, S2Point axis) {
298     double maxAngle = 0;
299     for (int i = 0; i < covering.size(); ++i) {
300       S2Cell cell = new S2Cell(covering.cellId(i));
301       S2Cap cellCap = cell.getCapBound();
302       double angle = axis.angle(cellCap.axis()) + cellCap.angle().radians();
303       maxAngle = Math.max(maxAngle, angle);
304     }
305     return maxAngle;
306   }
307 
testExpand()308   public void testExpand() {
309     logger.info("TestExpand");
310 
311     // This test generates coverings for caps of random sizes, and expands
312     // the coverings by a random radius, and then make sure that the new
313     // covering covers the expanded cap. It also makes sure that the
314     // new covering is not too much larger than expected.
315 
316     S2RegionCoverer coverer = new S2RegionCoverer();
317     for (int i = 0; i < 1000; ++i) {
318       S2Cap cap = getRandomCap(S2Cell.averageArea(S2CellId.MAX_LEVEL), 4 * S2.M_PI);
319 
320       // Expand the cap by a random factor whose log is uniformly distributed
321       // between 0 and log(1e2).
322       S2Cap expandedCap =
323           S2Cap.fromAxisHeight(cap.axis(), Math.min(2.0, Math.pow(1e2, rand.nextDouble())
324               * cap.height()));
325 
326       double radius = expandedCap.angle().radians() - cap.angle().radians();
327       int maxLevelDiff = random(8);
328 
329       S2CellUnion covering = new S2CellUnion();
330       coverer.setMaxCells(1 + skewed(10));
331       coverer.getCovering(cap, covering);
332       checkCovering(cap, covering, true, new S2CellId());
333 
334       double maxAngle = getMaxAngle(covering, cap.axis());
335       int minLevel = S2CellId.MAX_LEVEL;
336       for (int j = 0; j < covering.size(); ++j) {
337         minLevel = Math.min(minLevel, covering.cellId(j).level());
338       }
339       covering.expand(S1Angle.radians(radius), maxLevelDiff);
340       checkCovering(expandedCap, covering, false, new S2CellId());
341 
342       int expandLevel =
343           Math.min(minLevel + maxLevelDiff, S2Projections.MIN_WIDTH.getMaxLevel(radius));
344       double expandedMaxAngle = getMaxAngle(covering, cap.axis());
345 
346       // If the covering includes a tiny cell along the boundary, in theory the
347       // maximum angle of the covering from the cap axis can increase by up to
348       // twice the maximum length of a cell diagonal. We allow for an increase
349       // of slightly more than this because cell bounding caps are not exact.
350       assertTrue(expandedMaxAngle - maxAngle <= 2.01 * S2Projections.MAX_DIAG
351           .getValue(expandLevel));
352     }
353   }
354 
testLeafCellsCovered()355   public void testLeafCellsCovered() {
356     S2CellUnion cellUnion = new S2CellUnion();
357 
358     // empty union
359     assertEquals(0, cellUnion.leafCellsCovered());
360 
361     ArrayList<S2CellId> ids = Lists.newArrayList();
362     ids.add(S2CellId.fromFacePosLevel(
363         0, (1L << ((S2CellId.MAX_LEVEL << 1) - 1)), S2CellId.MAX_LEVEL));
364 
365     // One leaf on face 0.
366     cellUnion.initFromCellIds(ids);
367     assertEquals(1L, cellUnion.leafCellsCovered());
368 
369     // Face 0.
370     ids.add(S2CellId.fromFacePosLevel(0, 0, 0));
371     cellUnion.initFromCellIds(ids);
372     assertEquals(1L << 60, cellUnion.leafCellsCovered());
373 
374     // Five faces.
375     cellUnion.expand(0);
376     assertEquals(5L << 60, cellUnion.leafCellsCovered());
377 
378     // Whole world.
379     cellUnion.expand(0);
380     assertEquals(6L << 60, cellUnion.leafCellsCovered());
381 
382     // Add some disjoint cells.
383     ids.add(S2CellId.fromFacePosLevel(1, 0, 1));
384     ids.add(S2CellId.fromFacePosLevel(2, 0, 2));
385     ids.add(S2CellId.fromFacePosLevel(2, (1L << 60), 2));
386     ids.add(S2CellId.fromFacePosLevel(3, 0, 14));
387     ids.add(S2CellId.fromFacePosLevel(4, (1L << 60), 15));
388     ids.add(S2CellId.fromFacePosLevel(4, 0, 27));
389     ids.add(S2CellId.fromFacePosLevel(5, 0, 30));
390     cellUnion.initFromCellIds(ids);
391     long expected = 1L + (1L << 6) + (1L << 30) + (1L << 32) + (2L << 56) + (1L << 58) + (1L << 60);
392     assertEquals(expected, cellUnion.leafCellsCovered());
393   }
394 
395 
testAverageBasedArea()396   public void testAverageBasedArea() {
397     S2CellUnion cellUnion = new S2CellUnion();
398 
399     // empty union
400     assertEquals(0.0, cellUnion.averageBasedArea());
401 
402     ArrayList<S2CellId> ids = Lists.newArrayList();
403     ids.add(S2CellId.fromFacePosLevel(1, 0, 1));
404     ids.add(S2CellId.fromFacePosLevel(5, 0, 30));
405     cellUnion.initFromCellIds(ids);
406 
407     double expected = S2Cell.averageArea(S2CellId.MAX_LEVEL) * (1L + (1L << 58));
408     assertEquals(expected, cellUnion.averageBasedArea());
409   }
410 
testApproxArea()411   public void testApproxArea() {
412     S2CellUnion cellUnion = new S2CellUnion();
413 
414     // empty union
415     assertEquals(0.0, cellUnion.approxArea());
416 
417     ArrayList<S2CellId> ids = Lists.newArrayList();
418     ids.add(S2CellId.fromFacePosLevel(1, 0, 1));
419     ids.add(S2CellId.fromFacePosLevel(5, 0, 30));
420     cellUnion.initFromCellIds(ids);
421 
422     double expected = new S2Cell(ids.get(0)).approxArea() + new S2Cell(ids.get(1)).approxArea();
423     assertEquals(expected, cellUnion.approxArea());
424   }
425 
testExactArea()426   public void testExactArea() {
427     S2CellUnion cellUnion = new S2CellUnion();
428 
429     // empty union
430     assertEquals(0.0, cellUnion.exactArea());
431 
432     ArrayList<S2CellId> ids = Lists.newArrayList();
433     ids.add(S2CellId.fromFacePosLevel(1, 0, 1));
434     ids.add(S2CellId.fromFacePosLevel(5, 0, 30));
435     cellUnion.initFromCellIds(ids);
436 
437     double expected = new S2Cell(ids.get(0)).exactArea() + new S2Cell(ids.get(1)).exactArea();
438     assertEquals(expected, cellUnion.averageBasedArea());
439   }
440 }
441