Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

linking solara.Button() callback function to method of geemap.Map() instance #718

Open
gregorhd opened this issue Jul 22, 2024 · 3 comments

Comments

@gregorhd
Copy link

gregorhd commented Jul 22, 2024

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.

@giswqs
Copy link
Contributor

giswqs commented Jul 24, 2024

You can do it with just ipywidgets. See this example: https://github.com/opengeos/surface-water-app

@iisakkirotko
Copy link
Collaborator

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 possible1.

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

Footnotes

  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.

@gregorhd
Copy link
Author

gregorhd commented Jul 25, 2024

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants