or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

tessl/npm-supercluster

A very fast geospatial point clustering library for browsers and Node.js environments.

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/supercluster@8.0.x

To install, run

npx @tessl/cli install tessl/npm-supercluster@8.0.0

0

# 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

```