High-Performance Point Clustering
The ClusterController module allows rendering tens of thousands of spatial coordinates smoothly
at 60 FPS by leveraging the Supercluster spatial index.
Key Highlights
- Massive datasets: handles 10,000+ points dynamically, indexed in under 15ms.
- Custom badges:
badgeRendererreturns a style object (color, radius, and optional border) based on cluster count, or a custom weighted value. - Weighted clustering: aggregate a custom numeric property across clustered points instead of just counting them.
- Safe dataset updates:
setPoints()swaps or refreshes coordinates using MapLibre'ssetDataunder the hood, preventing canvas flicker, duplicate layers, or broken Supercluster state.
Default: Count-Based Clustering
By default, ClusterController aggregates based on the native feature count (point_count), and
badgeRenderer receives that count:
import { ClusterController } from '@sovereignsolutions/ss-map-gl';
const clusterController = new ClusterController(mapEngine, {
clusterRadius: 50,
clusterMaxZoom: 14,
badgeRenderer: (count) => {
// badgeRenderer receives the count of points in the cluster
if (count < 100) return { color: '#6366f1', radius: 18 };
if (count < 500) return { color: '#3b82f6', radius: 24 };
return { color: '#ef4444', radius: 32, borderWidth: 3, borderColor: '#ffffff' };
},
});
Custom Weighting
Cluster based on the sum of a custom property instead of point count with clusterValueProperty
and clusterProperties:
import { ClusterController } from '@sovereignsolutions/ss-map-gl';
const weightedClusterController = new ClusterController(mapEngine, {
clusterRadius: 50,
clusterMaxZoom: 14,
clusterValueProperty: 'total_value',
clusterProperties: {
// Sum the custom "value" of clustered pins, falling back to 1 if missing
total_value: ['+', ['coalesce', ['get', 'value'], 1]],
},
badgeRenderer: (value) => {
// badgeRenderer receives the custom total_value sum
if (value < 1000) return { color: '#6366f1', radius: 18 };
return { color: '#ef4444', radius: 32 };
},
});
Cluster Data Format
Input points for the clustering engine use the following format:
interface ClusterPoint {
coordinates: [number, number]; // [longitude, latitude]
value?: number; // Optional custom value/weight property
properties?: Record<string, any>; // Optional user-defined properties
}
Dataset Updates
Consume the safe point update API to dynamically swap or refresh coordinates. This operation uses
MapLibre's setData under the hood, preventing canvas flicker, duplicate layers, or broken
Supercluster state:
// Replace point dataset safely (displays count by default)
clusterController.setPoints([
{ coordinates: [78.96, 20.59] },
{ coordinates: [79.00, 20.60] },
]);
Cleanup
Source: Cleanup & Unmount Behavior in the SDK README.
// Unmount Cluster Controller layers and event listeners
clusterController.destroy();
Performance & QA Evidence
Verified with a 10,000+ coordinate dataset on Chrome 126.0, Windows 11 Enterprise (Intel Core i7, 32GB RAM):
- Point indexing via Supercluster finishes in under 15ms.
- Frame rates remain at a solid 60 FPS during map pans and zooms.
- Tapping a cluster produces a smooth
easeTotransition (duration 800ms) into the expanded view. - Calling
.destroy()and unmounting is confirmed (via browser devtools) to fully reclaim map memory - no leaked layers or listeners.
See also: GeoJSON Import & Export.