diff --git a/src/index.ts b/src/index.ts index 561967c..aac1e84 100644 --- a/src/index.ts +++ b/src/index.ts @@ -107,6 +107,32 @@ export function cellToParent(quadbin: Quadbin): Quadbin { return parent; } +/** + * Returns the children of a cell, in row-major order starting from NW and ending at SE. + */ +export function cellToChildren(quadbin: Quadbin, resolution: bigint): Quadbin[] { + if (resolution < 0 || resolution > 26 || resolution < getResolution(quadbin)) { + throw new Error('Invalid resolution'); + } + + const zoomLevelMask = ~(0x1fn << 52n); + const blockRange = 1n << ((resolution - ((quadbin >> 52n) & 0x1fn)) << 1n); + const sqrtBlockRange = 1n << (resolution - ((quadbin >> 52n) & 0x1fn)); + const blockShift = 52n - (resolution << 1n); + + const childBase = + ((quadbin & zoomLevelMask) | (resolution << 52n)) & ~((blockRange - 1n) << blockShift); + + const children: Quadbin[] = []; + for (let blockRow = 0n; blockRow < sqrtBlockRange; blockRow++) { + for (let blockColumn = 0n; blockColumn < sqrtBlockRange; blockColumn++) { + children.push(childBase | ((blockRow * sqrtBlockRange + blockColumn) << blockShift)); + } + } + + return children; +} + export function geometryToCells(geometry, resolution: bigint): Quadbin[] { const zoom = Number(resolution); return tiles(geometry, { diff --git a/test/index.spec.js b/test/index.spec.js index 0521b83..65399b1 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -1,11 +1,13 @@ import test from 'tape'; import { + cellToBoundary, tileToCell, + cellToChildren, cellToTile, cellToParent, geometryToCells, getResolution, - cellToBoundary + hexToBigInt } from 'quadbin'; import {tileToQuadkey} from './quadkey-utils.js'; @@ -31,7 +33,7 @@ test('Quadbin conversion', async t => { t.end(); }); -test('Quadbin getParent', async t => { +test('Quadbin cellToParent', async t => { let tile = {x: 134, y: 1238, z: 10}; const quadkey = tileToQuadkey(tile); @@ -49,6 +51,29 @@ test('Quadbin getParent', async t => { t.end(); }); +test('Quadbin cellToChildren', async t => { + const parentTile = { z: 8, x: 59, y: 97 }; + const parent = tileToCell(parentTile); + + t.deepEquals(cellToChildren(parent, 8n), [parent], 'children at resolution + 0'); + + // Order is row major, starting from NW and ending at SE. + t.deepEquals( + cellToChildren(parent, 9n).map(cellToTile), + [ + { z: 9, x: 118, y: 194 }, // nw + { z: 9, x: 119, y: 194 }, // ne + { z: 9, x: 118, y: 195 }, // sw + { z: 9, x: 119, y: 195 } // se + ], + 'children at resolution + 1' + ); + + t.deepEquals(cellToChildren(parent, 10n).length, 16, 'children at resolution + 2'); + + t.end(); +}); + // Zoom:26 test not agreeing with Python import PointGeometry from './data/PointGeometry.json' with {type: 'json'}; import MultiPointGeometry from './data/MultiPointGeometry.json' with {type: 'json'};