[Bug] Issues with Masking Extension in deck.gl for Vector Tiles
Description
The Masking Extension in deck.gl does not behave as expected when used with MVTLayer. Specifically, when attempting to use the Mask Extension to control the visibility of certain areas based on another layer, both layers become completely hidden regardless of the specified settings.
Flavors
- [X] Script tag
- [ ] React
- [ ] Python/Jupyter notebook
- [X] MapboxOverlay
- [ ] GoogleMapsOverlay
- [ ] CartoLayer
- [ ] ArcGIS
Expected Behavior
The expected behavior is that the areasLayer should act as a mask for the buildingsLayer, allowing only the areas covered by areasLayer to display buildingsLayer data. The rest of the buildingsLayer should be masked out.
Steps to Reproduce
- Set up a basic Mapbox instance with deck.gl overlay.
- Define two MVTLayers: one for the mask (areasLayer) and one to be masked (buildingsLayer).
- Apply the Mask Extension to areasLayer with appropriate maskId.
- Observe that instead of masking, both layers are completely hidden.
Pen: https://codepen.io/JacobWeinbren/pen/yLWJWVP
<div id="map" class="w-full h-full"></div>
<script>
import { Deck } from "@deck.gl/core";
import { MapboxOverlay } from '@deck.gl/mapbox';
import { MVTLayer } from "@deck.gl/geo-layers";
import { MaskExtension } from '@deck.gl/extensions';
import mapboxgl from 'mapbox-gl';
import { GL } from "@luma.gl/constants";
const MAPBOX_TOKEN = import.meta.env.PUBLIC_MAPBOX_TOKEN;
// Initialize Mapbox map
const map = new mapboxgl.Map({
container: 'map',
style: "mapbox://styles/mapbox/dark-v11",
center: [-4.2026, 56.4907],
zoom: 6,
accessToken: MAPBOX_TOKEN,
});
map.on('load', () => {
const firstLabelLayerId = map
.getStyle()
.layers.find((layer) => layer.type === "symbol").id;
console.log(firstLabelLayerId);
// Define the mask layer
const areasLayer = new MVTLayer({
id: "mask",
data: "https://map.jacobweinbren.workers.dev/scottish-areas-ages/{z}/{x}/{y}.mvt",
minZoom: 0,
maxZoom: 22,
getFillColor: [255, 255, 255, 255],
beforeId: firstLabelLayerId,
extensions: [new MaskExtension({ maskId: 'mask-layer' })],
maskId: 'mask-layer',
});
// Define the intersected layer
const buildingsLayer = new MVTLayer({
id: "mask-layer",
data: "https://map.jacobweinbren.workers.dev/scottish-intersected-ages/{z}/{x}/{y}.mvt",
minZoom: 0,
maxZoom: 22,
operation: 'mask',
beforeId: firstLabelLayerId
});
const deckOverlay = new MapboxOverlay({
interleaved: true,
layers: [areasLayer, buildingsLayer],
});
map.addControl(deckOverlay);
});
</script>
Environment
Framework version: [email protected] Browser: Chrome 110.0
Logs
No response
Is the bug also present when used without Mapbox? If so, could you update the repro to not use Mapbox as the example isn't working for me
@felixpalmer I made a code sandbox - you just need to update the .env and you get the same error https://codesandbox.io/p/devbox/lucid-swirles-65sv3c hopefully this helps.
@felixpalmer You can get the same error running this code in it's place (no mapbox)
<div id="map" class="w-full h-full"></div>
<script>
import { Deck } from '@deck.gl/core';
import { MVTLayer } from "@deck.gl/geo-layers";
import { MaskExtension } from '@deck.gl/extensions';
// Initial view state for the map
const initialViewState = {
longitude: -4.2026,
latitude: 56.4907,
zoom: 6,
pitch: 0,
bearing: 0
};
// Define the intersected layer
const buildingsLayer = new MVTLayer({
id: "mask-layer",
data: "https://map.jacobweinbren.workers.dev/uk-cleaned/{z}/{x}/{y}.mvt",
minZoom: 0,
maxZoom: 22,
operation: 'mask'
});
// Define the mask layer
const areasLayer = new MVTLayer({
id: "mask",
data: "https://map.jacobweinbren.workers.dev/scottish-areas-ages/{z}/{x}/{y}.mvt",
minZoom: 0,
maxZoom: 22,
getFillColor: [0, 0, 0, 255],
extensions: [new MaskExtension({ maskId: 'mask-layer' })],
maskId: 'mask-layer',
});
// Create the Deck instance
const deck = new Deck({
initialViewState: initialViewState,
controller: true,
layers: [areasLayer, buildingsLayer],
mapStyle: 'https://basemaps.cartocdn.com/gl/positron-nolabels-gl-style/style.json'
});
</script>
<div id="map" class="w-full h-full"></div>
<link
href="https://api.mapbox.com/mapbox-gl-js/v2.3.1/mapbox-gl.css"
rel="stylesheet"
/>
<script>
import { GeoJsonLayer } from "@deck.gl/layers";
import { MapboxOverlay } from "@deck.gl/mapbox";
import { MVTLayer } from "@deck.gl/geo-layers";
import { MaskExtension } from "@deck.gl/extensions";
import mapboxgl from "mapbox-gl";
const MAPBOX_TOKEN = import.meta.env.PUBLIC_MAPBOX_TOKEN;
const maskLayer = new MVTLayer({
id: "mask-layer",
data: "https://map.jacobweinbren.workers.dev/uk-cleaned/{z}/{x}/{y}.mvt",
renderSubLayers: (props) => {
return new GeoJsonLayer({
...props,
operation: "mask"
});
},
});
const areaLayer = new MVTLayer({
id: "area-layer",
data: "https://map.jacobweinbren.workers.dev/scottish-areas-ages/{z}/{x}/{y}.mvt",
binary: true,
maskId: "mask-layer",
extensions: [new MaskExtension({maskId: "mask-layer"})],
renderSubLayers: (props) => {
return new GeoJsonLayer({
...props,
});
},
});
const map = new mapboxgl.Map({
container: "map",
style: "mapbox://styles/mapbox/dark-v11",
center: [-4.2026, 56.4907],
zoom: 6,
accessToken: MAPBOX_TOKEN,
});
const deckOverlay = new MapboxOverlay({
layers: [maskLayer, areaLayer],
});
map.addControl(deckOverlay);
</script>
I also tried this - based on - https://github.com/visgl/deck.gl/issues/8438 but with the same result (nothing is rendered)
@felixpalmer Good news! After spending many, many hours on this problem - I found that it was down to the single line binary: true, when it should be binary: false.
My guess is that MaskExtension requires GeoJSON and not binary data to work.
However - it still returns the error chunk-MSLAY6QI.js?v=1e75a6e6:9969 deck: Picked non-existent layer. Is picking buffer corrupt? and I am not sure why it does.
<canvas id="map" class="w-full h-full"></canvas>
<link
href="https://api.mapbox.com/mapbox-gl-js/v2.3.1/mapbox-gl.css"
rel="stylesheet"
/>
<script>
import { Deck } from '@deck.gl/core';
import { MVTLayer } from "@deck.gl/geo-layers";
import { MaskExtension } from "@deck.gl/extensions";
const maskLayer = new MVTLayer({
id: "mask-layer",
data: "https://map.jacobweinbren.workers.dev/uk-cleaned/{z}/{x}/{y}.mvt",
binary: false,
getFillColor: [255, 0, 0],
operation: "mask",
});
const areaLayer = new MVTLayer({
id: "area-layer",
data: "https://map.jacobweinbren.workers.dev/scottish-areas-ages/{z}/{x}/{y}.mvt",
getFillColor: [255, 0, 0],
extensions: [new MaskExtension()],
maskId: "mask-layer",
});
new Deck({
controller: true,
canvas: 'map',
initialViewState: {
longitude: -4.2026,
latitude: 56.4907,
zoom: 6,
},
layers: [maskLayer, areaLayer],
style: {
background: 'black',
},
});
</script>
@felixpalmer Small note, turning binary off has significantly reduced performance. Would you know why?
http://deck.gl/docs/api-reference/geo-layers/mvt-layer#binary
Use tile data in binary format to improve performance (2-3x faster on large datasets). It removes the need for serialization and deserialization of data transferred by the worker back to the main process.