gee-community / geemap

A Python package for interactive geospatial analysis and visualization with Google Earth Engine.
https://geemap.org
MIT License
3.47k stars 1.09k forks source link

shiny + geemap #1992

Closed cidreNMBU closed 5 months ago

cidreNMBU commented 6 months ago

Hello,

I am an R user who has been using the rgee package for a while, but unfortunately, it might not be longer maintained (see this issue. One thing that was great about rgee was the possibility of creating Shiny Applications using R Shiny thanks to the effort of Cesar of creating this template which unfortunately is no longer working.

However, Shiny for Python was released less than 2 years ago and I think it would be a good chance to create applications that use geemap in the backend. The issue is that it's not very straightforward to develop a Shiny Application that uses geemap because it's necesary to use a token or other authentication way.

I was wondering if you are interested in creating an automated pipeline like the one that was created for rgee (pipeline to deploy).

Best regards, Cidre

giswqs commented 6 months ago

@cidreNMBU Thank you for submitting the feature request. I have not followed the development of rgee for a while and I am surprised and sad to see that rgee is no longer actively maintained. I am not an R user and I don't know if I would be able to develop and maintain a shiny app for geemap.

However, I have recently discovered the py-maplibregl package, which supports Rshiny integration. I am working on adding the maplibre support for leafmap. I guess it should work for geemap as well. I will see if I can get a proof of concept soon.

Contributions are always welcome.

from maplibre import Map, MapContext, output_maplibregl, render_maplibregl
from maplibre.controls import Marker
from shiny import App, reactive, ui

app_ui = ui.page_fluid(
    output_maplibregl("maplibre", height=600),
    ui.div("Click on map to set a marker"),
)

def server(input, output, session):
    @render_maplibregl
    def maplibre():
        m = Map()
        return m

    @reactive.Effect
    @reactive.event(input.maplibre)
    async def coords():
        async with MapContext("maplibre") as m:
            print(input.maplibre())
            lng_lat = tuple(input.maplibre()["coords"].values())
            marker = Marker(lng_lat=lng_lat)
            m.add_marker(marker)
            m.add_call("flyTo", {"center": lng_lat})

app = App(app_ui, server)

if __name__ == "__main__":
    app.run()
cidreNMBU commented 6 months ago

Thank you for your quick answer @giswqs.

Actually, there's support and fantastic integration of ipyleaflet through the shinywidgets package. My feature request is related to support the deployment of the application maybe through shinyapps.io. Here I have a reproducible example of a simple app:

import ee
from shiny import App, reactive, render, ui
from shinywidgets import output_widget, render_widget
from faicons import icon_svg

ee.Initialize()

app_ui = ui.page_sidebar(
    ui.sidebar(
        ui.input_action_button(
            'button',
            'Generate map',
            icon = icon_svg('play')
        )
    ),
    ui.layout_column_wrap(
       output_widget('result')
    ),
    title    = "Test geemap",
    fillable = True,
)

def server(input, output, session):

    @reactive.calc
    @reactive.event(input.button)
    def generate_map():

        Map = geemap.Map(center=[40, -100], zoom=4)

        landsat7 = (
              ee.Image('LANDSAT/LE7_TOA_5YEAR/1999_2003')
              .select(['B1', 'B2', 'B3', 'B4', 'B5', 'B7'])
        )

        landsat_vis = {'bands': ['B4', 'B3', 'B2'], 'gamma': 1.4}

        Map.addLayer(landsat7, landsat_vis, "Landsat")

        hyperion = ee.ImageCollection('EO1/HYPERION').filter(ee.Filter.date('2016-01-01', '2017-03-01'))

        hyperion_vis = {
             'min': 1000.0,
             'max': 14000.0,
             'gamma': 2.5,
             }
        Map.addLayer(hyperion, hyperion_vis, 'Hyperion')

        return Map

    @output
    @render_widget
    def result():
        return generate_map()

app = App(app_ui, server)

The issue arises because when deploying the app it looks for the credentials file. In rgee this was the template content for deployment:

# 1. Create the credentials file
ee_Initialize()

# 2. Copy credentials file to the project folder
file_credentials <- sprintf("%s/credentials", dirname(rgee::ee_get_earthengine_path()))
file.copy(file_credentials, to = ".")

# 3. Set ShinyApps account info
# FIRST MODIFY LOCAL .Renviron!!
error_on_missing_name <- function(name){
  var <- Sys.getenv(name, unset=NA)
  if(is.na(var)){
    stop(paste0("cannot find ",name),call. = FALSE)
  }
  gsub("\"", '',var)
}

rsconnect::setAccountInfo(name   = error_on_missing_name("SHINY_ACC_NAME"),
               token  = error_on_missing_name("TOKEN"),
               secret = error_on_missing_name("SECRET"))

# 4. Run the application
rsconnect::deployApp(
  appFiles = c("app.R", "utils.R", "credentials"),
  appTitle = "rgee_app_demo",
  lint = FALSE
)

# 5. Delete EE credentials file
file.remove("credentials")

I suppose something similar can be achieved with geemap . I think the steps consist on:

  1. Get credentials file info
  2. Create a temporary copy on the Shiny App directory
  3. Set up the account information of shinyapps.io using environment variables
  4. Run the app
  5. Delete the copy of the credentials file

I am not sure if rsconnect for python has these built-in functions to create a deploy template like this one. As far as I know, the rsconnect package in Python uses the command line to deploy apps, but I am still a beginner in Python to know how to do this. So, do you think that it would be possible and a good idea to work in a template that helps to deploy an app with security (keeping account info in environment variables and managing credentials)?

giswqs commented 6 months ago

Great to hear that shinywidgets support ipyleaflet. Does shiny app supports setting environment? If yes, set the environment EARTHENGINE_TOKEN to the contents of the credentials file. geemap should be able to retrieve the env variable and generate the credentials file. See example at https://github.com/opengeos/solara-geemap

giswqs commented 5 months ago

Here is an example for using geemap with shinywidgets.

https://huggingface.co/spaces/giswqs/shiny-geemap image

Steps

  1. Follow the instructions here to create an Earth Engine service account.
  2. Create a private key for the service account
  3. Download the JSON key file
  4. Open the JSON key file and copy the entire content of the file and set it as the value for the environment vaiable EARTHENGINE_TOKEN
  5. Use geemap.ee_initialize(service_account=True) in your code to authenticate EE. Do NOT use ee.Authenticate() or ee.Initialize() in your code.

Notes

It apppears that shinywidgets have some issues. It can't render the geemap.Map(), but it works for ipyleaflet.Map(). Nevertheless, you can see the the EE authentication is no longer a problem. The EE image tile layer can be added the ipyleaflet map properly.

Error on client while running Shiny app Class ToggleButtonStyleModel not found in module @jupyter-widgets/controls@2.0.0