linking solara.Button() callback function to method of geemap.Map() instance
Hi there,
I'm trying to write a simple solara + geemap app where two solara.Button() elements, embedded in a separate row of a solara.Column() , cause the map, embedded in the row below the buttons, to be centered on two different coordinates when pressed.
import os
import ee
import geemap
import solara
# Get the Earth Engine token from the environment variable
earthengine_token = os.getenv('EARTHENGINE_TOKEN')
# Authenticate and initialize Earth Engine
if earthengine_token:
credentials = ee.ServiceAccountCredentials(None, key_data=earthengine_token)
ee.Initialize(credentials)
else:
ee.Authenticate(auth_mode='localhost')
ee.Initialize()
# Set zoom and coordinates
zoom = 15
# create a FeatureCollection of the two points
point1 = ee.Geometry.Point([28.902667, -2.633444])
point2 = ee.Geometry.Point([28.940827, -2.687268])
point1_feature = ee.Feature(point1, {'name': 'Point 1'})
point2_feature = ee.Feature(point2, {'name': 'Point 2'})
points = ee.FeatureCollection([point1_feature, point2_feature])
class Map(geemap.Map):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.add_data()
def add_data(self):
# Add points II and III points to the map
self.addLayer(points, {'color': 'black'}, "points II and III")
self.add_labels(
points,
"name",
font_size="10pt",
font_color="black",
font_family="arial",
font_weight="bold",
)
# Center the map on Point 2 on load
self.centerObject(ee.Feature(points.toList(points.size()).get(1)), zoom)
def on_button1_clicked(self, *args):
print("Button 1 clicked")
self.centerObject(ee.Feature(points.toList(points.size()).get(0)), zoom)
def on_button2_clicked(self, *args):
print("Button 2 clicked")
self.centerObject(ee.Feature(points.toList(points.size()).get(1)), zoom)
# not sure how else to have the buttons be able to access the class methods on_button1_clicked and on_button2_clicked, respectively
map_instance = Map()
@solara.component
def Page():
with solara.Column():
with solara.Columns([0,0,1]):
# call the class methods directly when clicking
solara.Button(label="Point 1", color='green', on_click=map_instance.on_button1_clicked)
solara.Button(label="Point 2", on_click=map_instance.on_button2_clicked)
solara.Markdown("""
<div style="text-align: right;">
... some HTML...
</div>
""")
map_instance.element(height="600px")
The UI elements all show up, and the print() calls are shown in the terminal, but the map doesn't center on the respectively other point.
solara 1.33.0 pyhd8ed1ab_0 conda-forge solara-assets 1.33.0 pyhff2d567_0 conda-forge solara-server 1.33.0 pyhff2d567_0 conda-forge solara-ui 1.33.0 pyhd8ed1ab_0 conda-forge geemap 0.32.1 pyhd8ed1ab_0 conda-forge
I'm testing this locally, but this will be running on a HuggingFace Docker image, similar to this example. Locally, I can get this to work with vanilla ipywidgets without solara but those don't seem to work when deployed on HF in combination with solara (or I'm not sure how to display an ipywidgets.widgets.VBox() as an element in the Page() function.
Something must be wrong in how I'm instantiating the Map.element() object or that it's maybe not quite the same as a Map() object as hinted at here.
Any hints would be much appreciated.
You can do it with just ipywidgets. See this example: https://github.com/opengeos/surface-water-app

Hey @gregorhd!
@giswqs proposes a very good solution, where you can work completely within the widget paradigm.
How to do this in Solara is a very good question; in the solara documentation page concerning ipywidgets libraries, as I think you noticed, we say
The map element object does not have an add_layer method. That is the downside of using the React-like API of Solara. We cannot call methods on the widget anymore.
So unfortunately I have to say doing this using the widget methods is not possible[^1].
How I would approach the problem you proposed (centering the map on different points) in Solara would be by using the center (and possibly zoom) arguments to Map. So I'd store (or get) the coordinates of all points into a list and then do something like:
active_point_index = solara.reactive(0)
def Page():
with solara.Column():
with solara.Columns([0,0,1]):
...
Map.element(center=points[active_point_index.value])
And use the buttons to set active_point_index
[^1]: Giving this some more thought, I guess you could do something with solara.use_effect and solara.get_widget. See for example the input_date component's use_close_menu-hook. But doing this can get quite complicated.
Thank you both for the helpful feedback. I did end up going back to vanilla ipywidgets, as part of the Map() class, as I couldn't get the trick with solara.reactive(0) to work.
@iisakkirotko I'll give the approach hinted at in your footnote a longer look as well, though it might be beyond me.
Nevertheless, the two projects really gel well together otherwise. Keep up the good work!