---
title: "Wind Layers"
description: "Animated wind particle visualization with speed-based color ramps"
canonical_url: "https://v-maplibre.geoql.in/layers/wind-layers"
---
# Wind Layers

> Animated wind particle visualization with speed-based color ramps

> See [Peer Dependencies](https://v-maplibre.geoql.in/guide/peer-dependencies) for the complete list of required packages for each component.

<callout icon="i-lucide-wind" target="_blank" to="https://mapcn-vue.geoql.in/examples/wind">

**Live Demo** - GPU-accelerated wind particle flow visualization

</callout>

## VLayerWindParticle

GPU-accelerated animated wind particle layer using deck.gl transform feedback. Visualize wind speed and direction with flowing particles colored by velocity.

### Installation

```bash
pnpm 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:

1. **Wind Data Points** - Array of `{lat, lon, speed, direction}` objects (IDW-interpolated to texture)
2. **Pre-rendered Image** - Wind velocity texture URL (for weather API outputs)

### Basic Usage

```vue
<script setup lang="ts">
  import { VMap } from '@geoql/v-maplibre';
  import { VLayerWindParticle } from '@geoql/v-maplibre/wind';

  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">
    <VLayerWindParticle
      id="wind-layer"
      :wind-data="windData"
      :num-particles="8192"
      :speed-factor="50"
    />
  </VMap>
</template>
```

### Props

<table>
<thead>
  <tr>
    <th>
      Prop
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        id
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      required
    </td>
    
    <td>
      Unique layer identifier
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        windData
      </code>
    </td>
    
    <td>
      <code>
        WindDataPoint[]
      </code>
    </td>
    
    <td>
      -
    </td>
    
    <td>
      Array of wind observations (IDW-interpolated)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        imageUrl
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      -
    </td>
    
    <td>
      Pre-rendered wind texture URL
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        bounds
      </code>
    </td>
    
    <td>
      <code>
        [number, number, number, number]
      </code>
    </td>
    
    <td>
      <code>
        [-180, -90, 180, 90]
      </code>
    </td>
    
    <td>
      Geographic bounds <span>
        west, south, east, north
      </span>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        uMin
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        -50
      </code>
    </td>
    
    <td>
      Minimum U component (only for imageUrl mode)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        uMax
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        50
      </code>
    </td>
    
    <td>
      Maximum U component (only for imageUrl mode)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        vMin
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        -50
      </code>
    </td>
    
    <td>
      Minimum V component (only for imageUrl mode)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        vMax
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        50
      </code>
    </td>
    
    <td>
      Maximum V component (only for imageUrl mode)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        numParticles
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        8192
      </code>
    </td>
    
    <td>
      Number of particles to render
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        maxAge
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        30
      </code>
    </td>
    
    <td>
      Particle lifetime in frames
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        speedFactor
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        50
      </code>
    </td>
    
    <td>
      Particle movement speed multiplier
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        color
      </code>
    </td>
    
    <td>
      <code>
        Color
      </code>
    </td>
    
    <td>
      <code>
        [255, 255, 255, 200]
      </code>
    </td>
    
    <td>
      Fallback color when colorRamp not used
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        colorRamp
      </code>
    </td>
    
    <td>
      <code>
        ColorStop[]
      </code>
    </td>
    
    <td>
      See below
    </td>
    
    <td>
      Speed-based color gradient
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        speedRange
      </code>
    </td>
    
    <td>
      <code>
        [number, number]
      </code>
    </td>
    
    <td>
      <code>
        [0, 30]
      </code>
    </td>
    
    <td>
      Speed range for color mapping (m/s)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        width
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1.5
      </code>
    </td>
    
    <td>
      Particle trail width in pixels
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        animate
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Enable particle animation
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        opacity
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      Layer opacity (0-1)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        visible
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Layer visibility
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pickable
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Enable picking
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        beforeId
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      -
    </td>
    
    <td>
      Insert layer before this layer ID
    </td>
  </tr>
</tbody>
</table>

### Default Color Ramp

```typescript
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

<table>
<thead>
  <tr>
    <th>
      Event
    </th>
    
    <th>
      Payload
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        @loaded
      </code>
    </td>
    
    <td>
      -
    </td>
    
    <td>
      Layer initialized and ready
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        @error
      </code>
    </td>
    
    <td>
      <code>
        Error
      </code>
    </td>
    
    <td>
      Error during initialization
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        @click
      </code>
    </td>
    
    <td>
      <code>
        PickingInfo
      </code>
    </td>
    
    <td>
      Clicked on layer
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        @hover
      </code>
    </td>
    
    <td>
      <code>
        PickingInfo
      </code>
    </td>
    
    <td>
      Hovering over layer
    </td>
  </tr>
</tbody>
</table>

### WindDataPoint Type

```typescript
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

```vue
<VLayerWindParticle
  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:

```vue
<VLayerWindParticle
  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

```vue
<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">
    <VLayerWindParticle
      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

1. **IDW Interpolation**: When using `windData`, the component generates a wind velocity texture using Inverse Distance Weighting interpolation, creating smooth gradients between observation points.
2. **Transform Feedback**: Particles are animated using GPU transform feedback shaders. Each particle reads the wind velocity at its position and moves accordingly.
3. **Color Mapping**: Particle colors are determined by the wind speed at their position, mapped through the `colorRamp` gradient.
4. **Particle Lifecycle**: Each particle has a maximum age (`maxAge`). When a particle expires, it's respawned at a random position within the bounds.


## Sitemap

See the full [sitemap](https://v-maplibre.geoql.in/sitemap.md) for all pages.
