Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
9 changes: 6 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export default class Supercluster {
this.trees = new Array(this.options.maxZoom + 1);
this.stride = this.options.reduce ? 7 : 6;
this.clusterProps = [];
// Reserve enough bits of the cluster id for the origin zoom so that a
// maxZoom of 31 or more does not overflow into the origin-index bits.
this.zoomBase = 2 ** Math.max(5, Math.ceil(Math.log2(this.options.maxZoom + 2)));
}

load(points) {
Expand Down Expand Up @@ -311,7 +314,7 @@ export default class Supercluster {
let clusterPropIndex = -1;

// encode both zoom and point index on which the cluster originated -- offset by total length of features
const id = ((i / stride | 0) << 5) + (zoom + 1) + this.points.length;
const id = (i / stride | 0) * this.zoomBase + (zoom + 1) + this.points.length;

for (const neighborId of neighborIds) {
const k = neighborId * stride;
Expand Down Expand Up @@ -358,12 +361,12 @@ export default class Supercluster {

// get index of the point from which the cluster originated
_getOriginId(clusterId) {
return (clusterId - this.points.length) >> 5;
return Math.floor((clusterId - this.points.length) / this.zoomBase);
}

// get zoom of the point from which the cluster originated
_getOriginZoom(clusterId) {
return (clusterId - this.points.length) % 32;
return (clusterId - this.points.length) % this.zoomBase;
}

_map(data, i, clone) {
Expand Down
18 changes: 18 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,24 @@ test('returns cluster expansion zoom for maxZoom', () => {
assert.deepEqual(index.getClusterExpansionZoom(2504), 5);
});

test('handles a maxZoom of 31 or more without corrupting cluster ids', () => {
const point = (lng, lat, name) => ({
type: 'Feature',
properties: {name},
geometry: {type: 'Point', coordinates: [lng, lat]},
});

for (const maxZoom of [31, 32, 40]) {
const index = new Supercluster({maxZoom}).load([point(10, 50, 'a'), point(10, 50, 'b')]);
const clusterId = index.getClusters([-180, -90, 180, 90], 0)
.find(f => f.properties.cluster).properties.cluster_id;

assert.deepEqual(index.getLeaves(clusterId).map(f => f.properties.name).sort(), ['a', 'b']);
assert.deepEqual(index.getChildren(clusterId).map(f => f.properties.name).sort(), ['a', 'b']);
assert.equal(index.getClusterExpansionZoom(clusterId), maxZoom + 1);
}
});

test('aggregates cluster properties with reduce', () => {
const index = new Supercluster({
map: props => ({sum: props.scalerank}),
Expand Down