Skip to main content

streamlit-lonboard

A Streamlit custom component for lonboard — fast, GPU-accelerated geospatial visualization in Streamlit, powered by deck.gl and GeoArrow.

AI Slop This library was completely generated by the Claude Code Agent utilizing the latest models (Sonnet 5, Opus 5, and Fable 5). I needed this connector for another project, where I also tested its functionality. Every single line of code was reviewed by myself; overall, the code looked okay-ish to me. Nothing I would do myself, but it works and it works well enough. Of course, I would welcome proper first-party support by Lonboard to make this package obsolete. Until then, this slop may allow us to use Lonboard for Streamlit dashboards. If you hesitate to use AI-generated code, do NOT use this library!

Status: early development. Scatterplot/Path/Polygon/SolidPolygon/Column/PointCloud layers, multi-layer maps, click/hover picking, and view-state persistence across reruns all work. Heatmap is wired but untested. DGGS layers — H3, S2, A5, Geohash — and Arc layers work too, but they carry geometry in accessor columns (cell IDs / point pairs) rather than a bounding geometry column, so lonboard can't auto-compute a default view — pass an explicit view_state= (H3 can auto-center, but only if h3-py is installed). Trip layers render a single static frame (drive layer._current_time yourself, e.g. from a slider, for animation — no built-in animation loop). Text/Bitmap/BitmapTile/Raster layers aren't supported yet (Text is provisional upstream; Bitmap/Raster carry raster, not GeoArrow, data).

All four lonboard layer extensions work — PathStyleExtension, DataFilterExtension (pairs well with st.slider driving layer.filter_range), BrushingExtension, CollisionFilterExtension. st_lonboard() also forwards picking_radius, parameters, use_device_pixels, custom_attribution, map.controls (fullscreen/zoom/scale, on by default - GeocoderControl isn't supported), and hover tooltip= (bool or explicit column list). See examples/extensions_app.py.

pyarrow 25.0.0 is excluded (pyarrow>=14,!=25.0.0 in pyproject.toml): its bundled mimalloc 3.3.1 segfaults when libarrow is first loaded on a non-main thread that then exits — which is exactly how Streamlit runs every script. Known upstream as apache/arrow#50471 / microsoft/mimalloc#1287; no fixed release yet. If another dependency forces 25.0.0 on you, set ARROW_DEFAULT_MEMORY_POOL=system as a workaround. All Python versions ≥3.11 (including 3.14) are supported.

Install

uv add streamlit-lonboard

or via pip

pip install streamlit-lonboard

Why?

Streamlit's built-in DeckGL support (st.pydeck_chart) goes through pydeck, which serializes data as GeoJSON/JSON — slow to encode, slow to transfer, slow to parse, and impractical beyond ~100k features.

Lonboard instead moves data as Apache Arrow (GeoArrow) binary buffers that deck.gl can consume with zero parsing. But lonboard is built on anywidget / Jupyter widgets, which Streamlit does not support. The lonboard maintainers consider a Streamlit connector out of scope for lonboard itself, but support a third-party one — this project is that connector.

The previously suggested workarounds don't cut it:

  • Map.to_html() + st.components.v1.html: static snapshot, no bidirectionality, full re-render on every rerun, huge inlined HTML.
  • streamlit-deckgl: pydeck/JSON only — exactly the bottleneck we want to avoid.

How it works

lonboard Map/Layers (Python)          frontend (TypeScript)
  pyarrow.Table (GeoArrow)              apache-arrow: parse IPC
    → Arrow IPC bytes          ──────►    → @geoarrow/deck.gl-layers
  layer props → JSON                       → deck.gl + MapLibre basemap
        ▲                                       │
        └── picking / view state (bidi) ◄───────┘

Data crosses the Python↔browser boundary as raw Arrow IPC bytes via Streamlit's custom components v2 — no GeoJSON anywhere in the pipeline.

API

import geopandas as gpd
import streamlit as st
from lonboard import ScatterplotLayer
from streamlit_lonboard import st_lonboard

gdf = gpd.read_parquet("internet-speeds.parquet")
layer = ScatterplotLayer.from_geopandas(gdf, get_fill_color=[255, 0, 0])

result = st_lonboard(layers=[layer], height=600, key="map")
st.write("Clicked feature index:", result.clicked)

See examples/app.py for scatterplot/path/polygon, examples/dggs_app.py for H3 and Arc layers (the "geometry lives in accessor columns" case — see the status note above), and examples/extensions_app.py for layer extensions, tooltip=, and map controls/attribution.

Performance

Streamlit reruns your whole script on every interaction, so building this ScatterplotLayer from scratch happens again on every rerun unless you cache it. Wrap layer construction in @st.cache_resource:

@st.cache_resource
def build_layer():
    gdf = gpd.read_parquet("internet-speeds.parquet")
    return ScatterplotLayer.from_geopandas(gdf, get_fill_color=[255, 0, 0])

layer = build_layer()
result = st_lonboard(layers=[layer], height=600, key="map")

This matters more than it might look like: st_lonboard() memoizes its own Arrow serialization keyed on the layer object, so a cached layer skips re-serialization entirely on reruns that don't touch it (invalidated automatically if you mutate a layer's properties). Without @st.cache_resource, a fresh layer object is built every rerun and the cache never hits. See examples/app.py for a full example and IMPLEMENTATION_PLAN.md Phase 4 for the full performance investigation, including a genuinely surprising find: Streamlit's component runtime already skips re-parsing and re-rendering on the frontend entirely when a rerun's output is byte-for-byte unchanged (see benchmarks/RESULTS.md for measured numbers at 10k/100k/1M points) — so the main thing left to optimize is Python-side re-serialization, which is exactly what the cache above avoids.

Compression

st_lonboard(..., compression="auto" | "gzip" | None) (default "auto") gzips the Arrow payload above a 1MB threshold. Measure before relying on this — at 1M points it only shaved off ~11% (clustered and uniform-random data compressed about the same; gzip finds repeated byte sequences, not spatial/numeric proximity, so real GPS-precision coordinates don't compress much better than random ones) while costing ~900ms-1s of Python-side CPU plus ~200ms of browser-side decompression per rerun — a net loss on localhost or any reasonably fast link, and only a likely win on slow/high-latency connections where the transfer savings outweigh that added CPU time. See benchmarks/RESULTS.md for the numbers behind this. Pass compression=None to disable it outright.

vs. st.pydeck_chart and Map.to_html()

Measured across 10k-10M points (benchmarks/playwright_driver.py, benchmarks/payload_sizes.py; full numbers and methodology in benchmarks/RESULTS.md):

  • Wire size: st_lonboard's Arrow IPC payload is a consistent ~8.3x smaller than st.pydeck_chart's JSON at every scale tested (230MB vs. 1.9GB at 10M points).
  • Map.to_html() embedded via st.components.v1.html — the workaround people use today without a custom component — doesn't render at all, at any scale. Root cause: inside Streamlit's sandboxed srcdoc iframe, document.location.href is the opaque string "about:srcdoc", which breaks requirejs/anywidget's module-loading URL resolution; the actual widget bundle never loads and no error is shown. The same HTML renders fine served standalone (outside an iframe).
  • st.pydeck_chart itself renders fine interactively, but rendering timing wasn't reliably measurable under headless browser automation in our environment (an intermittent WebGL/GPU stall unrelated to pydeck's correctness) — reported as an environment limitation rather than forced.

Development

Managed with uv. A Hatchling build hook runs npm install && npm run build automatically whenever the package is built or synced, so uv sync/uv build produce a wheel with the frontend already bundled into src/streamlit_lonboard/frontend_dist/ (gitignored source-tree-side; only Node is required to build it, not to install the published wheel):

uv sync --extra dev
uv run streamlit run examples/app.py

If you edit the frontend, run cd frontend && npm run dev (watch build) or npm run build (one-off) yourself and refresh the browser tab — the build hook only runs when the package itself is (re)built (uv sync/uv build), not on every uv run.

uv build          # sdist + wheel into dist/
uv run pytest     # tests/test_serialize.py
uv run ruff check # lint

License

MIT for this project's own code (Python and frontend/src/). The built frontend_dist/index.js bundles compiled code from deck.gl, apache-arrow, maplibre-gl and their transitive dependencies under their own licenses (mostly MIT/BSD-3-Clause, with Apache-2.0 for apache-arrow and flatbuffers); a generated THIRD-PARTY-NOTICES.txt listing them and their license texts ships alongside it in every wheel.

Acknowledgements

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

streamlit_lonboard-0.2.0.tar.gz (2.6 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

streamlit_lonboard-0.2.0-py3-none-any.whl (2.6 MB view details)

Uploaded Python 3

File details

Details for the file streamlit_lonboard-0.2.0.tar.gz.

File metadata

  • Download URL: streamlit_lonboard-0.2.0.tar.gz
  • Upload date:
  • Size: 2.6 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for streamlit_lonboard-0.2.0.tar.gz
Algorithm Hash digest
SHA256 ac6d35e4044a46ccc4547b31f48ed2ae5d9d9395c35ec1866a055a6d6368c1e1
MD5 e250a2f5e4261cc2b73ae6defd7b2c59
BLAKE2b-256 9843320331894b4d24d8f16c1cf74c4187a5d44d61b34aeec69f3184e32cce53

See more details on using hashes here.

Provenance

The following attestation bundles were made for streamlit_lonboard-0.2.0.tar.gz:

Publisher: release.yml on relativityhd/streamlit-lonboard

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file streamlit_lonboard-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for streamlit_lonboard-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f168c1080677923ac558410d90b012a30dd18498f51a1a18f49b3a6da62aa9f2
MD5 88ba0ffd66474d39463d4714472a3e03
BLAKE2b-256 91407a7f8525109f54806052b4638a6f90b29fe2743cfe032ffbe21d89c736d0

See more details on using hashes here.

Provenance

The following attestation bundles were made for streamlit_lonboard-0.2.0-py3-none-any.whl:

Publisher: release.yml on relativityhd/streamlit-lonboard

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.2.1

2 files

This release

0.2.0 This release

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page