Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accept byteOffset when creating an index from a buffer #55

Merged
merged 9 commits into from
Jan 20, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 19 additions & 9 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,22 @@ export default class Flatbush {
/**
* Recreate a Flatbush index from raw `ArrayBuffer` or `SharedArrayBuffer` data.
* @param {ArrayBuffer | SharedArrayBuffer} data
* @param {number} byteOffset byte offset to the start of the Flatbush buffer in the referenced ArrayBuffer.
kylebarron marked this conversation as resolved.
Show resolved Hide resolved
* @returns {Flatbush} index
*/
static from(data) {
static from(data, byteOffset = 0) {
if (byteOffset % 8 !== 0) {
throw new Error('Uint8Array offset must be 8-byte aligned.');
kylebarron marked this conversation as resolved.
Show resolved Hide resolved
}

// @ts-expect-error duck typing array buffers
if (!data || data.byteLength === undefined || data.buffer) {
throw new Error('Data must be an instance of ArrayBuffer or SharedArrayBuffer.');
throw new Error(
'Data must be an instance of ArrayBuffer, or SharedArrayBuffer.'
);
kylebarron marked this conversation as resolved.
Show resolved Hide resolved
}
const [magic, versionAndType] = new Uint8Array(data, 0, 2);

const [magic, versionAndType] = new Uint8Array(data, byteOffset + 0, 2);
if (magic !== 0xfb) {
throw new Error('Data does not appear to be in a Flatbush format.');
}
Expand All @@ -29,10 +37,10 @@ export default class Flatbush {
if (!ArrayType) {
throw new Error('Unrecognized array type.');
}
const [nodeSize] = new Uint16Array(data, 2, 1);
const [numItems] = new Uint32Array(data, 4, 1);
const [nodeSize] = new Uint16Array(data, byteOffset + 2, 1);
const [numItems] = new Uint32Array(data, byteOffset + 4, 1);

return new Flatbush(numItems, nodeSize, ArrayType, undefined, data);
return new Flatbush(numItems, nodeSize, ArrayType, undefined, data, byteOffset);
}

/**
Expand All @@ -42,13 +50,15 @@ export default class Flatbush {
* @param {TypedArrayConstructor} [ArrayType=Float64Array] The array type used for coordinates storage (`Float64Array` by default).
* @param {ArrayBufferConstructor | SharedArrayBufferConstructor} [ArrayBufferType=ArrayBuffer] The array buffer type used to store data (`ArrayBuffer` by default).
* @param {ArrayBuffer | SharedArrayBuffer} [data] (Only used internally)
* @param {number} [byteOffset] (Only used internally)
kylebarron marked this conversation as resolved.
Show resolved Hide resolved
*/
constructor(numItems, nodeSize = 16, ArrayType = Float64Array, ArrayBufferType = ArrayBuffer, data) {
constructor(numItems, nodeSize = 16, ArrayType = Float64Array, ArrayBufferType = ArrayBuffer, data, byteOffset = 0) {
if (numItems === undefined) throw new Error('Missing required argument: numItems.');
if (isNaN(numItems) || numItems <= 0) throw new Error(`Unexpected numItems value: ${numItems}.`);

this.numItems = +numItems;
this.nodeSize = Math.min(Math.max(+nodeSize, 2), 65535);
this.byteOffset = byteOffset;

// calculate the total number of nodes in the R-tree to allocate space for
// and the index of each tree level (used in search later)
Expand All @@ -74,8 +84,8 @@ export default class Flatbush {
// @ts-expect-error duck typing array buffers
if (data && data.byteLength !== undefined && !data.buffer) {
this.data = data;
this._boxes = new this.ArrayType(this.data, 8, numNodes * 4);
this._indices = new this.IndexArrayType(this.data, 8 + nodesByteSize, numNodes);
this._boxes = new this.ArrayType(this.data, byteOffset + 8, numNodes * 4);
this._indices = new this.IndexArrayType(this.data, byteOffset + 8 + nodesByteSize, numNodes);

this._pos = numNodes * 4;
this.minX = this._boxes[this._pos - 4];
Expand Down
29 changes: 29 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,35 @@ test('reconstructs an index from array buffer', () => {
assert.deepEqual(index, index2);
});

test('throws an error when reconstructing an index from array buffer if not 8-byte aligned', () => {
const index = createIndex();
const byteOffset = 12;
const newArrayBuffer = new ArrayBuffer(index.data.byteLength + byteOffset);
const newView = new Uint8Array(newArrayBuffer, byteOffset);
newView.set(new Uint8Array(index.data));

assert.throws(() => {
Flatbush.from(newArrayBuffer, byteOffset);
});
});

test('reconstructs an index from a Uint8Array', () => {
const index = createIndex();
const byteOffset = 16;
const newArrayBuffer = new ArrayBuffer(index.data.byteLength + byteOffset);
const newView = new Uint8Array(newArrayBuffer, byteOffset);
newView.set(new Uint8Array(index.data));

const index2 = Flatbush.from(newArrayBuffer, byteOffset);

assert.deepEqual(index._boxes, index2._boxes);
assert.deepEqual(index._indices, index2._indices);
assert.deepEqual(index.numItems, index2.numItems);
assert.deepEqual(index.nodeSize, index2.nodeSize);
assert.deepEqual(index._levelBounds, index2._levelBounds);
assert.notDeepEqual(index.byteOffset, index2.byteOffset);
kylebarron marked this conversation as resolved.
Show resolved Hide resolved
});

test('throws an error if added less items than the index size', () => {
assert.throws(() => {
const index = new Flatbush(data.length / 4);
Expand Down
Loading