S2Cell.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811
  1. /* eslint-disable new-cap */
  2. import Cartesian3 from "./Cartesian3.js";
  3. import Cartographic from "./Cartographic.js";
  4. import Check from "./Check.js";
  5. import defaultValue from "./defaultValue.js";
  6. import defined from "./defined.js";
  7. import DeveloperError from "./DeveloperError.js";
  8. import Ellipsoid from "./Ellipsoid.js";
  9. import FeatureDetection from "./FeatureDetection.js";
  10. import RuntimeError from "./RuntimeError.js";
  11. /**
  12. * S2
  13. * --
  14. *
  15. * This implementation is based on the S2 C++ reference implementation: https://github.com/google/s2geometry
  16. *
  17. *
  18. * Overview:
  19. * ---------
  20. * The S2 library decomposes the unit sphere into a hierarchy of cells. A cell is a quadrilateral bounded by 4 geodesics.
  21. * The 6 root cells are obtained by projecting the six faces of a cube on a unit sphere. Each root cell follows a quadtree
  22. * subdivision scheme, i.e. each cell subdivides into 4 smaller cells that cover the same area as the parent cell. The S2 cell
  23. * hierarchy extends from level 0 (root cells) to level 30 (leaf cells). The root cells are rotated to enable a continuous Hilbert
  24. * curve to map all 6 faces of the cube.
  25. *
  26. *
  27. * Cell ID:
  28. * --------
  29. * Each cell in S2 can be uniquely identified using a 64-bit unsigned integer, its cell ID. The first 3 bits of the cell ID are the face bits, i.e.
  30. * they indicate which of the 6 faces of the cube a cell lies on. After the face bits are the position bits, i.e. they indicate the position
  31. * of the cell along the Hilbert curve. After the positions bits is the sentinel bit, which is always set to 1, and it indicates the level of the
  32. * cell. Again, the level can be between 0 and 30 in S2.
  33. *
  34. * Note: In the illustration below, the face bits are marked with 'f', the position bits are marked with 'p', the zero bits are marked with '-'.
  35. *
  36. * Cell ID (base 10): 3170534137668829184
  37. * Cell ID (base 2) : 0010110000000000000000000000000000000000000000000000000000000000
  38. *
  39. * 001 0110000000000000000000000000000000000000000000000000000000000
  40. * fff pps----------------------------------------------------------
  41. *
  42. * For the cell above, we can see that it lies on face 1 (01), with a Hilbert index of 1 (1).
  43. *
  44. *
  45. * Cell Subdivision:
  46. * ------------------
  47. * Cells in S2 subdivide recursively using quadtree subdivision. For each cell, you can get a child of index [0-3]. To compute the child at index i,
  48. * insert the base 2 representation of i to the right of the parent's position bits. Ensure that the sentinel bit is also shifted two places to the right.
  49. *
  50. * Parent Cell ID (base 10) : 3170534137668829184
  51. * Parent Cell ID (base 2) : 0010110000000000000000000000000000000000000000000000000000000000
  52. *
  53. * 001 0110000000000000000000000000000000000000000000000000000000000
  54. * fff pps----------------------------------------------------------
  55. *
  56. * To get the 3rd child of the cell above, we insert the binary representation of 3 to the right of the parent's position bits:
  57. *
  58. * Note: In the illustration below, the bits to be added are highlighted with '^'.
  59. *
  60. * 001 0111100000000000000000000000000000000000000000000000000000000
  61. * fff pppps--------------------------------------------------------
  62. * ^^
  63. *
  64. * Child(3) Cell ID (base 10) : 3386706919782612992
  65. * Child(3) Cell ID (base 2) : 0010111100000000000000000000000000000000000000000000000000000000
  66. *
  67. * Cell Token:
  68. * -----------
  69. * To provide a more concise representation of the S2 cell ID, we can use their hexadecimal representation.
  70. *
  71. * Cell ID (base 10): 3170534137668829184
  72. * Cell ID (base 2) : 0010110000000000000000000000000000000000000000000000000000000000
  73. *
  74. * We remove all trailing zero bits, until we reach the nybble (4 bits) that contains the sentinel bit.
  75. *
  76. * Note: In the illustration below, the bits to be removed are highlighted with 'X'.
  77. *
  78. * 0010110000000000000000000000000000000000000000000000000000000000
  79. * fffpps--XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  80. *
  81. * We convert the remaining bits to their hexadecimal representation.
  82. *
  83. * Base 2: 0010 1100
  84. * Base 16: "2" "c"
  85. *
  86. * Cell Token: "2c"
  87. *
  88. * To compute the cell ID from the token, we simply add enough zeros to the right to make the ID span 64 bits.
  89. *
  90. * Coordinate Transforms:
  91. * ----------------------
  92. *
  93. * To go from a cell in S2 to a point on the ellipsoid, the following order of transforms is applied:
  94. *
  95. * 1. (Cell ID): S2 cell ID
  96. * 2. (Face, I, J): Leaf cell coordinates, where i and j are in range [0, 2^30 - 1]
  97. * 3. (Face, S, T): Cell space coordinates, where s and t are in range [0, 1].
  98. * 4. (Face, Si, Ti): Discrete cell space coordinates, where si and ti are in range [0, 2^31]
  99. * 5. (Face, U, V): Cube space coordinates, where u and v are in range [-1, 1]. We apply the non-linear quadratic transform here.
  100. * 6. (X, Y, Z): Direction vector, where vector may not be unit length. Can be normalized to obtain point on unit sphere
  101. * 7. (Latitude, Longitude): Direction vector, where latitude is in range [-90, 90] and longitude is in range [-180, 180]
  102. *
  103. * @ignore
  104. */
  105. // The maximum level supported within an S2 cell ID. Each level is represented by two bits in the final cell ID
  106. const S2_MAX_LEVEL = 30;
  107. // The maximum index of a valid leaf cell plus one. The range of valid leaf cell indices is [0..S2_LIMIT_IJ-1].
  108. const S2_LIMIT_IJ = 1 << S2_MAX_LEVEL;
  109. // The maximum value of an si- or ti-coordinate. The range of valid (si,ti) values is [0..S2_MAX_SITI]. Use `>>>` to convert to unsigned.
  110. const S2_MAX_SITI = (1 << (S2_MAX_LEVEL + 1)) >>> 0;
  111. // The number of bits in a S2 cell ID used for specifying the position along the Hilbert curve
  112. const S2_POSITION_BITS = 2 * S2_MAX_LEVEL + 1;
  113. // The number of bits per I and J in the lookup tables
  114. const S2_LOOKUP_BITS = 4;
  115. // Lookup table for mapping 10 bits of IJ + orientation to 10 bits of Hilbert curve position + orientation.
  116. const S2_LOOKUP_POSITIONS = [];
  117. // Lookup table for mapping 10 bits of IJ + orientation to 10 bits of Hilbert curve position + orientation.
  118. const S2_LOOKUP_IJ = [];
  119. // Lookup table of two bits of IJ from two bits of curve position, based also on the current curve orientation from the swap and invert bits
  120. const S2_POSITION_TO_IJ = [
  121. [0, 1, 3, 2], // 0: Normal order, no swap or invert
  122. [0, 2, 3, 1], // 1: Swap bit set, swap I and J bits
  123. [3, 2, 0, 1], // 2: Invert bit set, invert bits
  124. [3, 1, 0, 2], // 3: Swap and invert bits set
  125. ];
  126. // Mask that specifies the swap orientation bit for the Hilbert curve
  127. const S2_SWAP_MASK = 1;
  128. // Mask that specifies the invert orientation bit for the Hilbert curve
  129. const S2_INVERT_MASK = 2;
  130. // Lookup for the orientation update mask of one of the four sub-cells within a higher level cell.
  131. // This mask is XOR'ed with the current orientation to get the sub-cell orientation.
  132. const S2_POSITION_TO_ORIENTATION_MASK = [
  133. S2_SWAP_MASK,
  134. 0,
  135. 0,
  136. S2_SWAP_MASK | S2_INVERT_MASK,
  137. ];
  138. /**
  139. * Represents a cell in the S2 geometry library.
  140. *
  141. * @alias S2Cell
  142. * @constructor
  143. *
  144. * @param {bigint} [cellId] The 64-bit S2CellId.
  145. * @private
  146. */
  147. function S2Cell(cellId) {
  148. if (!FeatureDetection.supportsBigInt()) {
  149. throw new RuntimeError("S2 required BigInt support");
  150. }
  151. //>>includeStart('debug', pragmas.debug);
  152. if (!defined(cellId)) {
  153. throw new DeveloperError("cell ID is required.");
  154. }
  155. if (!S2Cell.isValidId(cellId)) {
  156. throw new DeveloperError("cell ID is invalid.");
  157. }
  158. //>>includeEnd('debug');
  159. this._cellId = cellId;
  160. this._level = S2Cell.getLevel(cellId);
  161. }
  162. /**
  163. * Creates a new S2Cell from a token. A token is a hexadecimal representation of the 64-bit S2CellId.
  164. *
  165. * @param {string} token The token for the S2 Cell.
  166. * @returns {S2Cell} Returns a new S2Cell.
  167. * @private
  168. */
  169. S2Cell.fromToken = function (token) {
  170. //>>includeStart('debug', pragmas.debug);
  171. Check.typeOf.string("token", token);
  172. if (!S2Cell.isValidToken(token)) {
  173. throw new DeveloperError("token is invalid.");
  174. }
  175. //>>includeEnd('debug');
  176. return new S2Cell(S2Cell.getIdFromToken(token));
  177. };
  178. /**
  179. * Validates an S2 cell ID.
  180. *
  181. * @param {bigint} [cellId] The S2CellId.
  182. * @returns {boolean} Returns true if the cell ID is valid, returns false otherwise.
  183. * @private
  184. */
  185. S2Cell.isValidId = function (cellId) {
  186. //>>includeStart('debug', pragmas.debug);
  187. Check.typeOf.bigint("cellId", cellId);
  188. //>>includeEnd('debug');
  189. // Check if sentinel bit is missing.
  190. if (cellId <= 0) {
  191. return false;
  192. }
  193. // Check if face bits indicate a valid value, in range [0-5].
  194. // eslint-disable-next-line
  195. if (cellId >> BigInt(S2_POSITION_BITS) > 5) {
  196. return false;
  197. }
  198. // Check trailing 1 bit is in one of the even bit positions allowed for the 30 levels, using a bitmask.
  199. // eslint-disable-next-line no-undef
  200. const lowestSetBit = cellId & (~cellId + BigInt(1));
  201. // eslint-disable-next-line
  202. if (!(lowestSetBit & BigInt("0x1555555555555555"))) {
  203. return false;
  204. }
  205. return true;
  206. };
  207. /**
  208. * Validates an S2 cell token.
  209. *
  210. * @param {string} [token] The hexadecimal representation of an S2CellId.
  211. * @returns {boolean} Returns true if the token is valid, returns false otherwise.
  212. * @private
  213. */
  214. S2Cell.isValidToken = function (token) {
  215. //>>includeStart('debug', pragmas.debug);
  216. Check.typeOf.string("token", token);
  217. //>>includeEnd('debug');
  218. if (!/^[0-9a-fA-F]{1,16}$/.test(token)) {
  219. return false;
  220. }
  221. return S2Cell.isValidId(S2Cell.getIdFromToken(token));
  222. };
  223. /**
  224. * Converts an S2 cell token to a 64-bit S2 cell ID.
  225. *
  226. * @param {string} [token] The hexadecimal representation of an S2CellId. Expected to be a valid S2 token.
  227. * @returns {bigint} Returns the S2 cell ID.
  228. * @private
  229. */
  230. S2Cell.getIdFromToken = function (token) {
  231. //>>includeStart('debug', pragmas.debug);
  232. Check.typeOf.string("token", token);
  233. //>>includeEnd('debug');
  234. return BigInt("0x" + token + "0".repeat(16 - token.length)); // eslint-disable-line
  235. };
  236. /**
  237. * Converts a 64-bit S2 cell ID to an S2 cell token.
  238. *
  239. * @param {bigint} [cellId] The S2 cell ID.
  240. * @returns {string} Returns hexadecimal representation of an S2CellId.
  241. * @private
  242. */
  243. S2Cell.getTokenFromId = function (cellId) {
  244. //>>includeStart('debug', pragmas.debug);
  245. Check.typeOf.bigint("cellId", cellId);
  246. //>>includeEnd('debug');
  247. const trailingZeroHexChars = Math.floor(countTrailingZeroBits(cellId) / 4);
  248. const hexString = cellId.toString(16).replace(/0*$/, "");
  249. const zeroString = Array(17 - trailingZeroHexChars - hexString.length).join(
  250. "0"
  251. );
  252. return zeroString + hexString;
  253. };
  254. /**
  255. * Gets the level of the cell from the cell ID.
  256. *
  257. * @param {bigint} [cellId] The S2 cell ID.
  258. * @returns {number} Returns the level of the cell.
  259. * @private
  260. */
  261. S2Cell.getLevel = function (cellId) {
  262. //>>includeStart('debug', pragmas.debug);
  263. Check.typeOf.bigint("cellId", cellId);
  264. if (!S2Cell.isValidId(cellId)) {
  265. throw new DeveloperError();
  266. }
  267. //>>includeEnd('debug');
  268. let lsbPosition = 0;
  269. // eslint-disable-next-line
  270. while (cellId !== BigInt(0)) {
  271. // eslint-disable-next-line
  272. if (cellId & BigInt(1)) {
  273. break;
  274. }
  275. lsbPosition++;
  276. cellId = cellId >> BigInt(1); // eslint-disable-line
  277. }
  278. // We use (>> 1) because there are 2 bits per level.
  279. return S2_MAX_LEVEL - (lsbPosition >> 1);
  280. };
  281. /**
  282. * Gets the child cell of the cell at the given index.
  283. *
  284. * @param {number} index An integer index of the child.
  285. * @returns {S2Cell} The child of the S2Cell.
  286. * @private
  287. */
  288. S2Cell.prototype.getChild = function (index) {
  289. //>>includeStart('debug', pragmas.debug);
  290. Check.typeOf.number("index", index);
  291. if (index < 0 || index > 3) {
  292. throw new DeveloperError("child index must be in the range [0-3].");
  293. }
  294. if (this._level === 30) {
  295. throw new DeveloperError("cannot get child of leaf cell.");
  296. }
  297. //>>includeEnd('debug');
  298. // Shift sentinel bit 2 positions to the right.
  299. // eslint-disable-next-line no-undef
  300. const newLsb = lsb(this._cellId) >> BigInt(2);
  301. // Insert child index before the sentinel bit.
  302. // eslint-disable-next-line no-undef
  303. const childCellId = this._cellId + BigInt(2 * index + 1 - 4) * newLsb;
  304. return new S2Cell(childCellId);
  305. };
  306. /**
  307. * Gets the parent cell of an S2Cell.
  308. *
  309. * @returns {S2Cell} Returns the parent of the S2Cell.
  310. * @private
  311. */
  312. S2Cell.prototype.getParent = function () {
  313. //>>includeStart('debug', pragmas.debug);
  314. if (this._level === 0) {
  315. throw new DeveloperError("cannot get parent of root cell.");
  316. }
  317. //>>includeEnd('debug');
  318. // Shift the sentinel bit 2 positions to the left.
  319. // eslint-disable-next-line no-undef
  320. const newLsb = lsb(this._cellId) << BigInt(2);
  321. // Erase the left over bits to the right of the sentinel bit.
  322. // eslint-disable-next-line no-undef
  323. return new S2Cell((this._cellId & (~newLsb + BigInt(1))) | newLsb);
  324. };
  325. /**
  326. * Gets the parent cell at the given level.
  327. *
  328. * @returns {S2Cell} Returns the parent of the S2Cell.
  329. * @private
  330. */
  331. S2Cell.prototype.getParentAtLevel = function (level) {
  332. //>>includeStart('debug', pragmas.debug);
  333. if (this._level === 0 || level < 0 || this._level < level) {
  334. throw new DeveloperError("cannot get parent at invalid level.");
  335. }
  336. //>>includeEnd('debug');
  337. const newLsb = lsbForLevel(level);
  338. return new S2Cell((this._cellId & -newLsb) | newLsb);
  339. };
  340. /**
  341. * Get center of the S2 cell.
  342. *
  343. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid.
  344. * @returns {Cartesian3} The position of center of the S2 cell.
  345. * @private
  346. */
  347. S2Cell.prototype.getCenter = function (ellipsoid) {
  348. ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
  349. let center = getS2Center(this._cellId, this._level);
  350. // Normalize XYZ.
  351. center = Cartesian3.normalize(center, center);
  352. const cartographic = new Cartographic.fromCartesian(
  353. center,
  354. Ellipsoid.UNIT_SPHERE
  355. );
  356. // Interpret as geodetic coordinates on the ellipsoid.
  357. return Cartographic.toCartesian(cartographic, ellipsoid, new Cartesian3());
  358. };
  359. /**
  360. * Get vertex of the S2 cell. Vertices are indexed in CCW order.
  361. *
  362. * @param {number} index An integer index of the vertex. Must be in the range [0-3].
  363. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid.
  364. * @returns {Cartesian3} The position of the vertex of the S2 cell.
  365. * @private
  366. */
  367. S2Cell.prototype.getVertex = function (index, ellipsoid) {
  368. //>>includeStart('debug', pragmas.debug);
  369. Check.typeOf.number("index", index);
  370. if (index < 0 || index > 3) {
  371. throw new DeveloperError("vertex index must be in the range [0-3].");
  372. }
  373. //>>includeEnd('debug');
  374. ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
  375. let vertex = getS2Vertex(this._cellId, this._level, index);
  376. // Normalize XYZ.
  377. vertex = Cartesian3.normalize(vertex, vertex);
  378. const cartographic = new Cartographic.fromCartesian(
  379. vertex,
  380. Ellipsoid.UNIT_SPHERE
  381. );
  382. // Interpret as geodetic coordinates on the ellipsoid.
  383. return Cartographic.toCartesian(cartographic, ellipsoid, new Cartesian3());
  384. };
  385. /**
  386. * Creates an S2Cell from its face, position along the Hilbert curve for a given level.
  387. *
  388. * @param {number} face The root face of S2 this cell is on. Must be in the range [0-5].
  389. * @param {bigint} position The position along the Hilbert curve. Must be in the range [0-4**level).
  390. * @param {number} level The level of the S2 curve. Must be in the range [0-30].
  391. * @returns {S2Cell} A new S2Cell from the given parameters.
  392. * @private
  393. */
  394. S2Cell.fromFacePositionLevel = function (face, position, level) {
  395. //>>includeStart('debug', pragmas.debug);
  396. Check.typeOf.bigint("position", position);
  397. if (face < 0 || face > 5) {
  398. throw new DeveloperError("Invalid S2 Face (must be within 0-5)");
  399. }
  400. if (level < 0 || level > S2_MAX_LEVEL) {
  401. throw new DeveloperError("Invalid level (must be within 0-30)");
  402. }
  403. if (position < 0 || position >= Math.pow(4, level)) {
  404. throw new DeveloperError("Invalid Hilbert position for level");
  405. }
  406. //>>includeEnd('debug');
  407. const faceBitString =
  408. (face < 4 ? "0" : "") + (face < 2 ? "0" : "") + face.toString(2);
  409. const positionBitString = position.toString(2);
  410. const positionPrefixPadding = Array(
  411. 2 * level - positionBitString.length + 1
  412. ).join("0");
  413. const positionSuffixPadding = Array(S2_POSITION_BITS - 2 * level).join("0");
  414. // eslint-disable-next-line no-undef
  415. const cellId = BigInt(
  416. `0b${faceBitString}${positionPrefixPadding}${positionBitString}1${
  417. // Adding the sentinel bit that always follows the position bits.
  418. positionSuffixPadding
  419. }`
  420. );
  421. return new S2Cell(cellId);
  422. };
  423. /**
  424. * @private
  425. */
  426. function getS2Center(cellId, level) {
  427. const faceSiTi = convertCellIdToFaceSiTi(cellId, level);
  428. return convertFaceSiTitoXYZ(faceSiTi[0], faceSiTi[1], faceSiTi[2]);
  429. }
  430. /**
  431. * @private
  432. */
  433. function getS2Vertex(cellId, level, index) {
  434. const faceIJ = convertCellIdToFaceIJ(cellId, level);
  435. const uv = convertIJLeveltoBoundUV([faceIJ[1], faceIJ[2]], level);
  436. // Handles CCW ordering of the vertices.
  437. const y = (index >> 1) & 1;
  438. return convertFaceUVtoXYZ(faceIJ[0], uv[0][y ^ (index & 1)], uv[1][y]);
  439. }
  440. // S2 Coordinate Conversions
  441. /**
  442. * @private
  443. */
  444. function convertCellIdToFaceSiTi(cellId, level) {
  445. const faceIJ = convertCellIdToFaceIJ(cellId);
  446. const face = faceIJ[0];
  447. const i = faceIJ[1];
  448. const j = faceIJ[2];
  449. // We're resolving the center when we do the coordinate transform here. For the leaf cells, we're adding half the cell size
  450. // (remember that this space has 31 levels - which allows us to pick center and edges of the leaf cells). For non leaf cells,
  451. // we get one of either two cells diagonal to the cell center. The correction is used to make sure we pick the leaf cell edges
  452. // that represent the parent cell center.
  453. const isLeaf = level === 30;
  454. const shouldCorrect =
  455. !isLeaf && (BigInt(i) ^ (cellId >> BigInt(2))) & BigInt(1); // eslint-disable-line
  456. const correction = isLeaf ? 1 : shouldCorrect ? 2 : 0;
  457. const si = (i << 1) + correction;
  458. const ti = (j << 1) + correction;
  459. return [face, si, ti];
  460. }
  461. /**
  462. * @private
  463. */
  464. function convertCellIdToFaceIJ(cellId) {
  465. if (S2_LOOKUP_POSITIONS.length === 0) {
  466. generateLookupTable();
  467. }
  468. // eslint-disable-next-line no-undef
  469. const face = Number(cellId >> BigInt(S2_POSITION_BITS));
  470. let bits = face & S2_SWAP_MASK;
  471. const lookupMask = (1 << S2_LOOKUP_BITS) - 1;
  472. let i = 0;
  473. let j = 0;
  474. for (let k = 7; k >= 0; k--) {
  475. const numberOfBits =
  476. k === 7 ? S2_MAX_LEVEL - 7 * S2_LOOKUP_BITS : S2_LOOKUP_BITS;
  477. const extractMask = (1 << (2 * numberOfBits)) - 1;
  478. bits +=
  479. Number(
  480. (cellId >> BigInt(k * 2 * S2_LOOKUP_BITS + 1)) & BigInt(extractMask) // eslint-disable-line
  481. ) << 2;
  482. bits = S2_LOOKUP_IJ[bits];
  483. const offset = k * S2_LOOKUP_BITS;
  484. i += (bits >> (S2_LOOKUP_BITS + 2)) << offset;
  485. j += ((bits >> 2) & lookupMask) << offset;
  486. bits &= S2_SWAP_MASK | S2_INVERT_MASK;
  487. }
  488. return [face, i, j];
  489. }
  490. /**
  491. * @private
  492. */
  493. function convertFaceSiTitoXYZ(face, si, ti) {
  494. const s = convertSiTitoST(si);
  495. const t = convertSiTitoST(ti);
  496. const u = convertSTtoUV(s);
  497. const v = convertSTtoUV(t);
  498. return convertFaceUVtoXYZ(face, u, v);
  499. }
  500. /**
  501. * @private
  502. */
  503. function convertFaceUVtoXYZ(face, u, v) {
  504. switch (face) {
  505. case 0:
  506. return new Cartesian3(1, u, v);
  507. case 1:
  508. return new Cartesian3(-u, 1, v);
  509. case 2:
  510. return new Cartesian3(-u, -v, 1);
  511. case 3:
  512. return new Cartesian3(-1, -v, -u);
  513. case 4:
  514. return new Cartesian3(v, -1, -u);
  515. default:
  516. return new Cartesian3(v, u, -1);
  517. }
  518. }
  519. /**
  520. * S2 provides 3 methods for the non-linear transform: linear, quadratic and tangential.
  521. * This implementation uses the quadratic method because it provides a good balance of
  522. * accuracy and speed.
  523. *
  524. * For a more detailed comparison of these transform methods, see
  525. * {@link https://github.com/google/s2geometry/blob/0c4c460bdfe696da303641771f9def900b3e440f/src/s2/s2metrics.cc}
  526. * @private
  527. */
  528. function convertSTtoUV(s) {
  529. if (s >= 0.5) {
  530. return (1 / 3) * (4 * s * s - 1);
  531. }
  532. return (1 / 3) * (1 - 4 * (1 - s) * (1 - s));
  533. }
  534. /**
  535. * @private
  536. */
  537. function convertSiTitoST(si) {
  538. return (1.0 / S2_MAX_SITI) * si;
  539. }
  540. /**
  541. * @private
  542. */
  543. function convertIJLeveltoBoundUV(ij, level) {
  544. const result = [[], []];
  545. const cellSize = getSizeIJ(level);
  546. for (let d = 0; d < 2; ++d) {
  547. const ijLow = ij[d] & -cellSize;
  548. const ijHigh = ijLow + cellSize;
  549. result[d][0] = convertSTtoUV(convertIJtoSTMinimum(ijLow));
  550. result[d][1] = convertSTtoUV(convertIJtoSTMinimum(ijHigh));
  551. }
  552. return result;
  553. }
  554. /**
  555. * @private
  556. */
  557. function getSizeIJ(level) {
  558. return (1 << (S2_MAX_LEVEL - level)) >>> 0;
  559. }
  560. /**
  561. * @private
  562. */
  563. function convertIJtoSTMinimum(i) {
  564. return (1.0 / S2_LIMIT_IJ) * i;
  565. }
  566. // Utility Functions
  567. /**
  568. * This function generates 4 variations of a Hilbert curve of level 4, based on the S2_POSITION_TO_IJ table, for fast lookups of (i, j)
  569. * to position along Hilbert curve. The reference C++ implementation uses an iterative approach, however, this function is implemented
  570. * recursively.
  571. *
  572. * See {@link https://github.com/google/s2geometry/blob/c59d0ca01ae3976db7f8abdc83fcc871a3a95186/src/s2/s2cell_id.cc#L75-L109}
  573. * @private
  574. */
  575. function generateLookupCell(
  576. level,
  577. i,
  578. j,
  579. originalOrientation,
  580. position,
  581. orientation
  582. ) {
  583. if (level === S2_LOOKUP_BITS) {
  584. const ij = (i << S2_LOOKUP_BITS) + j;
  585. S2_LOOKUP_POSITIONS[(ij << 2) + originalOrientation] =
  586. (position << 2) + orientation;
  587. S2_LOOKUP_IJ[(position << 2) + originalOrientation] =
  588. (ij << 2) + orientation;
  589. } else {
  590. level++;
  591. i <<= 1;
  592. j <<= 1;
  593. position <<= 2;
  594. const r = S2_POSITION_TO_IJ[orientation];
  595. generateLookupCell(
  596. level,
  597. i + (r[0] >> 1),
  598. j + (r[0] & 1),
  599. originalOrientation,
  600. position,
  601. orientation ^ S2_POSITION_TO_ORIENTATION_MASK[0]
  602. );
  603. generateLookupCell(
  604. level,
  605. i + (r[1] >> 1),
  606. j + (r[1] & 1),
  607. originalOrientation,
  608. position + 1,
  609. orientation ^ S2_POSITION_TO_ORIENTATION_MASK[1]
  610. );
  611. generateLookupCell(
  612. level,
  613. i + (r[2] >> 1),
  614. j + (r[2] & 1),
  615. originalOrientation,
  616. position + 2,
  617. orientation ^ S2_POSITION_TO_ORIENTATION_MASK[2]
  618. );
  619. generateLookupCell(
  620. level,
  621. i + (r[3] >> 1),
  622. j + (r[3] & 1),
  623. originalOrientation,
  624. position + 3,
  625. orientation ^ S2_POSITION_TO_ORIENTATION_MASK[3]
  626. );
  627. }
  628. }
  629. /**
  630. * @private
  631. */
  632. function generateLookupTable() {
  633. generateLookupCell(0, 0, 0, 0, 0, 0);
  634. generateLookupCell(0, 0, 0, S2_SWAP_MASK, 0, S2_SWAP_MASK);
  635. generateLookupCell(0, 0, 0, S2_INVERT_MASK, 0, S2_INVERT_MASK);
  636. generateLookupCell(
  637. 0,
  638. 0,
  639. 0,
  640. S2_SWAP_MASK | S2_INVERT_MASK,
  641. 0,
  642. S2_SWAP_MASK | S2_INVERT_MASK
  643. );
  644. }
  645. /**
  646. * Return the lowest-numbered bit that is on for this cell id
  647. * @private
  648. */
  649. function lsb(cellId) {
  650. return cellId & (~cellId + BigInt(1)); // eslint-disable-line
  651. }
  652. /**
  653. * Return the lowest-numbered bit that is on for cells at the given level.
  654. * @private
  655. */
  656. function lsbForLevel(level) {
  657. return BigInt(1) << BigInt(2 * (S2_MAX_LEVEL - level)); // eslint-disable-line
  658. }
  659. // Lookup table for getting trailing zero bits.
  660. // https://graphics.stanford.edu/~seander/bithacks.html
  661. const Mod67BitPosition = [
  662. 64,
  663. 0,
  664. 1,
  665. 39,
  666. 2,
  667. 15,
  668. 40,
  669. 23,
  670. 3,
  671. 12,
  672. 16,
  673. 59,
  674. 41,
  675. 19,
  676. 24,
  677. 54,
  678. 4,
  679. 64,
  680. 13,
  681. 10,
  682. 17,
  683. 62,
  684. 60,
  685. 28,
  686. 42,
  687. 30,
  688. 20,
  689. 51,
  690. 25,
  691. 44,
  692. 55,
  693. 47,
  694. 5,
  695. 32,
  696. 65,
  697. 38,
  698. 14,
  699. 22,
  700. 11,
  701. 58,
  702. 18,
  703. 53,
  704. 63,
  705. 9,
  706. 61,
  707. 27,
  708. 29,
  709. 50,
  710. 43,
  711. 46,
  712. 31,
  713. 37,
  714. 21,
  715. 57,
  716. 52,
  717. 8,
  718. 26,
  719. 49,
  720. 45,
  721. 36,
  722. 56,
  723. 7,
  724. 48,
  725. 35,
  726. 6,
  727. 34,
  728. 33,
  729. 0,
  730. ];
  731. /**
  732. * Return the number of trailing zeros in number.
  733. * @private
  734. */
  735. function countTrailingZeroBits(x) {
  736. return Mod67BitPosition[(-x & x) % BigInt(67)]; // eslint-disable-line
  737. }
  738. export default S2Cell;