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