0

I writing a jupyter notebook, which I would like to render using Voila to create a small web app/tool. What the tool does is to get one Geojson file containing many polygons from the user and return a ZIP file containing many GeoJSON files (a file per polygon). For example, if the user uploads a GeoJSON file with 20 polygons (they are all in the same file), then the output should be a ZIP file containing 20 separate GeoJSON files - file per polygon. I am able to do it locally and the ZIP file is saved as needed.

However, I would like to render it using Voila so that it can later work from anywhere, meaning the ZIP file will be created in-memory/on-the-fly/as-a-buffer (not sure which term is the accurate one) and then the user will be able to download the ZIP file, via automatic download, or by clicking on a button or with a pop-up window to download, it really does not matter here.

Here is a snippet of my code (let me know if it's not enough):

def on_button_clicked(event):
    with output:
        clear_output()
        df = gpd.GeoDataFrame().from_features(json.loads(upload.data[0])) # if the file is geojson
        display(HTML(f'<h4><left>There are {len(df)} polygons in the file</left></h4>'))
        
        # make results.zip in temp directory
        # https://gist.github.com/simonthompson99/362404d6142db3ed14908244f5750d08
        tmpdir = tempfile.mkdtemp()
        zip_fn = os.path.join(tmpdir, 'results.zip')
        zip_obj = zipfile.ZipFile(zip_fn, 'w')

        for i in range(df.shape[0]):

            if len(field_names_col.value)==0:
                field_name = f'field_{str(i+1)}'
            else:
                field_name = df[field_names_col.value][i]

            output_name = f'{field_name}.{output_format.value.lower()}'

            df.iloc[[i]].to_file(f'{tmpdir}\\{output_name}', driver=output_format.value)

        for f in glob.glob(f"{tmpdir}/*"):
            zip_obj.write(f, os.path.basename(f)) # add file to archive, second argument is the structure to be represented in zip archive, i.e. this just makes flat strucutre

        zip_obj.close()

button_send.on_click(on_button_clicked)

vbox_result = widgets.VBox([button_send, output])

The important part is near the end:

for f in glob.glob(f"{tmpdir}/*"):
    zip_obj.write(f, os.path.basename(f)) # add file to archive, second argument is the structure to be represented in zip archive, i.e. this just makes flat strucutre

zip_obj.close()

I iterate over the temporary separate files and create a temporary ZIP file (results.zip) stored in the zip_obj. How can I "push" this ZIP object to the user for download using Jupyter Notebook?

I tried using (just before or just after zip_obj.close()):

local_file = FileLink(os.path.basename(f), result_html_prefix="Click here to download: ")
display(local_file)

But I get an error when rendering it with Voila:

Path (results.zip) doesn't exist. It may still be in the process of being generated, or you may have the incorrect path.

For example, to save it locally I did:

with zipfile.ZipFile('c:/tool/results.zip', 'w') as zipf:
    for f in tmpdir.glob("*"):
      zipf.write(f, arcname=f.name)
user88484
  • 1,249
  • 1
  • 13
  • 34

1 Answers1

0

"I iterate over the temporary separate files and create a temporary ZIP file (results.zip) stored in the zip_obj. How can I "push" this ZIP object to the user for download using Jupyter Notebook?"

There's an example of making a download link that will show up in the Voila rendering here. (Learned of it from here, although the focus there was on file upload. The example covers downloading the results, too.) A simpler take on this is found at the bottom of this answer here, converted to your case:

%%html
<a href="./voila/static/the_archive.zip" download="demo.xlsx">Download the Resulting Files as an Archive</a>

These two options are illustrated for a different type of file in the bottom section (everything below the line break presently there) of the answer here.

Wayne
  • 6,607
  • 8
  • 36
  • 93
  • I had trouble with the temp folder in-memory ZIP file so I decided to save it locally to the current directory (where the jupyter notebook is found), and it works in the notebook but not when rendering using Voila. Inside the notebook, this code works: `HTML('Download ')` . The `HTML` is imported from from `IPython.core.display`. When I render this using Voila I get `500: Internal Server Error`. When I try: `HTML('Download ')` I get: `404 : Not Found You are requesting a page that does not exist!` – user88484 May 25 '22 at 08:16
  • Your location won't be the same as the example provided. You have to work out your path. Did you try the code versions that work in Voila with `./results.zip` or `results.zip` as the path? You need to use widgets or things compatible with widgets in the Voila rendering. Not all things that work in the notebook work in Voila. Usually though if it works in Voila, it works in the notebook though. Did you test some of the examples I provided and see they work in Voila? If those instances, if they work, try making something closer to what you have and see if it continues to work. – Wayne May 25 '22 at 15:20
  • 1
    I did try several solutions and I was able to make it work locally only through the jupyter notebook and only if the file was saved in the same folder as the notebook. I still can't get it to download via Voila. My final goal is to use it with Voila and MyBinder to run it from GitHub, and I guess over there the access to the file is quite different. I need some more time to get it to work – user88484 May 27 '22 at 12:07