Layers
Wind Layers
Animated wind particle visualization with speed-based color ramps
VLayerDeckglWindParticle
GPU-accelerated animated wind particle layer using deck.gl transform feedback. Visualize wind speed and direction with flowing particles colored by velocity.
Installation
bun add maplibre-gl-wind
Features
- GPU-accelerated particle animation via transform feedback
- Speed-based color ramps for wind intensity visualization
- IDW interpolation from sparse data points to full-coverage texture
- Pre-rendered wind texture support (e.g., from weather APIs)
- Configurable particle count, lifetime, and speed scaling
Data Sources
The wind layer supports two input modes:
- Wind Data Points - Array of
{lat, lon, speed, direction}objects (IDW-interpolated to texture) - Pre-rendered Image - Wind velocity texture URL (for weather API outputs)
Basic Usage
<script setup lang="ts">
import { VMap, VLayerDeckglWindParticle } from '@geoql/v-maplibre';
const mapOptions = {
style: 'https://basemaps.cartocdn.com/gl/dark-matter-nolabels-gl-style/style.json',
center: [0, 20],
zoom: 2,
};
// Wind data: speed in m/s, direction in degrees (0 = North, 90 = East)
const windData = [
{ lat: 40.7128, lon: -74.006, speed: 5.2, direction: 180 },
{ lat: 34.0522, lon: -118.2437, speed: 3.1, direction: 270 },
{ lat: 51.5074, lon: -0.1278, speed: 8.5, direction: 225 },
{ lat: 35.6762, lon: 139.6503, speed: 4.8, direction: 90 },
{ lat: -33.8688, lon: 151.2093, speed: 6.2, direction: 315 },
];
</script>
<template>
<VMap :options="mapOptions" style="height: 500px">
<VLayerDeckglWindParticle
id="wind-layer"
:wind-data="windData"
:num-particles="8192"
:speed-factor="50"
/>
</VMap>
</template>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
id | string | required | Unique layer identifier |
windData | WindDataPoint[] | - | Array of wind observations (IDW-interpolated) |
imageUrl | string | - | Pre-rendered wind texture URL |
bounds | [number, number, number, number] | [-180, -90, 180, 90] | Geographic bounds west, south, east, north |
uMin | number | -50 | Minimum U component (only for imageUrl mode) |
uMax | number | 50 | Maximum U component (only for imageUrl mode) |
vMin | number | -50 | Minimum V component (only for imageUrl mode) |
vMax | number | 50 | Maximum V component (only for imageUrl mode) |
numParticles | number | 8192 | Number of particles to render |
maxAge | number | 30 | Particle lifetime in frames |
speedFactor | number | 50 | Particle movement speed multiplier |
color | Color | [255, 255, 255, 200] | Fallback color when colorRamp not used |
colorRamp | ColorStop[] | See below | Speed-based color gradient |
speedRange | [number, number] | [0, 30] | Speed range for color mapping (m/s) |
width | number | 1.5 | Particle trail width in pixels |
animate | boolean | true | Enable particle animation |
opacity | number | 1 | Layer opacity (0-1) |
visible | boolean | true | Layer visibility |
pickable | boolean | false | Enable picking |
beforeId | string | - | Insert layer before this layer ID |
Default Color Ramp
const defaultColorRamp = [
[0.0, [59, 130, 189, 255]], // Blue - calm
[0.1, [102, 194, 165, 255]], // Teal
[0.2, [171, 221, 164, 255]], // Light green
[0.3, [230, 245, 152, 255]], // Yellow-green
[0.4, [254, 224, 139, 255]], // Yellow
[0.5, [253, 174, 97, 255]], // Orange
[0.6, [244, 109, 67, 255]], // Red-orange
[1.0, [213, 62, 79, 255]], // Red - strong
];
Events
| Event | Payload | Description |
|---|---|---|
@loaded | - | Layer initialized and ready |
@error | Error | Error during initialization |
@click | PickingInfo | Clicked on layer |
@hover | PickingInfo | Hovering over layer |
WindDataPoint Type
interface WindDataPoint {
lat: number; // Latitude (-90 to 90)
lon: number; // Longitude (-180 to 180)
speed: number; // Wind speed in m/s
direction: number; // Wind direction in degrees (0 = North, clockwise)
}
Custom Color Ramp
<VLayerDeckglWindParticle
id="wind"
:wind-data="windData"
:color-ramp="[
[0.0, [65, 105, 225, 255]], // Royal blue - calm
[0.3, [50, 205, 50, 255]], // Lime green - moderate
[0.6, [255, 165, 0, 255]], // Orange - strong
[1.0, [255, 0, 0, 255]], // Red - extreme
]"
:speed-range="[0, 40]"
/>
Pre-rendered Wind Texture
For weather API outputs that provide pre-rendered wind textures:
<VLayerDeckglWindParticle
id="wind"
image-url="https://example.com/wind-texture.png"
:bounds="[-180, -90, 180, 90]"
:u-min="-50"
:u-max="50"
:v-min="-50"
:v-max="50"
/>
The texture encodes U/V wind components in the red and green channels respectively.
Real-time Wind Data
<script setup lang="ts">
import { ref, onMounted } from 'vue';
const windData = ref([]);
const fetchWindData = async () => {
// Fetch from your weather API
const response = await fetch('/api/wind-observations');
windData.value = await response.json();
};
onMounted(fetchWindData);
// Refresh every 5 minutes
setInterval(fetchWindData, 5 * 60 * 1000);
</script>
<template>
<VMap :options="mapOptions" style="height: 500px">
<VLayerDeckglWindParticle
v-if="windData.length > 0"
id="wind"
:wind-data="windData"
:num-particles="16384"
:speed-factor="60"
/>
</VMap>
</template>
Performance Tips
- Particle Count: Start with 8192 particles. Increase to 16384+ for denser coverage on large screens.
- Max Age: Lower values (15-20) create shorter trails, higher values (40-60) create longer flowing trails.
- Speed Factor: Adjust based on your speed range. Higher factors make particles move faster.
- Animation: Set
:animate="false"to pause animation (useful for static screenshots).
How It Works
- IDW Interpolation: When using
windData, the component generates a wind velocity texture using Inverse Distance Weighting interpolation, creating smooth gradients between observation points. - Transform Feedback: Particles are animated using GPU transform feedback shaders. Each particle reads the wind velocity at its position and moves accordingly.
- Color Mapping: Particle colors are determined by the wind speed at their position, mapped through the
colorRampgradient. - Particle Lifecycle: Each particle has a maximum age (
maxAge). When a particle expires, it's respawned at a random position within the bounds.