diff --git a/index.js b/index.js index 53c5da3..db47277 100644 --- a/index.js +++ b/index.js @@ -85,8 +85,7 @@ export default class Supercluster { } getChildren(clusterId) { - const originId = clusterId >> 5; - const originZoom = clusterId % 32; + const {originId, originZoom} = this._decodeClusterId(clusterId); const errorMsg = 'No cluster with the specified id.'; const index = this.trees[originZoom]; @@ -151,14 +150,14 @@ export default class Supercluster { } getClusterExpansionZoom(clusterId) { - let clusterZoom = (clusterId % 32) - 1; - while (clusterZoom <= this.options.maxZoom) { + let expansionZoom = this._decodeClusterId(clusterId).originZoom - 1; + while (expansionZoom <= this.options.maxZoom) { const children = this.getChildren(clusterId); - clusterZoom++; + expansionZoom++; if (children.length !== 1) break; clusterId = children[0].properties.cluster_id; } - return clusterZoom; + return expansionZoom; } _appendLeaves(result, clusterId, limit, offset, skipped) { @@ -192,18 +191,30 @@ export default class Supercluster { _addTileFeatures(ids, points, x, y, z2, tile) { for (const i of ids) { const c = points[i]; + const isCluster = c.numPoints; const f = { type: 1, geometry: [[ Math.round(this.options.extent * (c.x * z2 - x)), Math.round(this.options.extent * (c.y * z2 - y)) ]], - tags: c.numPoints ? getClusterProperties(c) : this.points[c.index].properties + tags: isCluster ? getClusterProperties(c) : this.points[c.index].properties }; - const id = c.numPoints ? c.id : this.points[c.index].id; - if (id !== undefined) { - f.id = id; + + // assign id + let id; + if (isCluster) { + id = c.id; + } else if (this.options.generateId) { + // optionally generate id + id = c.index; + } else if (this.points[c.index].id) { + // keep id if already assigned + id = this.points[c.index].id; } + + if (id !== undefined) f.id = id; + tile.features.push(f); } } @@ -234,8 +245,8 @@ export default class Supercluster { let clusterProperties = reduce && numPoints > 1 ? this._map(p, true) : null; - // encode both zoom and point index on which the cluster originated - const id = (i << 5) + (zoom + 1); + // encode both zoom and point index on which the cluster originated -- offset by total length of features + const id = (i << 5) + (zoom + 1) + this.points.length; for (const neighborId of neighborIds) { const b = tree.points[neighborId]; @@ -267,6 +278,13 @@ export default class Supercluster { return clusters; } + _decodeClusterId(clusterId) { + const decremented = clusterId - this.points.length; + const originId = decremented >> 5; + const originZoom = decremented % 32; + return {originZoom, originId}; + } + _map(point, clone) { if (point.numPoints) { return clone ? extend({}, point.properties) : point.properties; diff --git a/test/fixtures/places-z0-0-0.json b/test/fixtures/places-z0-0-0.json index de2ed89..4bae8ac 100644 --- a/test/fixtures/places-z0-0-0.json +++ b/test/fixtures/places-z0-0-0.json @@ -2,91 +2,91 @@ "features": [ { "type": 1, - "id": 1, + "id": 164, "geometry": [ [150, 205] ], "tags": { "cluster": true, - "cluster_id": 1, + "cluster_id": 164, "point_count": 16, "point_count_abbreviated": 16 } }, { "type": 1, - "id": 33, + "id": 196, "geometry": [ [165, 240] ], "tags": { "cluster": true, - "cluster_id": 33, + "cluster_id": 196, "point_count": 18, "point_count_abbreviated": 18 } }, { "type": 1, - "id": 65, + "id": 228, "geometry": [ [179, 303] ], "tags": { "cluster": true, - "cluster_id": 65, + "cluster_id": 228, "point_count": 13, "point_count_abbreviated": 13 } }, { "type": 1, - "id": 97, + "id": 260, "geometry": [ [336, 234] ], "tags": { "cluster": true, - "cluster_id": 97, + "cluster_id": 260, "point_count": 8, "point_count_abbreviated": 8 } }, { "type": 1, - "id": 129, + "id": 292, "geometry": [ [299, 285] ], "tags": { "cluster": true, - "cluster_id": 129, + "cluster_id": 292, "point_count": 15, "point_count_abbreviated": 15 } }, { "type": 1, - "id": 161, + "id": 324, "geometry": [ [71, 419] ], "tags": { "cluster": true, - "cluster_id": 161, + "cluster_id": 324, "point_count": 4, "point_count_abbreviated": 4 } }, { "type": 1, - "id": 257, + "id": 420, "geometry": [ [92, 212] ], "tags": { "cluster": true, - "cluster_id": 257, + "cluster_id": 420, "point_count": 6, "point_count_abbreviated": 6 } @@ -110,39 +110,39 @@ }, { "type": 1, - "id": 353, + "id": 516, "geometry": [ [162, 345] ], "tags": { "cluster": true, - "cluster_id": 353, + "cluster_id": 516, "point_count": 3, "point_count_abbreviated": 3 } }, { "type": 1, - "id": 417, + "id": 580, "geometry": [ [236, 232] ], "tags": { "cluster": true, - "cluster_id": 417, + "cluster_id": 580, "point_count": 4, "point_count_abbreviated": 4 } }, { "type": 1, - "id": 481, + "id": 644, "geometry": [ [259, 193] ], "tags": { "cluster": true, - "cluster_id": 481, + "cluster_id": 644, "point_count": 6, "point_count_abbreviated": 6 } @@ -217,26 +217,26 @@ }, { "type": 1, - "id": 673, + "id": 836, "geometry": [ [220, 147] ], "tags": { "cluster": true, - "cluster_id": 673, + "cluster_id": 836, "point_count": 3, "point_count_abbreviated": 3 } }, { "type": 1, - "id": 737, + "id": 900, "geometry": [ [27, 270] ], "tags": { "cluster": true, - "cluster_id": 737, + "cluster_id": 900, "point_count": 6, "point_count_abbreviated": 6 } @@ -277,52 +277,52 @@ }, { "type": 1, - "id": 833, + "id": 996, "geometry": [ [26, 115] ], "tags": { "cluster": true, - "cluster_id": 833, + "cluster_id": 996, "point_count": 2, "point_count_abbreviated": 2 } }, { "type": 1, - "id": 961, + "id": 1124, "geometry": [ [449, 304] ], "tags": { "cluster": true, - "cluster_id": 961, + "cluster_id": 1124, "point_count": 13, "point_count_abbreviated": 13 } }, { "type": 1, - "id": 1025, + "id": 1188, "geometry": [ [455, 272] ], "tags": { "cluster": true, - "cluster_id": 1025, + "cluster_id": 1188, "point_count": 5, "point_count_abbreviated": 5 } }, { "type": 1, - "id": 1121, + "id": 1284, "geometry": [ [227, 121] ], "tags": { "cluster": true, - "cluster_id": 1121, + "cluster_id": 1284, "point_count": 2, "point_count_abbreviated": 2 } @@ -346,26 +346,26 @@ }, { "type": 1, - "id": 1217, + "id": 1380, "geometry": [ [484, 235] ], "tags": { "cluster": true, - "cluster_id": 1217, + "cluster_id": 1380, "point_count": 13, "point_count_abbreviated": 13 } }, { "type": 1, - "id": 1281, + "id": 1444, "geometry": [ [503, 260] ], "tags": { "cluster": true, - "cluster_id": 1281, + "cluster_id": 1444, "point_count": 4, "point_count_abbreviated": 4 } @@ -390,13 +390,13 @@ }, { "type": 1, - "id": 1505, + "id": 1668, "geometry": [ [475, 165] ], "tags": { "cluster": true, - "cluster_id": 1505, + "cluster_id": 1668, "point_count": 7, "point_count_abbreviated": 7 } @@ -454,39 +454,39 @@ }, { "type": 1, - "id": 1857, + "id": 2020, "geometry": [ [202, 262] ], "tags": { "cluster": true, - "cluster_id": 1857, + "cluster_id": 2020, "point_count": 2, "point_count_abbreviated": 2 } }, { "type": 1, - "id": 1217, + "id": 1380, "geometry": [ [-28, 235] ], "tags": { "cluster": true, - "cluster_id": 1217, + "cluster_id": 1380, "point_count": 13, "point_count_abbreviated": 13 } }, { "type": 1, - "id": 1281, + "id": 1444, "geometry": [ [-9, 260] ], "tags": { "cluster": true, - "cluster_id": 1281, + "cluster_id": 1444, "point_count": 4, "point_count_abbreviated": 4 } @@ -511,13 +511,13 @@ }, { "type": 1, - "id": 1505, + "id": 1668, "geometry": [ [-37, 165] ], "tags": { "cluster": true, - "cluster_id": 1505, + "cluster_id": 1668, "point_count": 7, "point_count_abbreviated": 7 } @@ -541,26 +541,26 @@ }, { "type": 1, - "id": 737, + "id": 900, "geometry": [ [539, 270] ], "tags": { "cluster": true, - "cluster_id": 737, + "cluster_id": 900, "point_count": 6, "point_count_abbreviated": 6 } }, { "type": 1, - "id": 833, + "id": 996, "geometry": [ [538, 115] ], "tags": { "cluster": true, - "cluster_id": 833, + "cluster_id": 996, "point_count": 2, "point_count_abbreviated": 2 } diff --git a/test/test.js b/test/test.js index 975dc38..afd3add 100644 --- a/test/test.js +++ b/test/test.js @@ -14,14 +14,14 @@ test('generates clusters properly', (t) => { test('returns children of a cluster', (t) => { const index = new Supercluster().load(places.features); - const childCounts = index.getChildren(1).map(p => p.properties.point_count || 1); + const childCounts = index.getChildren(164).map(p => p.properties.point_count || 1); t.same(childCounts, [6, 7, 2, 1]); t.end(); }); test('returns leaves of a cluster', (t) => { const index = new Supercluster().load(places.features); - const leafNames = index.getLeaves(1, 10, 5).map(p => p.properties.name); + const leafNames = index.getLeaves(164, 10, 5).map(p => p.properties.name); t.same(leafNames, [ 'Niagara Falls', 'Cape San Blas', @@ -46,18 +46,18 @@ test('getLeaves handles null-property features', (t) => { coordinates: [-79.04411780507252, 43.08771393436908] } }])); - const leaves = index.getLeaves(1, 1, 6); + const leaves = index.getLeaves(165, 1, 6); t.equal(leaves[0].properties, null); t.end(); }); test('returns cluster expansion zoom', (t) => { const index = new Supercluster().load(places.features); - t.same(index.getClusterExpansionZoom(1), 1); - t.same(index.getClusterExpansionZoom(33), 1); - t.same(index.getClusterExpansionZoom(353), 2); - t.same(index.getClusterExpansionZoom(833), 2); - t.same(index.getClusterExpansionZoom(1857), 3); + t.same(index.getClusterExpansionZoom(164), 1); + t.same(index.getClusterExpansionZoom(196), 1); + t.same(index.getClusterExpansionZoom(516), 2); + t.same(index.getClusterExpansionZoom(996), 2); + t.same(index.getClusterExpansionZoom(2020), 3); t.end(); }); @@ -68,7 +68,7 @@ test('returns cluster expansion zoom for maxZoom', (t) => { maxZoom: 4, }).load(places.features); - t.same(index.getClusterExpansionZoom(2436), 5); + t.same(index.getClusterExpansionZoom(2599), 5); t.end(); });