Wind
These examples are simplified, using only static images created from tiles for some given bounds. We are not implementing it in a map context; we are simply showing examples of how to read and use the data using WebGL shaders.
Wind data
Wind data is fetched from a tile server that contains XYZ tiles with wind data. The data is then used to create a wind map using WebGL. To simplify the example, we are using a static image of the wind data and a static image of the map background. The image below consists of four tiles of wind data, stitched together to create a larger image. In the next example, we will use WebGL to render this data together with our background map.

Render raw wind data with WebGL
Setting up WebGL demands some boilerplate code, which we will not provide in these simplified examples. If you are unfamiliar with WebGL, we recommend visiting WebGL Fundamentals to get a grasp of the basics. After creating a canvas element and setting up the WebGL context, we can start creating our shaders.
Vertex shader
attribute vec2 aPosition;
attribute vec2 aTexCoords;
uniform vec2 uResolution;
varying vec2 vTexCoord;
void main() {
// Get a normalized position based on the resolution and the position of the vertex
vec2 normalizedPosition = aPosition / uResolution;
// In WebGL the vertex shader transforms vertices into what is called clip space
// The clip space goes from -1 to 1 in both x and y
vec2 positionInClipSpace = (normalizedPosition * 2.0) -1.0;
// Update the varying variable vTexCoord (containing texture coordinates)
vTexCoord = aTexCoords;
gl_Position = vec4(positionInClipSpace, 0, 1);
}
A simple vertex shader whose sole purpose is to provide our fragment shader with the texture coordinates.
Fragment shader
precision mediump float;
uniform sampler2D uDataTexture; // Texture containing the wind data
uniform sampler2D uMapTexture; // Texture containing the background map
varying vec2 vTexCoord;
void main() {
// Calling texture2D to get the color of the wind data and the background map
vec4 windDataColor = texture2D(uDataTexture, vTexCoord);
vec4 mapTextureColor = texture2D(uMapTexture, vTexCoord);
// Blend the colors by multiplying them
gl_FragColor = windDataColor * mapTextureColor;
}
The fragment shader gets the texture coordinates (vTexCoord) to look up which color this coordinate represents in both the wind data and our background map. As a simple way of blending these colors, we multiply them by each other.
Output
This is the result that is drawn to our canvas element.
Applying our own color gradient
The raw data is not easily readable for humans, as it only contains dimensions for a directional vector stored in the R and G channels of the image. To make it more readable, we need to make some changes in the fragment shader. The vertex shader will remain the same.
But first, we can create the gradient we want to use.
We don't need to render this gradient to the screen; it is simply done to visualize the example.
Fragment shader changes
To read the color from the gradient instead of just using the raw data, we have added uniform sampler2D uColorGradientTexture
, uniform float uMaxSpeed
, and the function vec2 getDirectionFromWindData(vec4 windData)
to help us achieve this.
As briefly mentioned, the wind data only contains a directional vector, stored in the R and G channels of the image. So in this example, we first get the direction of the wind, then calculate the length of the vector to get the speed. We then normalize the speed and use it as a lookup in our color gradient. The color gradient moves from left to right, so we send the normalized speed as the x value.
precision mediump float;
uniform sampler2D uDataTexture; // Our wind data
uniform sampler2D uMapTexture; // Our background map
uniform sampler2D uColorGradientTexture; // Our color gradient
uniform float uMaxSpeed;
varying vec2 vTexCoord;
// This function takes the wind data and returns a direction vector
vec2 getDirectionFromWindData(vec4 windData) {
return vec2(
(windData.r * 255.0 - 128.0) / 2.0,
(windData.g * 255.0 - 128.0) / 2.0
);
}
void main() {
// Getting wind data from the wind data texture, and getting the color from the backgroundmap texture
vec4 windDataColor = texture2D(uDataTexture, vTexCoord);
vec4 mapTextureColor = texture2D(uMapTexture, vTexCoord);
// Calling getDirectionFromWindData to get the direction of the wind, in form of a vector
vec2 direction = getDirectionFromWindData(windDataColor);
// Calculating the length of the vector, which is the speed of the wind
float speed = sqrt((direction.x * direction.x) + (direction.y * direction.y));
// Normalizing the speed to be between 0 and 1, so we can use it as a lookup in our color gradient
float normalizedSpeed = speed / uMaxSpeed;
// In this example the color gradient is moving from left to right, so we send the normalized speed as the x value.
vec4 windColor = texture2D(uColorGradientTexture, vec2(normalizedSpeed, 0.0));
// Multiplying the wind color with the background map color
// This could (and probably should) be done by rendering the wind color on top of the background map,
// and using a blend func in WebGL
gl_FragColor = windColor * mapTextureColor;
}
Output
After making these changes to the fragment shader, we can see the color gradient has been rendered in the end result.