4

my objective is to have a mapbox map with a custom layer overlaid on it. that custom layer should be masked by an other mapbox map.

usecase:

i have some default map. on top of that i want to render some special heatmap. that heatmap has to only be shown above roads.

current approach:

  • add layers to mapbox that produce a map where all roads are white and background is black
  • add a custom layer that copies the content of the framebuffer to a texture
  • add layers to render some default map
  • add custom layer that draws the special heatmap using the above texture as a mask.

problem:

the order in which the layers are rendered seems to not match the order in which they are added to mapbox. this example has a custom layer that outputs the color of the center pixel in the framebuffer at the moment it is called:

var map = window.map = new mapboxgl.Map({
container: 'map',
zoom: 16,
center: [8.30468, 47.05232],
style: {
    "version":8, 
    "name":"test", 
    "sources": {
        "openmaptiles": {
            "type": "vector",
            "url": "https://api.maptiler.com/tiles/v3/tiles.json?key=X0HhMcrAjHfR6MvFLSSn"
        }
    },
    "layers":[]}
});

var getPixelLayer = {
    id: 'highlight',
    type: 'custom',

    onAdd: function (map, gl) { },

    render: function (gl, matrix) {
        var pixels = new Uint8Array(4);
        gl.readPixels(Math.round(gl.drawingBufferWidth/2), Math.round(gl.drawingBufferHeight/2), 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
        document.getElementById("color").innerHTML = pixels[0]+","+pixels[1]+","+pixels[2];
    }
};

map.on('load', function() {
  //expected output: "255,255,255" everywhere
  //actual output: "0,0,0" everywhere
    /*
    map.addLayer({"id":"bkg1","paint":{"background-color":"rgb(255, 255, 255)"},"type":"background"});
    map.addLayer(getPixelLayer);
    map.addLayer({"id":"bkg2","paint":{"background-color":"rgb(0, 0, 0)"},"type":"background"});
    */

  //expected output: "0,0,0" everywhere
  //actual output: "177,177,177" above buildings
  //               "0,0,0" above streets
  //               "0,0,0" above background
    map.addLayer({"id":"bkg1","paint":{"background-color":"rgb(0, 0, 0)"},"type":"background"});
    map.addLayer(getPixelLayer);
    map.addLayer({"id":"roads","layout":{"line-cap":"round","line-join":"round","visibility":"visible"},"paint":{"line-color":"#fff","line-offset":0,"line-width":{"base":1.4,"stops":[[8,1.2],[16,12]]}},"source":"openmaptiles","source-layer":"transportation","type":"line"});
    map.addLayer({"id":"building","paint":{"fill-color":"rgb(177, 177, 177)"},"source":"openmaptiles","source-layer":"building","type":"fill"});


});

https://jsfiddle.net/t31cj5v0/

as can be seen the rendering order seems to be background->buildings->custom_layer->streets instead of background->custom_layer->streets->buildings. The layer orderings appears correct on screen at the end, but doing what i want is not possible like this.

does anyone know what causes this behaviour and how it could be avoided?

other implementations i tried:

i also tried to render the black-white street map to a different mapbox object with synced movement. this works, but since each mapbox has its own webgl context i cant use the resulting black-white texture in the other context.

featurerequest (?)

it would be really nice if some "mask" layer could be implemented to accomplish this objective natively. but right now i'm just searching for some hack to do this. thanks in advance.

1 Answer 1

1

mapbox layers are drawn in 3 rendering passes. "offscreen", "opaque" and "translucent". "offscreen" is not important as the subsequent passes always draw over it. the problem appears since first all layers classifiead as "opaque" are drawn, then all layers classified as "translucent" (inside the render passes layer order is respected). because of this the rendering order does not match the declared order.

custom layers are drawn in the "translucent" pass, but because of the above it can happen that later layers are already rendered (if "opaque"). usually this is not a problem as mapbox uses a stencil buffer to track which layer goes under/over which one even if they are not drawn in order.

to make the render order match add this line after creating the mapbox instance. it will disable the "opaque" rendering pass and force every layer that was "opaque" to be "translucent":

map.painter.opaquePassEnabledForLayer = function() { return false; }
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.