deck.gl icon indicating copy to clipboard operation
deck.gl copied to clipboard

[Bug] Issues with Masking Extension in deck.gl for Vector Tiles

Open JacobWeinbren opened this issue 1 year ago • 7 comments

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

  1. Set up a basic Mapbox instance with deck.gl overlay.
  2. Define two MVTLayers: one for the mask (areasLayer) and one to be masked (buildingsLayer).
  3. Apply the Mask Extension to areasLayer with appropriate maskId.
  4. 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

JacobWeinbren avatar May 24 '24 03:05 JacobWeinbren

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 avatar May 24 '24 06:05 felixpalmer

@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.

JacobWeinbren avatar May 24 '24 12:05 JacobWeinbren

@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>

JacobWeinbren avatar May 24 '24 13:05 JacobWeinbren

<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)

JacobWeinbren avatar May 27 '24 05:05 JacobWeinbren

@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>

JacobWeinbren avatar May 28 '24 07:05 JacobWeinbren

@felixpalmer Small note, turning binary off has significantly reduced performance. Would you know why?

JacobWeinbren avatar May 28 '24 09:05 JacobWeinbren

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.

Pessimistress avatar May 28 '24 16:05 Pessimistress