opengeos/solara-geospatial

Can't upload shape files to Solara-geospatial

kirimaru-jp opened this issue · 2 comments

As mentioned in this issue, I would like to develop a GEE web app allowing shape file upload (similar to this), hence I am trying to combine geemap and Solara. Your tutorial above worked flawlessly.

I am trying to adapt it to Solara, locally on my computer first:

import os
import ee
import geemap
import ipywidgets as widgets
from ipyleaflet import WidgetControl

import solara

zoom = solara.reactive(4)
center = solara.reactive([40, -100])


def get_vector(upload_widget, out_dir=None):
    import zipfile
    import glob

    if out_dir is None:
        out_dir = os.path.join(os.path.expanduser('~'), 'Downloads')
    if not os.path.exists(out_dir):
        os.makedirs(out_dir)

    vector = None

    try:
        [uploaded_file] = upload_widget.value
        file = upload_widget.value[0]
        name = file['name']
        content = file['content']
        out_file = os.path.join(out_dir, name)
        with open(out_file, "wb") as fp:
            fp.write(content)

        if name.endswith('.zip'):
            with zipfile.ZipFile(out_file, "r") as zip_ref:
                extract_dir = os.path.join(
                    out_dir, name[:-4] + "_" + geemap.random_string(3)
                )
                zip_ref.extractall(extract_dir)
                files = glob.glob(extract_dir + '/*.shp')
                if len(files) > 0:
                    shp = files[0]
                    vector = geemap.shp_to_ee(shp)
                else:
                    files = glob.glob(extract_dir + '/*.geojson')
                    if len(files) > 0:
                        geojson = files[0]
                        vector = geemap.geojson_to_ee(geojson)
        else:
            vector = geemap.geojson_to_ee(out_file)

    except Exception as e:
        print(e)

    return vector


class Map(geemap.Map):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.upload_local_data()
        self.add_layer_manager()
        self.add_inspector()

    def upload_local_data(self):
        # Add Earth Engine dataset
        dem = ee.Image('USGS/SRTMGL1_003')

        # Set visualization parameters.
        vis_params = {
            'min': 0,
            'max': 4000,
            'palette': ['006633', 'E5FFCC', '662A00', 'D8D8D8', 'F5F5F5'],
        }
        # Add Earth Engine layers to Map
        self.addLayer(dem, vis_params, 'SRTM DEM', True, 0.7)

        output_widget = widgets.Output(layout={'border': '1px solid black'})
        output_control = WidgetControl(widget=output_widget, position='bottomright')
        self.add_control(output_control)

        style = {'description_width': 'initial'}
        uploader = widgets.FileUpload(
            description='Upload data',
            accept='.zip, .json, .geojson',
            multiple=False,
            button_style='primary',
            style=style,
        )
        submit = widgets.Button(
            description='Display data', button_style='success', tooltip='Click me', style=style
        )
        reset = widgets.Button(
            description='Reset', button_style='warning', tooltip='Click me', style=style
        )

        with output_widget:
            print('Upload shapefile or \ngeojson as a zip file')
            display(uploader)
            display(submit)
            display(reset)

        def submit_clicked(b):
            if len(uploader.value) > 0:
                Map.default_style = {'cursor': 'wait'}
                try:
                    fc = get_vector(uploader)
                    layer_name = 'Layer ' + geemap.random_string(3)
                    Map.addLayer(fc, {}, layer_name)
                    Map.centerObject(fc)
                    uploader.value = ()
                except Exception as e:
                    print(e)
                Map.default_style = {'cursor': 'pointer'}
        submit.on_click(submit_clicked)

        def reset_clicked(b):
            Map.layers = Map.layers[:3]
            output_widget.clear_output()
            with output_widget:
                print('Upload shapefile or \ngeojson as a zip file')
                display(uploader)
                display(submit)
                display(reset)
            uploader.value = ()
        reset.on_click(reset_clicked)


@solara.component
def Page():
    with solara.Column(style={"min-width": "500px"}):
        # solara components support reactive variables
        # solara.SliderInt(label="Zoom level", value=zoom, min=1, max=20)
        # using 3rd party widget library require wiring up the events manually
        # using zoom.value and zoom.set
        Map.element(  # type: ignore
            zoom=zoom.value,
            on_zoom=zoom.set,
            center=center.value,
            on_center=center.set,
            scroll_wheel_zoom=True,
            add_google_map=True,
            height="700px",
        )
        solara.Text(f"Zoom: {zoom.value}")
        solara.Text(f"Center: {center.value}")

When I run the command solara run .\geemap_upload.py, I saw this error in the Terminal but the map was still displayed

ERROR:    nbextension repr_style/main not found

Then I clicked the "Upload data" button, select the file us_states.zip, then clicked the "Display data". Nothing showed up, and this error appeared in the Terminal

'FeatureCollection' object has no attribute 'layers'

If I click the "Reset" button, this error appeares

ERROR:    Uncaught exception: Traceback (most recent call last):
  File "D:\ProgramData\miniconda3\envs\geemap\lib\site-packages\ipywidgets\widgets\widget.py", line 189, in __call__
    local_value = callback(*args, **kwargs)
  File "~\geemap_upload.py", line 147, in reset_clicked
    Map.layers = Map.layers[:3]
TypeError: 'Tuple' object is not subscriptable

Could you please suggest any solution/workaround? Many thanks!

I thought that I found my mistakes, I should replace Map. by self. in the class Map, like the following code

class Map(geemap.Map):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.upload_local_data()
        self.add_layer_manager()
        self.add_inspector()

    def upload_local_data(self):
        # Add Earth Engine dataset
        dem = ee.Image('USGS/SRTMGL1_003')

        # Set visualization parameters.
        vis_params = {
            'min': 0,
            'max': 4000,
            'palette': ['006633', 'E5FFCC', '662A00', 'D8D8D8', 'F5F5F5'],
        }
        # Add Earth Engine layers to Map
        self.addLayer(dem, vis_params, 'SRTM DEM', True, 0.7)

        output_widget = widgets.Output(layout={'border': '1px solid black'})
        output_control = WidgetControl(widget=output_widget, position='bottomright')
        self.add_control(output_control)

        style = {'description_width': 'initial'}
        uploader = widgets.FileUpload(
            description='Upload data',
            accept='.zip, .json, .geojson',
            multiple=False,
            button_style='primary',
            style=style,
        )
        submit = widgets.Button(
            description='Display data', button_style='success', tooltip='Click me', style=style
        )
        reset = widgets.Button(
            description='Reset', button_style='warning', tooltip='Click me', style=style
        )

        with output_widget:
            print('Upload shapefile or \ngeojson as a zip file')
            display(uploader)
            display(submit)
            display(reset)

        def submit_clicked(b):
            if len(uploader.value) > 0:
                self.default_style = {'cursor': 'wait'}
                try:
                    fc = get_vector(uploader)
                    layer_name = 'Layer ' + geemap.random_string(3)
                    self.addLayer(fc, {}, layer_name)
                    self.centerObject(fc)
                    uploader.value = ()
                except Exception as e:
                    print(e)
                self.default_style = {'cursor': 'pointer'}
        submit.on_click(submit_clicked)

        def reset_clicked(b):
            self.layers = self.layers[:3]
            output_widget.clear_output()
            with output_widget:
                print('Upload shapefile or \ngeojson as a zip file')
                display(uploader)
                display(submit)
                display(reset)
            uploader.value = ()
        reset.on_click(reset_clicked)

However, when I clicked the "Reset" button continuously, this appears
solara-geospatial

Ok, so I see that the following commands

display(uploader)
display(submit)
display(reset)

are not necessary for the function reset_clicked and they caused the issue above.

Remove them and the problem was solved!