Weird behaviour in adding geometry into folium pane

I want to add geometry dynamically into folium. However, I have difficulty understanding the behaviour.
The following code snippet generates two folium map. The left one will update dynamically while I select a different LineString or press the generate random line button. While the right one doesn’t update at all.

Moreover, the button click doesn’t refresh on the left but will refresh on the right hand side. I am quite confused.

import random
import folium
import param
from shapely.geometry import LineString
pn.extension()
class PanelFoliumMap(param.Parameterized):
    # action = param.Action(lambda x:self._update_map)
    lines = param.Selector([
        LineString([[random.uniform(0, 180), random.uniform(-90, 90)] for _ in range(3)]),
        LineString([[random.uniform(0, 180), random.uniform(-90, 90)] for _ in range(3)]),
        LineString([[random.uniform(0, 180), random.uniform(-90, 90)] for _ in range(3)])
    ])
    
    def __init__(self, **params):
        super().__init__(**params)
        self.map = folium.Map()
        self.folium_pane = pn.pane.plot.Folium(self.map, sizing_mode="stretch_both", min_height=500, margin=0)
        self.button = pn.widgets.Button(name='Generate random line')
        self.button.on_click(self._update_map)
        self.view = pn.Column(
            self.param,
            self.folium_pane,
            self.button,
            self.button.clicks,
            sizing_mode="stretch_both", height=500
        )
        

    def get_polyline(self):
        # lon = random.uniform(0, 180)
        # lat = random.uniform(-90, 90)
        # lon2 = random.uniform(0, 180)
        # lat2 = random.uniform(-90, 90)
        return LineString([[random.uniform(0, 180), random.uniform(-90, 90)] for _ in range(3)])
        
    
    def _update_map(self, event):
        folium.GeoJson(self.get_polyline()).add_to(self.map)
        # folium.LayerControl().add_to(self.map)
        self.folium_pane.object = self.map
    
    @param.depends('lines')
    def _udpate_map2(self):
        print('_update_map2')
        folium.GeoJson(self.lines).add_to(self.map)
        self.folium_pane.object = self.map
        return self.folium_pane

    @param.depends('lines', watch=True)
    def view2(self):
        return self._udpate_map2()
    
    def view3(self):
        return self.button.clicks

        
app = PanelFoliumMap()
pn.Row(app.view, app._udpate_map2, app.view3).show()

Hi @david6l34

Welcome to the community :+1:

From what I understand your questions are really how does param.depends work and why does it not work for me here?

Looking at your code a few things catch my eye

Issue 1

The two function below will both trigger and end up running folium.GeoJson(self.lines).add_to(self.map).

image

You are also combining an update of self.folium_pane.object with returning self.folium_pane. Normally your param.depends function would do one (with watch=True) or the other (without watch=True).

Issue 2

When you set self.folium_pane.object = self.map you are just assigning the map object that was already assigned. I.e. the folium_pane will not detect that its .object was changed. Thus it will not raise events to get the map updated. Instead of assigning the same object again you will have to run self.folium_pane.param.trigger("object") to trigger an update of the map.

Issue 3

The view3 method does not .depends on anything and the value self.button.clicks is 0 the first time the function is evaluated. So 0 will always be shown no matter how many times you click the button.

Example Solution

I don’t know exactly what you are trying to achieve. But hopefully something like the below example will help you.

import random

import folium
import panel as pn
import param
from shapely.geometry import LineString

ACCENT = "#F08080"

pn.extension(template="fast", theme="dark")
pn.state.template.param.update(
    site="Panel", title="Adding lines to a FOLIUM map",
    accent_base_color=ACCENT, header_background=ACCENT,
)


LINES = {
        "Line A": LineString([[random.uniform(0, 180), random.uniform(-90, 90)] for _ in range(3)]),
        "Line B": LineString([[random.uniform(0, 180), random.uniform(-90, 90)] for _ in range(3)]),
        "Line C": LineString([[random.uniform(0, 180), random.uniform(-90, 90)] for _ in range(3)])
}

class PanelFoliumMap(param.Parameterized):
    lines = param.Selector(LINES)
    add_random_line = param.Event()
    reset = param.Event()
    
    def __init__(self, **params):
        super().__init__(**params)
        self.folium_pane = pn.pane.plot.Folium(sizing_mode="stretch_both", min_height=500, margin=0)
        self.reset_map()
        self.view = pn.Column(
            pn.Param(self.param, sizing_mode="stretch_width", widgets={"add_random_line": {"button_type": "primary"}}, show_name=False),
            self.folium_pane,
            sizing_mode="stretch_both", height=500
        )
    
    def get_polyline(self):
        return LineString([[random.uniform(0, 180), random.uniform(-90, 90)] for _ in range(3)])
    
    @property
    def map(self):
        return self.folium_pane.object
        
    @param.depends("add_random_line", watch=True)
    def _update_map(self, event=None):
        folium.GeoJson(self.get_polyline()).add_to(self.map)
        self.folium_pane.param.trigger("object")
    
    @param.depends('lines', watch=True)
    def update_map(self):
        folium.GeoJson(self.lines).add_to(self.map)
        self.folium_pane.param.trigger("object")

    @param.depends('reset', watch=True)
    def reset_map(self):
        self.folium_pane.object = folium.Map()

        
app = PanelFoliumMap()
pn.Row(app.view).servable()
panel serve script.py

folium-app