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 java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.Map;
23 import java.util.Set;
24 import java.util.logging.Logger;
25 
26 /**
27  */
28 public strictfp class S2CellIdTest extends GeometryTestCase {
29 
30   private static final Logger logger = Logger.getLogger(S2CellIdTest.class.getName());
31 
getCellId(double latDegrees, double lngDegrees)32   private S2CellId getCellId(double latDegrees, double lngDegrees) {
33     S2CellId id = S2CellId.fromLatLng(S2LatLng.fromDegrees(latDegrees, lngDegrees));
34     logger.info(Long.toString(id.id(), 16));
35     return id;
36   }
37 
testBasic()38   public void testBasic() {
39     logger.info("TestBasic");
40     // Check default constructor.
41     S2CellId id = new S2CellId();
42     assertEquals(id.id(), 0);
43     assertTrue(!id.isValid());
44 
45     // Check basic accessor methods.
46     id = S2CellId.fromFacePosLevel(3, 0x12345678, S2CellId.MAX_LEVEL - 4);
47     assertTrue(id.isValid());
48     assertEquals(id.face(), 3);
49     // assertEquals(id.pos(), 0x12345700);
50     assertEquals(id.level(), S2CellId.MAX_LEVEL - 4);
51     assertTrue(!id.isLeaf());
52 
53     // Check face definitions
54     assertEquals(getCellId(0, 0).face(), 0);
55     assertEquals(getCellId(0, 90).face(), 1);
56     assertEquals(getCellId(90, 0).face(), 2);
57     assertEquals(getCellId(0, 180).face(), 3);
58     assertEquals(getCellId(0, -90).face(), 4);
59     assertEquals(getCellId(-90, 0).face(), 5);
60 
61     // Check parent/child relationships.
62     assertEquals(id.childBegin(id.level() + 2).pos(), 0x12345610);
63     assertEquals(id.childBegin().pos(), 0x12345640);
64     assertEquals(id.parent().pos(), 0x12345400);
65     assertEquals(id.parent(id.level() - 2).pos(), 0x12345000);
66 
67     // Check ordering of children relative to parents.
68     assertTrue(id.childBegin().lessThan(id));
69     assertTrue(id.childEnd().greaterThan(id));
70     assertEquals(id.childBegin().next().next().next().next(), id.childEnd());
71     assertEquals(id.childBegin(S2CellId.MAX_LEVEL), id.rangeMin());
72     assertEquals(id.childEnd(S2CellId.MAX_LEVEL), id.rangeMax().next());
73 
74     // Check wrapping from beginning of Hilbert curve to end and vice versa.
75     assertEquals(S2CellId.begin(0).prevWrap(), S2CellId.end(0).prev());
76 
77     assertEquals(S2CellId.begin(S2CellId.MAX_LEVEL).prevWrap(),
78         S2CellId.fromFacePosLevel(5, ~0L >>> S2CellId.FACE_BITS, S2CellId.MAX_LEVEL));
79 
80     assertEquals(S2CellId.end(4).prev().nextWrap(), S2CellId.begin(4));
81     assertEquals(S2CellId.end(S2CellId.MAX_LEVEL).prev().nextWrap(),
82         S2CellId.fromFacePosLevel(0, 0, S2CellId.MAX_LEVEL));
83 
84     // Check that cells are represented by the position of their center
85     // along the Hilbert curve.
86     assertEquals(id.rangeMin().id() + id.rangeMax().id(), 2 * id.id());
87   }
88 
testInverses()89   public void testInverses() {
90     logger.info("TestInverses");
91     // Check the conversion of random leaf cells to S2LatLngs and back.
92     for (int i = 0; i < 200000; ++i) {
93       S2CellId id = getRandomCellId(S2CellId.MAX_LEVEL);
94       assertTrue(id.isLeaf() && id.level() == S2CellId.MAX_LEVEL);
95       S2LatLng center = id.toLatLng();
96       assertEquals(S2CellId.fromLatLng(center).id(), id.id());
97     }
98   }
99 
100 
testToToken()101   public void testToToken() {
102     assertEquals("000000000000010a", new S2CellId(266).toToken());
103     assertEquals("80855c", new S2CellId(-9185834709882503168L).toToken());
104   }
105 
testTokens()106   public void testTokens() {
107     logger.info("TestTokens");
108 
109     // Test random cell ids at all levels.
110     for (int i = 0; i < 10000; ++i) {
111       S2CellId id = getRandomCellId();
112       if (!id.isValid()) {
113         continue;
114       }
115       String token = id.toToken();
116       assertTrue(token.length() <= 16);
117       assertEquals(S2CellId.fromToken(token), id);
118     }
119     // Check that invalid cell ids can be encoded.
120     String token = S2CellId.none().toToken();
121     assertEquals(S2CellId.fromToken(token), S2CellId.none());
122   }
123 
124   private static final int kMaxExpandLevel = 3;
125 
expandCell( S2CellId parent, ArrayList<S2CellId> cells, Map<S2CellId, S2CellId> parentMap)126   private void expandCell(
127       S2CellId parent, ArrayList<S2CellId> cells, Map<S2CellId, S2CellId> parentMap) {
128     cells.add(parent);
129     if (parent.level() == kMaxExpandLevel) {
130       return;
131     }
132     MutableInteger i = new MutableInteger(0);
133     MutableInteger j = new MutableInteger(0);
134     MutableInteger orientation = new MutableInteger(0);
135     int face = parent.toFaceIJOrientation(i, j, orientation);
136     assertEquals(face, parent.face());
137 
138     int pos = 0;
139     for (S2CellId child = parent.childBegin(); !child.equals(parent.childEnd());
140         child = child.next()) {
141       // Do some basic checks on the children
142       assertEquals(child.level(), parent.level() + 1);
143       assertTrue(!child.isLeaf());
144       MutableInteger childOrientation = new MutableInteger(0);
145       assertEquals(child.toFaceIJOrientation(i, j, childOrientation), face);
146       assertEquals(
147           childOrientation.intValue(), orientation.intValue() ^ S2.posToOrientation(pos));
148 
149       parentMap.put(child, parent);
150       expandCell(child, cells, parentMap);
151       ++pos;
152     }
153   }
154 
testContainment()155   public void testContainment() {
156     logger.info("TestContainment");
157     Map<S2CellId, S2CellId> parentMap = new HashMap<S2CellId, S2CellId>();
158     ArrayList<S2CellId> cells = new ArrayList<S2CellId>();
159     for (int face = 0; face < 6; ++face) {
160       expandCell(S2CellId.fromFacePosLevel(face, 0, 0), cells, parentMap);
161     }
162     for (int i = 0; i < cells.size(); ++i) {
163       for (int j = 0; j < cells.size(); ++j) {
164         boolean contained = true;
165         for (S2CellId id = cells.get(j); id != cells.get(i); id = parentMap.get(id)) {
166           if (!parentMap.containsKey(id)) {
167             contained = false;
168             break;
169           }
170         }
171         assertEquals(cells.get(i).contains(cells.get(j)), contained);
172         assertEquals(cells.get(j).greaterOrEquals(cells.get(i).rangeMin())
173             && cells.get(j).lessOrEquals(cells.get(i).rangeMax()), contained);
174         assertEquals(cells.get(i).intersects(cells.get(j)),
175             cells.get(i).contains(cells.get(j)) || cells.get(j).contains(cells.get(i)));
176       }
177     }
178   }
179 
180   private static final int MAX_WALK_LEVEL = 8;
181 
testContinuity()182   public void testContinuity() {
183     logger.info("TestContinuity");
184     // Make sure that sequentially increasing cell ids form a continuous
185     // path over the surface of the sphere, i.e. there are no
186     // discontinuous jumps from one region to another.
187 
188     double maxDist = S2Projections.MAX_EDGE.getValue(MAX_WALK_LEVEL);
189     S2CellId end = S2CellId.end(MAX_WALK_LEVEL);
190     S2CellId id = S2CellId.begin(MAX_WALK_LEVEL);
191     for (; !id.equals(end); id = id.next()) {
192       assertTrue(id.toPointRaw().angle(id.nextWrap().toPointRaw()) <= maxDist);
193 
194       // Check that the ToPointRaw() returns the center of each cell
195       // in (s,t) coordinates.
196       S2Point p = id.toPointRaw();
197       int face = S2Projections.xyzToFace(p);
198       R2Vector uv = S2Projections.validFaceXyzToUv(face, p);
199       assertDoubleNear(Math.IEEEremainder(
200           S2Projections.uvToST(uv.x()), 1.0 / (1 << MAX_WALK_LEVEL)), 0);
201       assertDoubleNear(Math.IEEEremainder(
202           S2Projections.uvToST(uv.y()), 1.0 / (1 << MAX_WALK_LEVEL)), 0);
203     }
204   }
205 
testCoverage()206   public void testCoverage() {
207     logger.info("TestCoverage");
208     // Make sure that random points on the sphere can be represented to the
209     // expected level of accuracy, which in the worst case is sqrt(2/3) times
210     // the maximum arc length between the points on the sphere associated with
211     // adjacent values of "i" or "j". (It is sqrt(2/3) rather than 1/2 because
212     // the cells at the corners of each face are stretched -- they have 60 and
213     // 120 degree angles.)
214 
215     double maxDist = 0.5 * S2Projections.MAX_DIAG.getValue(S2CellId.MAX_LEVEL);
216     for (int i = 0; i < 1000000; ++i) {
217       // randomPoint();
218       S2Point p = new S2Point(0.37861576725894824, 0.2772406863275093, 0.8830558887338725);
219       S2Point q = S2CellId.fromPoint(p).toPointRaw();
220 
221       assertTrue(p.angle(q) <= maxDist);
222     }
223   }
224 
testAllNeighbors(S2CellId id, int level)225   public void testAllNeighbors(S2CellId id, int level) {
226     assertTrue(level >= id.level() && level < S2CellId.MAX_LEVEL);
227 
228     // We compute GetAllNeighbors, and then add in all the children of "id"
229     // at the given level. We then compare this against the result of finding
230     // all the vertex neighbors of all the vertices of children of "id" at the
231     // given level. These should give the same result.
232     ArrayList<S2CellId> all = new ArrayList<S2CellId>();
233     ArrayList<S2CellId> expected = new ArrayList<S2CellId>();
234     id.getAllNeighbors(level, all);
235     S2CellId end = id.childEnd(level + 1);
236     for (S2CellId c = id.childBegin(level + 1); !c.equals(end); c = c.next()) {
237       all.add(c.parent());
238       c.getVertexNeighbors(level, expected);
239     }
240     // Sort the results and eliminate duplicates.
241     Collections.sort(all);
242     Collections.sort(expected);
243     Set<S2CellId> allSet = new HashSet<S2CellId>(all);
244     Set<S2CellId> expectedSet = new HashSet<S2CellId>(expected);
245     assertTrue(allSet.equals(expectedSet));
246   }
247 
testNeighbors()248   public void testNeighbors() {
249     logger.info("TestNeighbors");
250 
251     // Check the edge neighbors of face 1.
252     final int outFaces[] = {5, 3, 2, 0};
253     S2CellId faceNbrs[] = new S2CellId[4];
254     S2CellId.fromFacePosLevel(1, 0, 0).getEdgeNeighbors(faceNbrs);
255     for (int i = 0; i < 4; ++i) {
256       assertTrue(faceNbrs[i].isFace());
257       assertEquals(faceNbrs[i].face(), outFaces[i]);
258     }
259 
260     // Check the vertex neighbors of the center of face 2 at level 5.
261     ArrayList<S2CellId> nbrs = new ArrayList<S2CellId>();
262     S2CellId.fromPoint(new S2Point(0, 0, 1)).getVertexNeighbors(5, nbrs);
263     Collections.sort(nbrs);
264     for (int i = 0; i < 4; ++i) {
265       assertEquals(nbrs.get(i), S2CellId.fromFaceIJ(
266           2, (1 << 29) - (i < 2 ? 1 : 0), (1 << 29) - ((i == 0 || i == 3) ? 1 : 0)).parent(5));
267     }
268     nbrs.clear();
269 
270     // Check the vertex neighbors of the corner of faces 0, 4, and 5.
271     S2CellId id = S2CellId.fromFacePosLevel(0, 0, S2CellId.MAX_LEVEL);
272     id.getVertexNeighbors(0, nbrs);
273     Collections.sort(nbrs);
274     assertEquals(nbrs.size(), 3);
275     assertEquals(nbrs.get(0), S2CellId.fromFacePosLevel(0, 0, 0));
276     assertEquals(nbrs.get(1), S2CellId.fromFacePosLevel(4, 0, 0));
277     assertEquals(nbrs.get(2), S2CellId.fromFacePosLevel(5, 0, 0));
278 
279     // Check that GetAllNeighbors produces results that are consistent
280     // with GetVertexNeighbors for a bunch of random cells.
281     for (int i = 0; i < 1000; ++i) {
282       S2CellId id1 = getRandomCellId();
283       if (id1.isLeaf()) {
284         id1 = id1.parent();
285       }
286 
287       // TestAllNeighbors computes approximately 2**(2*(diff+1)) cell id1s,
288       // so it's not reasonable to use large values of "diff".
289       int maxDiff = Math.min(6, S2CellId.MAX_LEVEL - id1.level() - 1);
290       int level = id1.level() + random(maxDiff);
291       testAllNeighbors(id1, level);
292     }
293   }
294 }
295