Weather layer switcher
Switch the display of various weather layers within MapTiler Weather dataset and access the data details at the location indicated by the cursor.
By utilizing the animateByFactor(3600) function, we can create a time-based animation that spans the next four days. Each second of the animation represents an hour.
Utilize the provided time animation bar to explore the weather forecast for the upcoming four-day period.
NPM module setup
npm install --save @maptiler/sdk @maptiler/weather
import { Map, MapStyle, config } from '@maptiler/sdk';
import '@maptiler/sdk/dist/maptiler-sdk.css';
import { WindLayer, PressureIsolineLayer, PrecipitationLayer, TemperatureLayer, PressureLayer, RadarLayer, WindArrowLayer, ColorRamp } from '@maptiler/weather';
config.apiKey = 'YOUR_MAPTILER_API_KEY_HERE';
const weatherLayers = {
"precipitation": {
"layer": null,
"value": "value",
"units": " mm"
},
"pressure": {
"layer": null,
"value": "value",
"units": " hPa"
},
"radar": {
"layer": null,
"value": "value",
"units": " dBZ"
},
"temperature": {
"layer": null,
"value": "value",
"units": "°"
},
"wind": {
"layer": null,
"value": "speedMetersPerSecond",
"units": " m/s"
}
};
const map = (window.map = new Map({
container: 'map', // container's id or the HTML element to render the map
style: MapStyle.BACKDROP, // stylesheet location
zoom: 2,
center: [-42.66, 37.63],
hash: true,
projectionControl: true,
projection: 'globe'
}));
const initWeatherLayer = "wind";
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;
let activeLayer = null;
let isPlaying = false;
let currentTime = null;
timeSlider.addEventListener("input", (evt) => {
const weatherLayer = weatherLayers[activeLayer]?.layer;
if (weatherLayer) {
weatherLayer.setAnimationTime(parseInt(timeSlider.value / 1000));
}
});
// When clicking on the play/pause
playPauseButton.addEventListener("click", () => {
const weatherLayer = weatherLayers[activeLayer]?.layer;
if (weatherLayer) {
if (isPlaying) {
pauseAnimation(weatherLayer);
} else {
playAnimation(weatherLayer);
}
}
});
function pauseAnimation(weatherLayer) {
weatherLayer.animateByFactor(0);
playPauseButton.innerText = "Play 3600x";
isPlaying = false;
}
function playAnimation(weatherLayer) {
weatherLayer.animateByFactor(3600);
playPauseButton.innerText = "Pause";
isPlaying = true;
}
map.on('load', function () {
map.setPaintProperty("Water", 'fill-color', "rgba(0, 0, 0, 0.4)");
initWeatherMap(initWeatherLayer);
});
map.on('mouseout', function(evt) {
if (!evt.originalEvent.relatedTarget) {
pointerDataDiv.innerText = "";
pointerLngLat = null;
}
});
function updatePointerValue(lngLat) {
if (!lngLat) return;
pointerLngLat = lngLat;
const weatherLayer = weatherLayers[activeLayer]?.layer;
const weatherLayerValue = weatherLayers[activeLayer]?.value;
const weatherLayerUnits = weatherLayers[activeLayer]?.units;
if (weatherLayer) {
const value = weatherLayer.pickAt(lngLat.lng, lngLat.lat);
if (!value) {
pointerDataDiv.innerText = "";
return;
}
pointerDataDiv.innerText = `${value[weatherLayerValue].toFixed(1)}${weatherLayerUnits}`
}
}
map.on('mousemove', (e) => {
updatePointerValue(e.lngLat);
});
document.getElementById('buttons').addEventListener('click', function (event) {
// Change layer based on button id
changeWeatherLayer(event.target.id);
});
function changeWeatherLayer(type) {
if (type !== activeLayer) {
if (map.getLayer(activeLayer)) {
const activeWeatherLayer = weatherLayers[activeLayer]?.layer;
if (activeWeatherLayer) {
currentTime = activeWeatherLayer.getAnimationTime();
map.setLayoutProperty(activeLayer, 'visibility', 'none');
}
}
activeLayer = type;
const weatherLayer = weatherLayers[activeLayer].layer || createWeatherLayer(activeLayer);
if (map.getLayer(activeLayer)) {
map.setLayoutProperty(activeLayer, 'visibility', 'visible');
} else {
map.addLayer(weatherLayer, 'Water');
}
changeLayerLabel(activeLayer);
activateButton(activeLayer);
changeLayerAnimation(weatherLayer);
return weatherLayer;
}
}
function activateButton(activeLayer) {
const buttons = document.getElementsByClassName('button');
for (let i = 0; i < buttons.length; i++) {
const btn = buttons[i];
if (btn.id === activeLayer) {
btn.classList.add('active');
} else {
btn.classList.remove('active');
}
}
}
function changeLayerAnimation(weatherLayer) {
weatherLayer.setAnimationTime(parseInt(timeSlider.value / 1000));
if (isPlaying) {
playAnimation(weatherLayer);
} else {
pauseAnimation(weatherLayer);
}
}
function createWeatherLayer(type){
let weatherLayer = null;
switch (type) {
case 'precipitation':
weatherLayer = new PrecipitationLayer({id: 'precipitation'});
break;
case 'pressure':
weatherLayer = new PressureLayer({
opacity: 0.8,
id: 'pressure'
});
break;
case 'radar':
weatherLayer = new RadarLayer({
opacity: 0.8,
id: 'radar'
});
break;
case 'temperature':
weatherLayer = new TemperatureLayer({
colorramp: ColorRamp.builtin.TEMPERATURE_3,
id: 'temperature'
});
break;
case 'wind':
weatherLayer = new WindLayer({id: 'wind'});
break;
}
// 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();
});
// 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();
if (timeSlider.min > 0){
weatherLayer.setAnimationTime(currentTime);
changeLayerAnimation(weatherLayer);
} else {
const currentDate = weatherLayer.getAnimationTimeDate();
timeSlider.min = +startDate;
timeSlider.max = +endDate;
timeSlider.value = +currentDate;
}
});
weatherLayers[type].layer = weatherLayer;
return weatherLayer;
}
// Update the date time display
function refreshTime() {
const weatherLayer = weatherLayers[activeLayer]?.layer;
if (weatherLayer) {
const d = weatherLayer.getAnimationTimeDate();
timeTextDiv.innerText = d.toString();
timeSlider.value = +d;
}
}
function changeLayerLabel(type) {
document.getElementById("variable-name").innerText = type;
}
function initWeatherMap(type) {
const weatherLayer = changeWeatherLayer(type);
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="robots" content="noindex">
<title>Weather layer switcher | NPM example</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div id="time-info">
<span id="time-text"></span>
<button id="play-pause-bt" class="btn btn-primary btn-sm time-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">
<ul id="buttons">
<li id="precipitation" class="btn btn-primary button">Precipitation</li>
<li id="pressure" class="btn btn-primary button">Pressure</li>
<li id="radar" class="btn btn-primary button">Radar</li>
<li id="temperature" class="btn btn-primary button">Temperature</li>
<li id="wind" class="btn btn-primary button">Wind</li>
</ul>
</div>
<script type="module" src="./main.js"></script>
</body>
</html>
body { margin: 0; padding: 0; font-family: sans-serif; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; background-color: #3E4048;}
#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; text-transform:capitalize;}
#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-text{ font-size: 12px; font-weight: 600;}
#time-slider{ width: 100%; height: fit-content; left: 0; right: 0; z-index: 1; filter: drop-shadow(0 0 7px #000a); margin-top: 10px;}
#buttons{ width: auto; margin: 0 10px; padding:0; position:absolute; top: 50px; left:0; z-index:99;}
.button{ display: block; position: relative; margin: 10px 0 0 0; font-size: 0.9em;}
Learn more
Check out the Weather JS module reference
Related examples
Weather custom popup
ExamplesCreate highly customizable popups for weather maps using MarkerLayout.
Weather+ pressure isolines layer
ExamplesVisualize atmospheric pressure isolines using Weather Plus datasets.
Weather+ wind arrow layer animated
ExamplesAnimate wind arrows and view cursor-specific wind speed data.