I have a shiny
app that displays a number of layers on a map using folium
.
I want to give the user the possibility to download one of the layers (a linestring geodataframe) as a geopackage file.
Here is the code I have so far:
# relevant imported packages
from shiny import App, render, ui, reactive
import pandas as pd
import geopandas as gpd
# code in the ui for the download button
ui.download_button(id="downloadRoute", label="Get the route file"),
# code in the server function
@output
@render.download
def downloadRoute():
result_value = result()
route = result_value['route_detailed']
with io.BytesIO() as buffer:
route.to_file(buffer, driver='GPKG')
buffer.seek(0)
return ui.download(filename="my_route.gpkg", file=buffer.read(), content_type="application/geopackage+sqlite3")
I have verified that route
is actually a valid geodataframe. If I download it outside shiny, it is a valid geopackage.
In shiny, clicking the download button doesn't do anything on the UI. It only prints this error in the console:
500 Internal Server Error
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "pyogrio\_io.pyx", line 1268, in pyogrio._io.ogr_create
File "pyogrio\_err.pyx", line 177, in pyogrio._err.exc_wrap_pointer
pyogrio._err.CPLE_OpenFailedError: sqlite3_open(<_io.BytesIO object at 0x000002494BFCE3E0>) failed: unable to open database file
What could be the thing I am doing wrong? Are there other ways I can achieve this?
As a side note, the @output
decorator is no longer necessary since v0.6.0. And regarding the download issue, you (must?) yield the value of the buffer in the render.download
, something like below :
from io import BytesIO
from shiny import App, render, ui
import geopandas as gpd
from shapely import LineString
app_ui = ui.page_fluid(
ui.download_button(id="downloadRoute", label="Download as GPKG")
)
route = gpd.GeoDataFrame(geometry=[LineString([(0, 0), (1, 0)])])
def server(input):
@render.download(
filename="file.gpkg",
# media_type="application/geopackage+sqlite3", # optional
)
def downloadRoute():
with BytesIO() as buff:
route.to_file(buff, driver="GPKG")
yield buff.getvalue()
app = App(app_ui, server)
Demo at shinylive: