1 /* 2 * Copyright (C) 2022 The Android Open Source Project 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 17 package com.android.internal.location.altitude; 18 19 import android.annotation.NonNull; 20 21 import java.util.Arrays; 22 import java.util.Locale; 23 24 /** 25 * Provides lightweight S2 cell ID utilities without traditional geometry dependencies. 26 * 27 * <p>See <a href="https://s2geometry.io/">the S2 Geometry Library website</a> for more details. 28 */ 29 public final class S2CellIdUtils { 30 31 /** The level of all leaf S2 cells. */ 32 public static final int MAX_LEVEL = 30; 33 34 private static final int MAX_SIZE = 1 << MAX_LEVEL; 35 private static final double ONE_OVER_MAX_SIZE = 1.0 / MAX_SIZE; 36 private static final int NUM_FACES = 6; 37 private static final int POS_BITS = 2 * MAX_LEVEL + 1; 38 private static final int SWAP_MASK = 0x1; 39 private static final int LOOKUP_BITS = 4; 40 private static final int LOOKUP_MASK = (1 << LOOKUP_BITS) - 1; 41 private static final int INVERT_MASK = 0x2; 42 private static final int LEAF_MASK = 0x1; 43 private static final int[] LOOKUP_POS = new int[1 << (2 * LOOKUP_BITS + 2)]; 44 private static final int[] LOOKUP_IJ = new int[1 << (2 * LOOKUP_BITS + 2)]; 45 private static final int[] POS_TO_ORIENTATION = {SWAP_MASK, 0, 0, INVERT_MASK + SWAP_MASK}; 46 private static final int[][] POS_TO_IJ = 47 {{0, 1, 3, 2}, {0, 2, 3, 1}, {3, 2, 0, 1}, {3, 1, 0, 2}}; 48 private static final double UV_LIMIT = calculateUvLimit(); 49 private static final UvTransform[] UV_TRANSFORMS = createUvTransforms(); 50 private static final XyzTransform[] XYZ_TRANSFORMS = createXyzTransforms(); 51 52 // Used to encode (i, j, o) coordinates into primitive longs. 53 private static final int I_SHIFT = 33; 54 private static final int J_SHIFT = 2; 55 private static final long J_MASK = (1L << 31) - 1; 56 57 static { initLookupCells()58 initLookupCells(); 59 } 60 61 /** Prevents instantiation. */ S2CellIdUtils()62 private S2CellIdUtils() { 63 } 64 65 /** 66 * Returns the leaf S2 cell ID for the specified latitude and longitude, both measured in 67 * degrees. 68 */ fromLatLngDegrees(double latDegrees, double lngDegrees)69 public static long fromLatLngDegrees(double latDegrees, double lngDegrees) { 70 return fromLatLngRadians(Math.toRadians(latDegrees), Math.toRadians(lngDegrees)); 71 } 72 73 /** 74 * Returns the ID of the parent of the specified S2 cell at the specified parent level. 75 * Behavior is undefined for invalid S2 cell IDs or parent levels not in 76 * [0, {@code getLevel(s2CellId)}[. 77 */ getParent(long s2CellId, int level)78 public static long getParent(long s2CellId, int level) { 79 long newLsb = getLowestOnBitForLevel(level); 80 return (s2CellId & -newLsb) | newLsb; 81 } 82 83 /** 84 * Inserts into {@code neighbors} the four S2 cell IDs corresponding to the neighboring 85 * cells adjacent across the specified cell's four edges. This array must be of minimum 86 * length four, and elements at the tail end of the array not corresponding to a neighbor 87 * are set to zero. A reference to this array is returned. 88 * 89 * <p>Inserts in the order of down, right, up, and left directions, in that order. All 90 * neighbors are guaranteed to be distinct. 91 */ getEdgeNeighbors(long s2CellId, @NonNull long[] neighbors)92 public static void getEdgeNeighbors(long s2CellId, @NonNull long[] neighbors) { 93 int level = getLevel(s2CellId); 94 int size = levelToSizeIj(level); 95 int face = getFace(s2CellId); 96 long ijo = toIjo(s2CellId); 97 int i = ijoToI(ijo); 98 int j = ijoToJ(ijo); 99 100 int iPlusSize = i + size; 101 int iMinusSize = i - size; 102 int jPlusSize = j + size; 103 int jMinusSize = j - size; 104 boolean iPlusSizeLtMax = iPlusSize < MAX_SIZE; 105 boolean iMinusSizeGteZero = iMinusSize >= 0; 106 boolean jPlusSizeLtMax = jPlusSize < MAX_SIZE; 107 boolean jMinusSizeGteZero = jMinusSize >= 0; 108 109 int index = 0; 110 // Down direction. 111 neighbors[index++] = getParent(fromFijSame(face, i, jMinusSize, jMinusSizeGteZero), 112 level); 113 // Right direction. 114 neighbors[index++] = getParent(fromFijSame(face, iPlusSize, j, iPlusSizeLtMax), level); 115 // Up direction. 116 neighbors[index++] = getParent(fromFijSame(face, i, jPlusSize, jPlusSizeLtMax), level); 117 // Left direction. 118 neighbors[index++] = getParent(fromFijSame(face, iMinusSize, j, iMinusSizeGteZero), 119 level); 120 121 // Pad end of neighbor array with zeros. 122 Arrays.fill(neighbors, index, neighbors.length, 0); 123 } 124 125 /** Returns the "i" coordinate for the specified S2 cell. */ getI(long s2CellId)126 public static int getI(long s2CellId) { 127 return ijoToI(toIjo(s2CellId)); 128 } 129 130 /** Returns the "j" coordinate for the specified S2 cell. */ getJ(long s2CellId)131 public static int getJ(long s2CellId) { 132 return ijoToJ(toIjo(s2CellId)); 133 } 134 135 /** 136 * Returns the leaf S2 cell ID for the specified latitude and longitude, both measured in 137 * radians. 138 */ fromLatLngRadians(double latRadians, double lngRadians)139 private static long fromLatLngRadians(double latRadians, double lngRadians) { 140 double cosLat = Math.cos(latRadians); 141 double x = Math.cos(lngRadians) * cosLat; 142 double y = Math.sin(lngRadians) * cosLat; 143 double z = Math.sin(latRadians); 144 return fromXyz(x, y, z); 145 } 146 147 /** 148 * Returns the level of the specified S2 cell. The returned level is in [0, 30] for valid 149 * S2 cell IDs. Behavior is undefined for invalid S2 cell IDs. 150 */ getLevel(long s2CellId)151 static int getLevel(long s2CellId) { 152 if (isLeaf(s2CellId)) { 153 return MAX_LEVEL; 154 } 155 return MAX_LEVEL - (Long.numberOfTrailingZeros(s2CellId) >> 1); 156 } 157 158 /** Returns the lowest-numbered bit that is on for the specified S2 cell. */ getLowestOnBit(long s2CellId)159 static long getLowestOnBit(long s2CellId) { 160 return s2CellId & -s2CellId; 161 } 162 163 /** Returns the lowest-numbered bit that is on for any S2 cell on the specified level. */ getLowestOnBitForLevel(int level)164 static long getLowestOnBitForLevel(int level) { 165 return 1L << (2 * (MAX_LEVEL - level)); 166 } 167 168 /** 169 * Returns the ID of the first S2 cell in a traversal of the children S2 cells at the specified 170 * level, in Hilbert curve order. 171 */ getTraversalStart(long s2CellId, int level)172 static long getTraversalStart(long s2CellId, int level) { 173 return s2CellId - getLowestOnBit(s2CellId) + getLowestOnBitForLevel(level); 174 } 175 176 /** Returns the ID of the next S2 cell at the same level along the Hilbert curve. */ getTraversalNext(long s2CellId)177 static long getTraversalNext(long s2CellId) { 178 return s2CellId + (getLowestOnBit(s2CellId) << 1); 179 } 180 181 /** 182 * Encodes the S2 cell id to compact text strings suitable for display or indexing. Cells at 183 * lower levels (i.e., larger cells) are encoded into fewer characters. 184 */ 185 @NonNull getToken(long s2CellId)186 static String getToken(long s2CellId) { 187 if (s2CellId == 0) { 188 return "X"; 189 } 190 191 // Convert to a hex string with as many digits as necessary. 192 String hex = Long.toHexString(s2CellId).toLowerCase(Locale.US); 193 // Prefix 0s to get a length 16 string. 194 String padded = padStart(hex); 195 // Trim zeroes off the end. 196 return padded.replaceAll("0*$", ""); 197 } 198 padStart(String string)199 private static String padStart(String string) { 200 if (string.length() >= 16) { 201 return string; 202 } 203 return "0".repeat(16 - string.length()) + string; 204 } 205 206 /** Returns the leaf S2 cell ID of the specified (x, y, z) coordinate. */ fromXyz(double x, double y, double z)207 private static long fromXyz(double x, double y, double z) { 208 int face = xyzToFace(x, y, z); 209 UvTransform uvTransform = UV_TRANSFORMS[face]; 210 double u = uvTransform.xyzToU(x, y, z); 211 double v = uvTransform.xyzToV(x, y, z); 212 return fromFuv(face, u, v); 213 } 214 215 /** Returns the leaf S2 cell ID of the specified (face, u, v) coordinate. */ fromFuv(int face, double u, double v)216 private static long fromFuv(int face, double u, double v) { 217 int i = uToI(u); 218 int j = vToJ(v); 219 return fromFij(face, i, j); 220 } 221 222 /** Returns the leaf S2 cell ID of the specified (face, i, j) coordinate. */ fromFij(int face, int i, int j)223 private static long fromFij(int face, int i, int j) { 224 int bits = (face & SWAP_MASK); 225 // Update most significant bits. 226 long msb = ((long) face) << (POS_BITS - 33); 227 for (int k = 7; k >= 4; --k) { 228 bits = lookupBits(i, j, k, bits); 229 msb = updateBits(msb, k, bits); 230 bits = maskBits(bits); 231 } 232 // Update least significant bits. 233 long lsb = 0; 234 for (int k = 3; k >= 0; --k) { 235 bits = lookupBits(i, j, k, bits); 236 lsb = updateBits(lsb, k, bits); 237 bits = maskBits(bits); 238 } 239 return (((msb << 32) + lsb) << 1) + 1; 240 } 241 fromFijWrap(int face, int i, int j)242 private static long fromFijWrap(int face, int i, int j) { 243 double u = iToU(i); 244 double v = jToV(j); 245 246 XyzTransform xyzTransform = XYZ_TRANSFORMS[face]; 247 double x = xyzTransform.uvToX(u, v); 248 double y = xyzTransform.uvToY(u, v); 249 double z = xyzTransform.uvToZ(u, v); 250 251 int newFace = xyzToFace(x, y, z); 252 UvTransform uvTransform = UV_TRANSFORMS[newFace]; 253 double newU = uvTransform.xyzToU(x, y, z); 254 double newV = uvTransform.xyzToV(x, y, z); 255 256 int newI = uShiftIntoI(newU); 257 int newJ = vShiftIntoJ(newV); 258 return fromFij(newFace, newI, newJ); 259 } 260 fromFijSame(int face, int i, int j, boolean isSameFace)261 private static long fromFijSame(int face, int i, int j, boolean isSameFace) { 262 if (isSameFace) { 263 return fromFij(face, i, j); 264 } 265 return fromFijWrap(face, i, j); 266 } 267 268 /** 269 * Returns the face associated with the specified (x, y, z) coordinate. For a coordinate 270 * on a face boundary, the returned face is arbitrary but repeatable. 271 */ xyzToFace(double x, double y, double z)272 private static int xyzToFace(double x, double y, double z) { 273 double absX = Math.abs(x); 274 double absY = Math.abs(y); 275 double absZ = Math.abs(z); 276 if (absX > absY) { 277 if (absX > absZ) { 278 return (x < 0) ? 3 : 0; 279 } 280 return (z < 0) ? 5 : 2; 281 } 282 if (absY > absZ) { 283 return (y < 0) ? 4 : 1; 284 } 285 return (z < 0) ? 5 : 2; 286 } 287 uToI(double u)288 private static int uToI(double u) { 289 double s; 290 if (u >= 0) { 291 s = 0.5 * Math.sqrt(1 + 3 * u); 292 } else { 293 s = 1 - 0.5 * Math.sqrt(1 - 3 * u); 294 } 295 return Math.max(0, Math.min(MAX_SIZE - 1, (int) Math.round(MAX_SIZE * s - 0.5))); 296 } 297 vToJ(double v)298 private static int vToJ(double v) { 299 // Same calculation as uToI. 300 return uToI(v); 301 } 302 lookupBits(int i, int j, int k, int bits)303 private static int lookupBits(int i, int j, int k, int bits) { 304 bits += ((i >> (k * LOOKUP_BITS)) & LOOKUP_MASK) << (LOOKUP_BITS + 2); 305 bits += ((j >> (k * LOOKUP_BITS)) & LOOKUP_MASK) << 2; 306 return LOOKUP_POS[bits]; 307 } 308 updateBits(long sb, int k, int bits)309 private static long updateBits(long sb, int k, int bits) { 310 return sb | ((((long) bits) >> 2) << ((k & 0x3) * 2 * LOOKUP_BITS)); 311 } 312 maskBits(int bits)313 private static int maskBits(int bits) { 314 return bits & (SWAP_MASK | INVERT_MASK); 315 } 316 getFace(long s2CellId)317 private static int getFace(long s2CellId) { 318 return (int) (s2CellId >>> POS_BITS); 319 } 320 isLeaf(long s2CellId)321 private static boolean isLeaf(long s2CellId) { 322 return ((int) s2CellId & LEAF_MASK) != 0; 323 } 324 iToU(int i)325 private static double iToU(int i) { 326 int satI = Math.max(-1, Math.min(MAX_SIZE, i)); 327 return Math.max( 328 -UV_LIMIT, 329 Math.min(UV_LIMIT, ONE_OVER_MAX_SIZE * ((satI << 1) + 1 - MAX_SIZE))); 330 } 331 jToV(int j)332 private static double jToV(int j) { 333 // Same calculation as iToU. 334 return iToU(j); 335 } 336 toIjo(long s2CellId)337 private static long toIjo(long s2CellId) { 338 int face = getFace(s2CellId); 339 int bits = face & SWAP_MASK; 340 int i = 0; 341 int j = 0; 342 for (int k = 7; k >= 0; --k) { 343 int nbits = (k == 7) ? (MAX_LEVEL - 7 * LOOKUP_BITS) : LOOKUP_BITS; 344 bits += ((int) (s2CellId >>> (k * 2 * LOOKUP_BITS + 1)) & ((1 << (2 * nbits)) 345 - 1)) << 2; 346 bits = LOOKUP_IJ[bits]; 347 i += (bits >> (LOOKUP_BITS + 2)) << (k * LOOKUP_BITS); 348 j += ((bits >> 2) & ((1 << LOOKUP_BITS) - 1)) << (k * LOOKUP_BITS); 349 bits &= (SWAP_MASK | INVERT_MASK); 350 } 351 int orientation = 352 ((getLowestOnBit(s2CellId) & 0x1111111111111110L) != 0) ? (bits ^ SWAP_MASK) 353 : bits; 354 return (((long) i) << I_SHIFT) | (((long) j) << J_SHIFT) | orientation; 355 } 356 ijoToI(long ijo)357 private static int ijoToI(long ijo) { 358 return (int) (ijo >>> I_SHIFT); 359 } 360 ijoToJ(long ijo)361 private static int ijoToJ(long ijo) { 362 return (int) ((ijo >>> J_SHIFT) & J_MASK); 363 } 364 uShiftIntoI(double u)365 private static int uShiftIntoI(double u) { 366 double s = 0.5 * (u + 1); 367 return Math.max(0, Math.min(MAX_SIZE - 1, (int) Math.round(MAX_SIZE * s - 0.5))); 368 } 369 vShiftIntoJ(double v)370 private static int vShiftIntoJ(double v) { 371 // Same calculation as uShiftIntoI. 372 return uShiftIntoI(v); 373 } 374 levelToSizeIj(int level)375 private static int levelToSizeIj(int level) { 376 return 1 << (MAX_LEVEL - level); 377 } 378 initLookupCells()379 private static void initLookupCells() { 380 initLookupCell(0, 0, 0, 0, 0, 0); 381 initLookupCell(0, 0, 0, SWAP_MASK, 0, SWAP_MASK); 382 initLookupCell(0, 0, 0, INVERT_MASK, 0, INVERT_MASK); 383 initLookupCell(0, 0, 0, SWAP_MASK | INVERT_MASK, 0, SWAP_MASK | INVERT_MASK); 384 } 385 initLookupCell( int level, int i, int j, int origOrientation, int pos, int orientation)386 private static void initLookupCell( 387 int level, int i, int j, int origOrientation, int pos, int orientation) { 388 if (level == LOOKUP_BITS) { 389 int ij = (i << LOOKUP_BITS) + j; 390 LOOKUP_POS[(ij << 2) + origOrientation] = (pos << 2) + orientation; 391 LOOKUP_IJ[(pos << 2) + origOrientation] = (ij << 2) + orientation; 392 } else { 393 level++; 394 i <<= 1; 395 j <<= 1; 396 pos <<= 2; 397 for (int subPos = 0; subPos < 4; subPos++) { 398 int ij = POS_TO_IJ[orientation][subPos]; 399 int orientationMask = POS_TO_ORIENTATION[subPos]; 400 initLookupCell( 401 level, 402 i + (ij >>> 1), 403 j + (ij & 0x1), 404 origOrientation, 405 pos + subPos, 406 orientation ^ orientationMask); 407 } 408 } 409 } 410 calculateUvLimit()411 private static double calculateUvLimit() { 412 double machEps = 1.0; 413 do { 414 machEps /= 2.0f; 415 } while ((1.0 + (machEps / 2.0)) != 1.0); 416 return 1.0 + machEps; 417 } 418 419 @NonNull createUvTransforms()420 private static UvTransform[] createUvTransforms() { 421 UvTransform[] uvTransforms = new UvTransform[NUM_FACES]; 422 uvTransforms[0] = 423 new UvTransform() { 424 425 @Override 426 public double xyzToU(double x, double y, double z) { 427 return y / x; 428 } 429 430 @Override 431 public double xyzToV(double x, double y, double z) { 432 return z / x; 433 } 434 }; 435 uvTransforms[1] = 436 new UvTransform() { 437 438 @Override 439 public double xyzToU(double x, double y, double z) { 440 return -x / y; 441 } 442 443 @Override 444 public double xyzToV(double x, double y, double z) { 445 return z / y; 446 } 447 }; 448 uvTransforms[2] = 449 new UvTransform() { 450 451 @Override 452 public double xyzToU(double x, double y, double z) { 453 return -x / z; 454 } 455 456 @Override 457 public double xyzToV(double x, double y, double z) { 458 return -y / z; 459 } 460 }; 461 uvTransforms[3] = 462 new UvTransform() { 463 464 @Override 465 public double xyzToU(double x, double y, double z) { 466 return z / x; 467 } 468 469 @Override 470 public double xyzToV(double x, double y, double z) { 471 return y / x; 472 } 473 }; 474 uvTransforms[4] = 475 new UvTransform() { 476 477 @Override 478 public double xyzToU(double x, double y, double z) { 479 return z / y; 480 } 481 482 @Override 483 public double xyzToV(double x, double y, double z) { 484 return -x / y; 485 } 486 }; 487 uvTransforms[5] = 488 new UvTransform() { 489 490 @Override 491 public double xyzToU(double x, double y, double z) { 492 return -y / z; 493 } 494 495 @Override 496 public double xyzToV(double x, double y, double z) { 497 return -x / z; 498 } 499 }; 500 return uvTransforms; 501 } 502 503 @NonNull createXyzTransforms()504 private static XyzTransform[] createXyzTransforms() { 505 XyzTransform[] xyzTransforms = new XyzTransform[NUM_FACES]; 506 xyzTransforms[0] = 507 new XyzTransform() { 508 509 @Override 510 public double uvToX(double u, double v) { 511 return 1; 512 } 513 514 @Override 515 public double uvToY(double u, double v) { 516 return u; 517 } 518 519 @Override 520 public double uvToZ(double u, double v) { 521 return v; 522 } 523 }; 524 xyzTransforms[1] = 525 new XyzTransform() { 526 527 @Override 528 public double uvToX(double u, double v) { 529 return -u; 530 } 531 532 @Override 533 public double uvToY(double u, double v) { 534 return 1; 535 } 536 537 @Override 538 public double uvToZ(double u, double v) { 539 return v; 540 } 541 }; 542 xyzTransforms[2] = 543 new XyzTransform() { 544 545 @Override 546 public double uvToX(double u, double v) { 547 return -u; 548 } 549 550 @Override 551 public double uvToY(double u, double v) { 552 return -v; 553 } 554 555 @Override 556 public double uvToZ(double u, double v) { 557 return 1; 558 } 559 }; 560 xyzTransforms[3] = 561 new XyzTransform() { 562 563 @Override 564 public double uvToX(double u, double v) { 565 return -1; 566 } 567 568 @Override 569 public double uvToY(double u, double v) { 570 return -v; 571 } 572 573 @Override 574 public double uvToZ(double u, double v) { 575 return -u; 576 } 577 }; 578 xyzTransforms[4] = 579 new XyzTransform() { 580 581 @Override 582 public double uvToX(double u, double v) { 583 return v; 584 } 585 586 @Override 587 public double uvToY(double u, double v) { 588 return -1; 589 } 590 591 @Override 592 public double uvToZ(double u, double v) { 593 return -u; 594 } 595 }; 596 xyzTransforms[5] = 597 new XyzTransform() { 598 599 @Override 600 public double uvToX(double u, double v) { 601 return v; 602 } 603 604 @Override 605 public double uvToY(double u, double v) { 606 return u; 607 } 608 609 @Override 610 public double uvToZ(double u, double v) { 611 return -1; 612 } 613 }; 614 return xyzTransforms; 615 } 616 617 /** 618 * Transform from (x, y, z) coordinates to (u, v) coordinates, indexed by face. For a 619 * (x, y, z) coordinate within a face, each element of the resulting (u, v) coordinate 620 * should lie in the inclusive range [-1, 1], with the face center having a (u, v) 621 * coordinate equal to (0, 0). 622 */ 623 private interface UvTransform { 624 625 /** 626 * Returns for the specified (x, y, z) coordinate the corresponding u-coordinate 627 * (which may lie outside the range [-1, 1]). 628 */ xyzToU(double x, double y, double z)629 double xyzToU(double x, double y, double z); 630 631 /** 632 * Returns for the specified (x, y, z) coordinate the corresponding v-coordinate 633 * (which may lie outside the range [-1, 1]). 634 */ xyzToV(double x, double y, double z)635 double xyzToV(double x, double y, double z); 636 } 637 638 /** 639 * Transform from (u, v) coordinates to (x, y, z) coordinates, indexed by face. The 640 * resulting vectors are not necessarily of unit length. 641 */ 642 private interface XyzTransform { 643 644 /** Returns for the specified (u, v) coordinate the corresponding x-coordinate. */ uvToX(double u, double v)645 double uvToX(double u, double v); 646 647 /** Returns for the specified (u, v) coordinate the corresponding y-coordinate. */ uvToY(double u, double v)648 double uvToY(double u, double v); 649 650 /** Returns for the specified (u, v) coordinate the corresponding z-coordinate. */ uvToZ(double u, double v)651 double uvToZ(double u, double v); 652 } 653 } 654