A high-performance wind particle visualization layer for deck.gl. Renders animated wind flow particles with speed-based coloring using GPU transform feedback.
Based on Az's deck.gl 9.x particle layer implementation.
- GPU-accelerated particle animation using transform feedback
- Speed-based color ramps (Windy.com style)
- Configurable particle count, speed, and lifetime
- IDW interpolation for wind data points
- Works with deck.gl 9.x and MapLibre GL JS
# npm
npm install maplibre-gl-wind @deck.gl/core @deck.gl/layers
# bun
bun add maplibre-gl-wind @deck.gl/core @deck.gl/layers
# JSR
bunx jsr add @geoql/maplibre-gl-windimport { Deck } from '@deck.gl/core';
import { WindParticleLayer } from 'maplibre-gl-wind';
const deck = new Deck({
initialViewState: {
longitude: 0,
latitude: 20,
zoom: 2,
},
controller: true,
layers: [
new WindParticleLayer({
id: 'wind',
image: 'path/to/wind-texture.png',
bounds: [-180, -90, 180, 90],
imageUnscale: [-50, 50],
numParticles: 8192,
maxAge: 50,
speedFactor: 50,
colorRamp: [
[0.0, [59, 130, 189, 255]],
[0.5, [253, 174, 97, 255]],
[1.0, [213, 62, 79, 255]],
],
speedRange: [0, 30],
}),
],
});import { WindParticleLayer, generateWindTexture } from 'maplibre-gl-wind';
const windData = [
{ lat: 40.7, lon: -74.0, speed: 5.2, direction: 180 },
{ lat: 34.0, lon: -118.2, speed: 3.1, direction: 270 },
// ... more points
];
const { canvas, uMin, uMax, vMin, vMax } = generateWindTexture(windData, {
width: 360,
height: 180,
bounds: [-180, -90, 180, 90],
});
const layer = new WindParticleLayer({
id: 'wind',
image: canvas.toDataURL(),
bounds: [-180, -90, 180, 90],
imageUnscale: [Math.min(uMin, vMin), Math.max(uMax, vMax)],
numParticles: 8192,
colorRamp: [
[0.0, [59, 130, 189, 255]],
[1.0, [213, 62, 79, 255]],
],
});import maplibregl from 'maplibre-gl';
import { MapboxOverlay } from '@deck.gl/mapbox';
import { WindParticleLayer } from 'maplibre-gl-wind';
const map = new maplibregl.Map({
container: 'map',
style: 'https://demotiles.maplibre.org/style.json',
center: [0, 20],
zoom: 2,
});
map.on('load', () => {
const overlay = new MapboxOverlay({
layers: [
new WindParticleLayer({
id: 'wind',
image: 'path/to/wind-texture.png',
bounds: [-180, -90, 180, 90],
imageUnscale: [-50, 50],
numParticles: 8192,
}),
],
});
map.addControl(overlay);
});| Option | Type | Default | Description |
|---|---|---|---|
image |
string |
- | URL or data URL of wind texture |
bounds |
number[] |
[-180, -90, 180, 90] |
Geographic bounds [west, south, east, north] |
imageUnscale |
number[] |
[0, 0] |
Wind velocity range [min, max] |
numParticles |
number |
8192 |
Number of particles |
maxAge |
number |
50 |
Particle lifetime in frames |
speedFactor |
number |
50 |
Particle speed multiplier |
colorRamp |
ColorStop[] |
Blue to red gradient | Speed-based color stops [position, color] |
speedRange |
number[] |
[0, 30] |
Speed range for color mapping [min, max] |
width |
number |
1.5 |
Particle trail width |
animate |
boolean |
true |
Enable animation |
Converts wind data points to a texture using IDW interpolation.
function generateWindTexture(
windData: WindDataPoint[],
options?: {
width?: number;
height?: number;
bounds?: [number, number, number, number];
power?: number;
},
): WindTextureResult;Helper to convert OpenWeatherMap API responses to wind data points.
function createWindDataFromOpenWeatherMap(
responses: Array<{
coord: { lat: number; lon: number };
wind?: { speed?: number; deg?: number };
}>,
): WindDataPoint[];Wind textures encode U (east-west) and V (north-south) velocity components:
- R channel: U component (normalized 0-255)
- G channel: V component (normalized 0-255)
- A channel: Should be 255 for valid data
- Node.js >= 24.0.0
- deck.gl >= 9.0.0
- Fork and create a feature branch from
main - Make changes following conventional commits
- Ensure commits are signed
- Submit a PR
bun install
bun run build
bun run lint
bun run typecheckMIT © Vinayak Kulkarni
- Az's deck.gl particle layer - Original implementation
- deck.gl - WebGL visualization framework
- luma.gl - WebGL2 engine