CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-supercluster

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

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

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
Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/supercluster@8.0.x
Publish Source
CLI
Badge
tessl/npm-supercluster badge