or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

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

index.mddocs/

Supercluster

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.

Package Information

  • Package Name: supercluster
  • Package Type: npm
  • Language: JavaScript (ES2020)
  • Installation: npm install supercluster

Core Imports

import Supercluster from 'supercluster';

For CommonJS:

const Supercluster = require('supercluster');

CDN/Browser (ES Module):

import Supercluster from 'https://esm.run/supercluster';

Script tag (UMD):

<script src="https://unpkg.com/supercluster@8.0.1/dist/supercluster.min.js"></script>
<!-- Creates global Supercluster variable -->

Basic Usage

import Supercluster from 'supercluster';

// Create clustering index with default options
const index = new Supercluster({
  radius: 40,
  maxZoom: 16,
  minPoints: 2
});

// Load GeoJSON Point features
const points = [
  {
    type: 'Feature',
    properties: { name: 'Location A' },
    geometry: {
      type: 'Point',
      coordinates: [-73.97, 40.77] // [longitude, latitude]
    }
  },
  {
    type: 'Feature',
    properties: { name: 'Location B' },
    geometry: {
      type: 'Point',
      coordinates: [-73.96, 40.78]
    }
  }
];

// Build the clustering index
index.load(points);

// Get clusters and points for a bounding box and zoom level
const clusters = index.getClusters([-74.0, 40.7, -73.9, 40.8], 10);

Architecture

Supercluster is built around several key components:

  • Spatial Indexing: Uses KDBush for efficient spatial queries and range searches
  • Hierarchical Clustering: Multi-zoom level clustering that builds a hierarchy from max zoom to min zoom
  • Memory Optimization: Flat numeric arrays for internal data storage to minimize memory footprint
  • Vector Tile Support: Compatible with vector tile rendering pipelines through getTile method
  • Property Aggregation: Configurable map/reduce functions for custom cluster properties

Capabilities

Clustering Index Creation

Create and configure a new Supercluster instance with customizable clustering behavior.

/**
 * Creates a new Supercluster instance
 * @param options - Configuration options for clustering behavior
 */
constructor(options?: SuperclusterOptions);

interface SuperclusterOptions {
  /** Minimum zoom level at which clusters are generated (default: 0) */
  minZoom?: number;
  /** Maximum zoom level at which clusters are generated (default: 16) */
  maxZoom?: number;
  /** Minimum number of points to form a cluster (default: 2) */
  minPoints?: number;
  /** Cluster radius in pixels (default: 40) */
  radius?: number;
  /** Tile extent - radius is calculated relative to this value (default: 512) */
  extent?: number;
  /** Size of the KD-tree leaf node, affects performance (default: 64) */
  nodeSize?: number;
  /** Whether to log timing info (default: false) */
  log?: boolean;
  /** Whether to generate numeric ids for input features in vector tiles (default: false) */
  generateId?: boolean;
  /** Function that returns cluster properties corresponding to a single point (default: props => props) */
  map?: (props: any) => any;
  /** Function that merges properties of two clusters into one (default: null) */
  reduce?: (accumulated: any, props: any) => void;
}

Usage Examples:

// Basic clustering with default options
const index = new Supercluster();

// Custom clustering configuration
const index = new Supercluster({
  radius: 60,        // Larger cluster radius
  maxZoom: 14,       // Stop clustering at zoom 14
  minPoints: 5,      // Require at least 5 points to form cluster
  extent: 256        // Use 256-pixel tile extent
});

// With property aggregation
const index = new Supercluster({
  map: (props) => ({ sum: props.value }),
  reduce: (accumulated, props) => { accumulated.sum += props.sum; }
});

Data Loading

Load GeoJSON Point features into the clustering index.

/**
 * Loads an array of GeoJSON Point features and builds the clustering index
 * @param points - Array of GeoJSON Feature objects with Point geometry
 * @returns The Supercluster instance for chaining
 */
load(points: GeoJSONFeature[]): Supercluster;

interface GeoJSONFeature {
  type: 'Feature';
  properties: any;
  geometry: {
    type: 'Point';
    coordinates: [number, number]; // [longitude, latitude]
  };
  id?: any;
}

Usage Examples:

const points = [
  {
    type: 'Feature',
    properties: { name: 'Coffee Shop', category: 'restaurant' },
    geometry: { type: 'Point', coordinates: [-73.97, 40.77] }
  },
  {
    type: 'Feature',
    properties: { name: 'Park', category: 'recreation' },
    geometry: { type: 'Point', coordinates: [-73.96, 40.78] }
  }
];

index.load(points);

Cluster and Point Retrieval

Get clusters and individual points within a geographic bounding box at a specific zoom level.

/**
 * Returns clusters and points within a bounding box at a given zoom level
 * @param bbox - Bounding box as [westLng, southLat, eastLng, northLat]
 * @param zoom - Integer zoom level
 * @returns Array of GeoJSON Features (clusters and individual points)
 */
getClusters(bbox: [number, number, number, number], zoom: number): GeoJSONFeature[];

interface ClusterFeature extends GeoJSONFeature {
  properties: {
    cluster: true;
    cluster_id: number;
    point_count: number;
    point_count_abbreviated: string;
    [key: string]: any; // Custom properties from reduce function
  };
}

Usage Examples:

// Get all clusters/points visible in New York area at zoom level 10
const bbox = [-74.1, 40.6, -73.8, 40.9];
const clusters = index.getClusters(bbox, 10);

// Handle clusters vs individual points
clusters.forEach(feature => {
  if (feature.properties.cluster) {
    console.log(`Cluster with ${feature.properties.point_count} points`);
  } else {
    console.log(`Individual point: ${feature.properties.name}`);
  }
});

// World-wide view at low zoom
const worldClusters = index.getClusters([-180, -85, 180, 85], 2);

Vector Tile Generation

Generate vector tile data compatible with vector tile rendering systems.

/**
 * Returns a geojson-vt-compatible tile object for given tile coordinates
 * @param z - Zoom level
 * @param x - Tile x coordinate
 * @param y - Tile y coordinate  
 * @returns Tile object with features array, or null if empty
 */
getTile(z: number, x: number, y: number): TileObject | null;

interface TileObject {
  features: TileFeature[];
}

interface TileFeature {
  type: 1; // Point type
  geometry: [[number, number]]; // Tile-space coordinates
  tags: any; // Feature properties
  id?: any;
}

Usage Examples:

// Get tile data for zoom 10, tile coordinates (512, 384)
const tile = index.getTile(10, 512, 384);

if (tile) {
  console.log(`Tile contains ${tile.features.length} features`);
  
  // Process tile features for rendering
  tile.features.forEach(feature => {
    const [x, y] = feature.geometry[0]; // Tile pixel coordinates
    const properties = feature.tags;
    // Render feature at tile coordinates
  });
}

Cluster Navigation

Navigate cluster hierarchies by getting direct children of a cluster.

/**
 * Returns the direct children of a cluster on the next zoom level
 * @param clusterId - Cluster ID from cluster properties
 * @returns Array of GeoJSON Features (child clusters and points)
 * @throws Error if cluster ID is invalid
 */
getChildren(clusterId: number): GeoJSONFeature[];

Usage Examples:

// Get children of a specific cluster
const clusterId = 164; // From cluster_id property
const children = index.getChildren(clusterId);

console.log(`Cluster ${clusterId} has ${children.length} children`);

children.forEach(child => {
  if (child.properties.cluster) {
    console.log(`Child cluster with ${child.properties.point_count} points`);
  } else {
    console.log(`Individual point: ${child.properties.name}`);
  }
});

Point Pagination

Get individual points within a cluster with pagination support.

/**
 * Returns all individual points within a cluster with pagination
 * @param clusterId - Cluster ID from cluster properties
 * @param limit - Number of points to return (default: 10)
 * @param offset - Number of points to skip (default: 0)
 * @returns Array of GeoJSON Features (individual points only)
 */
getLeaves(clusterId: number, limit?: number, offset?: number): GeoJSONFeature[];

Usage Examples:

// Get first 10 individual points from a cluster
const clusterId = 164;
const firstBatch = index.getLeaves(clusterId, 10, 0);

// Get next 10 points (pagination)
const secondBatch = index.getLeaves(clusterId, 10, 10);

// Get all points in cluster
const allPoints = index.getLeaves(clusterId, Infinity, 0);

console.log(`Cluster contains ${allPoints.length} total points`);

Cluster Expansion Zoom

Determine the optimal zoom level for expanding a cluster.

/**
 * Returns the zoom level at which a cluster expands into multiple children
 * @param clusterId - Cluster ID from cluster properties
 * @returns Integer zoom level for cluster expansion
 */
getClusterExpansionZoom(clusterId: number): number;

Usage Examples:

// Implement "click to zoom" functionality
const clusterId = 164;
const expansionZoom = index.getClusterExpansionZoom(clusterId);

console.log(`Zoom to level ${expansionZoom} to expand this cluster`);

// Use in map interaction
map.on('click', 'clusters-layer', (e) => {
  const clusterId = e.features[0].properties.cluster_id;
  const expansionZoom = index.getClusterExpansionZoom(clusterId);
  
  map.easeTo({
    center: e.lngLat,
    zoom: expansionZoom
  });
});

Error Handling

Supercluster throws specific errors for invalid operations:

try {
  const children = index.getChildren(999999); // Invalid cluster ID
} catch (error) {
  console.error(error.message); // "No cluster with the specified id."
}

// Graceful handling of edge cases
const emptyIndex = new Supercluster();
emptyIndex.load([]); // Handles empty arrays gracefully
const emptyClusters = emptyIndex.getClusters([-180, -85, 180, 85], 0); // Returns []

Advanced Usage

Custom Property Aggregation

const index = new Supercluster({
  map: (props) => ({
    sum: props.value || 0,
    categories: [props.category].filter(Boolean)
  }),
  reduce: (accumulated, props) => {
    accumulated.sum += props.sum;
    accumulated.categories = accumulated.categories.concat(props.categories);
  }
});

index.load(points);

// Clusters will have aggregated properties
const clusters = index.getClusters(bbox, zoom);
clusters.forEach(cluster => {
  if (cluster.properties.cluster) {
    console.log(`Total value: ${cluster.properties.sum}`);
    console.log(`Categories: ${cluster.properties.categories.join(', ')}`);
  }
});

Performance Optimization

// For large datasets, tune performance parameters
const index = new Supercluster({
  radius: 60,       // Larger radius reduces cluster count
  nodeSize: 128,    // Larger node size for better performance with more data
  maxZoom: 14,      // Lower max zoom reduces processing levels
  extent: 1024      // Higher extent for better precision
});

// Enable logging to monitor performance
const debugIndex = new Supercluster({ log: true });
debugIndex.load(millionsOfPoints); // Will log timing information

International Dateline Handling

// Supercluster automatically handles dateline crossing
const pacificBbox = [170, -10, -170, 10]; // Crosses dateline
const clusters = index.getClusters(pacificBbox, 5);
// Returns all relevant clusters on both sides of dateline