A very fast geospatial point clustering library for browsers and Node.js environments.
npx @tessl/cli install tessl/npm-supercluster@8.0.00
# Supercluster
1
2
Supercluster is a very fast JavaScript library for geospatial point clustering for browsers and Node.js environments. It provides high-performance clustering of millions of points with configurable zoom levels, hierarchical cluster navigation, and custom property aggregation through map/reduce functions.
3
4
## Package Information
5
6
- **Package Name**: supercluster
7
- **Package Type**: npm
8
- **Language**: JavaScript (ES2020)
9
- **Installation**: `npm install supercluster`
10
11
## Core Imports
12
13
```javascript
14
import Supercluster from 'supercluster';
15
```
16
17
For CommonJS:
18
19
```javascript
20
const Supercluster = require('supercluster');
21
```
22
23
CDN/Browser (ES Module):
24
25
```javascript
26
import Supercluster from 'https://esm.run/supercluster';
27
```
28
29
Script tag (UMD):
30
31
```html
32
<script src="https://unpkg.com/supercluster@8.0.1/dist/supercluster.min.js"></script>
33
<!-- Creates global Supercluster variable -->
34
```
35
36
## Basic Usage
37
38
```javascript
39
import Supercluster from 'supercluster';
40
41
// Create clustering index with default options
42
const index = new Supercluster({
43
radius: 40,
44
maxZoom: 16,
45
minPoints: 2
46
});
47
48
// Load GeoJSON Point features
49
const points = [
50
{
51
type: 'Feature',
52
properties: { name: 'Location A' },
53
geometry: {
54
type: 'Point',
55
coordinates: [-73.97, 40.77] // [longitude, latitude]
56
}
57
},
58
{
59
type: 'Feature',
60
properties: { name: 'Location B' },
61
geometry: {
62
type: 'Point',
63
coordinates: [-73.96, 40.78]
64
}
65
}
66
];
67
68
// Build the clustering index
69
index.load(points);
70
71
// Get clusters and points for a bounding box and zoom level
72
const clusters = index.getClusters([-74.0, 40.7, -73.9, 40.8], 10);
73
```
74
75
## Architecture
76
77
Supercluster is built around several key components:
78
79
- **Spatial Indexing**: Uses KDBush for efficient spatial queries and range searches
80
- **Hierarchical Clustering**: Multi-zoom level clustering that builds a hierarchy from max zoom to min zoom
81
- **Memory Optimization**: Flat numeric arrays for internal data storage to minimize memory footprint
82
- **Vector Tile Support**: Compatible with vector tile rendering pipelines through getTile method
83
- **Property Aggregation**: Configurable map/reduce functions for custom cluster properties
84
85
## Capabilities
86
87
### Clustering Index Creation
88
89
Create and configure a new Supercluster instance with customizable clustering behavior.
90
91
```javascript { .api }
92
/**
93
* Creates a new Supercluster instance
94
* @param options - Configuration options for clustering behavior
95
*/
96
constructor(options?: SuperclusterOptions);
97
98
interface SuperclusterOptions {
99
/** Minimum zoom level at which clusters are generated (default: 0) */
100
minZoom?: number;
101
/** Maximum zoom level at which clusters are generated (default: 16) */
102
maxZoom?: number;
103
/** Minimum number of points to form a cluster (default: 2) */
104
minPoints?: number;
105
/** Cluster radius in pixels (default: 40) */
106
radius?: number;
107
/** Tile extent - radius is calculated relative to this value (default: 512) */
108
extent?: number;
109
/** Size of the KD-tree leaf node, affects performance (default: 64) */
110
nodeSize?: number;
111
/** Whether to log timing info (default: false) */
112
log?: boolean;
113
/** Whether to generate numeric ids for input features in vector tiles (default: false) */
114
generateId?: boolean;
115
/** Function that returns cluster properties corresponding to a single point (default: props => props) */
116
map?: (props: any) => any;
117
/** Function that merges properties of two clusters into one (default: null) */
118
reduce?: (accumulated: any, props: any) => void;
119
}
120
```
121
122
**Usage Examples:**
123
124
```javascript
125
// Basic clustering with default options
126
const index = new Supercluster();
127
128
// Custom clustering configuration
129
const index = new Supercluster({
130
radius: 60, // Larger cluster radius
131
maxZoom: 14, // Stop clustering at zoom 14
132
minPoints: 5, // Require at least 5 points to form cluster
133
extent: 256 // Use 256-pixel tile extent
134
});
135
136
// With property aggregation
137
const index = new Supercluster({
138
map: (props) => ({ sum: props.value }),
139
reduce: (accumulated, props) => { accumulated.sum += props.sum; }
140
});
141
```
142
143
### Data Loading
144
145
Load GeoJSON Point features into the clustering index.
146
147
```javascript { .api }
148
/**
149
* Loads an array of GeoJSON Point features and builds the clustering index
150
* @param points - Array of GeoJSON Feature objects with Point geometry
151
* @returns The Supercluster instance for chaining
152
*/
153
load(points: GeoJSONFeature[]): Supercluster;
154
155
interface GeoJSONFeature {
156
type: 'Feature';
157
properties: any;
158
geometry: {
159
type: 'Point';
160
coordinates: [number, number]; // [longitude, latitude]
161
};
162
id?: any;
163
}
164
```
165
166
**Usage Examples:**
167
168
```javascript
169
const points = [
170
{
171
type: 'Feature',
172
properties: { name: 'Coffee Shop', category: 'restaurant' },
173
geometry: { type: 'Point', coordinates: [-73.97, 40.77] }
174
},
175
{
176
type: 'Feature',
177
properties: { name: 'Park', category: 'recreation' },
178
geometry: { type: 'Point', coordinates: [-73.96, 40.78] }
179
}
180
];
181
182
index.load(points);
183
```
184
185
### Cluster and Point Retrieval
186
187
Get clusters and individual points within a geographic bounding box at a specific zoom level.
188
189
```javascript { .api }
190
/**
191
* Returns clusters and points within a bounding box at a given zoom level
192
* @param bbox - Bounding box as [westLng, southLat, eastLng, northLat]
193
* @param zoom - Integer zoom level
194
* @returns Array of GeoJSON Features (clusters and individual points)
195
*/
196
getClusters(bbox: [number, number, number, number], zoom: number): GeoJSONFeature[];
197
198
interface ClusterFeature extends GeoJSONFeature {
199
properties: {
200
cluster: true;
201
cluster_id: number;
202
point_count: number;
203
point_count_abbreviated: string;
204
[key: string]: any; // Custom properties from reduce function
205
};
206
}
207
```
208
209
**Usage Examples:**
210
211
```javascript
212
// Get all clusters/points visible in New York area at zoom level 10
213
const bbox = [-74.1, 40.6, -73.8, 40.9];
214
const clusters = index.getClusters(bbox, 10);
215
216
// Handle clusters vs individual points
217
clusters.forEach(feature => {
218
if (feature.properties.cluster) {
219
console.log(`Cluster with ${feature.properties.point_count} points`);
220
} else {
221
console.log(`Individual point: ${feature.properties.name}`);
222
}
223
});
224
225
// World-wide view at low zoom
226
const worldClusters = index.getClusters([-180, -85, 180, 85], 2);
227
```
228
229
### Vector Tile Generation
230
231
Generate vector tile data compatible with vector tile rendering systems.
232
233
```javascript { .api }
234
/**
235
* Returns a geojson-vt-compatible tile object for given tile coordinates
236
* @param z - Zoom level
237
* @param x - Tile x coordinate
238
* @param y - Tile y coordinate
239
* @returns Tile object with features array, or null if empty
240
*/
241
getTile(z: number, x: number, y: number): TileObject | null;
242
243
interface TileObject {
244
features: TileFeature[];
245
}
246
247
interface TileFeature {
248
type: 1; // Point type
249
geometry: [[number, number]]; // Tile-space coordinates
250
tags: any; // Feature properties
251
id?: any;
252
}
253
```
254
255
**Usage Examples:**
256
257
```javascript
258
// Get tile data for zoom 10, tile coordinates (512, 384)
259
const tile = index.getTile(10, 512, 384);
260
261
if (tile) {
262
console.log(`Tile contains ${tile.features.length} features`);
263
264
// Process tile features for rendering
265
tile.features.forEach(feature => {
266
const [x, y] = feature.geometry[0]; // Tile pixel coordinates
267
const properties = feature.tags;
268
// Render feature at tile coordinates
269
});
270
}
271
```
272
273
### Cluster Navigation
274
275
Navigate cluster hierarchies by getting direct children of a cluster.
276
277
```javascript { .api }
278
/**
279
* Returns the direct children of a cluster on the next zoom level
280
* @param clusterId - Cluster ID from cluster properties
281
* @returns Array of GeoJSON Features (child clusters and points)
282
* @throws Error if cluster ID is invalid
283
*/
284
getChildren(clusterId: number): GeoJSONFeature[];
285
```
286
287
**Usage Examples:**
288
289
```javascript
290
// Get children of a specific cluster
291
const clusterId = 164; // From cluster_id property
292
const children = index.getChildren(clusterId);
293
294
console.log(`Cluster ${clusterId} has ${children.length} children`);
295
296
children.forEach(child => {
297
if (child.properties.cluster) {
298
console.log(`Child cluster with ${child.properties.point_count} points`);
299
} else {
300
console.log(`Individual point: ${child.properties.name}`);
301
}
302
});
303
```
304
305
### Point Pagination
306
307
Get individual points within a cluster with pagination support.
308
309
```javascript { .api }
310
/**
311
* Returns all individual points within a cluster with pagination
312
* @param clusterId - Cluster ID from cluster properties
313
* @param limit - Number of points to return (default: 10)
314
* @param offset - Number of points to skip (default: 0)
315
* @returns Array of GeoJSON Features (individual points only)
316
*/
317
getLeaves(clusterId: number, limit?: number, offset?: number): GeoJSONFeature[];
318
```
319
320
**Usage Examples:**
321
322
```javascript
323
// Get first 10 individual points from a cluster
324
const clusterId = 164;
325
const firstBatch = index.getLeaves(clusterId, 10, 0);
326
327
// Get next 10 points (pagination)
328
const secondBatch = index.getLeaves(clusterId, 10, 10);
329
330
// Get all points in cluster
331
const allPoints = index.getLeaves(clusterId, Infinity, 0);
332
333
console.log(`Cluster contains ${allPoints.length} total points`);
334
```
335
336
### Cluster Expansion Zoom
337
338
Determine the optimal zoom level for expanding a cluster.
339
340
```javascript { .api }
341
/**
342
* Returns the zoom level at which a cluster expands into multiple children
343
* @param clusterId - Cluster ID from cluster properties
344
* @returns Integer zoom level for cluster expansion
345
*/
346
getClusterExpansionZoom(clusterId: number): number;
347
```
348
349
**Usage Examples:**
350
351
```javascript
352
// Implement "click to zoom" functionality
353
const clusterId = 164;
354
const expansionZoom = index.getClusterExpansionZoom(clusterId);
355
356
console.log(`Zoom to level ${expansionZoom} to expand this cluster`);
357
358
// Use in map interaction
359
map.on('click', 'clusters-layer', (e) => {
360
const clusterId = e.features[0].properties.cluster_id;
361
const expansionZoom = index.getClusterExpansionZoom(clusterId);
362
363
map.easeTo({
364
center: e.lngLat,
365
zoom: expansionZoom
366
});
367
});
368
```
369
370
## Error Handling
371
372
Supercluster throws specific errors for invalid operations:
373
374
```javascript
375
try {
376
const children = index.getChildren(999999); // Invalid cluster ID
377
} catch (error) {
378
console.error(error.message); // "No cluster with the specified id."
379
}
380
381
// Graceful handling of edge cases
382
const emptyIndex = new Supercluster();
383
emptyIndex.load([]); // Handles empty arrays gracefully
384
const emptyClusters = emptyIndex.getClusters([-180, -85, 180, 85], 0); // Returns []
385
```
386
387
## Advanced Usage
388
389
### Custom Property Aggregation
390
391
```javascript
392
const index = new Supercluster({
393
map: (props) => ({
394
sum: props.value || 0,
395
categories: [props.category].filter(Boolean)
396
}),
397
reduce: (accumulated, props) => {
398
accumulated.sum += props.sum;
399
accumulated.categories = accumulated.categories.concat(props.categories);
400
}
401
});
402
403
index.load(points);
404
405
// Clusters will have aggregated properties
406
const clusters = index.getClusters(bbox, zoom);
407
clusters.forEach(cluster => {
408
if (cluster.properties.cluster) {
409
console.log(`Total value: ${cluster.properties.sum}`);
410
console.log(`Categories: ${cluster.properties.categories.join(', ')}`);
411
}
412
});
413
```
414
415
### Performance Optimization
416
417
```javascript
418
// For large datasets, tune performance parameters
419
const index = new Supercluster({
420
radius: 60, // Larger radius reduces cluster count
421
nodeSize: 128, // Larger node size for better performance with more data
422
maxZoom: 14, // Lower max zoom reduces processing levels
423
extent: 1024 // Higher extent for better precision
424
});
425
426
// Enable logging to monitor performance
427
const debugIndex = new Supercluster({ log: true });
428
debugIndex.load(millionsOfPoints); // Will log timing information
429
```
430
431
### International Dateline Handling
432
433
```javascript
434
// Supercluster automatically handles dateline crossing
435
const pacificBbox = [170, -10, -170, 10]; // Crosses dateline
436
const clusters = index.getClusters(pacificBbox, 5);
437
// Returns all relevant clusters on both sides of dateline
438
```