mapbox-gl-js icon indicating copy to clipboard operation
mapbox-gl-js copied to clipboard

Make query methods return raster features/sources

Open samanpwbb opened this issue 8 years ago • 18 comments

It would be useful if raster layers were included in featuresAt. Especially for raster layers with sources with limited bounds.

I see here that this is a noop: https://github.com/mapbox/mapbox-gl-js/blob/2a2aec44a39e11e73bdf663258bd6d52b83775f5/js/source/raster_tile_source.js#L98

samanpwbb avatar Jul 31 '15 03:07 samanpwbb

Looks to already be working.

screen shot 2015-07-31 at 1 40 01 pm

scothis avatar Jul 31 '15 20:07 scothis

@scothis in that example, the raster layers are hacked in to show up across the whole map. Ideally the raster layer only appears in the popup if it exists where user clicked.

samanpwbb avatar Aug 03 '15 20:08 samanpwbb

At this time we don't return raster sources or features from map.query*Features at all. We should do this.

This will be dependent on https://github.com/mapbox/mapbox-gl-js/issues/3186: querying searches for features in a tile, but since we shoehorn features into a tile, querying a point on a visible raster layer may search for points past a tile (the source cache's tile)'s "extent."

The equivalent of the noop mentioned in https://github.com/mapbox/mapbox-gl-js/issues/1404#issue-98309860 is now query_features.js#L12: raster layers don't have feature indices. querySourceFeatures also only queries geojson and vector data.

This was brought up in https://github.com/mapbox/mapbox-gl-js/issues/3745. Indeed for something like a canvas-based raster layer it would be helpful to return an [x,y] coordinate mapped back to the original raster data.

lbud avatar Dec 08 '16 22:12 lbud

This could be particularly useful for raster layers like the terrain rgb where users could query the pixel values and calculate elevation at a point based on the raster data 💭

mollymerp avatar Dec 12 '16 02:12 mollymerp

Any luck on this? This would very useful.

jucor avatar Oct 11 '17 12:10 jucor

#5916 notes that raster layers with a canvas source should be included with this feature.

jfirebaugh avatar Jan 01 '18 18:01 jfirebaugh

This could be particularly useful for raster layers like the terrain rgb where users could query the pixel values and calculate elevation at a point based on the raster data 💭

Exactly the use case I have. Layer with rgb data that I want to be able to query with a click event point.

shawnd avatar Sep 28 '19 17:09 shawnd

Any updates on this?

felix-ht avatar Dec 03 '19 13:12 felix-ht

any updates or solution ?

ansarikhurshid786 avatar Jan 13 '20 11:01 ansarikhurshid786

any updates or solution ?

dongmingwang2198881 avatar Apr 20 '20 02:04 dongmingwang2198881

Oh gosh.. This would be so helpful especially for querying via RGB values.. fingers crossed on this one

gagecarto avatar May 22 '20 19:05 gagecarto

Any news here? I'd like to query RGB values of underlying raster pixel too.

nextstopsun avatar Jul 03 '20 08:07 nextstopsun

While this is not working, you can add a note to the documentation that it does not work for raster layers. This would reduce confusion. https://docs.mapbox.com/mapbox-gl-js/api/map/#map#on

mprove avatar Nov 08 '21 10:11 mprove

I'm interested in a solution. Adding my upvote here... 👍

nm2501 avatar Feb 02 '22 16:02 nm2501

Yes, and it would be great to get a distribution of raster color values within a given polygon.

jbeuckm avatar Mar 30 '22 18:03 jbeuckm

Well, I worked a new method through to the tile.js layer here . The idea is that you could ask for the value from a raster source at a given location:

map.on("mousemove", (event) => {
    map.queryRasterSource(<my raster sourceId>, event.point);
});

But this still needs the function to pull a pixel value out of the raster source:

    // Find raster value at a given location
    queryRasterValues(
        layers: { [_: string]: StyleLayer },
        serializedLayers: { [string]: Object },
        sourceFeatureState: SourceFeatureState,
        tileResult: TilespaceQueryGeometry,
        params: {
            filter: FilterSpecification,
            layers: Array<string>,
            availableImages: Array<string>,
        },
        transform: Transform,
        pixelPosMatrix: Float32Array,
        visualizeQueryGeometry: boolean
    ): Color {
        // const texturePos = pixelPosMatrix.getTexturePos()
        // const color = new ArrayBuffer()
        // tileResult.tile.texture.readPixels(texturePos.x, texturePos.y, 1, 1, type, &color)
        // return color
    }

It might be that we need to use the Painter to render the source/layer for a viewport that is one pixel square? That seems like a lot of code for one pixel. Maybe there is a way to query the screen x,y for the already-rendered layer? Would a new painter function work?

painter.renderLayerPixel(layerId, mousePos) {
        this.context.viewport.set([mouseX, mouseY, 1, 1]);
        this.renderLayer(this, sourceCache, layer, coords);
        // how to get the pixel value???
}

I'm hoping someone who knows the internals well can jump in.

jbeuckm avatar Apr 07 '22 05:04 jbeuckm

Here is something that is starting to kind of work. I started with the Painter.render() function and tried to pare down to rendering just the requested layer. Then I read the requested pixel value. When I try this with multi-layer maps, it interferes with the main map render and the result is not stable. Someone who understands the gl buffers and how mapbox uses them should be able to fix that and maybe improve this to just render the pixel rather than the entire viewport.

colorForLayerPixel

    colorForLayerPoint(layerId: string, point: PointLike) {

        const layer = this.style._layers[layerId];
        const sourceCache = this.style._getLayerSourceCache(layer);

        sourceCache.prepare(this.context);

        const coordsAscending: Array<OverscaledTileID> = sourceCache.getVisibleCoordinates();
        const coordsDescending: Array<OverscaledTileID> = coordsAscending.slice().reverse();

        // Rebind the main framebuffer now that all offscreen layers have been rendered:
        // this.context.bindFramebuffer.set(null);
        this.context.viewport.set([0, 0, this.width, this.height]);

        this.context.clear({
            color: 'rgba(0,0,0,0)', //options.showOverdrawInspector ? Color.black : clearColor,
            depth: 1,
        });
        this.clearStencil();

        this._showOverdrawInspector = false; //options.showOverdrawInspector;

        this.renderPass = "translucent";


        // For symbol layers in the translucent pass, we add extra tiles to the renderable set
        // for cross-tile symbol fading. Symbol layers don't use tile clipping, so no need to render
        // separate clipping masks
        const coords = sourceCache
            ? coordsDescending
            : undefined;

        this._renderTileClippingMasks(
            layer,
            sourceCache,
            sourceCache ? coordsAscending : undefined
        );
        this.renderLayer(this, sourceCache, layer, coords);

        const gl = this.context.gl;
        var pixel = new Uint8Array(4);
        gl.readPixels(
            point.x * window.devicePixelRatio,
            this.height - point.y * window.devicePixelRatio,
            1,
            1,
            gl.RGBA,
            gl.UNSIGNED_BYTE,
            pixel
        );

        return pixel;
    }

jbeuckm avatar Apr 08 '22 18:04 jbeuckm

This would be very useful, for generating interactive tooltip information for raster sources. My use case is generating a tooltip for a weather map.

Carlos-Carreno-Berlanga avatar May 19 '22 07:05 Carlos-Carreno-Berlanga

any updates?

mkaskov avatar Jun 01 '23 14:06 mkaskov

You can force the map to repaint after querying the color and it will render correctly. I didn't see any flickering or stuttering but it's not great performance wise when dragging the cursor over the map. For my use it was good enough to only query the color when the cursor has stopped moving to improve that.

let timeout
map.on("mousemove", e -> {
    clearTimeout(timeout)
    timeout = setTimeout(() => {
        //query the color for a layer created elsewhere
        const clr = map.colorForLayerPoint("layer_id", e.point)
        //draw the map to fix anything getting the color broke
        map.triggerRepaint()
        //use clr to do something interesting
        useColor(clr)
    }, 150)
})

If you want to use the color as a lookup as I did you also need to be careful how you add the layer to the map. Raster layers need resampling set to nearest and you need the latest version with the fix from #12577 to disable interpolation as best as possible.

const layer = {
    id: "layer_id",
    type: "raster",
    source: "source_id",
    paint: {
        "raster-resampling": "nearest"
    }
}

tredpath avatar Jun 20 '23 19:06 tredpath