Recreating the wind showcase
The reusable wind layers and the historical showcase are experimental. GPU particle advection has
been independently verified on WebGL2 and WebGPU, along with filled wind arrows and station
surfaces. Image-derived mountain terrain still depends on upstream TerrainLayer support, so the
WebGPU showcase substitutes the station-triangulated surface.
The Wind Map restores Nicolas Belmonte's original deck.gl wind showcase using reusable, publicly imported community layers and the original United States station forecast.
Install
npm install @deck.gl/core @deck.gl-community/geo-layers
Load and index the forecast once
import {
createWindField,
parseWindData,
type WindField,
type WindStation
} from '@deck.gl-community/geo-layers';
const dataUrl =
'https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/wind';
async function loadWindField(): Promise<WindField> {
const [stations, weather] = await Promise.all([
fetch(`${dataUrl}/stations.json`).then(response => response.json()) as Promise<WindStation[]>,
fetch(`${dataUrl}/weather.bin`).then(response => response.arrayBuffer())
]);
return createWindField(stations, parseWindData(weather, stations.length));
}
const windField = await loadWindField();
The forecast contains 72 hourly frames. Station longitudes use the original positive-west format;
createWindField converts them into deck.gl geographic coordinates and computes a robust Delaunay
triangulation once.
Render terrain, arrows, and animated particles
import {
AmbientLight,
Deck,
DirectionalLight,
LightingEffect,
MapView
} from '@deck.gl/core';
import {
ElevationLayer,
ParticleLayer,
WindLayer,
type WindField
} from '@deck.gl-community/geo-layers';
function mountWindMap(container: HTMLElement, windField: WindField): () => void {
const terrain = new ElevationLayer({
id: 'wind-terrain',
elevationData: `${dataUrl}/elevation.png`,
bounds: [-125, 24.4, -66.7, 49.6],
elevationRange: [-100, 4126],
elevationScale: 24,
meshMaxError: 12
});
const lighting = new LightingEffect({
ambient: new AmbientLight({color: [194, 210, 235], intensity: 0.7}),
sunlight: new DirectionalLight({
color: [255, 226, 198],
intensity: 1.15,
direction: [-1, -2, -2]
})
});
const deck = new Deck({
parent: container,
views: new MapView({repeat: false}),
initialViewState: {
longitude: -98.319,
latitude: 37.614,
zoom: 4.05,
pitch: 52,
maxPitch: 85
},
controller: {dragRotate: true, touchRotate: true},
effects: [lighting]
});
let animationFrame = 0;
let lastTimestamp = 0;
let lastArrowUpdate = -Infinity;
let time = 0;
let arrows: WindLayer | undefined;
function animate(timestamp: number): void {
const elapsed = lastTimestamp ? Math.min(timestamp - lastTimestamp, 100) : 0;
lastTimestamp = timestamp;
time += elapsed / 1800;
if (timestamp - lastArrowUpdate >= 250) {
arrows = new WindLayer({
id: 'wind-arrows',
windField,
time,
gridWidth: 40,
gridHeight: 22,
speedScale: 1.8,
elevationScale: 24,
surfaceOffset: 1200
});
lastArrowUpdate = timestamp;
}
deck.setProps({
layers: [
terrain,
arrows,
new ParticleLayer({
id: 'wind-particles',
windField,
time,
numParticles: 100_000,
speedScale: 0.16,
elevationScale: 24,
surfaceOffset: 1700,
pointRadiusPixels: 0.7,
color: [186, 233, 223, 34]
})
]
});
animationFrame = requestAnimationFrame(animate);
}
animationFrame = requestAnimationFrame(animate);
return () => {
cancelAnimationFrame(animationFrame);
deck.finalize();
};
}
const unmount = mountWindMap(document.querySelector<HTMLElement>('#app')!, windField);
// Call unmount() before removing the containing element.
Keep the windField, terrain layer, particle layer id, and graphics device stable. deck.gl
transfers particle simulation state to each new ParticleLayer instance; only its time advances.
Update the station-sampled arrows less frequently than the GPU particle frame to avoid repeating
CPU interpolation every frame.
Smooth terrain versus the station mesh
ElevationLayer creates the actual mountain terrain from the original elevation.png. The full
showcase applies two separable Gaussian smoothing passes to that image before displaying it, then
uses elevationScale: 24 and meshMaxError: 12. This preserves broad mountain ridges without
turning the weather-station Delaunay triangles into jagged terrain.
DelaunayCoverLayer visualizes the weather station mesh, not the elevation image. Enable it
when debugging interpolation coverage; do not replace the mountain terrain with it.
Particle density and performance
The example starts with 100_000 particles and provides a debounced slider from 1_000 to
1_000_000. WebGL2 advances GPU-resident particle buffers using transform feedback and renders
single-vertex point primitives. WebGPU advances them with a WGSL compute shader and renders native
GPU-buffer-backed point primitives. Neither production animation path reads particle positions
back to the CPU.
Weather textures are cached, static terrain is retained, arrow resampling is throttled, and high-density rendering favors particle heads over additional line geometry. Changing the particle count necessarily allocates a new simulation, so debounce density controls rather than rebuilding buffers on every slider event.
Backend compatibility
| Component | WebGL2 | WebGPU | Notes |
|---|---|---|---|
ParticleLayer | Supported | Supported | Independently browser-tested GPU advection and rendering. |
Wind data and DelaunayInterpolation | Supported | Supported | Backend-independent indexing and explicit sampling. |
WindLayer | Supported | Supported | Native GLSL/WGSL filled arrows and portable line shafts. |
ElevationLayer | Supported | Blocked | Image-derived mountain terrain depends on upstream TerrainLayer. |
DelaunayCoverLayer | Supported | Supported | Native GLSL/WGSL station-triangulated surface. |
| Complete original showcase | Supported | In progress | WebGPU renders state boundaries and uses station terrain while image-derived mountains remain blocked. |
See the full WebGPU compatibility matrix.
Run the repository example
yarn
yarn workspace wind-layer-example start
The standalone example is in examples/geo-layers/wind. The website mounts the same named
mountWindExample implementation for the Wind Map and the inline
wind-layer documentation.