From 89331993af8429300a4ba98e793cf8842d7cf04b Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Wed, 17 Jul 2024 23:39:16 +0200 Subject: [PATCH] s2: cellid --- .github/workflows/_test.yml | 24 +++ .github/workflows/pull_request.yml | 7 + .github/workflows/push.yml | 5 + .gitignore | 2 + .prettierrc | 5 + index.ts | 3 + int/uint64.ts | 169 +++++++++++++++++++ int/uint64_test.ts | 70 ++++++++ int/uint8.ts | 257 +++++++++++++++++++++++++++++ package.json | 14 ++ s2/_index.ts | 13 ++ s2/cellid.ts | 130 +++++++++++++++ s2/cellid_test.ts | 108 ++++++++++++ 13 files changed, 807 insertions(+) create mode 100644 .github/workflows/_test.yml create mode 100644 .github/workflows/pull_request.yml create mode 100644 .github/workflows/push.yml create mode 100644 .gitignore create mode 100644 .prettierrc create mode 100644 index.ts create mode 100644 int/uint64.ts create mode 100644 int/uint64_test.ts create mode 100644 int/uint8.ts create mode 100644 package.json create mode 100644 s2/_index.ts create mode 100644 s2/cellid.ts create mode 100644 s2/cellid_test.ts diff --git a/.github/workflows/_test.yml b/.github/workflows/_test.yml new file mode 100644 index 0000000..a82341d --- /dev/null +++ b/.github/workflows/_test.yml @@ -0,0 +1,24 @@ +name: Unit Tests +on: workflow_call +jobs: + unit-tests: + runs-on: '${{ matrix.os }}' + timeout-minutes: 10 + strategy: + matrix: + os: + - 'ubuntu-22.04' + node-version: + - 22.x + - 20.x + - 18.x + steps: + - uses: actions/checkout@v4 + - name: 'Install node.js ${{ matrix.node-version }}' + uses: actions/setup-node@v3 + with: + node-version: '${{ matrix.node-version }}' + - name: Run unit tests + run: | + npm install + npm test diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml new file mode 100644 index 0000000..c9533fc --- /dev/null +++ b/.github/workflows/pull_request.yml @@ -0,0 +1,7 @@ +name: Continuous Integration +on: pull_request +jobs: + unit-tests: + # only run this job for forks + if: github.event.pull_request.head.repo.full_name != github.repository + uses: ./.github/workflows/_test.yml diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml new file mode 100644 index 0000000..b650bf9 --- /dev/null +++ b/.github/workflows/push.yml @@ -0,0 +1,5 @@ +name: Continuous Integration +on: push +jobs: + unit-tests: + uses: ./.github/workflows/_test.yml \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d5f19d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +package-lock.json diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..31ba22d --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "semi": false, + "singleQuote": true, + "printWidth": 120 +} diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..9477ab2 --- /dev/null +++ b/index.ts @@ -0,0 +1,3 @@ +export * as r1 from './r1/_index' +export * as r2 from './r2/_index' +export * as s2 from './s2/_index' diff --git a/int/uint64.ts b/int/uint64.ts new file mode 100644 index 0000000..42bb889 --- /dev/null +++ b/int/uint64.ts @@ -0,0 +1,169 @@ +import type { uint8 } from './uint8' + +/** + * @module uint64 + * + * 64-bit integer manipulation/conversion methods + */ + +/** + * valid returns false if i is the wrong type or exceeds 64 bits + */ +export const valid = (i: bigint): boolean => { + return typeof i === 'bigint' && BigInt.asUintN(64, i) == i +} + +/** + * byte returns the byte at offset n (left-to-right) + */ +export const byte = (i: bigint, o: offset): uint8 => { + const offset = BigInt((7 - o) * 8) + const mask = 0b11111111n << offset + return Number((i & mask) >> offset) as uint8 +} + +/** + * trailingZeros8 returns the number of trailing zero bits in byte + */ +const trailingZeros8 = (byte: uint8): number => { + const lsb = byte & -byte + if (lsb === 0) return 8 + return 31 - Math.clz32(lsb) +} + +/** + * trailingZeros returns the number of trailing zero bits in an uint64 + */ +export const trailingZeros = (i: bigint): position => { + for (let n = 7; n >= 0; n--) { + const z = trailingZeros8(byte(i, n as offset)) + if (z < 8) return ((7 - n) * 8 + z) as position + } + return 64 as position +} + +/** + * flip flip the bit at position p in uint64 + */ +export const flip = (i: bigint, p: position): bigint => { + return i ^ (1n << BigInt(p)) +} + +/** + * set sets the bit at position p in uint64 to v + */ +export const set = (i: bigint, p: position, v: bit): bigint => { + const mask = 1n << BigInt(p) + return v ? i | mask : i & ~mask +} + +/** + * setTrailingBits fills the rightmost n bits in uint64 with v + */ +export const setTrailingBits = (i: bigint, p: position, v: bit): bigint => { + const mask = (1n << BigInt(p + 1)) - 1n + return v ? i | mask : i & ~mask +} + +/** + * lsb returns the least significant bit that is set + */ +export const lsb = (i: bigint) => i & -i + +/** + * marshal writes an uint64 to an Uint8Array + */ +export const marshal = (i: bigint): Uint8Array => { + const arr = new Uint8Array(8) + new DataView(arr.buffer, 0, 8).setBigUint64(0, i, false) + return arr +} + +/** + * unmarshal reads an uint64 from an Uint8Array + */ +export const unmarshal = (arr: Uint8Array): bigint => { + return new DataView(arr.buffer, 0, 8).getBigUint64(0, false) +} + +/** + * toBinaryString returns the binary string representation of an uint64 + */ +export const toBinaryString = (i: bigint): string => i.toString(2).padStart(64, '0') + +/** + * Types + */ + +/** bit value */ +export type bit = 0 | 1 + +/** byte offset */ +export type offset = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 + +/** bit position */ +export type position = + | 0 + | 1 + | 2 + | 3 + | 4 + | 5 + | 6 + | 7 + | 8 + | 9 + | 10 + | 11 + | 12 + | 13 + | 14 + | 15 + | 16 + | 17 + | 18 + | 19 + | 20 + | 21 + | 22 + | 23 + | 24 + | 25 + | 26 + | 27 + | 28 + | 29 + | 30 + | 31 + | 32 + | 33 + | 34 + | 35 + | 36 + | 37 + | 38 + | 39 + | 40 + | 41 + | 42 + | 43 + | 44 + | 45 + | 46 + | 47 + | 48 + | 49 + | 50 + | 51 + | 52 + | 53 + | 54 + | 55 + | 56 + | 57 + | 58 + | 59 + | 60 + | 61 + | 62 + | 63 diff --git a/int/uint64_test.ts b/int/uint64_test.ts new file mode 100644 index 0000000..03f6467 --- /dev/null +++ b/int/uint64_test.ts @@ -0,0 +1,70 @@ +import test from 'node:test' +import { equal } from 'node:assert/strict' +import * as uint64 from './uint64.ts' + +test('flip', (t) => { + const i = 0b0000000000000000000000000000000000000000000000000000000000000000n + equal(uint64.flip(i, 0), 0b0000000000000000000000000000000000000000000000000000000000000001n) + equal(uint64.flip(i, 1), 0b0000000000000000000000000000000000000000000000000000000000000010n) + equal(uint64.flip(i, 7), 0b0000000000000000000000000000000000000000000000000000000010000000n) + equal(uint64.flip(i, 9), 0b0000000000000000000000000000000000000000000000000000001000000000n) + equal(uint64.flip(i, 61), 0b0010000000000000000000000000000000000000000000000000000000000000n) + equal(uint64.flip(i, 63), 0b1000000000000000000000000000000000000000000000000000000000000000n) +}) + +test('byte', (t) => { + const i = 0b1111111101111111001111110001111100001111000001110000001100000001n + equal(uint64.byte(i, 0), 0b11111111) + equal(uint64.byte(i, 1), 0b01111111) + equal(uint64.byte(i, 2), 0b00111111) + equal(uint64.byte(i, 3), 0b00011111) + equal(uint64.byte(i, 4), 0b00001111) + equal(uint64.byte(i, 5), 0b00000111) + equal(uint64.byte(i, 6), 0b00000011) + equal(uint64.byte(i, 7), 0b00000001) +}) + +test('trailingZeros', (t) => { + equal(uint64.trailingZeros(0b0000000000000000000000000000000000000000000000000000000000000001n), 0) + equal(uint64.trailingZeros(0b0000000000000000000000000000000000000000000000000000000000000010n), 1) + equal(uint64.trailingZeros(0b0000000000000000000000000000000000000000000000000000000000000100n), 2) + equal(uint64.trailingZeros(0b0000000000000000000000000000000000000000000000000000000000001000n), 3) + equal(uint64.trailingZeros(0b0000000000000000000000000000000000000000000000000000000000010000n), 4) + equal(uint64.trailingZeros(0b0000000000000000000000000000000000000000000000000000000000100000n), 5) + equal(uint64.trailingZeros(0b0000000000000000000000000000000000000000000000000000000001000000n), 6) + equal(uint64.trailingZeros(0b0000000000000000000000000000000000000000000000000000000010000000n), 7) + equal(uint64.trailingZeros(0b0000000000000000000000000000000000000000000000000000000100000000n), 8) + equal(uint64.trailingZeros(0b0000000000000000000000000000000010000000000000000000000000000000n), 31) + equal(uint64.trailingZeros(0b0000000000000000000000000000000001000000000000000000000000000000n), 30) + equal(uint64.trailingZeros(0b0000000000000000000000000000000000100000000000000000000000000000n), 29) + equal(uint64.trailingZeros(0b0000000000000000000000000000000000010000000000000000000000000000n), 28) + equal(uint64.trailingZeros(0b0000000000000000000000000000000000001000000000000000000000000000n), 27) + equal(uint64.trailingZeros(0b0000000000000000000000000000000000000100000000000000000000000000n), 26) + equal(uint64.trailingZeros(0b0000000000000000000000000000000000000010000000000000000000000000n), 25) + equal(uint64.trailingZeros(0b0000000000000000000000000000000000000001000000000000000000000000n), 24) + equal(uint64.trailingZeros(0b0000000000000000000000000000000000000000000000000000000000000000n), 64) +}) + +test('setTrailingBits', (t) => { + const i = 0b0000000000000000000000000000000000000000000000000000000000000000n + equal(uint64.setTrailingBits(i, 0, 1), 0b0000000000000000000000000000000000000000000000000000000000000001n) + equal(uint64.setTrailingBits(i, 1, 1), 0b0000000000000000000000000000000000000000000000000000000000000011n) + equal(uint64.setTrailingBits(i, 2, 1), 0b0000000000000000000000000000000000000000000000000000000000000111n) + equal(uint64.setTrailingBits(i, 5, 1), 0b0000000000000000000000000000000000000000000000000000000000111111n) + equal(uint64.setTrailingBits(i, 63, 1), 0b1111111111111111111111111111111111111111111111111111111111111111n) +}) + +test('setBit', (t) => { + equal(uint64.set(0b00000000000000000000000000000000n, 0, 1), 0b00000000000000000000000000000001n) + equal(uint64.set(0b00000000000000000000000000000000n, 1, 1), 0b00000000000000000000000000000010n) + equal(uint64.set(0b00000000000000000000000000000000n, 9, 1), 0b00000000000000000000001000000000n) + equal(uint64.set(0b00000000000000000000000000000000n, 17, 1), 0b00000000000000100000000000000000n) + equal(uint64.set(0b00000000000000000000000000000000n, 25, 1), 0b00000010000000000000000000000000n) + equal(uint64.set(0b00000000000000000000000000000000n, 31, 1), 0b10000000000000000000000000000000n) + equal(uint64.set(0b11111111111111111111111111111111n, 0, 0), 0b11111111111111111111111111111110n) + equal(uint64.set(0b11111111111111111111111111111111n, 1, 0), 0b11111111111111111111111111111101n) + equal(uint64.set(0b11111111111111111111111111111111n, 9, 0), 0b11111111111111111111110111111111n) + equal(uint64.set(0b11111111111111111111111111111111n, 17, 0), 0b11111111111111011111111111111111n) + equal(uint64.set(0b11111111111111111111111111111111n, 25, 0), 0b11111101111111111111111111111111n) + equal(uint64.set(0b11111111111111111111111111111111n, 31, 0), 0b01111111111111111111111111111111n) +}) diff --git a/int/uint8.ts b/int/uint8.ts new file mode 100644 index 0000000..e80ce29 --- /dev/null +++ b/int/uint8.ts @@ -0,0 +1,257 @@ +export type uint8 = + | 0 + | 1 + | 2 + | 3 + | 4 + | 5 + | 6 + | 7 + | 8 + | 9 + | 10 + | 11 + | 12 + | 13 + | 14 + | 15 + | 16 + | 17 + | 18 + | 19 + | 20 + | 21 + | 22 + | 23 + | 24 + | 25 + | 26 + | 27 + | 28 + | 29 + | 30 + | 31 + | 32 + | 33 + | 34 + | 35 + | 36 + | 37 + | 38 + | 39 + | 40 + | 41 + | 42 + | 43 + | 44 + | 45 + | 46 + | 47 + | 48 + | 49 + | 50 + | 51 + | 52 + | 53 + | 54 + | 55 + | 56 + | 57 + | 58 + | 59 + | 60 + | 61 + | 62 + | 63 + | 64 + | 65 + | 66 + | 67 + | 68 + | 69 + | 70 + | 71 + | 72 + | 73 + | 74 + | 75 + | 76 + | 77 + | 78 + | 79 + | 80 + | 81 + | 82 + | 83 + | 84 + | 85 + | 86 + | 87 + | 88 + | 89 + | 90 + | 91 + | 92 + | 93 + | 94 + | 95 + | 96 + | 97 + | 98 + | 99 + | 100 + | 101 + | 102 + | 103 + | 104 + | 105 + | 106 + | 107 + | 108 + | 109 + | 110 + | 111 + | 112 + | 113 + | 114 + | 115 + | 116 + | 117 + | 118 + | 119 + | 120 + | 121 + | 122 + | 123 + | 124 + | 125 + | 126 + | 127 + | 128 + | 129 + | 130 + | 131 + | 132 + | 133 + | 134 + | 135 + | 136 + | 137 + | 138 + | 139 + | 140 + | 141 + | 142 + | 143 + | 144 + | 145 + | 146 + | 147 + | 148 + | 149 + | 150 + | 151 + | 152 + | 153 + | 154 + | 155 + | 156 + | 157 + | 158 + | 159 + | 160 + | 161 + | 162 + | 163 + | 164 + | 165 + | 166 + | 167 + | 168 + | 169 + | 170 + | 171 + | 172 + | 173 + | 174 + | 175 + | 176 + | 177 + | 178 + | 179 + | 180 + | 181 + | 182 + | 183 + | 184 + | 185 + | 186 + | 187 + | 188 + | 189 + | 190 + | 191 + | 192 + | 193 + | 194 + | 195 + | 196 + | 197 + | 198 + | 199 + | 200 + | 201 + | 202 + | 203 + | 204 + | 205 + | 206 + | 207 + | 208 + | 209 + | 210 + | 211 + | 212 + | 213 + | 214 + | 215 + | 216 + | 217 + | 218 + | 219 + | 220 + | 221 + | 222 + | 223 + | 224 + | 225 + | 226 + | 227 + | 228 + | 229 + | 230 + | 231 + | 232 + | 233 + | 234 + | 235 + | 236 + | 237 + | 238 + | 239 + | 240 + | 241 + | 242 + | 243 + | 244 + | 245 + | 246 + | 247 + | 248 + | 249 + | 250 + | 251 + | 252 + | 253 + | 254 + | 255 diff --git a/package.json b/package.json new file mode 100644 index 0000000..2411856 --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "s2", + "version": "1.0.0", + "scripts": { + "test": "node --import tsx --test **/*_test.ts" + }, + "author": "Peter Johnson", + "license": "MIT", + "description": "javascript port of s2 geometry", + "devDependencies": { + "@types/node": "^20.14.11", + "tsx": "^4.16.2" + } +} diff --git a/s2/_index.ts b/s2/_index.ts new file mode 100644 index 0000000..ff269d3 --- /dev/null +++ b/s2/_index.ts @@ -0,0 +1,13 @@ +/** + * Module s2 is a library for working with geometry in S² (spherical geometry). + * + * Its related modules, parallel to this one, are s1 (operates on S¹), r1 (operates on ℝ¹), + * r2 (operates on ℝ²) and r3 (operates on ℝ³). + + * This package provides types and functions for the S2 cell hierarchy and coordinate systems. + * The S2 cell hierarchy is a hierarchical decomposition of the surface of a unit sphere (S²) into “cells”; it is highly efficient, scales from continental size to under 1 cm² and preserves spatial locality (nearby cells have close IDs). + * + * More information including an in-depth introduction to S2 can be found on the S2 website https://s2geometry.io/ + * @module s2 + */ +export * as cellid from './cellid' diff --git a/s2/cellid.ts b/s2/cellid.ts new file mode 100644 index 0000000..743418e --- /dev/null +++ b/s2/cellid.ts @@ -0,0 +1,130 @@ +import * as uint64 from '../int/uint64' +export type cellid = bigint + +/** + * Number of bits used to encode the face number + **/ +export const FACE_BITS = 3 + +/** + * Number of faces + */ +export const NUM_FACES = 6 + +/** + * Number of levels needed to specify a leaf cell + */ +export const MAX_LEVEL = 30 + +/** + * Total number of position bits. + * The extra bit (61 rather than 60) lets us encode each cell as its Hilbert curve position at the cell center (which is halfway along the portion of the Hilbert curve that fills that cell). + */ +export const POS_BITS = 2 * MAX_LEVEL + 1 + +/** + * Returns the cube face for this cell id, in the range [0,5]. + */ +export const face = (ci: cellid): number => { + return Number(ci >> BigInt(POS_BITS)) +} + +/** + * Returns the position along the Hilbert curve of this cell id, in the range [0,2^POS_BITS-1]. + */ +export const pos = (ci: cellid): cellid => { + return ci & (~0n >> BigInt(FACE_BITS)) +} + +/** + * Returns the subdivision level of this cell id, in the range [0, MAX_LEVEL]. + */ +export const level = (ci: cellid): number => { + return MAX_LEVEL - (uint64.trailingZeros(ci) >>> 1) +} + +/** + * Returns the cell id at the given level, which must be no greater than the current level. + */ +export const parent = (ci: cellid, level: number): cellid => { + const lsb = lsbForLevel(level) + return (ci & -lsb) | lsb +} + +/** + * Returns true is cell id is valid. + */ +export const valid = (ci: cellid): boolean => { + return uint64.valid(ci) && face(ci) <= NUM_FACES && (uint64.lsb(ci) & 0x1555555555555555n) != 0n +} + +// Bitwise + +/** + * Returns the lowest-numbered bit that is on for cells at the given level. + */ +export const lsbForLevel = (level: number): cellid => { + return 1n << BigInt(2 * (MAX_LEVEL - level)) +} + +// Ranges + +/** + * Returns the minimum CellID that is contained within this cell. + */ +export const rangeMin = (ci: cellid) => { + return ci - (uint64.lsb(ci) - 1n) +} + +/** + * Returns the maximum CellID that is contained within this cell. + */ +export const rangeMax = (ci: cellid): cellid => { + return ci + (uint64.lsb(ci) - 1n) +} + +/** + * Returns true iff ci contains oci. + */ +export const contains = (ci: cellid, oci: cellid) => { + return ci !== oci && rangeMin(ci) <= oci && oci <= rangeMax(ci) +} + +/** + * Returns true iff ci intersects oci. + */ +export const intersects = (ci: cellid, oci: cellid) => { + return rangeMin(oci) <= rangeMax(ci) && rangeMax(oci) >= rangeMin(ci) +} + +// Token + +/** + * Returns a hex-encoded string of the uint64 cell id, with leading zeros included but trailing zeros stripped + */ +export const toToken = (ci: cellid): string => { + const s = ci.toString(16).replace(/0+$/, '') + if (s.length === 0) return 'X' + return s +} + +/** + * Returns a cell id given a hex-encoded string. + */ +export const fromToken = (t: string): cellid => { + if (t.length > 16) return 0n + + let ci = BigInt('0x' + t) + if (t.length < 16) ci = ci << BigInt(4 * (16 - t.length)) + return ci +} + +// Constructors + +/** + * Returns a cell id given its face in the range [0,5], the 61-bit Hilbert curve position pos within that face, and the level in the range [0,MAX_LEVEL]. + * The position in the cell id will be truncated to correspond to the Hilbert curve position at the center of the returned cell. + */ +export const fromFacePosLevel = (face: number, pos: number, level: number): cellid => { + return parent((BigInt(face) << BigInt(POS_BITS)) + BigInt(pos || 1), level) +} diff --git a/s2/cellid_test.ts b/s2/cellid_test.ts new file mode 100644 index 0000000..9f961e4 --- /dev/null +++ b/s2/cellid_test.ts @@ -0,0 +1,108 @@ +import test from 'node:test' +import { equal, ok } from 'node:assert/strict' +import * as cellid from './cellid' + +test('face', t => { + equal(cellid.face(0b0001111111111111111111111111111111111111111111111111111111111111n), 0) + equal(cellid.face(0b0011111111111111111111111111111111111111111111111111111111111111n), 1) + equal(cellid.face(0b0101111111111111111111111111111111111111111111111111111111111111n), 2) + equal(cellid.face(0b0111111111111111111111111111111111111111111111111111111111111111n), 3) + equal(cellid.face(0b1001111111111111111111111111111111111111111111111111111111111111n), 4) + equal(cellid.face(0b1011111111111111111111111111111111111111111111111111111111111111n), 5) +}) + +test('level', t => { + equal(cellid.level(0b0000000000000000000000000000000000000000000000000000000000000001n), 30) + equal(cellid.level(0b0000000000000000000000000000000000000000000000000000000000000100n), 29) + equal(cellid.level(0b0000000000000000000000000000000000000000000000000000000000010000n), 28) + equal(cellid.level(0b0000000000000000000000000000000000000000000000000000000001000000n), 27) + equal(cellid.level(0b0000000000000000000000000000000000000000000000000000000100000000n), 26) + equal(cellid.level(0b0000000000000000000000000000000000000000000000000000010000000000n), 25) + equal(cellid.level(0b0000000000000000000000000000000000000000000000000001000000000000n), 24) + equal(cellid.level(0b0000000000000000000000000000000000000000000000000100000000000000n), 23) + equal(cellid.level(0b0000000000000000000000000000000000000000000000010000000000000000n), 22) + equal(cellid.level(0b0000000000000000000000000000000000000000000001000000000000000000n), 21) + equal(cellid.level(0b0000000000000000000000000000000000000000000100000000000000000000n), 20) + equal(cellid.level(0b0000000000000000000000000000000000000000010000000000000000000000n), 19) + equal(cellid.level(0b0000000000000000000000000000000000000001000000000000000000000000n), 18) + equal(cellid.level(0b0000000000000000000000000000000000000100000000000000000000000000n), 17) + equal(cellid.level(0b0000000000000000000000000000000000010000000000000000000000000000n), 16) + equal(cellid.level(0b0000000000000000000000000000000001000000000000000000000000000000n), 15) + equal(cellid.level(0b0000000000000000000000000000000100000000000000000000000000000000n), 14) + equal(cellid.level(0b0000000000000000000000000000010000000000000000000000000000000000n), 13) + equal(cellid.level(0b0000000000000000000000000001000000000000000000000000000000000000n), 12) + equal(cellid.level(0b0000000000000000000000000100000000000000000000000000000000000000n), 11) + equal(cellid.level(0b0000000000000000000000010000000000000000000000000000000000000000n), 10) + equal(cellid.level(0b0000000000000000000001000000000000000000000000000000000000000000n), 9) + equal(cellid.level(0b0000000000000000000100000000000000000000000000000000000000000000n), 8) + equal(cellid.level(0b0000000000000000010000000000000000000000000000000000000000000000n), 7) + equal(cellid.level(0b0000000000000001000000000000000000000000000000000000000000000000n), 6) + equal(cellid.level(0b0000000000000100000000000000000000000000000000000000000000000000n), 5) + equal(cellid.level(0b0000000000010000000000000000000000000000000000000000000000000000n), 4) + equal(cellid.level(0b0000000001000000000000000000000000000000000000000000000000000000n), 3) + equal(cellid.level(0b0000000100000000000000000000000000000000000000000000000000000000n), 2) + equal(cellid.level(0b0000010000000000000000000000000000000000000000000000000000000000n), 1) + equal(cellid.level(0b0001000000000000000000000000000000000000000000000000000000000000n), 0) +}) + +test('parent', t => { + const c1 = /* */ 0b0011110000111100001111000011110000000000000000000000000000000000n + equal(cellid.parent(c1, 9), 0b0011110000111100001111000000000000000000000000000000000000000000n) + equal(cellid.parent(c1, 5), 0b0011110000111100000000000000000000000000000000000000000000000000n) + equal(cellid.parent(c1, 1), 0b0011110000000000000000000000000000000000000000000000000000000000n) + equal(c1, /* */ 0b0011110000111100001111000011110000000000000000000000000000000000n) + + const c2 = /* */ 0b0011110000111100001111000011110000111100001111000011110000111101n + equal(cellid.parent(c2, 30), 0b0011110000111100001111000011110000111100001111000011110000111101n) + equal(cellid.parent(c2, 29), 0b0011110000111100001111000011110000111100001111000011110000111100n) + equal(cellid.parent(c2, 15), 0b0011110000111100001111000011110001000000000000000000000000000000n) + equal(cellid.parent(c2, 14), 0b0011110000111100001111000011110100000000000000000000000000000000n) + equal(cellid.parent(c2, 1), 0b0011110000000000000000000000000000000000000000000000000000000000n) + equal(cellid.parent(c2, 0), 0b0011000000000000000000000000000000000000000000000000000000000000n) +}) + +test('range', t => { + const c1 = /* */ 0b0011110000111100000001000000000000000000000000000000000000000000n + equal(cellid.rangeMin(c1), 0b0011110000111100000000000000000000000000000000000000000000000001n) + equal(cellid.rangeMax(c1), 0b0011110000111100000001111111111111111111111111111111111111111111n) + + const c2 = /* */ 0b0011110000111100001111000011110001000000000000000000000000000000n + equal(cellid.rangeMin(c2), 0b0011110000111100001111000011110000000000000000000000000000000001n) + equal(cellid.rangeMax(c2), 0b0011110000111100001111000011110001111111111111111111111111111111n) +}) + +test('contains', t => { + const c1 = 0b0011110000111100001111000011110000111100001111000011110000111101n + ok(!cellid.contains(c1, cellid.parent(c1, 10))) + ok(cellid.contains(cellid.parent(c1, 10), c1)) + + const c2 = 0b1011111111111111111111111111111111111111111111111111111111111111n + ok(!cellid.contains(c2, cellid.parent(c2, 10))) + ok(cellid.contains(cellid.parent(c2, 10), c2)) + + ok(!cellid.contains(c1, c2)) + ok(!cellid.contains(c2, c1)) +}) + +test('intersects', t => { + const c1 = 0b0011110000111100001111000011110000111100001111000011110000111101n + ok(cellid.intersects(c1, cellid.parent(c1, 10))) + ok(cellid.intersects(cellid.parent(c1, 10), c1)) + + const c2 = 0b1011111111111111111111111111111111111111111111111111111111111111n + ok(cellid.intersects(c2, cellid.parent(c2, 10))) + ok(cellid.intersects(cellid.parent(c2, 10), c2)) + + ok(cellid.intersects(c1, c1)) + ok(!cellid.intersects(c1, c2)) + ok(!cellid.intersects(c1, c2)) +}) + +test('valid', t => { + ok(cellid.valid(0b0000000000000000000000000000000000000000000000000000000000000001n)) + ok(!cellid.valid(0b1110000000000000000000000000000000000000000000000000000000000001n), 'face') + ok(!cellid.valid(0b0000000000000000000000000000000000000000000000000000000000000010n), 'level') + + ok(!cellid.valid(0b0000000000000000000000000000000000000000000000000000000000000010n)) + ok(!cellid.valid(0b0000000000000000000000000000000000000000000000000000000000001000n)) +})