@@ -4,7 +4,7 @@ import { faceSiTiToXYZ, faceUVToXYZ, siTiToST, stToUV, uvToST, xyzToFaceUV } fro
44import { FACE_BITS , MAX_LEVEL , MAX_SIZE , NUM_FACES , POS_BITS , WRAP_OFFSET } from './cellid_constants'
55import { LatLng } from './LatLng'
66import { Point } from './Point'
7- import { clampInt } from './util'
7+ import { abs , clampInt } from './util'
88import { Vector } from '../r3/Vector'
99import { Interval } from '../r1/Interval'
1010import { Rect } from '../r2/Rect'
@@ -40,12 +40,12 @@ import { Rect as R2Rect } from '../r2/Rect'
4040export type CellID = bigint
4141
4242/**
43- * An invalid cell ID guaranteed to be larger than any
44- * valid cell ID. It is used primarily by ShapeIndex. The value is also used
45- * by some S2 types when encoding data.
43+ * An invalid cell ID guaranteed to be larger than any valid cell ID.
44+ * It is used primarily by ShapeIndex.
45+ * The value is also used by some S2 types when encoding data.
4646 * Note that the sentinel's RangeMin == RangeMax == itself.
4747 */
48- export const SentinelCellID = 18446744073709551615n
48+ export const SentinelCellID = ( 1n << 64n ) - 1n
4949
5050/**
5151 * Returns the cube face for this cell id, in the range [0,5].
@@ -58,7 +58,7 @@ export const face = (ci: CellID): number => {
5858 * Returns the position along the Hilbert curve of this cell id, in the range [0,2^POS_BITS-1].
5959 */
6060export const pos = ( ci : CellID ) : CellID => {
61- return ci & ( ~ 0n >> BigInt ( FACE_BITS ) )
61+ return ci & ( SentinelCellID >> BigInt ( FACE_BITS ) )
6262}
6363
6464/**
@@ -101,6 +101,15 @@ export const valid = (ci: CellID): boolean => {
101101 */
102102export const isLeaf = ( ci : CellID ) : boolean => ( ci & 1n ) != 0n
103103
104+ /**
105+ * Returns the child position (0..3) of this cell's ancestor at the given level, relative to its parent.
106+ * The argument should be in the range 1..MaxLevel.
107+ * For example, childPosition(1) returns the position of this cell's level-1 ancestor within its top-level face cell.
108+ */
109+ export const childPosition = ( ci : CellID , level : number ) : number => {
110+ return Number ( ci >> ( 2n * BigInt ( MAX_LEVEL - level ) + 1n ) ) & 0b11
111+ }
112+
104113// Bitwise
105114
106115/**
@@ -213,7 +222,7 @@ export const intersects = (ci: CellID, oci: CellID) => {
213222 * Returns a hex-encoded string of the uint64 cell id, with leading zeros included but trailing zeros stripped
214223 */
215224export const toToken = ( ci : CellID ) : string => {
216- const s = ci . toString ( 16 ) . replace ( / 0 + $ / , '' )
225+ const s = ci . toString ( 16 ) . padStart ( 16 , '0' ) . replace ( / 0 + $ / , '' )
217226 if ( s . length === 0 ) return 'X'
218227 return s
219228}
@@ -224,11 +233,22 @@ export const toToken = (ci: CellID): string => {
224233 */
225234export const fromToken = ( t : string ) : CellID => {
226235 if ( t . length > 16 ) return 0n
236+ if ( ! / ^ [ A - F 0 - 9 ] + $ / i. test ( t ) ) return 0n
227237 let ci = BigInt ( '0x' + t )
228238 if ( t . length < 16 ) ci = ci << BigInt ( 4 * ( 16 - t . length ) )
229239 return ci
230240}
231241
242+ /**
243+ * Returns the string representation of the cell ID in the form "1/3210".
244+ */
245+ export const toString = ( ci : CellID ) : string => {
246+ if ( ! valid ( ci ) ) return `Invalid: ${ ci . toString ( 16 ) } `
247+ let result = `${ face ( ci ) } /`
248+ for ( let l = 1 ; l <= level ( ci ) ; l ++ ) result += childPosition ( ci , l )
249+ return result
250+ }
251+
232252/**
233253 * Converts a string in the form "1/3210" to a CellID.
234254 * @category Constructors
@@ -238,12 +258,12 @@ export const fromString = (s: string): CellID => {
238258 if ( level < 0 || level > MAX_LEVEL ) return 0n
239259
240260 const face = parseInt ( s [ 0 ] , 10 )
241- if ( face < 0 || face > 5 || s [ 1 ] !== '/' ) return 0n
261+ if ( isNaN ( face ) || face < 0 || face > 5 || s [ 1 ] !== '/' ) return 0n
242262
243263 let cid = fromFace ( face )
244264 for ( let i = 2 ; i < s . length ; i ++ ) {
245265 const childPos = parseInt ( s [ i ] , 10 )
246- if ( childPos < 0 || childPos > 3 ) return 0n
266+ if ( isNaN ( childPos ) || childPos < 0 || childPos > 3 ) return 0n
247267 cid = children ( cid ) [ childPos ]
248268 }
249269
@@ -303,7 +323,7 @@ export const stToIJ = (s: number): number => {
303323}
304324
305325export const sizeIJ = ( level : number ) : number => {
306- return 1 << ( MAX_LEVEL - level )
326+ return 1 << clampInt ( MAX_LEVEL - level , 0 , MAX_LEVEL )
307327}
308328
309329/** Returns the edge length of this CellID in (s,t)-space at the given level. */
@@ -471,6 +491,16 @@ export const ijLevelToBoundUV = (i: number, j: number, level: number): Rect => {
471491 )
472492}
473493
494+ /**
495+ * Returns the number of steps along the Hilbert curve that this cell is from the first node
496+ * in the S2 hierarchy at our level. (i.e., FromFace(0).ChildBeginAtLevel(ci.Level())).
497+ * This is analogous to Pos(), but for this cell's level.
498+ * The return value is always non-negative.
499+ */
500+ export const distanceFromBegin = ( ci : CellID ) : bigint => {
501+ return ci >> ( 2n * BigInt ( MAX_LEVEL - level ( ci ) ) + 1n )
502+ }
503+
474504/**
475505 * Returns an unnormalized r3 vector from the origin through the center
476506 * of the s2 cell on the sphere.
@@ -603,12 +633,12 @@ export const childEndAtLevel = (ci: CellID, level: number): CellID => {
603633 * or ChildBeginAtLevel and ChildEndAtLevel.
604634 */
605635export const next = ( ci : CellID ) : CellID => {
606- return ci + ( lsb ( ci ) << 1n )
636+ return ( ci + ( lsb ( ci ) << 1n ) ) & SentinelCellID
607637}
608638
609639/** Returns the previous cell along the Hilbert curve. */
610640export const prev = ( ci : CellID ) : CellID => {
611- return ci - ( lsb ( ci ) << 1n )
641+ return ( ci - ( lsb ( ci ) << 1n ) ) & SentinelCellID
612642}
613643
614644/**
@@ -618,7 +648,7 @@ export const prev = (ci: CellID): CellID => {
618648export const nextWrap = ( ci : CellID ) : CellID => {
619649 const n = next ( ci )
620650 if ( n < WRAP_OFFSET ) return n
621- return n - WRAP_OFFSET
651+ return ( n - WRAP_OFFSET ) & SentinelCellID
622652}
623653
624654/**
@@ -628,7 +658,44 @@ export const nextWrap = (ci: CellID): CellID => {
628658export const prevWrap = ( ci : CellID ) : CellID => {
629659 const p = prev ( ci )
630660 if ( p < WRAP_OFFSET ) return p
631- return p + WRAP_OFFSET
661+ return ( p + WRAP_OFFSET ) & SentinelCellID
662+ }
663+
664+ /**
665+ * Advances or retreats the indicated number of steps along the
666+ * Hilbert curve at the current level and returns the new position. The
667+ * position wraps between the first and last faces as necessary.
668+ */
669+ export const advanceWrap = ( ci : CellID , steps : bigint ) : CellID => {
670+ if ( steps === 0n ) return ci
671+
672+ // We clamp the number of steps if necessary to ensure that we do not
673+ // advance past the End() or before the Begin() of this level.
674+ const shift = BigInt ( 2 * ( MAX_LEVEL - level ( ci ) ) + 1 )
675+ if ( steps < 0n ) {
676+ const min = - ( ci >> shift )
677+ if ( steps < min ) {
678+ const wrap = WRAP_OFFSET >> shift
679+ steps %= wrap
680+ if ( steps < min ) {
681+ steps += wrap
682+ }
683+ }
684+ } else {
685+ // Unlike Advance(), we don't want to return End(level).
686+ const max = ( WRAP_OFFSET - ci ) >> shift
687+ if ( steps > max ) {
688+ const wrap = WRAP_OFFSET >> shift
689+ steps %= wrap
690+ if ( steps > max ) {
691+ steps -= wrap
692+ }
693+ }
694+ }
695+
696+ // If steps is negative, then shifting it left has undefined behavior.
697+ // Cast to uint64 for a 2's complement answer.
698+ return ci + ( steps << abs ( shift ) )
632699}
633700
634701/**
@@ -640,9 +707,7 @@ export const commonAncestorLevel = (ci: CellID, other: CellID): [number, boolean
640707 if ( bits < lsb ( other ) ) bits = lsb ( other )
641708
642709 const msbPos = findMSBSetNonZero64 ( bits )
643- if ( msbPos > 60 ) {
644- return [ 0 , false ]
645- }
710+ if ( msbPos > 60 ) return [ 0 , false ]
646711 return [ ( 60 - msbPos ) >> 1 , true ]
647712}
648713
@@ -691,20 +756,20 @@ export const maxTile = (ci: CellID, limit: CellID): CellID => {
691756 * Hilbert curve at the current level, and returns the new position. The
692757 * position is never advanced past End() or before Begin().
693758 */
694- export const advance = ( ci : CellID , steps : CellID ) : CellID => {
759+ export const advance = ( ci : CellID , steps : bigint ) : CellID => {
695760 if ( steps === 0n ) return ci
696761
697762 // We clamp the number of steps if necessary to ensure that we do not
698763 // advance past the End() or before the Begin() of this level.
699764 const stepShift = BigInt ( 2 * ( MAX_LEVEL - level ( ci ) ) + 1 )
700765
701766 if ( steps < 0n ) {
702- const minSteps = - ( ( ci >> stepShift ) as bigint )
767+ const minSteps = - ( ci >> stepShift )
703768 if ( steps < minSteps ) {
704769 steps = minSteps
705770 }
706771 } else {
707- const maxSteps = ( ( WRAP_OFFSET + lsb ( ci ) - ci ) >> stepShift ) as bigint
772+ const maxSteps = ( WRAP_OFFSET + lsb ( ci ) - ci ) >> stepShift
708773 if ( steps > maxSteps ) {
709774 steps = maxSteps
710775 }
0 commit comments