When running the code that builds the map, adds and removes points and the line using Positron IDE, everything works as expected. I've been trying to get things working for a while now, but to no avail. This includes adding marker groups with ipyleaflet and other approaches with the leafmap package.
import ipyleaflet as L
from shiny import App, reactive, ui
from shinywidgets import output_widget, render_widget
from numpy import random
CITIES = {
"New York": {"latitude": 40.7128, "longitude": -74.0060, "altitude": 33},
"London": {"latitude": 51.5074, "longitude": -0.1278, "altitude": 36},
"Paris": {"latitude": 48.8566, "longitude": 2.3522, "altitude": 35},
"Tokyo": {"latitude": 35.6895, "longitude": 139.6917, "altitude": 44},
"Sydney": {"latitude": -33.8688, "longitude": 151.2093, "altitude": 39},
"Los Angeles": {"latitude": 34.0522, "longitude": -118.2437, "altitude": 71},
"Berlin": {"latitude": 52.5200, "longitude": 13.4050, "altitude": 34},
"Rome": {"latitude": 41.9028, "longitude": 12.4964, "altitude": 21},
"Beijing": {"latitude": 39.9042, "longitude": 116.4074, "altitude": 44},
"Moscow": {"latitude": 55.7558, "longitude": 37.6176, "altitude": 156},
"Cairo": {"latitude": 30.0444, "longitude": 31.2357, "altitude": 23},
"Rio de Janeiro": {"latitude": -22.9068, "longitude": -43.1729, "altitude": 8},
"Toronto": {"latitude": 43.6511, "longitude": -79.3832, "altitude": 76},
"Dubai": {"latitude": 25.2769, "longitude": 55.2963, "altitude": 52},
"Mumbai": {"latitude": 19.0760, "longitude": 72.8777, "altitude": 14},
"Seoul": {"latitude": 37.5665, "longitude": 126.9780, "altitude": 38},
"Madrid": {"latitude": 40.4168, "longitude": -3.7038, "altitude": 667},
"Amsterdam": {"latitude": 52.3676, "longitude": 4.9041, "altitude": -2},
"Buenos Aires": {"latitude": -34.6037, "longitude": -58.3816, "altitude": 25},
"Stockholm": {"latitude": 59.3293, "longitude": 18.0686, "altitude": 14},
"Boulder": {"latitude": 40.0150, "longitude": -105.2705, "altitude": 1634},
"Lhasa": {"latitude": 29.6500, "longitude": 91.1000, "altitude": 3650},
"Khatmandu": {"latitude": 27.7172, "longitude": 85.3240, "altitude": 1400},
}
city_names = sorted(list(CITIES.keys()))
app_ui = ui.page_sidebar(
ui.sidebar(
ui.input_selectize(
"loc1", "Location 1", choices=city_names, selected="New York"
),
ui.input_selectize("loc2", "Location 2", choices=city_names, selected="London"),
ui.input_action_button("sample", "Sample point")
),
output_widget("map"),
title="Location Distance Calculator",
fillable=True,
class_="bslib-page-dashboard",
)
def server(input, output, session):
# Reactive values to store location information
loc1 = reactive.value()
loc2 = reactive.value()
locpoint = reactive.value()
# Update the reactive values when the selectize inputs change
@reactive.effect
def _():
loc1.set(CITIES.get(input.loc1()))
loc2.set(CITIES.get(input.loc2()))
@reactive.effect
@reactive.event(input.sample)
def _():
city = random.choice(list(CITIES.keys()), 1)[0]
locpoint.set(CITIES.get(city))
# Convenient way to get the lat/lons as a tuple
@reactive.calc
def loc1xy():
return loc1()["latitude"], loc1()["longitude"]
@reactive.calc
def loc2xy():
return loc2()["latitude"], loc2()["longitude"]
@reactive.calc
def locpointxy():
return locpoint()["latitude"], locpoint()["longitude"]
# For performance, render the map once and then perform partial updates
# via reactive side-effects
@render_widget
def map():
esri_sat = L.basemap_to_tiles(L.basemaps.Esri.WorldImagery)
esri_sat.name = "Esri.WorldImagery"
esri_sat.base = True
osm = L.basemap_to_tiles(L.basemaps.OpenStreetMap.Mapnik)
osm.name = "OpenStreetMap"
osm.base = True
m = L.Map(zoom=2, center=(0, 0), scroll_wheel_zoom=True, zoom_control = False, layers=[esri_sat, osm])
m.add(L.ZoomControl(position='topright'))
m.add(L.LayersControl(position='topleft'))
search_control = L.SearchControl(
position="topleft",
url='https://nominatim.openstreetmap.org/search?format=json&q={s}',
zoom=10,
auto_collapse = True
)
m.add(search_control)
return m
# Add marker for first location
@reactive.effect
def _():
update_marker(map.widget, loc1xy(), "loc1")
# Add marker for second location
@reactive.effect
def _():
update_marker(map.widget, loc2xy(), "loc2")
# Add marker for sample location
@reactive.effect
def _():
update_marker(map.widget, locpointxy(), "point")
# Add line between first and second location
@reactive.effect
def _():
update_line(map.widget, loc1xy(), loc2xy())
def update_marker(map: L.Map, loc: tuple, name: str):
remove_layer(map, name)
map.add(L.Marker(location=loc, draggable=False, name=name, title = name))
# Trying map.substitute() instead of map.remove() and map.add()
# m = L.Marker(location=loc, draggable=False, name=name)
# is_new_layer = True
# for layer in map.layers:
# if layer.name == name:
# map.substitute(layer, m)
# is_new_layer = False
# break
# if is_new_layer:
# map.add(m)
def update_line(map: L.Map, loc1: tuple, loc2: tuple):
remove_layer(map, "line")
map.add(
L.Polyline(locations=[loc1, loc2], color="blue", weight=2, name="line")
)
# Trying map.substitute() instead of map.remove() and map.add()
# name="line"
# is_new_layer = True
# for layer in map.layers:
# if layer.name == name:
# map.substitute(layer, L.Polyline(locations=[loc1, loc2], color="blue", weight=2, name="line"))
# is_new_layer = False
# break
# if is_new_layer:
# map.add(m)
def remove_layer(map: L.Map, name: str):
for layer in map.layers:
if layer.name == name:
map.remove(layer)
app = App(app_ui, server)
I am running the app on Ubuntu 24.04.3 LTS using Python 3.13.7 and the following packages.
anywidget==0.9.18
appdirs==1.4.4
asgiref==3.9.1
asttokens==3.0.0
branca==0.8.1
click==8.2.1
comm==0.2.3
decorator==5.2.1
executing==2.2.0
h11==0.16.0
htmltools==0.6.0
idna==3.10
ipyleaflet==0.20.0
ipython==9.4.0
ipython_pygments_lexers==1.1.1
ipywidgets==8.1.7
jedi==0.19.2
Jinja2==3.1.6
jupyter-leaflet==0.20.0
jupyter_core==5.8.1
jupyterlab_widgets==3.0.15
linkify-it-py==2.0.3
markdown-it-py==4.0.0
MarkupSafe==3.0.2
matplotlib-inline==0.1.7
mdit-py-plugins==0.5.0
mdurl==0.1.2
narwhals==2.1.2
numpy==2.3.2
orjson==3.11.2
packaging==25.0
parso==0.8.4
pexpect==4.9.0
platformdirs==4.3.8
prompt_toolkit==3.0.51
psygnal==0.14.1
ptyprocess==0.7.0
pure_eval==0.2.3
Pygments==2.19.2
python-dateutil==2.9.0.post0
python-multipart==0.0.20
questionary==2.1.0
setuptools==80.9.0
shiny==1.4.0
shinywidgets==0.7.0
six==1.17.0
sniffio==1.3.1
stack-data==0.6.3
starlette==0.47.2
traitlets==5.14.3
traittypes==0.2.1
typing_extensions==4.14.1
uc-micro-py==1.0.3
uvicorn==0.35.0
watchfiles==1.1.0
wcwidth==0.2.13
websockets==15.0.1
widgetsnbextension==4.0.14
xyzservices==2025.4.0
Hi! Thank you for the work you've already done on shinywidgets. I have a question related to ipyleaflet within shinywidgets. The following example is an adaptation of the map-distance template, aiming to update the map without needing to re-render it from scratch. When the app starts, everything works as expected, but strange behaviors begin to appear upon interaction. Let's go through them one by one:
When running the code that builds the map, adds and removes points and the line using Positron IDE, everything works as expected. I've been trying to get things working for a while now, but to no avail. This includes adding marker groups with ipyleaflet and other approaches with the leafmap package.
I am running the app on Ubuntu 24.04.3 LTS using Python 3.13.7 and the following packages.