All examplessymbologygpu-clustering
§ examples · symbology

GPU Clustering

5 000 random points aggregated through `GpuClusterLayer`. Cluster bucketing runs on CPU (grid-hash) with GPU-side rendering; radius + theme swap at runtime.

sluggpu-clustering
source95 lines
statuslive
tsexamples-src/gpu-clustering.ts
1/**
2 * GPU Clustering — 5 000 random points in the eastern Mediterranean
3 * wrapped in a GpuClusterLayer. The source feature layer is untouched;
4 * the cluster layer re-bucketizes as you zoom in and out, rendering
5 * aggregated counts until individual points are far enough apart.
6 */
7 
8import { MapView } from "mapgpu";
9import { GeoJSONLayer, GpuClusterLayer, RasterTileLayer } from "mapgpu/layers";
10import { RenderEngine } from "mapgpu/render";
11 
12import type { RunResultObject } from "@/components/examples/ExampleCanvas";
13 
14// Pseudo-random points in a bbox so we have a dense cluster surface.
15function randomPoints(bbox: [number, number, number, number], n: number) {
16 const [minLon, minLat, maxLon, maxLat] = bbox;
17 const features: unknown[] = [];
18 for (let i = 0; i < n; i++) {
19 const lon = minLon + Math.random() * (maxLon - minLon);
20 const lat = minLat + Math.random() * (maxLat - minLat);
21 features.push({
22 type: "Feature",
23 properties: { id: i },
24 geometry: { type: "Point", coordinates: [lon, lat] },
25 });
26 }
27 return { type: "FeatureCollection" as const, features } as unknown as {
28 type: "FeatureCollection";
29 features: never[];
30 };
31}
32 
33export async function run(container: HTMLElement): Promise<RunResultObject> {
34 const view = new MapView({
35 container,
36 renderEngine: new RenderEngine(),
37 mode: "2d",
38 center: [30, 37],
39 zoom: 5,
40 minZoom: 2,
41 maxZoom: 18,
42 backgroundColor: "transparent",
43 });
44 
45 view.map.add(
46 new RasterTileLayer({
47 id: "carto-dark",
48 urlTemplate: "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png",
49 subdomains: ["a", "b", "c", "d"],
50 maxZoom: 19,
51 attribution: "© CARTO · © OpenStreetMap contributors",
52 }),
53 );
54 
55 // Source data — a GeoJSONLayer acts as the feed the cluster layer
56 // reads from. We keep it non-visible; only the cluster layer renders.
57 const source = new GeoJSONLayer({
58 id: "pts-source",
59 data: randomPoints([25, 34, 38, 42], 5000),
60 visible: false,
61 });
62 view.map.add(source);
63 
64 const cluster = new GpuClusterLayer({
65 id: "cluster",
66 source,
67 clusterRadius: 60,
68 clusterMinPoints: 3,
69 themePreset: "ref-dark-cyan",
70 });
71 view.map.add(cluster);
72 
73 await view.when();
74 
75 return {
76 dispose: () => view.destroy(),
77 controls: [
78 {
79 kind: "segmented",
80 id: "theme",
81 initial: "ref-dark-cyan",
82 options: [
83 { value: "ref-dark-cyan", label: "Cyan" },
84 { value: "legacy-orange", label: "Orange" },
85 ],
86 onChange: (value) => {
87 // Cluster theme hot-swap via the public style setter.
88 (cluster as unknown as { themePreset: string }).themePreset = value;
89 cluster.refresh();
90 },
91 },
92 ],
93 };
94}