A comparison of Streamlit (Python) and R Shiny for building interactive map dashboards with DuckDB's ST_AsMVT function.
This project demonstrates on-demand vector tile generation from DuckDB to visualize millions of data points without crashing the browser or pre-generating tile pyramids. The test dataset contains 21 million buildings from Overture Maps (Netherlands).
Traditional approaches to mapping large datasets face challenges:
- GeoJSON: Crashes browsers with millions of features
- Pre-generated tiles: Expensive preprocessing, stale when data changes
- Tile servers (PostGIS): Requires running a separate database server
DuckDB's ST_AsMVT generates tiles on-demand in milliseconds from an embedded database file.
Both implementations display:
- Full-screen map with vector tiles served from DuckDB
- Reactive building count and total area statistics
- Style controls (color, opacity)
├── Shiny/ # R Shiny implementation
│ └── app.R
├── Streamlit/ # Python Streamlit implementation
│ ├── app.py
│ ├── tile_server.py
│ └── requirements.txt
├── download_nl_buildings.py # Download data from Overture Maps
├── build_indexed_db.py # Build indexed DuckDB database
└── README.md
| Aspect | Streamlit | Shiny |
|---|---|---|
| MapGL Support | Iframe workaround | Native via mapgl |
| Tile Server | Separate Flask process | Integrated httpuv |
| Map-App Communication | HTTP polling (1.5s delay) | Reactive input$map_bbox |
| Architecture | 2 processes | 1 process |
Conclusion: For map-centric dashboards, Shiny provided a more intuitive and reliable experience.
python download_nl_buildings.py
python build_indexed_db.py# Install dependencies
install.packages(c("shiny", "bslib", "colourpicker", "mapgl", "duckdb", "httpuv", "DBI"))
# Run app
shiny::runApp("Shiny")cd Streamlit
pip install -r requirements.txt
# Terminal 1: Start tile server
python tile_server.py
# Terminal 2: Start Streamlit
streamlit run app.py- Kyle Walker -
mapglR package - Max Gabrielsson - DuckDB spatial extension
- Overture Maps Foundation - Building data

