Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
a472cf4
Add latest packages
kylebarron Feb 18, 2026
e6d10b3
Bump async-geotiff
kylebarron Feb 19, 2026
7cc0411
update deck.gl-raster source
kylebarron Feb 19, 2026
08aa066
Define TMS and Projection traits
kylebarron Feb 19, 2026
2140e93
Add morecantile to mkdocs config
kylebarron Feb 19, 2026
32b8fe1
Define CRS and TMS on RasterLayer
kylebarron Feb 19, 2026
e710f93
initial wip of raster rendering
kylebarron Feb 19, 2026
58fd29b
fix fetching correct overview
kylebarron Feb 19, 2026
9b2bf4b
Improved typing
kylebarron Feb 19, 2026
87a069a
store converters on instance
kylebarron Feb 19, 2026
85a23df
try to fix types
kylebarron Feb 19, 2026
7619711
Force local resolution of deck and luma
kylebarron Feb 19, 2026
0ff9562
use local copy for all deck/luma modules
kylebarron Feb 19, 2026
50dc3c7
Fetch boundless
kylebarron Feb 20, 2026
41ceee4
check in example notebooks
kylebarron Feb 20, 2026
fb87bd4
bump to 0.3.0-beta.1 of async-geotiff
kylebarron Feb 20, 2026
d23afcf
use released packages
kylebarron Feb 20, 2026
04cf58c
rename from `from_geotiff`
kylebarron Feb 22, 2026
08effa6
bump versions
kylebarron Mar 20, 2026
d090aeb
Merge branch 'main' into kyle/deck.gl-raster-tmp
kylebarron Mar 20, 2026
762faf6
bump proj4
kylebarron Mar 20, 2026
d15d495
comment
kylebarron Mar 20, 2026
5e5fe72
fix comment
kylebarron Mar 20, 2026
ec6b315
example updates
kylebarron Mar 20, 2026
91122ea
re lock
kylebarron Mar 20, 2026
1a52849
Merge branch 'main' into kyle/deck.gl-raster-tmp
kylebarron Mar 25, 2026
9e3d721
Rename `render` to `render_tile`
kylebarron Mar 25, 2026
ae54385
rename `raster` extra to `geotiff`
kylebarron Mar 25, 2026
6752554
minimize deps
kylebarron Mar 25, 2026
142fb68
make class attributes private
kylebarron Mar 25, 2026
151caf9
comment
kylebarron Mar 25, 2026
2e95c61
Request cancellation
kylebarron Mar 25, 2026
951c94a
avoid needing to bind function
kylebarron Mar 25, 2026
81892f0
Don't parse ImageBitmap to ImageData
kylebarron Mar 25, 2026
10d2f48
docs on developing against local package
kylebarron Mar 25, 2026
14c1fa0
bump version
kylebarron Mar 25, 2026
5b3688a
fix type checker errors
kylebarron Mar 25, 2026
747e93c
Improve TS typing
kylebarron Mar 25, 2026
826ff9a
Merge branch 'main' into kyle/deck.gl-raster-tmp
kylebarron Mar 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions DEVELOP.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ All models on the TypeScript side are combined into a single entry point, which

Anywidget and its dependency ipywidgets handles the serialization from Python into JS, automatically keeping each side in sync.

### Developing against local packages

E.g. to test against a local copy of `deck.gl-raster`:

```bash
pnpm link ../deck.gl-raster/packages/*
```

You'll also want to ensure that deck.gl versions in both projects are pinned
exactly the same.

## Publishing

Push a new tag to the main branch of the format `v*`. A new version will be published to PyPI automatically.
Expand Down
22 changes: 22 additions & 0 deletions build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,28 @@ esbuild.build({
},
}),
],
// Force all packages (including those nested inside local file deps) to
// resolve to lonboard's own copies, preventing duplicate instances.
//
// This is useful when developing against a local copy of deck.gl-raster, to
// ensure we don't have duplicate luma.gl versions, which crashes the page.
alias: {
"@deck.gl/aggregation-layers": "./node_modules/@deck.gl/aggregation-layers",
"@deck.gl/core": "./node_modules/@deck.gl/core",
"@deck.gl/extensions": "./node_modules/@deck.gl/extensions",
"@deck.gl/geo-layers": "./node_modules/@deck.gl/geo-layers",
"@deck.gl/layers": "./node_modules/@deck.gl/layers",
"@deck.gl/mapbox": "./node_modules/@deck.gl/mapbox",
"@deck.gl/mesh-layers": "./node_modules/@deck.gl/mesh-layers",
"@deck.gl/react": "./node_modules/@deck.gl/react",
"@deck.gl/widgets": "./node_modules/@deck.gl/widgets",
"@luma.gl/constants": "./node_modules/@luma.gl/constants",
"@luma.gl/core": "./node_modules/@luma.gl/core",
"@luma.gl/engine": "./node_modules/@luma.gl/engine",
"@luma.gl/gltf": "./node_modules/@luma.gl/gltf",
"@luma.gl/shadertools": "./node_modules/@luma.gl/shadertools",
"@luma.gl/webgl": "./node_modules/@luma.gl/webgl",
},
platform: "browser",
loader: {
".worker.js": "text",
Expand Down
147 changes: 147 additions & 0 deletions examples/raster-cog-rgb.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 9,
"id": "1630622d-4dec-4d4b-8cf5-cac92c09d6d1",
"metadata": {},
"outputs": [],
"source": [
"from async_geotiff import GeoTIFF\n",
"from obstore.store import S3Store\n",
"from sidecar import Sidecar\n",
"\n",
"from lonboard import Map, RasterLayer"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "a51cfc55-ee99-482b-b7e9-f4de835b34f5",
"metadata": {},
"outputs": [],
"source": [
"BUCKET_URL = \"https://ds-wheels.s3.us-east-1.amazonaws.com\"\n",
"COG_URL = \"m_4007307_sw_18_060_20220803.tif\""
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "79b2c6fd-8a18-469c-9c06-760bc21da5d4",
"metadata": {},
"outputs": [],
"source": [
"store = S3Store.from_url(BUCKET_URL, skip_signature=True)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "a085dcf1-d5c4-446e-b89c-a4f20ec51359",
"metadata": {},
"outputs": [],
"source": [
"geotiff = await GeoTIFF.open(COG_URL, store=store)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "0fa65225-d058-489a-9187-78df46164906",
"metadata": {},
"outputs": [],
"source": [
"import io\n",
"\n",
"import numpy as np\n",
"from async_geotiff import Tile as GeoTIFFTile\n",
"from PIL import Image\n",
"\n",
"from lonboard.raster import EncodedImage\n",
"\n",
"\n",
"def render_rgb_tile(tile: GeoTIFFTile) -> EncodedImage:\n",
" # data is (bands, height, width), uint8\n",
" arr = tile.array.data # shape: (3, H, W)\n",
" img_arr = np.moveaxis(arr, 0, -1) # -> (H, W, 3)\n",
" img = Image.fromarray(img_arr, mode=\"RGB\")\n",
" buf = io.BytesIO()\n",
" img.save(buf, format=\"PNG\")\n",
" return EncodedImage(data=buf.getvalue(), mime_type=\"image/png\")"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "ace2d246-10b0-4886-8886-b38c232a2d10",
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"layer = RasterLayer.from_geotiff(geotiff, render=render_rgb_tile)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "c8a0a2c2-f3cd-4ac0-8789-fb2ca7a2cb8b",
"metadata": {},
"outputs": [],
"source": [
"sidecar = Sidecar(anchor=\"split-right\")"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "9d7ead9f-9a19-458d-ae01-62bf3a3d5812",
"metadata": {},
"outputs": [],
"source": [
"m = Map(layer, height=800)"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "78bc4b1e-38e5-41be-a3fb-d005a03735ac",
"metadata": {},
"outputs": [],
"source": [
"with sidecar:\n",
" display(m)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1b177230-d9d5-436c-b9ff-93185f7a8b67",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "lonboard",
"language": "python",
"name": "lonboard"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.7"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
192 changes: 192 additions & 0 deletions examples/raster-nlcd.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "bb7ec556-daa5-4f9e-9148-1779214b2773",
"metadata": {},
"source": [
"# Generic COG Rendering in Lonboard"
]
},
{
"cell_type": "markdown",
"id": "e9760672-bb84-4f01-b4b5-cc85c475ecea",
"metadata": {},
"source": [
"## Imports"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "bd2a90b2-bd6b-431b-8bc8-f2d432290626",
"metadata": {},
"outputs": [],
"source": [
"import io\n",
"\n",
"import numpy as np\n",
"from async_geotiff import GeoTIFF, Tile\n",
"from obstore.store import S3Store\n",
"from PIL import Image\n",
"from sidecar import Sidecar\n",
"\n",
"from lonboard import Map, RasterLayer\n",
"from lonboard.raster import EncodedImage"
]
},
{
"cell_type": "markdown",
"id": "9b9b0508-574b-4b5f-9227-297686294024",
"metadata": {},
"source": [
"## Create GeoTIFF object referencing remote S3 resource"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "8b46528c-674f-497f-802e-5071c81dbe02",
"metadata": {},
"outputs": [],
"source": [
"BUCKET_URL = \"https://s3.us-east-1.amazonaws.com/ds-deck.gl-raster-public\"\n",
"COG_URL = \"/cog/Annual_NLCD_LndCov_2024_CU_C1V1.tif\""
]
},
{
"cell_type": "markdown",
"id": "596e8a99-c008-4e68-989a-347c76906210",
"metadata": {},
"source": [
"Then we'll use Obstore and Async-GeoTIFF to open our remote file:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "03d9486b-3a5f-49ec-bcff-5a186fc8587e",
"metadata": {},
"outputs": [],
"source": [
"store = S3Store.from_url(BUCKET_URL, skip_signature=True)\n",
"geotiff = await GeoTIFF.open(COG_URL, store=store)"
]
},
{
"cell_type": "markdown",
"id": "a44d5694-a04e-4fb5-94db-0278aa0722b0",
"metadata": {},
"source": [
"## Create Render Callback\n",
"\n",
"Lonboard's COG support works by asynchronously fetching tiles _from Python_ and then transferring the tile to JavaScript for visualization.\n",
"\n",
"In this initial version of our support, we require the user to create a \"render callback\" that transforms the COG input to a PNG-formatted RGB tile.\n",
"\n",
"The benefit of this approach is that you can use _any Python code_ to perform the rendering characteristics you desire."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "1d1052fe-7d6d-4c36-a0d9-f2ad020f846c",
"metadata": {},
"outputs": [],
"source": [
"cmap_array = geotiff.colormap.as_array()\n",
"\n",
"\n",
"def render_paletted_tile(tile: Tile) -> EncodedImage:\n",
" # data is (1, height, width), uint8 — single band with palette indices\n",
" arr = tile.array.data[0] # shape: (H, W)\n",
"\n",
" # Add alpha channel: fully transparent where nodata, opaque elsewhere\n",
" alpha = np.full(arr.shape, 255, dtype=np.uint8)\n",
" if tile.array.nodata is not None:\n",
" alpha[arr == tile.array.nodata] = 0\n",
"\n",
" # Map palette indices to RGBA using fancy indexing\n",
" rgba = np.empty((*arr.shape, 4), dtype=np.uint8)\n",
" rgba[..., :3] = cmap_array[arr] # (H, W, 3)\n",
" rgba[..., 3] = alpha # (H, W)\n",
"\n",
" # Serialize to PNG\n",
" img = Image.fromarray(rgba, mode=\"RGBA\")\n",
" buf = io.BytesIO()\n",
" img.save(buf, format=\"PNG\")\n",
" return EncodedImage(data=buf.getvalue(), mime_type=\"image/png\")"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "519a6e8f-e768-4791-b0b9-ddade0e598a5",
"metadata": {},
"outputs": [],
"source": [
"layer = RasterLayer.from_geotiff(geotiff, render_tile=render_paletted_tile)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "2037c7ee-ea2f-4520-8d05-faae1dd4d31e",
"metadata": {},
"outputs": [],
"source": [
"m = Map(layer, height=1200)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "300c6cf3-3a41-4b0c-b373-0fbb0cc7dec6",
"metadata": {},
"outputs": [],
"source": [
"sidecar = Sidecar(anchor=\"split-right\")"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "107d4de1-8b49-4204-8e0c-2ff8d1de1f46",
"metadata": {},
"outputs": [],
"source": [
"with sidecar:\n",
" display(m)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d66f51a8-19a6-47c7-8efe-fb339f96947a",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "lonboard",
"language": "python",
"name": "lonboard"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.7"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Loading
Loading