PyDeck Basemap Update in Panel Application

Hello,

I’m trying to build a Panel application centered on a PyDeck map with buttons in the app that trigger particular basemap changes in the PyDeck map. I know how to update the basemap in PyDeck but I don’t know how to trigger a reload of the map in Panel while the application is running. Can someone clarify how that is done? Here’s a Google Colab notebook with a simple example that illustrates how I accomplish the basemap update.

Hi @diehl

Have you tried updating the value of ‘my_deckgl_pane.object’? This will trigger a complete update.

Please post a minimum, reproducible example. This will help understand and answer you exact issue. Thanks.

1 Like

Hi @Marc,

I have not because I had no idea about how to trigger the update in Panel. The notebook I shared represented the frontier of my current knowledge. Let me see if I can understand your proposal with a little digging and then I’ll circle back. Thanks!

Hi @Marc,

As promised, here’s a reproducible example that highlights my question. I put together a simple Panel application with a selector to update the PyDeck pane’s map style. I can confirm the selector is updating the map style but that change in state is not triggering an update of the PyDeck pane. I’m hoping you can clarify for me what’s required to trigger that update. Happy to add any additional context that is helpful.

Chris

And to make things easier, here’s a slightly modified version of this demo in Google Colab.

@diehl I am also trying to get bidirectional interaction working on_click using PyDeck and your example was helpful and reproducible. I have tried updating the .object as Mr. @Marc has suggested but that does not work. I have also tried updating through global variables and other hacks but there is not a lot of doc online. @philippjfr the bidirectional map interaction is a massive feature lacking in Python frameworks, I am trying to get something working using Bokeh and the Tap Stream but if you could help us out it would allow us to build a bunch of very useful apps that are now reserved to pure JS and are limited there due to JS lack of data science libraries. Folium, ipyleaflet, hvplot and leafmap all do not allow for bidirectional interaction and whoever allows this first would get the monopoly on climate science and a lot of BI and other fields. Please help :slight_smile:

1 Like

@StuckDuckF I so appreciate you weighing in here! I am frankly stunned that bidirectional interaction is not viewed as absolutely critical. And I could not agree more with your comment that whoever realizes this first will unlock incredible value and opportunity for many waiting to build more complex, interactive sensemaking tools. I realized this week that I’ve been assuming all along that I could add this type of interactivity to my Panel application. So I’ve been primarily focused on data conditioning tasks in support of what I wanted to build. Now I’m realizing that I need to bring more focus to finding a way to clear this hurdle in Panel or switch to another Python-based web application framework. I’m hoping I don’t have to do the latter!

@philippjfr @Marc Please let us know if there’s any way we can be helpful in finding a way past this roadblock. I’m very motivated. :wink:

1 Like

I went off from the Panel DeckGL example and managed to bind a callback (working example below although you might have to download the JSOn from the link), but for some wird reason I can not make the hvplot object update with the callback in the output. @Marc if you have any ideas other than wrapping the hvplot in a pn.panel let me know sir :slight_smile:

import hvplot.pandas
import pandas as pd
import panel as pn
import pydeck as pdk
from bokeh.sampledata.degrees import data as deg

pd.options.mode.chained_assignment = None
hvplot.extension('plotly')
pn.extension("deckgl", stretch_width_policy="both")


DATA_URL = "https://raw.githubusercontent.com/ajduberstein/geo_datasets/master/biergartens.json"
ICON_URL = (
    "https://upload.wikimedia.org/wikipedia/commons/c/c4/Projet_bi%C3%A8re_logo_v2.png"
)
data = pd.read_json(DATA_URL)

INITIAL_HVPLOT = pn.panel(deg.hvplot.line(
    x="Year",
    y=[
        "Art and Performance",
        "Business",
        "Biology",
        "Education",
        "Computer Science",
    ],
    value_label="% of Degrees Earned by Women",
    legend="top",
    height=500,
    title="Initial Title",
))

INITIAL_VIEW_STATE = pdk.ViewState(
    latitude=51, longitude=7.4, zoom=9, max_zoom=16, pitch=0, bearing=0
)

icon_data = {
    "url": ICON_URL,
    "width": 242,
    "height": 242,
    "anchorY": 242,
}

data["icon_data"] = None
for i in data.index:
    data["icon_data"][i] = icon_data

view_state = pdk.data_utils.compute_view(data[["lon", "lat"]], 0.1)

icon_layer = pdk.Layer(
    type="IconLayer",
    data=data,
    get_icon="icon_data",
    get_size=4,
    size_scale=15,
    get_position=["lon", "lat"],
    pickable=True,
)

deck_gl_obj = pdk.Deck(
    layers=[icon_layer],
    initial_view_state=INITIAL_VIEW_STATE,
    tooltip={"text": "{tags}"},
)
deck_gl = pn.pane.DeckGL(deck_gl_obj, sizing_mode="stretch_width", height=600)


def panel_watcher(events):
    INITIAL_HVPLOT.object.opts(title=f"Map Clicked at {events.new.get('coordinate')}")

deck_gl.param.watch(panel_watcher, "click_state")
out_row = pn.Row(deck_gl, INITIAL_HVPLOT)
out_row.servable()

For those who had the same hiccup trying to access the properties of the Event

Event(
    what='value', 
    name='click_state', 
    obj=DeckGL(
        dict, 
        click_state={'coordinate': [-2.9896073...}, 
        height=600, 
        hover_state={'coordinate': [-2.9896073...}, 
        mapbox_api_key='pk.eyJ1IjoicGFuZWxvcmciLC..., 
        sizing_mode='stretch_width', 
        view_state={'width': 1353, ...}), 
        cls=DeckGL(
            dict, 
            click_state={
                'coordinate': [-2.9896073...}, 
                height=600, 
                hover_state={
                    'coordinate': [-2.9896073...}, 
                    mapbox_api_key='pk.eyJ1IjoicGFuZWxvcmciLC..., 
                    sizing_mode='stretch_width', 
                    view_state={'width': 1353, ...
                }
            ), 
    old={}, 
    new={
        'coordinate': [-2.9896073038243594, 52.02611611860193], 
        'lngLat': [-2.9896073038243594, 52.02611611860193], 
        'index': -1}, 
    type='changed'
)

I am going to work on a more impressive demo and report back.

After many failures, I managed to find a way through. Here’s an updated demo on Google Colab that now shows the basemap update working. The key to trigger the update was to assign the update to map.object as opposed to an element within the object. I don’t claim to understand why that’s key but that was what did the trick. Onward!