An open API service indexing awesome lists of open source software.

https://github.com/opengeos/cog-tiler-wasm

Serverless Cloud Optimized GeoTIFF (COG) dynamic tiling in WebAssembly. TiTiler-style XYZ tiles, no backend, built on whitebox-wasm.
https://github.com/opengeos/cog-tiler-wasm

data-science geospatial raster wasm whiteboxtools

Last synced: 4 days ago
JSON representation

Serverless Cloud Optimized GeoTIFF (COG) dynamic tiling in WebAssembly. TiTiler-style XYZ tiles, no backend, built on whitebox-wasm.

Awesome Lists containing this project

README

          

# cog-tiler-wasm

[![CI](https://github.com/opengeos/cog-tiler-wasm/actions/workflows/ci.yml/badge.svg)](https://github.com/opengeos/cog-tiler-wasm/actions/workflows/ci.yml)
[![Live demo](https://img.shields.io/badge/demo-GitHub%20Pages-blue)](https://opengeos.github.io/cog-tiler-wasm/)

**Serverless, TiTiler-style XYZ tiling of Cloud Optimized GeoTIFFs, in
WebAssembly.** No backend, no GDAL, no PROJ - the map fetches COG byte ranges
directly and synthesizes `z/x/y` tiles client-side.

**[Live demo](https://opengeos.github.io/cog-tiler-wasm/)** - loads a sample
EPSG:3857 COG over HTTP range requests and renders it on a MapLibre map, all in
the browser. Paste any CORS- and range-enabled 3857 COG URL to try your own.

This crate is the **tiling brain**. It does the slippy-map math (tile -> source
pixel window), picks the right COG overview level, and renders a decoded window
into an RGBA tile (rescale + colormap + nodata alpha). It deliberately does
**not** parse COGs or do network I/O - that is delegated to
[`whitebox-wasm`](https://github.com/opengeos/whitebox-wasm)'s `CogStream`,
which already implements a pure-Rust codec stack (Deflate/LZW/JPEG/WebP/…) and
HTTP range streaming. The two compose into a complete tiler:

```
CogStream (whitebox-wasm) CogTiler (this crate)
───────────────────────── ─────────────────────
geo_transform(), levels_json() ──▶ new(gt, w, h, epsg, nodata, levels)
pixel_window_for_tile(z,x,y) ─▶ {level,x,y,w,h}
tiles_for_window(level,x,y,w,h) ◀── (JS fetches byte ranges, decodes tiles)
decode_tile_f64(...) ──▶ render(window, w, h, min, max, cmap) ─▶ RGBA
```

The result is a TiTiler-class viewer with **zero hosting cost**: wire it to a
MapLibre custom protocol and the browser does everything.

## Why this is feasible without a server

A dynamic tile server (TiTiler = FastAPI + rio-tiler + GDAL) does five things:
read a COG by HTTP **range request**, decode the relevant internal tiles,
resample to the requested XYZ tile, apply rescale/colormap/nodata, and encode.
Every one of those has a pure-Rust, WASM-clean implementation today - the two
historically hard parts (a C-free codec stack and CRS handling) are already
solved in `whitebox-wasm`. This crate adds the thin layer on top: mercator
addressing, overview selection, resampling, and rendering.

## Status

**v1 (this scaffold):** EPSG:3857 sources, single-band rendering, built-in
colormaps, MapLibre demo. The crate builds to `wasm32-unknown-unknown` and ships
a `wasm-pack` web package.

See [Roadmap](#roadmap) for warping, multi-band, and edge/WASI serving.

## Build

```bash
rustup target add wasm32-unknown-unknown
cargo install wasm-pack
wasm-pack build crates/cog-tiler-wasm --release --target web --out-dir pkg
```

### Run the demo locally

```bash
npm run dev # builds the wasm, assembles demo/, serves http://localhost:8000/
```

`npm run dev` builds the wasm into `demo/`, copies in `cog-tiler.js` + the sample,
and starts a zero-dependency static server with **HTTP range support** (which the
tile streaming needs - the stdlib `python -m http.server` does not do ranges).
Set `PORT` to change the port. No `npm install` is required (the dev scripts use
only Node built-ins; the demo loads its peer deps from a CDN via an import map).

The published [GitHub Pages demo](https://opengeos.github.io/cog-tiler-wasm/) is
built the same way by `.github/workflows/pages.yml`.

## Usage (reusable module)

[`cog-tiler.js`](cog-tiler.js) is the package's main entry. It wraps the wasm
tiler + `whitebox-wasm` and handles EPSG:3857 sources, on-the-fly **warping** of
any projected/4326 COG to Web Mercator, and **paletted (categorical)** rendering -
so apps import it instead of copying the demo.

It ships **inside the npm package** (`main`/`module` -> `cog-tiler.js`); the raw
wasm tiler is also available at the `cog-tiler-wasm/wasm` subpath. Install it
alongside its peer dependencies:

```bash
npm install cog-tiler-wasm whitebox-wasm proj4 geotiff geotiff-geokeys-to-proj4 maplibre-gl
```

```js
import maplibregl from "maplibre-gl";
import { init, openCog, registerCogProtocol } from "cog-tiler-wasm";

await init(); // load the wasm modules once

let source = null;
// The protocol resolves the active source + render settings per tile.
registerCogProtocol(maplibregl, "cog", () => ({
source,
render: { min: 0, max: 3000, colormap: "viridis" }, // ignored for paletted COGs
}));

source = await openCog(url); // EPSG:3857 fast path, or warped if projected/4326
// openCog also accepts a local raster: a File (e.g. from ),
// Blob, ArrayBuffer, or Uint8Array - read in memory, no server needed.
map.addSource("cog", { type: "raster", tiles: ["cog://{z}/{x}/{y}"], tileSize: 256 });
map.addLayer({ id: "cog", type: "raster", source: "cog" });

// source.crsLabel, source.levels, source.hasPalette, source.boundsLonLat
// and source.renderTileRGBA(z, x, y, render) / renderTilePNG(...) are also exposed.
```

### TiTiler-style COG API

`CogSource` mirrors the read endpoints of
[TiTiler's COG API](https://developmentseed.org/titiler/endpoints/cog/),
client-side (works on projected/paletted sources too, via the warp path):

```js
// Metadata / read
src.info(); // /cog/info -> bounds, count, dtype, nodata, overviews, min/maxzoom, ...
src.infoGeoJSON(); // /cog/info.geojson -> GeoJSON Feature (bbox polygon + info)
src.tilejson(); // /cog/tilejson.json -> Mapbox TileJSON document
await src.point(lon, lat); // /cog/point -> band value(s) at a WGS84 coordinate
await src.statistics({ maxSize }); // /cog/statistics -> per-band min/max/mean/std/count/
// valid_percent/median/percentiles/histogram (from an overview)

// Image generation (all accept render params below)
await src.previewPNG({ maxSize: 1024 }); // /cog/preview -> PNG bytes
await src.bboxPNG([minLon, minLat, maxLon, maxLat]); // /cog/bbox -> PNG bytes
await src.preview(opts); // -> { width, height, rgba } (bbox() likewise)

// Render params (on tiles, preview, bbox):
// bidx: 1-based bands; one -> colormap/palette, three -> RGB composite
// rescale: [[min,max], ...] per band, or [min,max]; or min/max shorthand
// colormap: one of colormaps() (viridis, magma, plasma, inferno, cividis,
// turbo, terrain, blues, greens, reds, rdylgn, spectral, gray)
// reversed: sample the colormap back-to-front (single-band)
// stretch: transfer curve "linear" | "sqrt" | "log" (applied after rescale)
// gamma: power-law gamma (1 = off; applied after the stretch)
// nodata: override the transparency value
// opacity: output alpha multiplier 0..1
await src.renderTilePNG(z, x, y, { bidx: [4, 3, 2], rescale: [0, 3000] }); // false-color RGB
import { colormaps } from "cog-tiler-wasm";
```

The render pipeline (nodata -> rescale -> stretch -> gamma -> colormap, plus
reverse/opacity) matches the controls of GPU raster viewers such as
[maplibre-gl-raster](https://github.com/opengeos/maplibre-gl-raster), so
`cog-tiler-wasm` can serve as a CPU/WASM rendering backend for the same UI - the
panel (band, rescale, colormap, curve, gamma, opacity) maps directly onto these
params and re-renders by re-requesting tiles.

Server-only endpoints (`/map.html`, `WMTSCapabilities.xml`, `/validate`,
`/stac`) are out of scope for a client-side library; band-math `expression` is
on the [roadmap](#roadmap).

For a no-build page, map the peer deps with an import map (see
[`demo/index.html`](demo/index.html)):

```html

{ "imports": {
"whitebox-wasm": "https://esm.sh/whitebox-wasm@0.4.0",
"proj4": "https://esm.sh/proj4@2.20.9",
"geotiff": "https://esm.sh/geotiff@3.0.5",
"geotiff-geokeys-to-proj4": "https://esm.sh/geotiff-geokeys-to-proj4@2024.4.13"
} }

```

### Low-level Rust API

The wasm crate (`CogTiler`) is the 3857 tiling brain underneath `cog-tiler.js`:
`pixel_window_for_tile(z, x, y)` maps a tile to a source-pixel window/overview,
and `render(window, w, h, min, max, colormap, nodata_alpha)` rasterizes an
assembled f64 window to RGBA. See [`cog-tiler.js`](cog-tiler.js) for the
window-assembly and warp loops built on top.

A runnable MapLibre example (custom `cog://` protocol) is in
[`demo/index.html`](demo/index.html).

## API

`version()`, `tile_bounds_3857(z, x, y) -> [minx,miny,maxx,maxy]`.

**`new CogTiler(geo_transform, width, height, epsg, nodata, levels_json)`**
- `geo_transform` - 6-element GDAL affine of the full-res raster (`Float64Array`)
- `width`/`height` - full-resolution pixel dimensions
- `epsg` - source CRS; must be `3857` in v1
- `nodata` - optional nodata value (`undefined`/`NaN` = none)
- `levels_json` - JSON array of level descriptors, finest level first; only
`width`/`height` are read (extra `whitebox-wasm` fields are ignored)

Properties: `epsg`, `num_levels`.

**`pixel_window_for_tile(z, x, y)`** -> `{ level, x, y, w, h, level_width, level_height, empty }`
The overview level and pixel window covering the tile. `empty` is true when the
tile lies outside the raster.

**`render(pixels, win_w, win_h, min, max, colormap, nodata_alpha)`** -> `Uint8Array`
A `256*256*4` RGBA tile. `pixels` is the decoded row-major `f64` window;
`colormap` is `"viridis" | "magma" | "terrain" | "gray"`. Empty windows render
fully transparent.

## Roadmap

- **TiTiler COG API parity** - done: `info`, `info.geojson`, `tilejson`,
`point`, `statistics`, `preview`, `bbox`, `bidx`/RGB band selection, and 13
colormaps (see [above](#titiler-style-cog-api)). Next: band-math
**`expression`** and `color_formula`.
- **Warping** of projected/4326 sources and **paletted/categorical** rendering
are done in [`cog-tiler.js`](cog-tiler.js) (proj4js + geotiff.js). **Planar**
(`INTERLEAVE=BAND`) multi-band COGs are read per-band via geotiff.js too, since
whitebox-wasm's streaming decoder is chunky-only. Next: planar support
**upstream in `whitebox-wasm`** (and exposing its proj string + color table) to
drop the geotiff.js dependency, then move the warp into the Rust crate
(`proj4rs`).
- **Edge / WASI serving** - run the same module as a serverless XYZ endpoint
near the data, not only in the browser.
- **STAC / mosaics** - multi-asset orchestration.

## Releasing

The npm package bundles the wasm tiler **and** the `cog-tiler.js` module
(assembled by [`scripts/prepare-pkg.mjs`](scripts/prepare-pkg.mjs)). To cut a
release, push a `vX.Y.Z` tag; [`release.yml`](.github/workflows/release.yml)
builds, assembles, and publishes to npm via Trusted Publishing (OIDC, no token):

```bash
git tag v0.2.0 && git push origin v0.2.0
```

One-time setup: configure the package's Trusted Publisher on npmjs.com (package
-> Settings -> Trusted Publisher) to this repo + `release.yml`.

## License

[MIT](LICENSE) © OpenGeos.