Get ID of a MVTLayer-feature from coordinates (using pydeck and panel)

Hello,

I’m want access the ID of a feature in a MVTLayer (or even better would be all of its properties) which is inside a Panel pane. Here is working example (or open it in Colab):

import panel as pn
import pydeck
from functools import partial
pn.extension("deckgl")

view_state = pydeck.ViewState(
    latitude = 33.740,
    longitude = 35.905,
    zoom = 10,
    max_zoom = 11.49,
    pitch = 45,
    bearing = 0
)

layer = pydeck.Layer(
    type = 'MVTLayer',
    data = "https://raw.githubusercontent.com/bertcoerver/MVTile_tests/main/tpc_folder/{z}/{x}/{y}.pbf",
    get_line_color = [0, 255, 0],
    get_fill_color = [155, 0, 100],
    line_width_min_pixels = 1,
    pickable = True,
)

deck = pydeck.Deck(
    layers = [layer],
    initial_view_state = view_state,
    map_style = "light",
    )

pn_deck = pn.pane.DeckGL(deck)

def on_map_click(event, deck):

    print(deck.layers[0])
    print(event.obj.click_state)
    print(event.obj.click_state["index"])

    # How to find the ID thats shown in the tooltip

pn_deck.param.watch(partial(on_map_click, deck = deck), ["click_state"])

pn.Column(pn_deck)

When I hover over the map I can see the ID in the tooltip. When I click the map, there is a callback function (called on_map_click) in which I have access to the coordinates of the click, and the Deck.

So, given the variable deck (which contains the layer) and a coordinate, can I retrieve the corresponding ID (or properties)?

Thanks for any help!
Bert

Reviving this as I’m still interested in getting this working.

In the meantime I have found a “workaround” by using the clicked lat/lon/zoom to determine which tile (i.e. {z}/{x}/{y}.pbf) I’m clicking on, then downloading that specific tile (with a request.get), and then using the “index” (from click_state) to find the feature, then I can read the features properties. But this seems overly complicated.

In the Deck.GL docs, I can see that the MVTLayer has a method called getRenderedFeature.

Is there a way I can somehow use this method when there is a click-event and store its output into a Parameter? Any clues on how to implement this?

Found a solution (very happy about this :partying_face:)

I’ve added a event to the pn.pane.DeckGL that takes whatever is in the tooltip and puts that into a new Parameter.

import panel as pn
import param
from panel.custom import JSComponent, Child

class SuperDeckGL(JSComponent):

    child: pn.pane.DeckGL = Child(class_=pn.pane.DeckGL)

    tooltip = param.String(default="nothing")

    _esm = """
    export function render({ model }) {
      const map_pane = document.createElement("map_pane");
      const child = model.get_child("child")
      child.addEventListener("click", (ev) => {
        var data = child.shadowRoot.querySelector(".deck-tooltip").innerHTML
        model.send_msg(data)
      });
      map_pane.append(child)
      return map_pane
    }
    """

    def _handle_msg(self, msg):
        print(msg)
        self.param.update(tooltip = str(msg))

json_spec = {
    "initialViewState": {
        "latitude": 9.05,
        "longitude": 0.6,
        "pitch": 35,
        "zoom": 4,
    },
    "layers": [
        {
            "@@type": "MVTLayer",
            "getLineColor":[255, 10, 255, 150],
            "getFillColor":[0, 255, 0, 0],
            "id": "L1",
            "data": "https://storage.googleapis.com/fao-cog-data/footprints/L1/{z}/{x}/{y}.pbf",
            "pickable": True,
        },
    ],
    "mapStyle": "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json",
    "views": [{"@@type": "MapView", "controller": True}],
}

pane_ = pn.pane.DeckGL(
        json_spec, 
        height=300,
        width=600,
        tooltips={"html": "{properties.fid}"}
    )

pane = SuperDeckGL(child=pane_)

pn.serve(
    pane,
    port=8000 # otherwise this particular MVTLayer gives CORS error.
)
  • This is already really convenient for my application. Even better would be if I could figure out how to get the Deck instance so that I can call functions like getRenderedFeature on a layer.
  • I’m not so familiar with javascript, so not sure if this is the most efficient way to write the _esm code (e.g. for now the event seems to trigger twice).

Guess I cheered too soon, would really appreciate some help…

The code above is basically working and doing what I want, but when I try to put it into my application everything seems to break. Especially when I remove the width=600 and replace it with sizing_mode="stretch_width". Its like there is some styling logic missing, but I have no clues on how to add that?

I’m seeing the following error in my console:

  • [Error] Error rendering Bokeh items: – TypeError: undefined is not an object (evaluating 'this.deckGL.redraw') — panel.min.js:174:5109 TypeError: undefined is not an object (evaluating 'this.deckGL.redraw')
  • [Error] TypeError: undefined is not an object (evaluating 'this.deckGL.redraw')
  • [Warning] [bokeh 3.7.3] – "panel.models.layout.ColumnView(p1004) wasn't built properly" (bokeh.min.js, line 594)

Can you submit a GitHub issue on Panel? And do you have the latest panel?

Hi @ahuang11 yes I’m on 1.7.2.

So there is no nothing “wrong” with my code? I’m new to these JSComponents, so wasnt sure if this is a bug or if I’m making a mistake.

Opened an issue here: Problem when making a JSComponent that includes a pn.pane.DeckGL Child (breaking hv.DynamicMap updates) · Issue #8009 · holoviz/panel · GitHub