---
title: "Weather Plus wind arrow layer animated | Weather SDK | Weather plus | JavaScript maps SDK"
description: "Animate wind arrows and view cursor-specific wind speed data."
---

# Weather Plus wind arrow layer animated

Explore the weather wind arrow layer from the [MapTiler Weather Plus](https://www.maptiler.com/weather/) Wind dataset and get the wind speed at the point under the cursor. View the wind animation up to 60 frames per second.

To display data for the next four days in an animated format, you can use the layer’s `animateByFactor` function. In the given example, we also demonstrate the inclusion of a time control feature, which allows users to choose a specific date and time to view the corresponding data.

### NPM module setup

```bash
npm install --save @maptiler/sdk @maptiler/weather-plus
```

#### main.js

```js
import { Map, MapStyle, config } from '@maptiler/sdk';
import '@maptiler/sdk/dist/maptiler-sdk.css';
import { WindArrowLayer } from '@maptiler/weather-plus';

config.apiKey = 'YOUR_MAPTILER_API_KEY_HERE';
const map = (window.map = new Map({
  container: 'map', // container's id or the HTML element to render the map
  style: MapStyle.BACKDROP,
  projectionControl: true,
  projection: 'globe',
  zoom: 2,
  center: [0, 40]
}));

const timeInfoContainer = document.getElementById("time-info");
const timeTextDiv = document.getElementById("time-text");
const timeSlider = document.getElementById("time-slider");
const playPauseButton = document.getElementById("play-pause-bt");
const pointerDataDiv = document.getElementById("pointer-data");
let pointerLngLat = null;

const weatherLayer = new WindArrowLayer({
  opacity: 0.8,
});
map.on('load', function () {
  map.setPaintProperty("Water", 'fill-color', "rgba(0, 0, 0, 0.4)");
  map.addLayer(weatherLayer, 'Water');
});

timeSlider.addEventListener("input", (evt) => {
  weatherLayer.setAnimationTime(parseInt(timeSlider.value / 1000))
});

// Event called when all the datasource for the next days are added and ready.
// From now on, the layer nows the start and end dates.
weatherLayer.on("sourceReady", event => {
  const startDate = weatherLayer.getAnimationStartDate();
  const endDate = weatherLayer.getAnimationEndDate();
  const currentDate = weatherLayer.getAnimationTimeDate();
  refreshTime()

  timeSlider.min = +startDate;
  timeSlider.max = +endDate;
  timeSlider.value = +currentDate;
});

// Called when the animation is progressing
weatherLayer.on("tick", event => {
  refreshTime();
  updatePointerValue(pointerLngLat);
});

// Called when the time is manually set
weatherLayer.on("animationTimeSet", event => {
  refreshTime();
});

// When clicking on the play/pause
let isPlaying = false;
playPauseButton.addEventListener("click", () => {
  if (isPlaying) {
    weatherLayer.animateByFactor(0);
    playPauseButton.innerText = "Play 3600x";
  } else {
    weatherLayer.animateByFactor(3600);
    playPauseButton.innerText = "Pause";
  }

  isPlaying = !isPlaying;
});

// Update the date time display
function refreshTime() {
  const d = weatherLayer.getAnimationTimeDate();
  timeTextDiv.innerText = d.toString();
  timeSlider.value = +d;
}

map.on('mouseout', function(evt) {
  if (!evt.originalEvent.relatedTarget) {
    pointerDataDiv.innerText = "";
    pointerLngLat = null;
  }
});

function updatePointerValue(lngLat) {
  if (!lngLat) return;
  pointerLngLat = lngLat;
  const value = weatherLayer.pickAt(lngLat.lng, lngLat.lat);
  if (!value) {
    pointerDataDiv.innerText = "";
    return;
  }
  pointerDataDiv.innerText = `${value.speedMetersPerSecond.toFixed(1)} m/s`
}

map.on('mousemove', (e) => {
  updatePointerValue(e.lngLat);
});
```

#### index.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="robots" content="noindex">
  <title>Display a wind arrow layer</title>
  <link rel="stylesheet" href="./style.css">
</head>
<body>
  <div id="time-info">
    <span id="time-text"></span>
    <button id="play-pause-bt" class="button">Play 3600x</button>
    <input type="range" id="time-slider" min="0" max="11" step="1">
  </div>
  <div id="variable-name">Wind</div>
  <div id="pointer-data"></div>
  <div id="map"></div>
  <script type="module" src="./main.js"></script>
</body>
</html>
```

#### style.css

```css
body { margin: 0; padding: 0; font-family: sans-serif; }
#map { position: absolute; top: 0; bottom: 0; width: 100%;  background-color: #444952; }
#time-info{ position: fixed; width: 60vw; bottom: 0; z-index: 1; margin: 10px; text-shadow: 0px 0px 5px black; color: white; font-size: 18px; font-weight: 500; text-align: center; left: 0; right: 0; margin: auto; padding: 20px;}
#time-slider{ width: 100%; height: fit-content; left: 0; right: 0; z-index: 1; filter: drop-shadow(0 0 7px #000a); margin-top: 10px;}
#time-text{ font-size: 12px; font-weight: 600;}
#pointer-data{ z-index: 1; position: fixed; font-size: 20px; font-weight: 900; margin: 27px 0px 0px 10px; color: #fff; text-shadow: 0px 0px 10px #0007;}
#variable-name{ z-index: 1; position: fixed; font-size: 20px; font-weight: 500; margin: 5px 0px 0px 10px; color: #fff; text-shadow: 0px 0px 10px #0007;}
.button{ cursor: pointer; width: auto; padding: 8px; border-radius: 3px; font-size: 10px; text-align: center; color: #fff; background: #3174ff; font-family: sans-serif; font-weight: bold;}
```

### Basic JavaScript setup

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Display a wind arrow layer</title>
  <script src="https://cdn.maptiler.com/maptiler-sdk-js/v4.2.0/maptiler-sdk.umd.min.js"></script>
  <link href="https://cdn.maptiler.com/maptiler-sdk-js/v4.2.0/maptiler-sdk.css" rel="stylesheet" />
  <script src="./maptiler-weather-plus.umd.js"></script>
  <style>
    body { margin: 0; padding: 0; font-family: sans-serif; }
    #map { position: absolute; top: 0; bottom: 0; width: 100%;  background-color: #444952; }
    #time-info{ position: fixed; width: 60vw; bottom: 0; z-index: 1; margin: 10px; text-shadow: 0px 0px 5px black; color: white; font-size: 18px; font-weight: 500; text-align: center; left: 0; right: 0; margin: auto; padding: 20px;}
    #time-slider{ width: 100%; height: fit-content; left: 0; right: 0; z-index: 1; filter: drop-shadow(0 0 7px #000a); margin-top: 10px;}
    #time-text{ font-size: 12px; font-weight: 600;}
    #pointer-data{ z-index: 1; position: fixed; font-size: 20px; font-weight: 900; margin: 27px 0px 0px 10px; color: #fff; text-shadow: 0px 0px 10px #0007;}
    #variable-name{ z-index: 1; position: fixed; font-size: 20px; font-weight: 500; margin: 5px 0px 0px 10px; color: #fff; text-shadow: 0px 0px 10px #0007;}
    .button{ cursor: pointer; width: auto; padding: 8px; border-radius: 3px; font-size: 10px; text-align: center; color: #fff; background: #3174ff; font-family: sans-serif; font-weight: bold;}
  </style>
</head>
<body>
  <div id="time-info">
    <span id="time-text"></span>
    <button id="play-pause-bt" class="button">Play 3600x</button>
    <input type="range" id="time-slider" min="0" max="11" step="1">
  </div>
  <div id="variable-name">Wind</div>
  <div id="pointer-data"></div>
  <div id="map"></div>
  <script>
      maptilersdk.config.apiKey = 'YOUR_MAPTILER_API_KEY_HERE';
      const map = (window.map = new maptilersdk.Map({
        container: 'map', // container's id or the HTML element to render the map
        style: maptilersdk.MapStyle.BACKDROP,
        projectionControl: true,
        projection: 'globe',
        zoom: 2,
        center: [0, 40]
      }));

      const timeInfoContainer = document.getElementById("time-info");
      const timeTextDiv = document.getElementById("time-text");
      const timeSlider = document.getElementById("time-slider");
      const playPauseButton = document.getElementById("play-pause-bt");
      const pointerDataDiv = document.getElementById("pointer-data");
      let pointerLngLat = null;

      const weatherLayer = new maptilerweatherplus.WindArrowLayer({
        opacity: 0.8,
      });
      map.on('load', function () {
        map.setPaintProperty("Water", 'fill-color', "rgba(0, 0, 0, 0.4)");
        map.addLayer(weatherLayer, 'Water');
      });

      timeSlider.addEventListener("input", (evt) => {
        weatherLayer.setAnimationTime(parseInt(timeSlider.value / 1000))
      });

      // Event called when all the datasource for the next days are added and ready.
      // From now on, the layer nows the start and end dates.
      weatherLayer.on("sourceReady", event => {
        const startDate = weatherLayer.getAnimationStartDate();
        const endDate = weatherLayer.getAnimationEndDate();
        const currentDate = weatherLayer.getAnimationTimeDate();
        refreshTime()

        timeSlider.min = +startDate;
        timeSlider.max = +endDate;
        timeSlider.value = +currentDate;
      });

      // Called when the animation is progressing
      weatherLayer.on("tick", event => {
        refreshTime();
        updatePointerValue(pointerLngLat);
      });

      // Called when the time is manually set
      weatherLayer.on("animationTimeSet", event => {
        refreshTime();
      });

      // When clicking on the play/pause
      let isPlaying = false;
      playPauseButton.addEventListener("click", () => {
        if (isPlaying) {
          weatherLayer.animateByFactor(0);
          playPauseButton.innerText = "Play 3600x";
        } else {
          weatherLayer.animateByFactor(3600);
          playPauseButton.innerText = "Pause";
        }

        isPlaying = !isPlaying;
      });

      // Update the date time display
      function refreshTime() {
        const d = weatherLayer.getAnimationTimeDate();
        timeTextDiv.innerText = d.toString();
        timeSlider.value = +d;
      }

      map.on('mouseout', function(evt) {
        if (!evt.originalEvent.relatedTarget) {
          pointerDataDiv.innerText = "";
          pointerLngLat = null;
        }
      });

      function updatePointerValue(lngLat) {
        if (!lngLat) return;
        pointerLngLat = lngLat;
        const value = weatherLayer.pickAt(lngLat.lng, lngLat.lat);
        if (!value) {
          pointerDataDiv.innerText = "";
          return;
        }
        pointerDataDiv.innerText = `${value.speedMetersPerSecond.toFixed(1)} m/s`
      }

      map.on('mousemove', (e) => {
        updatePointerValue(e.lngLat);
      });
      
  </script>
</body>
</html>
```

## Learn more

Look at the [Weather Plus wind arrow layer](https://docs.maptiler.com/sdk-js/examples/weather-plus-wind-arrow/) example

Check out the [Weather JS module reference](https://docs.maptiler.com/sdk-js/modules/weather/)

Check out the [Wind arrow layer reference](https://docs.maptiler.com/sdk-js/modules/weather/api/wind-arrow/)

## Related examples

### [Weather custom popup](https://docs.maptiler.com/sdk-js/examples/weather-custom-popup/)

Examples

Create highly customizable popups for weather maps using MarkerLayout.

### [Weather layer switcher](https://docs.maptiler.com/sdk-js/examples/weather-layer-switcher/)

Examples

Switch weather layers and view detailed data under cursors.

### [Weather wind direction](https://docs.maptiler.com/sdk-js/examples/weather-wind-direction/)

Examples

Visualize wind layer with speed and direction.

### [Weather+ pressure isolines layer](https://docs.maptiler.com/sdk-js/examples/weather-plus-pressure-isolines/)

Examples

Visualize atmospheric pressure isolines using Weather Plus datasets.
