Custom fire symbols using a shader

Custom fire symbols using a shader

This example customizes the symbols displayed by the fires-obs layer using a custom fragment shader to create an animated fire effect.

Custom shaders can be used for symbol or circle layer types to create a unique animated visual effect for each symbol based on the feature's data and properties.

custom-fires-shader.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>MapsGL SDK - Custom fire symbols using a shader</title>
    <meta name="description" content="Use a custom WebGL fragment shader to apply a realistic fire effect to wildfire data." />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
 
    <link href="https://api.mapbox.com/mapbox-gl-js/v2.8.0/mapbox-gl.css" rel="stylesheet" />
    <script defer src="https://api.mapbox.com/mapbox-gl-js/v2.8.0/mapbox-gl.js"></script>
 
    <link href="https://cdn.aerisapi.com/sdk/js/mapsgl/latest/aerisweather.mapsgl.css" rel="stylesheet" />
    <script defer src="https://cdn.aerisapi.com/sdk/js/mapsgl/latest/aerisweather.mapsgl.js"></script>
    
 
    <style>
    body, html {
        margin: 0;
        padding: 0;
    }
    #map {
        height: 100vh;
        width: 100%;
    }
    </style>
 
</head>
<body>
    <div id="map"></div>
 
    <script>
    window.addEventListener('load', () => {
 
        mapboxgl.accessToken = 'MAPBOX_TOKEN';
        const map = new mapboxgl.Map({
            container: document.getElementById('map'),
            style: 'mapbox://styles/mapbox/light-v9',
            center: [-94, 42],
            zoom: 2
        });
        
        const account = new aerisweather.mapsgl.Account('CLIENT_ID', 'CLIENT_SECRET');
        const controller = new aerisweather.mapsgl.MapboxMapController(map, { account });
 
        controller.on('load', () => {
            controller.addWeatherLayer('fires-obs', {
                type: 'symbol',
                paint: {
                    symbol: {
                        shader: `
        // https://www.shadertoy.com/view/Xtf3DX
        #extension GL_OES_standard_derivatives : enable
 
        precision mediump float;
 
        uniform vec2 resolution;
        uniform float dpr;
        uniform float time;
        uniform sampler2D tDiffuse;
 
        varying vec2 vUv;
        varying vec2 vPosition;
        varying float vFactor;
        varying float vRandom;
 
        #include <fbm>
 
        float rand(const vec2 co) {
            float t = dot(vec2(12.9898, 78.233), co);
            return fract(sin(t) * (4375.85453 + t));
        }
 
        void main() {
            vec2 uv = vec2(vUv.x, 1.0 - vUv.y);
            //vec2 uv = vUv;
            float t = (time + vRandom * 10.) * 0.5;
 
            vec2 q = uv;
            q.x *= 1.;
            q.y *= 2.;
            float strength = floor(q.x + 2.0);
            float T3 = max(3., 1.25 * strength) * t;
            q.x = mod(q.x, 1.) - 0.5;
            q.y -= 0.25;
 
            float n = fbm(strength * q - vec2(0, T3));
            float c = 1. - 16. * pow(max(0., length(q * vec2(1.8 + q.y * 1.5, .75)) - n * max(0., q.y + .25)), 1.2);
            float c1 = n * c * (1.5 - pow(1.25 * uv.y, 2.));
            c1 = clamp(c1, 0., 1.);
 
            // flame color
            vec3 col = vec3(3.5*c1, 1.5*c1*c1*c1, c1*c1*c1*c1*c1*c1);
            float alpha = 1.0;
 
            // adjust color and alpha based on vFactor (percent contained)
            if (vFactor == 1.) {
                col *= vec3(0.);
                alpha = 0.7;
            }
 
            // smoke intensity: lower number gives more smoke
            float a = c * (1. - pow(uv.y, 1.0));
            col = mix(vec3(0.3), col, a);
 
            if (col.r == col.g && col.g == col.b) {
                alpha *= 1.0 - col.r;
            }
 
            if (uv.y > 0.5) {
                float dist = length(2.0 * uv - 1.0);
                float delta = fwidth(dist);
                alpha *= 1.0 - smoothstep(1.0 - delta - 0.5, 1.0, dist);
            }
 
            gl_FragColor = vec4(col, alpha);
            gl_FragColor.a = alpha;
            gl_FragColor.rgb *= gl_FragColor.a;
        }
                        `,
                        animated: true,
                        anchor: 'bottom',
                        size: (data) => {
                            const area = Math.max(1, data.report?.areaAC);
                            const contained = data.report?.perContained || 0;
                            const factor = (area * (contained === 100 ? 0.75 : 1)) / 10000;
                            const size = { 
                                width: Math.min(80, Math.round(30 + 40 * factor)), 
                                height: Math.min(130, Math.round(40 + 80 * factor))
                            };
                            return size;
                        },
                        factor: (data) => {
                            const contained = data.report?.perContained || 0;
                            return contained / 100;
                        },
                        pitchWithMap: false,
                        rotateWithMap: false
                    }
                }
            });
        });
 
    });
    </script>
 
</body>
</html>