korri
korri

Reputation: 37

Mapbox Custom Layer WebGL

I am wondering how to be able to toggle a webgl layer in Mapbox GL JS.
When I call console.log(map.getStyle()) in the following example. The new custom layer (highlight) does not show up, so I am not able to remove the layer when I call map.removeLayer('highlight'). The layer does however show up in console.log(map.style.sourceCaches)

I am not sure how I would be able to remove the layer to create a toggle button. I've looked through the Mapbox Docs. This is probably something so simple but I am just not getting it...

Also, is there a way to control the opacity for the custom texture layer?

<html>
<head>
<meta charset="utf-8" />
<title>Add a custom style layer</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<script src="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js"></script>
<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css" rel="stylesheet" />
<style>
    body { margin: 0; padding: 0; }
    #map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<script>
    mapboxgl.accessToken = 'pk.eyJ1Ijoia29ycmlhIiwiYSI6ImNpeHdpeHR5aDAwMjQycXFxcDZ2amQ2cjIifQ.mMba8iFMCiuoL7hOmfKjwQ';
var map = (window.map = new mapboxgl.Map({
container: 'map',
zoom: 3,
center: [7.5, 58],
style: 'mapbox://styles/mapbox/light-v10',
antialias: true // create the gl context with MSAA antialiasing, so custom layers are antialiased
}));
 
// create a custom style layer to implement the WebGL content
var highlightLayer = {
id: 'highlight',
type: 'custom',
 
// method called when the layer is added to the map
// https://docs.mapbox.com/mapbox-gl-js/api/#styleimageinterface#onadd
onAdd: function (map, gl) {
// create GLSL source for vertex shader
var vertexSource =
'' +
'uniform mat4 u_matrix;' +
'attribute vec2 a_pos;' +
'void main() {' +
'    gl_Position = u_matrix * vec4(a_pos, 0.0, 1.0);' +
'}';
 
// create GLSL source for fragment shader
var fragmentSource =
'' +
'void main() {' +
'    gl_FragColor = vec4(1.0, 0.0, 0.0, 0.5);' +
'}';
 
// create a vertex shader
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexSource);
gl.compileShader(vertexShader);
 
// create a fragment shader
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentSource);
gl.compileShader(fragmentShader);
 
// link the two shaders into a WebGL program
this.program = gl.createProgram();
gl.attachShader(this.program, vertexShader);
gl.attachShader(this.program, fragmentShader);
gl.linkProgram(this.program);
 
this.aPos = gl.getAttribLocation(this.program, 'a_pos');
 
// define vertices of the triangle to be rendered in the custom style layer
var helsinki = mapboxgl.MercatorCoordinate.fromLngLat({
lng: 25.004,
lat: 60.239
});
var berlin = mapboxgl.MercatorCoordinate.fromLngLat({
lng: 13.403,
lat: 52.562
});
var kyiv = mapboxgl.MercatorCoordinate.fromLngLat({
lng: 30.498,
lat: 50.541
});
 
// create and initialize a WebGLBuffer to store vertex and color data
this.buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
helsinki.x,
helsinki.y,
berlin.x,
berlin.y,
kyiv.x,
kyiv.y
]),
gl.STATIC_DRAW
);
},
 
// method fired on each animation frame
// https://docs.mapbox.com/mapbox-gl-js/api/#map.event:render
render: function (gl, matrix) {
gl.useProgram(this.program);
gl.uniformMatrix4fv(
gl.getUniformLocation(this.program, 'u_matrix'),
false,
matrix
);
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
gl.enableVertexAttribArray(this.aPos);
gl.vertexAttribPointer(this.aPos, 2, gl.FLOAT, false, 0, 0);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 3);
}
};
 
// add the custom style layer to the map
map.on('load', function () {
map.addLayer(highlightLayer, 'building');
});
</script>
 
</body>
</html>

Upvotes: 1

Views: 2472

Answers (1)

jscastro
jscastro

Reputation: 3780

Basically you need to set the layer visibility to none, so the layer will stop render:

map.setLayoutProperty('highlight', 'visibility', 'none');

There is a caveat with custom layers (that are based on CustomLayerInterface), is that they don't have the visibility preset by default despite they are visible, so you have to set it explicitly to visible first, right after the creation with map.addLayer. Then you can toggle the layer with the same method from visible to none through setLayoutProperty as you want.

Adding a link button and a method to your code will work like this fiddle I have drafted with your sample.

The code is below...

<html>
<head>
    <meta charset="utf-8" />
    <title>Add a custom style layer</title>
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
    <script src="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js"></script>
    <link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css" rel="stylesheet" />
    <style>
        body {
            margin: 0;
            padding: 0;
        }

        #map {
            position: absolute;
            top: 0;
            bottom: 0;
            width: 100%;
        }
        #menu {
            background: #fff;
            position: absolute;
            z-index: 1;
            top: 10px;
            right: 10px;
            border-radius: 3px;
            width: 120px;
            border: 1px solid rgba(0, 0, 0, 0.4);
            font-family: 'Open Sans', sans-serif;
        }

            #menu a {
                font-size: 13px;
                color: #404040;
                display: block;
                margin: 0;
                padding: 0;
                padding: 10px;
                text-decoration: none;
                border-bottom: 1px solid rgba(0, 0, 0, 0.25);
                text-align: center;
            }

                #menu a:last-child {
                    border: none;
                }

                #menu a:hover {
                    background-color: #f8f8f8;
                    color: #404040;
                }

                #menu a.active {
                    background-color: #3887be;
                    color: #ffffff;
                }

                    #menu a.active:hover {
                        background: #3074a4;
                    }

    </style>
</head>
<body>
    <div id="map"></div>
    <nav id="menu">
        <a href="#" class="active" onclick="toggleLayer(this)">highlight</a>
    </nav>
    <script>
        mapboxgl.accessToken = 'pk.eyJ1Ijoia29ycmlhIiwiYSI6ImNpeHdpeHR5aDAwMjQycXFxcDZ2amQ2cjIifQ.mMba8iFMCiuoL7hOmfKjwQ';
        var map = (window.map = new mapboxgl.Map({
            container: 'map',
            zoom: 3,
            center: [7.5, 58],
            style: 'mapbox://styles/mapbox/light-v10',
            antialias: true // create the gl context with MSAA antialiasing, so custom layers are antialiased
        }));

        // create a custom style layer to implement the WebGL content
        var highlightLayer = {
            id: 'highlight',
            type: 'custom',

            // method called when the layer is added to the map
            // https://docs.mapbox.com/mapbox-gl-js/api/#styleimageinterface#onadd
            onAdd: function (map, gl) {
                // create GLSL source for vertex shader
                var vertexSource =
                    '' +
                    'uniform mat4 u_matrix;' +
                    'attribute vec2 a_pos;' +
                    'void main() {' +
                    '    gl_Position = u_matrix * vec4(a_pos, 0.0, 1.0);' +
                    '}';

                // create GLSL source for fragment shader
                var fragmentSource =
                    '' +
                    'void main() {' +
                    '    gl_FragColor = vec4(1.0, 0.0, 0.0, 0.5);' +
                    '}';

                // create a vertex shader
                var vertexShader = gl.createShader(gl.VERTEX_SHADER);
                gl.shaderSource(vertexShader, vertexSource);
                gl.compileShader(vertexShader);

                // create a fragment shader
                var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
                gl.shaderSource(fragmentShader, fragmentSource);
                gl.compileShader(fragmentShader);

                // link the two shaders into a WebGL program
                this.program = gl.createProgram();
                gl.attachShader(this.program, vertexShader);
                gl.attachShader(this.program, fragmentShader);
                gl.linkProgram(this.program);

                this.aPos = gl.getAttribLocation(this.program, 'a_pos');

                // define vertices of the triangle to be rendered in the custom style layer
                var helsinki = mapboxgl.MercatorCoordinate.fromLngLat({
                    lng: 25.004,
                    lat: 60.239
                });
                var berlin = mapboxgl.MercatorCoordinate.fromLngLat({
                    lng: 13.403,
                    lat: 52.562
                });
                var kyiv = mapboxgl.MercatorCoordinate.fromLngLat({
                    lng: 30.498,
                    lat: 50.541
                });

                // create and initialize a WebGLBuffer to store vertex and color data
                this.buffer = gl.createBuffer();
                gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
                gl.bufferData(
                    gl.ARRAY_BUFFER,
                    new Float32Array([
                        helsinki.x,
                        helsinki.y,
                        berlin.x,
                        berlin.y,
                        kyiv.x,
                        kyiv.y
                    ]),
                    gl.STATIC_DRAW
                );
            },

            // method fired on each animation frame
            // https://docs.mapbox.com/mapbox-gl-js/api/#map.event:render
            render: function (gl, matrix) {
                gl.useProgram(this.program);
                gl.uniformMatrix4fv(
                    gl.getUniformLocation(this.program, 'u_matrix'),
                    false,
                    matrix
                );
                gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
                gl.enableVertexAttribArray(this.aPos);
                gl.vertexAttribPointer(this.aPos, 2, gl.FLOAT, false, 0, 0);
                gl.enable(gl.BLEND);
                gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
                gl.drawArrays(gl.TRIANGLE_STRIP, 0, 3);
            }
        };


        // add the custom style layer to the map
        map.on('load', function () {
            map.addLayer(highlightLayer, 'building');
            map.setLayoutProperty('highlight', 'visibility', 'visible');
        });

        function toggleLayer(e) {
            var clickedLayer = e.textContent;

            var visibility = map.getLayoutProperty(clickedLayer, 'visibility');

            // toggle layer visibility by changing the layout object's visibility property
            if (visibility === 'visible') {
                map.setLayoutProperty(clickedLayer, 'visibility', 'none');
                this.className = '';

            } else {
                this.className = 'active';
                map.setLayoutProperty(clickedLayer, 'visibility', 'visible');
            }
        }
    </script>

</body>
</html>

If this answer solves your question, please mark it as answer accepted, in that way it will also help other users to know it was the right solution.

Upvotes: 2

Related Questions