visgl / deck.gl

WebGL2 powered visualization framework
https://deck.gl
MIT License
12.29k stars 2.09k forks source link

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

Open JacobWeinbren opened 6 months ago

JacobWeinbren commented 6 months ago

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

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: deck.gl@9.0.14 Browser: Chrome 110.0

Logs

No response

felixpalmer commented 6 months ago

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

JacobWeinbren commented 6 months ago

@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 commented 6 months ago

@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 commented 6 months ago
<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 commented 6 months ago

@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 commented 6 months ago

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

Pessimistress commented 6 months ago

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.