cog-tiler-wasm 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +239 -0
- package/cog-tiler.d.ts +90 -7
- package/cog-tiler.js +414 -67
- package/cog_tiler_wasm.d.ts +16 -0
- package/cog_tiler_wasm.js +61 -0
- package/cog_tiler_wasm_bg.wasm +0 -0
- package/package.json +5 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OpenGeos
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# cog-tiler-wasm
|
|
2
|
+
|
|
3
|
+
[](https://github.com/opengeos/cog-tiler-wasm/actions/workflows/ci.yml)
|
|
4
|
+
[](https://opengeos.github.io/cog-tiler-wasm/)
|
|
5
|
+
|
|
6
|
+
**Serverless, TiTiler-style XYZ tiling of Cloud Optimized GeoTIFFs, in
|
|
7
|
+
WebAssembly.** No backend, no GDAL, no PROJ - the map fetches COG byte ranges
|
|
8
|
+
directly and synthesizes `z/x/y` tiles client-side.
|
|
9
|
+
|
|
10
|
+
**[Live demo](https://opengeos.github.io/cog-tiler-wasm/)** - loads a sample
|
|
11
|
+
EPSG:3857 COG over HTTP range requests and renders it on a MapLibre map, all in
|
|
12
|
+
the browser. Paste any CORS- and range-enabled 3857 COG URL to try your own.
|
|
13
|
+
|
|
14
|
+
This crate is the **tiling brain**. It does the slippy-map math (tile -> source
|
|
15
|
+
pixel window), picks the right COG overview level, and renders a decoded window
|
|
16
|
+
into an RGBA tile (rescale + colormap + nodata alpha). It deliberately does
|
|
17
|
+
**not** parse COGs or do network I/O - that is delegated to
|
|
18
|
+
[`whitebox-wasm`](https://github.com/opengeos/whitebox-wasm)'s `CogStream`,
|
|
19
|
+
which already implements a pure-Rust codec stack (Deflate/LZW/JPEG/WebP/…) and
|
|
20
|
+
HTTP range streaming. The two compose into a complete tiler:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
CogStream (whitebox-wasm) CogTiler (this crate)
|
|
24
|
+
───────────────────────── ─────────────────────
|
|
25
|
+
geo_transform(), levels_json() ──▶ new(gt, w, h, epsg, nodata, levels)
|
|
26
|
+
pixel_window_for_tile(z,x,y) ─▶ {level,x,y,w,h}
|
|
27
|
+
tiles_for_window(level,x,y,w,h) ◀── (JS fetches byte ranges, decodes tiles)
|
|
28
|
+
decode_tile_f64(...) ──▶ render(window, w, h, min, max, cmap) ─▶ RGBA
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The result is a TiTiler-class viewer with **zero hosting cost**: wire it to a
|
|
32
|
+
MapLibre custom protocol and the browser does everything.
|
|
33
|
+
|
|
34
|
+
## Why this is feasible without a server
|
|
35
|
+
|
|
36
|
+
A dynamic tile server (TiTiler = FastAPI + rio-tiler + GDAL) does five things:
|
|
37
|
+
read a COG by HTTP **range request**, decode the relevant internal tiles,
|
|
38
|
+
resample to the requested XYZ tile, apply rescale/colormap/nodata, and encode.
|
|
39
|
+
Every one of those has a pure-Rust, WASM-clean implementation today - the two
|
|
40
|
+
historically hard parts (a C-free codec stack and CRS handling) are already
|
|
41
|
+
solved in `whitebox-wasm`. This crate adds the thin layer on top: mercator
|
|
42
|
+
addressing, overview selection, resampling, and rendering.
|
|
43
|
+
|
|
44
|
+
## Status
|
|
45
|
+
|
|
46
|
+
**v1 (this scaffold):** EPSG:3857 sources, single-band rendering, built-in
|
|
47
|
+
colormaps, MapLibre demo. The crate builds to `wasm32-unknown-unknown` and ships
|
|
48
|
+
a `wasm-pack` web package.
|
|
49
|
+
|
|
50
|
+
See [Roadmap](#roadmap) for warping, multi-band, and edge/WASI serving.
|
|
51
|
+
|
|
52
|
+
## Build
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
rustup target add wasm32-unknown-unknown
|
|
56
|
+
cargo install wasm-pack
|
|
57
|
+
wasm-pack build crates/cog-tiler-wasm --release --target web --out-dir pkg
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Run the demo locally
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm run dev # builds the wasm, assembles demo/, serves http://localhost:8000/
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`npm run dev` builds the wasm into `demo/`, copies in `cog-tiler.js` + the sample,
|
|
67
|
+
and starts a zero-dependency static server with **HTTP range support** (which the
|
|
68
|
+
tile streaming needs - the stdlib `python -m http.server` does not do ranges).
|
|
69
|
+
Set `PORT` to change the port. No `npm install` is required (the dev scripts use
|
|
70
|
+
only Node built-ins; the demo loads its peer deps from a CDN via an import map).
|
|
71
|
+
|
|
72
|
+
The published [GitHub Pages demo](https://opengeos.github.io/cog-tiler-wasm/) is
|
|
73
|
+
built the same way by `.github/workflows/pages.yml`.
|
|
74
|
+
|
|
75
|
+
## Usage (reusable module)
|
|
76
|
+
|
|
77
|
+
[`cog-tiler.js`](cog-tiler.js) is the package's main entry. It wraps the wasm
|
|
78
|
+
tiler + `whitebox-wasm` and handles EPSG:3857 sources, on-the-fly **warping** of
|
|
79
|
+
any projected/4326 COG to Web Mercator, and **paletted (categorical)** rendering -
|
|
80
|
+
so apps import it instead of copying the demo.
|
|
81
|
+
|
|
82
|
+
It ships **inside the npm package** (`main`/`module` -> `cog-tiler.js`); the raw
|
|
83
|
+
wasm tiler is also available at the `cog-tiler-wasm/wasm` subpath. Install it
|
|
84
|
+
alongside its peer dependencies:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npm install cog-tiler-wasm whitebox-wasm proj4 geotiff geotiff-geokeys-to-proj4 maplibre-gl
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
import maplibregl from "maplibre-gl";
|
|
92
|
+
import { init, openCog, registerCogProtocol } from "cog-tiler-wasm";
|
|
93
|
+
|
|
94
|
+
await init(); // load the wasm modules once
|
|
95
|
+
|
|
96
|
+
let source = null;
|
|
97
|
+
// The protocol resolves the active source + render settings per tile.
|
|
98
|
+
registerCogProtocol(maplibregl, "cog", () => ({
|
|
99
|
+
source,
|
|
100
|
+
render: { min: 0, max: 3000, colormap: "viridis" }, // ignored for paletted COGs
|
|
101
|
+
}));
|
|
102
|
+
|
|
103
|
+
source = await openCog(url); // EPSG:3857 fast path, or warped if projected/4326
|
|
104
|
+
// openCog also accepts a local raster: a File (e.g. from <input type=file>),
|
|
105
|
+
// Blob, ArrayBuffer, or Uint8Array - read in memory, no server needed.
|
|
106
|
+
map.addSource("cog", { type: "raster", tiles: ["cog://{z}/{x}/{y}"], tileSize: 256 });
|
|
107
|
+
map.addLayer({ id: "cog", type: "raster", source: "cog" });
|
|
108
|
+
|
|
109
|
+
// source.crsLabel, source.levels, source.hasPalette, source.boundsLonLat
|
|
110
|
+
// and source.renderTileRGBA(z, x, y, render) / renderTilePNG(...) are also exposed.
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### TiTiler-style COG API
|
|
114
|
+
|
|
115
|
+
`CogSource` mirrors the read endpoints of
|
|
116
|
+
[TiTiler's COG API](https://developmentseed.org/titiler/endpoints/cog/),
|
|
117
|
+
client-side (works on projected/paletted sources too, via the warp path):
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
// Metadata / read
|
|
121
|
+
src.info(); // /cog/info -> bounds, count, dtype, nodata, overviews, min/maxzoom, ...
|
|
122
|
+
src.infoGeoJSON(); // /cog/info.geojson -> GeoJSON Feature (bbox polygon + info)
|
|
123
|
+
src.tilejson(); // /cog/tilejson.json -> Mapbox TileJSON document
|
|
124
|
+
await src.point(lon, lat); // /cog/point -> band value(s) at a WGS84 coordinate
|
|
125
|
+
await src.statistics({ maxSize }); // /cog/statistics -> per-band min/max/mean/std/count/
|
|
126
|
+
// valid_percent/median/percentiles/histogram (from an overview)
|
|
127
|
+
|
|
128
|
+
// Image generation (all accept render params below)
|
|
129
|
+
await src.previewPNG({ maxSize: 1024 }); // /cog/preview -> PNG bytes
|
|
130
|
+
await src.bboxPNG([minLon, minLat, maxLon, maxLat]); // /cog/bbox -> PNG bytes
|
|
131
|
+
await src.preview(opts); // -> { width, height, rgba } (bbox() likewise)
|
|
132
|
+
|
|
133
|
+
// Render params (on tiles, preview, bbox):
|
|
134
|
+
// bidx: 1-based bands; one -> colormap/palette, three -> RGB composite
|
|
135
|
+
// rescale: [[min,max], ...] per band, or [min,max]; or min/max shorthand
|
|
136
|
+
// colormap: one of colormaps() (viridis, magma, plasma, inferno, cividis,
|
|
137
|
+
// turbo, terrain, blues, greens, reds, rdylgn, spectral, gray)
|
|
138
|
+
// reversed: sample the colormap back-to-front (single-band)
|
|
139
|
+
// stretch: transfer curve "linear" | "sqrt" | "log" (applied after rescale)
|
|
140
|
+
// gamma: power-law gamma (1 = off; applied after the stretch)
|
|
141
|
+
// nodata: override the transparency value
|
|
142
|
+
// opacity: output alpha multiplier 0..1
|
|
143
|
+
await src.renderTilePNG(z, x, y, { bidx: [4, 3, 2], rescale: [0, 3000] }); // false-color RGB
|
|
144
|
+
import { colormaps } from "cog-tiler-wasm";
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The render pipeline (nodata -> rescale -> stretch -> gamma -> colormap, plus
|
|
148
|
+
reverse/opacity) matches the controls of GPU raster viewers such as
|
|
149
|
+
[maplibre-gl-raster](https://github.com/opengeos/maplibre-gl-raster), so
|
|
150
|
+
`cog-tiler-wasm` can serve as a CPU/WASM rendering backend for the same UI - the
|
|
151
|
+
panel (band, rescale, colormap, curve, gamma, opacity) maps directly onto these
|
|
152
|
+
params and re-renders by re-requesting tiles.
|
|
153
|
+
|
|
154
|
+
Server-only endpoints (`/map.html`, `WMTSCapabilities.xml`, `/validate`,
|
|
155
|
+
`/stac`) are out of scope for a client-side library; band-math `expression` is
|
|
156
|
+
on the [roadmap](#roadmap).
|
|
157
|
+
|
|
158
|
+
For a no-build page, map the peer deps with an import map (see
|
|
159
|
+
[`demo/index.html`](demo/index.html)):
|
|
160
|
+
|
|
161
|
+
```html
|
|
162
|
+
<script type="importmap">
|
|
163
|
+
{ "imports": {
|
|
164
|
+
"whitebox-wasm": "https://esm.sh/whitebox-wasm@0.4.0",
|
|
165
|
+
"proj4": "https://esm.sh/proj4@2.20.9",
|
|
166
|
+
"geotiff": "https://esm.sh/geotiff@2.1.3",
|
|
167
|
+
"geotiff-geokeys-to-proj4": "https://esm.sh/geotiff-geokeys-to-proj4@2024.4.13"
|
|
168
|
+
} }
|
|
169
|
+
</script>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Low-level Rust API
|
|
173
|
+
|
|
174
|
+
The wasm crate (`CogTiler`) is the 3857 tiling brain underneath `cog-tiler.js`:
|
|
175
|
+
`pixel_window_for_tile(z, x, y)` maps a tile to a source-pixel window/overview,
|
|
176
|
+
and `render(window, w, h, min, max, colormap, nodata_alpha)` rasterizes an
|
|
177
|
+
assembled f64 window to RGBA. See [`cog-tiler.js`](cog-tiler.js) for the
|
|
178
|
+
window-assembly and warp loops built on top.
|
|
179
|
+
|
|
180
|
+
A runnable MapLibre example (custom `cog://` protocol) is in
|
|
181
|
+
[`demo/index.html`](demo/index.html).
|
|
182
|
+
|
|
183
|
+
## API
|
|
184
|
+
|
|
185
|
+
`version()`, `tile_bounds_3857(z, x, y) -> [minx,miny,maxx,maxy]`.
|
|
186
|
+
|
|
187
|
+
**`new CogTiler(geo_transform, width, height, epsg, nodata, levels_json)`**
|
|
188
|
+
- `geo_transform` - 6-element GDAL affine of the full-res raster (`Float64Array`)
|
|
189
|
+
- `width`/`height` - full-resolution pixel dimensions
|
|
190
|
+
- `epsg` - source CRS; must be `3857` in v1
|
|
191
|
+
- `nodata` - optional nodata value (`undefined`/`NaN` = none)
|
|
192
|
+
- `levels_json` - JSON array of level descriptors, finest level first; only
|
|
193
|
+
`width`/`height` are read (extra `whitebox-wasm` fields are ignored)
|
|
194
|
+
|
|
195
|
+
Properties: `epsg`, `num_levels`.
|
|
196
|
+
|
|
197
|
+
**`pixel_window_for_tile(z, x, y)`** -> `{ level, x, y, w, h, level_width, level_height, empty }`
|
|
198
|
+
The overview level and pixel window covering the tile. `empty` is true when the
|
|
199
|
+
tile lies outside the raster.
|
|
200
|
+
|
|
201
|
+
**`render(pixels, win_w, win_h, min, max, colormap, nodata_alpha)`** -> `Uint8Array`
|
|
202
|
+
A `256*256*4` RGBA tile. `pixels` is the decoded row-major `f64` window;
|
|
203
|
+
`colormap` is `"viridis" | "magma" | "terrain" | "gray"`. Empty windows render
|
|
204
|
+
fully transparent.
|
|
205
|
+
|
|
206
|
+
## Roadmap
|
|
207
|
+
|
|
208
|
+
- **TiTiler COG API parity** - done: `info`, `info.geojson`, `tilejson`,
|
|
209
|
+
`point`, `statistics`, `preview`, `bbox`, `bidx`/RGB band selection, and 13
|
|
210
|
+
colormaps (see [above](#titiler-style-cog-api)). Next: band-math
|
|
211
|
+
**`expression`** and `color_formula`.
|
|
212
|
+
- **Warping** of projected/4326 sources and **paletted/categorical** rendering
|
|
213
|
+
are done in [`cog-tiler.js`](cog-tiler.js) (proj4js + geotiff.js). **Planar**
|
|
214
|
+
(`INTERLEAVE=BAND`) multi-band COGs are read per-band via geotiff.js too, since
|
|
215
|
+
whitebox-wasm's streaming decoder is chunky-only. Next: planar support
|
|
216
|
+
**upstream in `whitebox-wasm`** (and exposing its proj string + color table) to
|
|
217
|
+
drop the geotiff.js dependency, then move the warp into the Rust crate
|
|
218
|
+
(`proj4rs`).
|
|
219
|
+
- **Edge / WASI serving** - run the same module as a serverless XYZ endpoint
|
|
220
|
+
near the data, not only in the browser.
|
|
221
|
+
- **STAC / mosaics** - multi-asset orchestration.
|
|
222
|
+
|
|
223
|
+
## Releasing
|
|
224
|
+
|
|
225
|
+
The npm package bundles the wasm tiler **and** the `cog-tiler.js` module
|
|
226
|
+
(assembled by [`scripts/prepare-pkg.mjs`](scripts/prepare-pkg.mjs)). To cut a
|
|
227
|
+
release, push a `vX.Y.Z` tag; [`release.yml`](.github/workflows/release.yml)
|
|
228
|
+
builds, assembles, and publishes to npm via Trusted Publishing (OIDC, no token):
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
git tag v0.2.0 && git push origin v0.2.0
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
One-time setup: configure the package's Trusted Publisher on npmjs.com (package
|
|
235
|
+
-> Settings -> Trusted Publisher) to this repo + `release.yml`.
|
|
236
|
+
|
|
237
|
+
## License
|
|
238
|
+
|
|
239
|
+
[MIT](LICENSE) © OpenGeos.
|
package/cog-tiler.d.ts
CHANGED
|
@@ -8,8 +8,31 @@ export interface RenderOptions {
|
|
|
8
8
|
min?: number;
|
|
9
9
|
/** High end of the rescale range (continuous bands). Default 1. */
|
|
10
10
|
max?: number;
|
|
11
|
-
/**
|
|
11
|
+
/** Per-band rescale `[[min,max], ...]`, or a single `[min,max]`. Overrides min/max. */
|
|
12
|
+
rescale?: [number, number][] | [number, number];
|
|
13
|
+
/** Colormap name (single-band). See {@link colormaps}. Default "viridis". */
|
|
12
14
|
colormap?: string;
|
|
15
|
+
/** 1-based band indices. One band -> palette/colormap; three -> RGB composite.
|
|
16
|
+
* Default: all-bands RGB if the source has >= 3 bands, else band 1. */
|
|
17
|
+
bidx?: number[];
|
|
18
|
+
/** Override the source nodata value for transparency. */
|
|
19
|
+
nodata?: number;
|
|
20
|
+
/** Transfer curve applied to the rescaled value before the colormap.
|
|
21
|
+
* Default "linear". */
|
|
22
|
+
stretch?: "linear" | "sqrt" | "log";
|
|
23
|
+
/** Power-law gamma (1 = off). Applied after the stretch. */
|
|
24
|
+
gamma?: number;
|
|
25
|
+
/** Sample the colormap back-to-front (single-band). */
|
|
26
|
+
reversed?: boolean;
|
|
27
|
+
/** Output alpha multiplier in [0,1]. Default 1. */
|
|
28
|
+
opacity?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A rendered image: row-major RGBA, `width * height * 4` bytes. */
|
|
32
|
+
export interface RenderedImage {
|
|
33
|
+
width: number;
|
|
34
|
+
height: number;
|
|
35
|
+
rgba: Uint8ClampedArray;
|
|
13
36
|
}
|
|
14
37
|
|
|
15
38
|
/** A level descriptor from `CogStream.levels_json()`. */
|
|
@@ -39,20 +62,80 @@ export declare class CogSource {
|
|
|
39
62
|
readonly boundsLonLat: number[];
|
|
40
63
|
/** True when the band is paletted (categorical) and rendered via its table. */
|
|
41
64
|
readonly hasPalette: boolean;
|
|
42
|
-
/** Render an XYZ tile to a 256x256 RGBA buffer, or null if empty.
|
|
43
|
-
|
|
65
|
+
/** Render an XYZ tile to a 256x256 RGBA buffer, or null if empty. (Paletted
|
|
66
|
+
* tiles are a `Uint8ClampedArray`; continuous tiles are the wasm `render()`
|
|
67
|
+
* `Uint8Array`.) */
|
|
68
|
+
renderTileRGBA(
|
|
69
|
+
z: number,
|
|
70
|
+
x: number,
|
|
71
|
+
y: number,
|
|
72
|
+
opts?: RenderOptions,
|
|
73
|
+
): Promise<Uint8Array | Uint8ClampedArray | null>;
|
|
44
74
|
/** Render an XYZ tile to PNG bytes (empty Uint8Array for a blank tile). */
|
|
45
75
|
renderTilePNG(z: number, x: number, y: number, opts?: RenderOptions): Promise<Uint8Array>;
|
|
76
|
+
|
|
77
|
+
// TiTiler-style read API.
|
|
78
|
+
|
|
79
|
+
/** Dataset info (bounds, bands, dtype, nodata, overviews, min/maxzoom, ...). */
|
|
80
|
+
info(): Record<string, unknown>;
|
|
81
|
+
/** Dataset info as a GeoJSON Feature (bbox polygon + info properties). */
|
|
82
|
+
infoGeoJSON(): Record<string, unknown>;
|
|
83
|
+
/** Mapbox TileJSON document. */
|
|
84
|
+
tilejson(opts?: {
|
|
85
|
+
tilesUrl?: string;
|
|
86
|
+
minzoom?: number;
|
|
87
|
+
maxzoom?: number;
|
|
88
|
+
scheme?: string;
|
|
89
|
+
}): Record<string, unknown>;
|
|
90
|
+
/** Band value(s) at a WGS84 lon/lat. `bidx` is 1-based; default all bands. */
|
|
91
|
+
point(
|
|
92
|
+
lon: number,
|
|
93
|
+
lat: number,
|
|
94
|
+
opts?: { bidx?: number[] },
|
|
95
|
+
): Promise<{ coordinates: [number, number]; values: number[]; band_names: string[]; outside?: boolean }>;
|
|
96
|
+
/** Per-band statistics from a decimated overview (≤ `maxSize` px wide). */
|
|
97
|
+
statistics(opts?: { maxSize?: number }): Promise<Record<string, Record<string, unknown>>>;
|
|
98
|
+
|
|
99
|
+
/** Render a preview of the whole dataset (≈ `/cog/preview`). */
|
|
100
|
+
preview(
|
|
101
|
+
opts?: RenderOptions & { maxSize?: number; width?: number; height?: number },
|
|
102
|
+
): Promise<RenderedImage>;
|
|
103
|
+
/** Render a WGS84 bbox `[minLon, minLat, maxLon, maxLat]` region (≈ `/cog/bbox`). */
|
|
104
|
+
bbox(
|
|
105
|
+
bbox: [number, number, number, number],
|
|
106
|
+
opts?: RenderOptions & { maxSize?: number; width?: number; height?: number },
|
|
107
|
+
): Promise<RenderedImage>;
|
|
108
|
+
/** {@link preview} encoded as PNG bytes. */
|
|
109
|
+
previewPNG(
|
|
110
|
+
opts?: RenderOptions & { maxSize?: number; width?: number; height?: number },
|
|
111
|
+
): Promise<Uint8Array>;
|
|
112
|
+
/** {@link bbox} encoded as PNG bytes. */
|
|
113
|
+
bboxPNG(
|
|
114
|
+
bbox: [number, number, number, number],
|
|
115
|
+
opts?: RenderOptions & { maxSize?: number; width?: number; height?: number },
|
|
116
|
+
): Promise<Uint8Array>;
|
|
46
117
|
}
|
|
47
118
|
|
|
119
|
+
/** Names of the built-in single-band colormaps. */
|
|
120
|
+
export declare function colormaps(): string[];
|
|
121
|
+
|
|
48
122
|
/** Initialize the wasm modules (idempotent). Resolve before `openCog`. */
|
|
49
123
|
export declare function init(): Promise<unknown>;
|
|
50
124
|
|
|
51
|
-
/**
|
|
52
|
-
|
|
125
|
+
/**
|
|
126
|
+
* Open a COG and return a {@link CogSource} ready to render XYZ tiles. Pass a URL
|
|
127
|
+
* string (read via HTTP range) or in-memory bytes / a Blob / a File for a local
|
|
128
|
+
* raster.
|
|
129
|
+
*/
|
|
130
|
+
export declare function openCog(source: string | ArrayBuffer | Uint8Array | Blob): Promise<CogSource>;
|
|
53
131
|
|
|
54
|
-
/** Encode
|
|
55
|
-
|
|
132
|
+
/** Encode an RGBA buffer to PNG bytes (browser; uses OffscreenCanvas).
|
|
133
|
+
* Defaults to 256x256 when `width`/`height` are omitted. */
|
|
134
|
+
export declare function rgbaToPng(
|
|
135
|
+
rgba: Uint8Array | Uint8ClampedArray,
|
|
136
|
+
width?: number,
|
|
137
|
+
height?: number,
|
|
138
|
+
): Promise<Uint8Array>;
|
|
56
139
|
|
|
57
140
|
/** Minimal shape of the maplibre-gl module needed to register a protocol. */
|
|
58
141
|
export interface MapLibreLike {
|
package/cog-tiler.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* map.addSource("cog", { type: "raster", tiles: ["cog://{z}/{x}/{y}"], tileSize: 256 });
|
|
22
22
|
*/
|
|
23
23
|
import initWhitebox, { CogStream } from "whitebox-wasm";
|
|
24
|
-
import initTiler, {
|
|
24
|
+
import initTiler, { colorize, colormap_names } from "./cog_tiler_wasm.js";
|
|
25
25
|
import proj4 from "proj4";
|
|
26
26
|
import * as GeoTIFF from "geotiff";
|
|
27
27
|
import geokeysToProj4 from "geotiff-geokeys-to-proj4";
|
|
@@ -54,6 +54,99 @@ const rangeFetcher = (url) => (a, b) =>
|
|
|
54
54
|
.then((r) => r.arrayBuffer())
|
|
55
55
|
.then((b) => new Uint8Array(b));
|
|
56
56
|
|
|
57
|
+
/** Map a level descriptor's sample format + bit depth to a numpy-style dtype. */
|
|
58
|
+
function dtypeOf(lv) {
|
|
59
|
+
const b = lv.bits_per_sample;
|
|
60
|
+
const f = (lv.sample_format || "").toLowerCase();
|
|
61
|
+
if (f.includes("float") || f.includes("ieee")) return "float" + b;
|
|
62
|
+
if (f === "uint" || f.includes("unsigned")) return "uint" + b;
|
|
63
|
+
if (f === "int" || f.includes("signed")) return "int" + b;
|
|
64
|
+
return (f || "uint") + b;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Min/max/mean/std/count/valid_percent/percentiles/histogram for a band buffer. */
|
|
68
|
+
function computeStats(buf, nodata) {
|
|
69
|
+
let min = Infinity, max = -Infinity, sum = 0, sumsq = 0;
|
|
70
|
+
const valid = [];
|
|
71
|
+
for (let i = 0; i < buf.length; i++) {
|
|
72
|
+
const v = buf[i];
|
|
73
|
+
if (Number.isNaN(v)) continue;
|
|
74
|
+
if (nodata != null && v === nodata) continue;
|
|
75
|
+
if (v < min) min = v;
|
|
76
|
+
if (v > max) max = v;
|
|
77
|
+
sum += v;
|
|
78
|
+
sumsq += v * v;
|
|
79
|
+
valid.push(v);
|
|
80
|
+
}
|
|
81
|
+
const count = valid.length;
|
|
82
|
+
if (count === 0) return { count: 0, valid_percent: 0 };
|
|
83
|
+
const mean = sum / count;
|
|
84
|
+
const std = Math.sqrt(Math.max(0, sumsq / count - mean * mean));
|
|
85
|
+
valid.sort((a, b) => a - b);
|
|
86
|
+
const pct = (p) => valid[Math.min(count - 1, Math.floor((p / 100) * count))];
|
|
87
|
+
const bins = 10, span = max - min || 1, hist = new Array(bins).fill(0);
|
|
88
|
+
for (const v of valid) {
|
|
89
|
+
let k = Math.floor(((v - min) / span) * bins);
|
|
90
|
+
if (k >= bins) k = bins - 1;
|
|
91
|
+
if (k < 0) k = 0;
|
|
92
|
+
hist[k]++;
|
|
93
|
+
}
|
|
94
|
+
const edges = Array.from({ length: bins + 1 }, (_, i) => min + (span * i) / bins);
|
|
95
|
+
return {
|
|
96
|
+
min, max, mean, std, count,
|
|
97
|
+
valid_percent: (count / buf.length) * 100,
|
|
98
|
+
median: pct(50),
|
|
99
|
+
percentile_2: pct(2),
|
|
100
|
+
percentile_98: pct(98),
|
|
101
|
+
histogram: [hist, edges],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Transfer curve for a rescaled value in 0..1: stretch then gamma (mirrors the
|
|
106
|
+
* Rust `transfer`; reversal is colormap-only so it's not applied to RGB). */
|
|
107
|
+
function transferCurve(t, stretch, gamma) {
|
|
108
|
+
if (stretch === "sqrt") t = Math.sqrt(t);
|
|
109
|
+
else if (stretch === "log") t = Math.log(1 + 99 * t) / Math.log(100);
|
|
110
|
+
if (Math.abs(gamma - 1) > 1e-9) t = Math.pow(t, 1 / Math.max(gamma, 1e-4));
|
|
111
|
+
return t;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Normalize render rescale options to a list of [min,max] pairs (per band). */
|
|
115
|
+
function rescaleList(opts) {
|
|
116
|
+
if (Array.isArray(opts.rescale) && opts.rescale.length) {
|
|
117
|
+
return Array.isArray(opts.rescale[0]) ? opts.rescale : [opts.rescale];
|
|
118
|
+
}
|
|
119
|
+
return [[opts.min ?? 0, opts.max ?? 1]];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** WGS84 [w,s,e,n] -> EPSG:3857 [minx,miny,maxx,maxy]. */
|
|
123
|
+
function mercExtentFromLonLat([w, s, e, n]) {
|
|
124
|
+
const t = proj4("EPSG:4326", "EPSG:3857");
|
|
125
|
+
const [minx, miny] = t.forward([w, s]);
|
|
126
|
+
const [maxx, maxy] = t.forward([e, n]);
|
|
127
|
+
return [minx, miny, maxx, maxy];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Output [w,h] honoring explicit width/height, else fitting `maxSize` to aspect.
|
|
131
|
+
* width/height/maxSize are user inputs, so validate them as positive integers
|
|
132
|
+
* before they reach typed-array / canvas constructors. */
|
|
133
|
+
function fitSize(extW, extH, maxSize, width, height) {
|
|
134
|
+
const posInt = (v, name) => {
|
|
135
|
+
if (v == null) return undefined;
|
|
136
|
+
const n = Math.floor(v);
|
|
137
|
+
if (!Number.isFinite(n) || n < 1) throw new Error(`${name} must be a positive integer`);
|
|
138
|
+
return n;
|
|
139
|
+
};
|
|
140
|
+
const w = posInt(width, "width"), h = posInt(height, "height"), max = posInt(maxSize, "maxSize") ?? 1024;
|
|
141
|
+
const ar = extW / extH || 1;
|
|
142
|
+
if (w && h) return [w, h];
|
|
143
|
+
if (w) return [w, Math.max(1, Math.round(w / ar))];
|
|
144
|
+
if (h) return [Math.max(1, Math.round(h * ar)), h];
|
|
145
|
+
return ar >= 1
|
|
146
|
+
? [max, Math.max(1, Math.round(max / ar))]
|
|
147
|
+
: [Math.max(1, Math.round(max * ar)), max];
|
|
148
|
+
}
|
|
149
|
+
|
|
57
150
|
/** Build a 256-entry RGBA palette from a TIFF ColorMap (16-bit R,G,B blocks). */
|
|
58
151
|
function buildPalette(colorMap) {
|
|
59
152
|
if (!colorMap) return null;
|
|
@@ -76,15 +169,60 @@ function bilin(v00, v10, v01, v11, tx, ty) {
|
|
|
76
169
|
return top + (bot - top) * ty;
|
|
77
170
|
}
|
|
78
171
|
|
|
172
|
+
// Bilinear sample of a row-major window at fractional (fc,fr). Returns NaN when
|
|
173
|
+
// the cell center is outside the window so out-of-raster pixels stay transparent
|
|
174
|
+
// (no edge smear); falls back to nearest at the window edge / next to nodata.
|
|
175
|
+
function sampleWindowBilinear(buf, w, h, fc, fr) {
|
|
176
|
+
const c0 = Math.floor(fc), r0 = Math.floor(fr);
|
|
177
|
+
if (c0 < 0 || c0 >= w || r0 < 0 || r0 >= h) return NaN;
|
|
178
|
+
const c1 = Math.min(c0 + 1, w - 1), r1 = Math.min(r0 + 1, h - 1);
|
|
179
|
+
const v00 = buf[r0 * w + c0];
|
|
180
|
+
if (Number.isNaN(v00)) return NaN;
|
|
181
|
+
const v10 = buf[r0 * w + c1], v01 = buf[r1 * w + c0], v11 = buf[r1 * w + c1];
|
|
182
|
+
if (Number.isNaN(v10) || Number.isNaN(v01) || Number.isNaN(v11)) return v00; // edge/nodata
|
|
183
|
+
const tx = fc - c0, ty = fr - r0;
|
|
184
|
+
const top = v00 + (v10 - v00) * tx, bot = v01 + (v11 - v01) * tx;
|
|
185
|
+
return top + (bot - top) * ty;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Build a byte reader from a URL string or an in-memory source (ArrayBuffer,
|
|
189
|
+
// Uint8Array, or Blob/File). `range(a,b)` yields bytes [a..b]; `openTiff()`
|
|
190
|
+
// returns a geotiff.js GeoTIFF for reading the CRS / color table from the header.
|
|
191
|
+
async function makeReader(source) {
|
|
192
|
+
if (typeof source === "string") {
|
|
193
|
+
return { label: source, range: rangeFetcher(source), openTiff: () => GeoTIFF.fromUrl(source) };
|
|
194
|
+
}
|
|
195
|
+
let bytes;
|
|
196
|
+
if (source instanceof Uint8Array) bytes = source;
|
|
197
|
+
else if (source instanceof ArrayBuffer) bytes = new Uint8Array(source);
|
|
198
|
+
else if (source && typeof source.arrayBuffer === "function") {
|
|
199
|
+
bytes = new Uint8Array(await source.arrayBuffer()); // Blob / File
|
|
200
|
+
} else {
|
|
201
|
+
throw new Error("openCog: expected a URL string, ArrayBuffer, Uint8Array, or Blob");
|
|
202
|
+
}
|
|
203
|
+
// Reuse the underlying buffer when the view spans it exactly; only copy for a
|
|
204
|
+
// partial view (avoids duplicating a large raster in memory).
|
|
205
|
+
const ab =
|
|
206
|
+
bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength
|
|
207
|
+
? bytes.buffer
|
|
208
|
+
: bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
209
|
+
return {
|
|
210
|
+
label: source.name || "(local file)",
|
|
211
|
+
range: (a, b) => Promise.resolve(bytes.subarray(a, Math.min(b + 1, bytes.length))),
|
|
212
|
+
openTiff: () => GeoTIFF.fromArrayBuffer(ab),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
79
216
|
/**
|
|
80
|
-
* Open a COG and return a {@link CogSource} ready to render XYZ tiles.
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
217
|
+
* Open a COG and return a {@link CogSource} ready to render XYZ tiles. `source`
|
|
218
|
+
* is a URL string (read via HTTP range) or in-memory bytes / a Blob / a File for
|
|
219
|
+
* a local raster. Detects EPSG:3857 (fast path) vs. any other CRS (warp path),
|
|
220
|
+
* reading the source projection + color table from the GeoTIFF header (which
|
|
221
|
+
* whitebox-wasm 0.4.0 does not expose).
|
|
84
222
|
*/
|
|
85
|
-
export async function openCog(
|
|
223
|
+
export async function openCog(source) {
|
|
86
224
|
await init();
|
|
87
|
-
const range =
|
|
225
|
+
const { range, openTiff, label } = await makeReader(source);
|
|
88
226
|
// Parse the COG header; grow the prefix and retry for large COGs whose IFDs
|
|
89
227
|
// exceed 64 KB (many overviews / huge tile-offset arrays).
|
|
90
228
|
let stream;
|
|
@@ -101,17 +239,18 @@ export async function openCog(url) {
|
|
|
101
239
|
if (!Array.isArray(levels) || levels.length === 0) {
|
|
102
240
|
throw new Error("levels_json() returned no levels");
|
|
103
241
|
}
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
242
|
+
// Open the GeoTIFF with geotiff.js when we need the CRS (non-3857) or to check
|
|
243
|
+
// the planar config (multi-band). whitebox-wasm's streaming decoder is
|
|
244
|
+
// chunky-only, so planar (INTERLEAVE=BAND) multi-band COGs are read per-band
|
|
245
|
+
// through geotiff.js instead (see _assembleWindow / point).
|
|
246
|
+
const multiBand = levels[0].bands > 1;
|
|
247
|
+
let tiff = null, img = null, planar = false;
|
|
248
|
+
if (stream.epsg !== 3857 || multiBand) {
|
|
249
|
+
tiff = await openTiff();
|
|
250
|
+
img = await tiff.getImage();
|
|
251
|
+
planar = multiBand && img.fileDirectory.PlanarConfiguration === 2;
|
|
252
|
+
}
|
|
253
|
+
const base = { url: label, range, stream, levels, gt, nodata: stream.nodata, tiff, planar };
|
|
115
254
|
|
|
116
255
|
if (stream.epsg === 3857) {
|
|
117
256
|
return new CogSource({
|
|
@@ -119,13 +258,12 @@ export async function openCog(url) {
|
|
|
119
258
|
mode: "3857",
|
|
120
259
|
crsLabel: "EPSG:3857",
|
|
121
260
|
palette: null,
|
|
261
|
+
toSource: { forward: (c) => c }, // identity: mercator meters == source meters
|
|
122
262
|
boundsLonLat: Array.from(stream.bounds_lonlat()),
|
|
123
263
|
});
|
|
124
264
|
}
|
|
125
265
|
|
|
126
266
|
// Warp path: read the real source CRS + optional palette from the header.
|
|
127
|
-
const tiff = await GeoTIFF.fromUrl(url);
|
|
128
|
-
const img = await tiff.getImage();
|
|
129
267
|
const srcDef = geokeysToProj4.toProj4(img.getGeoKeys()).proj4;
|
|
130
268
|
if (!srcDef) throw new Error("could not derive source CRS from GeoTIFF geokeys");
|
|
131
269
|
const toSource = proj4("EPSG:3857", srcDef); // forward: mercator -> source
|
|
@@ -166,11 +304,9 @@ export class CogSource {
|
|
|
166
304
|
return !!this.palette;
|
|
167
305
|
}
|
|
168
306
|
|
|
169
|
-
/** Render an XYZ tile to a 256x256 RGBA
|
|
307
|
+
/** Render an XYZ tile to a 256x256 RGBA buffer, or null if empty. */
|
|
170
308
|
async renderTileRGBA(z, x, y, opts = {}) {
|
|
171
|
-
return this.
|
|
172
|
-
? this._warp(z, x, y, opts)
|
|
173
|
-
: this._render3857(z, x, y, opts);
|
|
309
|
+
return this._renderExtent(tileBounds3857(z, x, y), TILE, TILE, opts);
|
|
174
310
|
}
|
|
175
311
|
|
|
176
312
|
/** Render an XYZ tile to PNG bytes (empty `Uint8Array` for a blank tile). */
|
|
@@ -199,9 +335,27 @@ export class CogSource {
|
|
|
199
335
|
return p;
|
|
200
336
|
}
|
|
201
337
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
338
|
+
/** geotiff.js image for an overview level (cached). Used for planar reads. */
|
|
339
|
+
_tiffImage(level) {
|
|
340
|
+
if (!this._imgs) this._imgs = new Map();
|
|
341
|
+
let p = this._imgs.get(level);
|
|
342
|
+
if (!p) {
|
|
343
|
+
p = this.tiff.getImage(level);
|
|
344
|
+
this._imgs.set(level, p);
|
|
345
|
+
}
|
|
346
|
+
return p;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Fetch + decode band `band` (0-based) over a level pixel window into a
|
|
350
|
+
// row-major buffer. Chunky COGs go through whitebox (cached, NaN for gaps);
|
|
351
|
+
// planar (INTERLEAVE=BAND) COGs are read per-band via geotiff.js, which
|
|
352
|
+
// whitebox's chunky-only streaming decoder can't address.
|
|
353
|
+
async _assembleWindow(level, x, y, w, h, band = 0) {
|
|
354
|
+
if (this.planar) {
|
|
355
|
+
const img = await this._tiffImage(level);
|
|
356
|
+
const rasters = await img.readRasters({ window: [x, y, x + w, y + h], samples: [band] });
|
|
357
|
+
return rasters[0]; // typed array, length w*h, row-major
|
|
358
|
+
}
|
|
205
359
|
const lv = this.levels[level];
|
|
206
360
|
const tiles = JSON.parse(this.stream.tiles_for_window(level, x, y, w, h));
|
|
207
361
|
const decoded = await Promise.all(tiles.map((t) => this._getTile(level, t)));
|
|
@@ -216,7 +370,7 @@ export class CogSource {
|
|
|
216
370
|
for (let rx = 0; rx < tw; rx++) {
|
|
217
371
|
const ox = tx0 + rx - x;
|
|
218
372
|
if (ox < 0 || ox >= w) continue;
|
|
219
|
-
buf[oy * w + ox] = px[(ry * tw + rx) * bands
|
|
373
|
+
buf[oy * w + ox] = px[(ry * tw + rx) * bands + band];
|
|
220
374
|
}
|
|
221
375
|
}
|
|
222
376
|
});
|
|
@@ -244,28 +398,40 @@ export class CogSource {
|
|
|
244
398
|
];
|
|
245
399
|
}
|
|
246
400
|
|
|
247
|
-
//
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
401
|
+
// Render a Web Mercator extent (3857 [minx,miny,maxx,maxy]) to an outW x outH
|
|
402
|
+
// RGBA buffer. A coarse grid of mercator->source samples (the proj4 transform,
|
|
403
|
+
// or identity for EPSG:3857) is bilinearly interpolated per output pixel to a
|
|
404
|
+
// source location, then sampled from the source window. Bands: 1 (paletted via
|
|
405
|
+
// the color table, else single-band colormap) or >=3 (RGB composite, per-band
|
|
406
|
+
// rescale). Out-of-raster pixels stay transparent. Powers tiles, preview, bbox.
|
|
407
|
+
async _renderExtent(merc, outW, outH, opts = {}) {
|
|
408
|
+
const [minx, miny, maxx, maxy] = merc;
|
|
409
|
+
const l0 = this.levels[0];
|
|
410
|
+
const wanted = opts.bidx && opts.bidx.length ? opts.bidx : this.palette ? [1] : l0.bands >= 3 ? [1, 2, 3] : [1];
|
|
411
|
+
const bands0 = wanted.map((b) => b - 1).filter((b) => b >= 0 && b < l0.bands);
|
|
412
|
+
if (!bands0.length) bands0.push(0);
|
|
413
|
+
const rgb = bands0.length >= 3;
|
|
414
|
+
const rescales = rescaleList(opts);
|
|
415
|
+
const colormap = opts.colormap || "viridis";
|
|
416
|
+
const nodata = opts.nodata != null ? opts.nodata : this.nodata;
|
|
417
|
+
const ndSet = nodata != null && !Number.isNaN(nodata);
|
|
418
|
+
// Transfer-curve params (parity with maplibre-gl-raster's shader pipeline).
|
|
419
|
+
// Normalize gamma/opacity here so the JS (RGB) and Rust (single-band) paths
|
|
420
|
+
// apply identical curves for the same options (e.g. gamma 0 -> clamped, not skipped).
|
|
421
|
+
const stretch = opts.stretch || "linear";
|
|
422
|
+
const gamma = Number.isFinite(+opts.gamma) ? Math.max(+opts.gamma, 1e-4) : 1;
|
|
423
|
+
const reversed = !!opts.reversed;
|
|
424
|
+
const opacity = Number.isFinite(+opts.opacity) ? Math.max(0, Math.min(1, +opts.opacity)) : 1;
|
|
425
|
+
const alpha = Math.round(opacity * 255);
|
|
254
426
|
|
|
255
|
-
|
|
256
|
-
// A coarse grid of mercator->source samples (proj4) is bilinearly interpolated
|
|
257
|
-
// per output pixel, then nearest-sampled from the source window (correct for
|
|
258
|
-
// categorical data). Paletted sources use the color table; others reuse the
|
|
259
|
-
// Rust colormap (render resamples 256->256, ~identity).
|
|
260
|
-
async _warp(z, x, y, { min = 0, max = 1, colormap = "viridis" } = {}) {
|
|
261
|
-
const tb = tileBounds3857(z, x, y);
|
|
427
|
+
// Gridded mercator -> source samples, and the source-coord bbox they span.
|
|
262
428
|
const nx = new Float64Array((NG + 1) * (NG + 1));
|
|
263
429
|
const ny = new Float64Array((NG + 1) * (NG + 1));
|
|
264
430
|
let sminx = Infinity, sminy = Infinity, smaxx = -Infinity, smaxy = -Infinity, any = false;
|
|
265
431
|
for (let gy = 0; gy <= NG; gy++) {
|
|
266
|
-
const my =
|
|
432
|
+
const my = maxy - (gy / NG) * (maxy - miny);
|
|
267
433
|
for (let gx = 0; gx <= NG; gx++) {
|
|
268
|
-
const mx =
|
|
434
|
+
const mx = minx + (gx / NG) * (maxx - minx);
|
|
269
435
|
let s;
|
|
270
436
|
try { s = this.toSource.forward([mx, my]); } catch { s = [NaN, NaN]; }
|
|
271
437
|
const i = gy * (NG + 1) + gx;
|
|
@@ -281,7 +447,7 @@ export class CogSource {
|
|
|
281
447
|
}
|
|
282
448
|
if (!any) return null;
|
|
283
449
|
|
|
284
|
-
const level = this._chooseLevel((smaxx - sminx) /
|
|
450
|
+
const level = this._chooseLevel((smaxx - sminx) / outW);
|
|
285
451
|
const lv = this.levels[level];
|
|
286
452
|
const [lpw, lph] = this._levelPixelSize(level);
|
|
287
453
|
const ox = this.gt[0], oy = this.gt[3];
|
|
@@ -291,49 +457,230 @@ export class CogSource {
|
|
|
291
457
|
r0 = Math.max(0, Math.min(r0, lv.height)); r1 = Math.max(0, Math.min(r1, lv.height));
|
|
292
458
|
const ww = c1 - c0, hh = r1 - r0;
|
|
293
459
|
if (ww <= 0 || hh <= 0) return null;
|
|
294
|
-
const buf = await this._assembleWindow(level, c0, r0, ww, hh);
|
|
295
460
|
|
|
461
|
+
// Assemble the needed band window(s): 1 (palette/colormap) or 3 (RGB).
|
|
462
|
+
const used = rgb ? bands0.slice(0, 3) : [bands0[0]];
|
|
463
|
+
const bufs = await Promise.all(used.map((b) => this._assembleWindow(level, c0, r0, ww, hh, b)));
|
|
296
464
|
const pal = this.palette;
|
|
297
|
-
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
465
|
+
|
|
466
|
+
const out = new Uint8ClampedArray(outW * outH * 4);
|
|
467
|
+
const grid = !pal && !rgb ? new Float64Array(outW * outH).fill(NaN) : null;
|
|
468
|
+
for (let py = 0; py < outH; py++) {
|
|
469
|
+
const fy = (py / outH) * NG, gy0 = Math.min(NG - 1, Math.floor(fy)), ty = fy - gy0;
|
|
470
|
+
for (let px = 0; px < outW; px++) {
|
|
471
|
+
const fx = (px / outW) * NG, gx0 = Math.min(NG - 1, Math.floor(fx)), tx = fx - gx0;
|
|
303
472
|
const i00 = gy0 * (NG + 1) + gx0;
|
|
304
473
|
const sx = bilin(nx[i00], nx[i00 + 1], nx[i00 + NG + 1], nx[i00 + NG + 2], tx, ty);
|
|
305
474
|
const sy = bilin(ny[i00], ny[i00 + 1], ny[i00 + NG + 1], ny[i00 + NG + 2], tx, ty);
|
|
306
475
|
if (!isFinite(sx) || !isFinite(sy)) continue;
|
|
307
|
-
const
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
const v = buf[row * ww + col];
|
|
311
|
-
if (!isFinite(v)) continue;
|
|
476
|
+
const fcol = (sx - ox) / lpw - c0;
|
|
477
|
+
const frow = (oy - sy) / lph - r0;
|
|
478
|
+
const o = (py * outW + px) * 4;
|
|
312
479
|
if (pal) {
|
|
480
|
+
// Categorical: nearest-neighbor + color table.
|
|
481
|
+
const col = Math.floor(fcol), row = Math.floor(frow);
|
|
482
|
+
if (col < 0 || col >= ww || row < 0 || row >= hh) continue;
|
|
483
|
+
const v = bufs[0][row * ww + col];
|
|
484
|
+
if (!isFinite(v)) continue;
|
|
313
485
|
const ci = v & 255;
|
|
314
|
-
|
|
315
|
-
|
|
486
|
+
// Transparency: the declared nodata when present, else the GDAL
|
|
487
|
+
// paletted convention that index 0 is the background/no-data class.
|
|
488
|
+
if (ndSet ? v === nodata : ci === 0) continue;
|
|
316
489
|
out[o] = pal[ci * 4]; out[o + 1] = pal[ci * 4 + 1];
|
|
317
|
-
out[o + 2] = pal[ci * 4 + 2]; out[o + 3] =
|
|
490
|
+
out[o + 2] = pal[ci * 4 + 2]; out[o + 3] = alpha;
|
|
491
|
+
} else if (rgb) {
|
|
492
|
+
// RGB composite: bilinear-sample each band, rescale -> curve -> gamma.
|
|
493
|
+
let ok = true;
|
|
494
|
+
for (let k = 0; k < 3; k++) {
|
|
495
|
+
const v = sampleWindowBilinear(bufs[k], ww, hh, fcol, frow);
|
|
496
|
+
if (!isFinite(v) || (ndSet && v === nodata)) { ok = false; break; }
|
|
497
|
+
const [mn, mx] = rescales[k] || rescales[0];
|
|
498
|
+
const t = transferCurve(Math.max(0, Math.min(1, (v - mn) / ((mx - mn) || 1))), stretch, gamma);
|
|
499
|
+
out[o + k] = Math.round(t * 255);
|
|
500
|
+
}
|
|
501
|
+
if (ok) out[o + 3] = alpha;
|
|
502
|
+
else { out[o] = out[o + 1] = out[o + 2] = 0; }
|
|
318
503
|
} else {
|
|
319
|
-
|
|
504
|
+
// Continuous single band: bilinear; colormap applied below.
|
|
505
|
+
const v = sampleWindowBilinear(bufs[0], ww, hh, fcol, frow);
|
|
506
|
+
if (!isFinite(v) || (ndSet && v === nodata)) continue;
|
|
507
|
+
grid[py * outW + px] = v;
|
|
320
508
|
}
|
|
321
509
|
}
|
|
322
510
|
}
|
|
323
|
-
if (pal) return out;
|
|
324
|
-
|
|
511
|
+
if (pal || rgb) return out;
|
|
512
|
+
const [mn, mx] = rescales[0];
|
|
513
|
+
// colorize returns a Uint8Array; expose a Uint8ClampedArray (zero-copy view,
|
|
514
|
+
// matching the palette/RGB branches and the RenderedImage type) so callers
|
|
515
|
+
// can pass it straight to ImageData.
|
|
516
|
+
const c = colorize(
|
|
517
|
+
grid, outW, outH, mn, mx, colormap, ndSet ? nodata : undefined, true,
|
|
518
|
+
stretch, gamma, reversed, opacity,
|
|
519
|
+
);
|
|
520
|
+
return new Uint8ClampedArray(c.buffer, c.byteOffset, c.length);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// --- TiTiler-style read API ----------------------------------------------
|
|
524
|
+
|
|
525
|
+
/** XYZ zoom whose tile resolution matches the full-res pixel size. */
|
|
526
|
+
_maxzoom() {
|
|
527
|
+
const res = Math.abs(this.gt[1]);
|
|
528
|
+
return Math.max(0, Math.min(24, Math.round(Math.log2((2 * OS) / (TILE * res)))));
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/** XYZ zoom whose tile resolution matches the coarsest overview. */
|
|
532
|
+
_minzoom() {
|
|
533
|
+
const c = this.levels[this.levels.length - 1];
|
|
534
|
+
const res = Math.abs(this.gt[1]) * (this.levels[0].width / c.width);
|
|
535
|
+
return Math.max(0, Math.min(24, Math.round(Math.log2((2 * OS) / (TILE * res)))));
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/** Dataset info (≈ TiTiler `/cog/info`). */
|
|
539
|
+
info() {
|
|
540
|
+
const l0 = this.levels[0];
|
|
541
|
+
const b = this.boundsLonLat;
|
|
542
|
+
return {
|
|
543
|
+
bounds: b, // WGS84 [minlon, minlat, maxlon, maxlat]
|
|
544
|
+
crs: this.crsLabel,
|
|
545
|
+
width: l0.width,
|
|
546
|
+
height: l0.height,
|
|
547
|
+
count: l0.bands,
|
|
548
|
+
dtype: dtypeOf(l0),
|
|
549
|
+
nodata: this.nodata == null || Number.isNaN(this.nodata) ? null : this.nodata,
|
|
550
|
+
colorinterp: this.palette ? ["palette"] : null,
|
|
551
|
+
overviews: this.levels.length - 1,
|
|
552
|
+
tile_size: [l0.tile_width, l0.tile_height],
|
|
553
|
+
minzoom: this._minzoom(),
|
|
554
|
+
maxzoom: this._maxzoom(),
|
|
555
|
+
band_descriptions: Array.from({ length: l0.bands }, (_, i) => `b${i + 1}`),
|
|
556
|
+
compression: l0.compression,
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/** Dataset info as a GeoJSON Feature (≈ TiTiler `/cog/info.geojson`). */
|
|
561
|
+
infoGeoJSON() {
|
|
562
|
+
const [w, s, e, n] = this.boundsLonLat;
|
|
563
|
+
return {
|
|
564
|
+
type: "Feature",
|
|
565
|
+
geometry: {
|
|
566
|
+
type: "Polygon",
|
|
567
|
+
coordinates: [[[w, s], [e, s], [e, n], [w, n], [w, s]]],
|
|
568
|
+
},
|
|
569
|
+
properties: this.info(),
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/** Mapbox TileJSON document (≈ TiTiler `/cog/tilejson.json`). */
|
|
574
|
+
tilejson({ tilesUrl = "cog://{z}/{x}/{y}", minzoom, maxzoom, scheme = "xyz" } = {}) {
|
|
575
|
+
const b = this.boundsLonLat;
|
|
576
|
+
const mn = minzoom ?? this._minzoom();
|
|
577
|
+
return {
|
|
578
|
+
tilejson: "2.2.0",
|
|
579
|
+
version: "1.0.0",
|
|
580
|
+
scheme,
|
|
581
|
+
tiles: [tilesUrl],
|
|
582
|
+
minzoom: mn,
|
|
583
|
+
maxzoom: maxzoom ?? this._maxzoom(),
|
|
584
|
+
bounds: b,
|
|
585
|
+
center: [(b[0] + b[2]) / 2, (b[1] + b[3]) / 2, mn],
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/** Band value(s) at a WGS84 lon/lat (≈ TiTiler `/cog/point/{lon},{lat}`). */
|
|
590
|
+
async point(lon, lat, { bidx } = {}) {
|
|
591
|
+
const [mx, my] = proj4("EPSG:4326", "EPSG:3857").forward([lon, lat]);
|
|
592
|
+
const [sx, sy] = this.toSource.forward([mx, my]); // source CRS coords
|
|
593
|
+
const l0 = this.levels[0];
|
|
594
|
+
const col = Math.floor((sx - this.gt[0]) / Math.abs(this.gt[1]));
|
|
595
|
+
const row = Math.floor((this.gt[3] - sy) / Math.abs(this.gt[5]));
|
|
596
|
+
if (col < 0 || col >= l0.width || row < 0 || row >= l0.height) {
|
|
597
|
+
return { coordinates: [lon, lat], values: [], band_names: [], outside: true };
|
|
598
|
+
}
|
|
599
|
+
const bands = bidx ? bidx.map((b) => b - 1) : Array.from({ length: l0.bands }, (_, i) => i);
|
|
600
|
+
let values;
|
|
601
|
+
if (this.planar) {
|
|
602
|
+
// Planar: whitebox can't address bands 1..n; read the pixel via geotiff.js.
|
|
603
|
+
const img = await this._tiffImage(0);
|
|
604
|
+
const r = await img.readRasters({ window: [col, row, col + 1, row + 1], samples: bands });
|
|
605
|
+
values = r.map((b) => b[0]);
|
|
606
|
+
} else {
|
|
607
|
+
const tcol = Math.floor(col / l0.tile_width), trow = Math.floor(row / l0.tile_height);
|
|
608
|
+
const [off, len] = Array.from(this.stream.tile_range(0, tcol, trow));
|
|
609
|
+
const px = await this._getTile(0, { col: tcol, row: trow, offset: off, length: len });
|
|
610
|
+
const base = ((row % l0.tile_height) * l0.tile_width + (col % l0.tile_width)) * l0.bands;
|
|
611
|
+
values = bands.map((b) => px[base + b]);
|
|
612
|
+
}
|
|
613
|
+
return {
|
|
614
|
+
coordinates: [lon, lat],
|
|
615
|
+
values,
|
|
616
|
+
band_names: bands.map((b) => `b${b + 1}`),
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/** Per-band statistics (≈ TiTiler `/cog/statistics`), from a decimated
|
|
621
|
+
* overview (the largest one whose width is ≤ `maxSize`). */
|
|
622
|
+
async statistics({ maxSize = 1024 } = {}) {
|
|
623
|
+
let level = this.levels.length - 1;
|
|
624
|
+
for (let i = 0; i < this.levels.length; i++) {
|
|
625
|
+
if (this.levels[i].width <= maxSize) { level = i; break; }
|
|
626
|
+
}
|
|
627
|
+
const lv = this.levels[level];
|
|
628
|
+
const nodata = this.nodata == null || Number.isNaN(this.nodata) ? null : this.nodata;
|
|
629
|
+
const out = {};
|
|
630
|
+
for (let b = 0; b < lv.bands; b++) {
|
|
631
|
+
const buf = await this._assembleWindow(level, 0, 0, lv.width, lv.height, b);
|
|
632
|
+
out[`b${b + 1}`] = computeStats(buf, nodata);
|
|
633
|
+
}
|
|
634
|
+
return out;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/** Render a preview of the whole dataset (≈ TiTiler `/cog/preview`).
|
|
638
|
+
* Returns `{ width, height, rgba }`. `opts` accepts the render params
|
|
639
|
+
* (`bidx`, `min`/`max`/`rescale`, `colormap`, `nodata`) plus `maxSize` /
|
|
640
|
+
* `width` / `height`. */
|
|
641
|
+
async preview({ maxSize = 1024, width, height, ...render } = {}) {
|
|
642
|
+
const merc = mercExtentFromLonLat(this.boundsLonLat);
|
|
643
|
+
const [w, h] = fitSize(merc[2] - merc[0], merc[3] - merc[1], maxSize, width, height);
|
|
644
|
+
const rgba = (await this._renderExtent(merc, w, h, render)) || new Uint8ClampedArray(w * h * 4);
|
|
645
|
+
return { width: w, height: h, rgba };
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/** Render a WGS84 bbox region (≈ TiTiler `/cog/bbox`). `bbox` is
|
|
649
|
+
* [minLon, minLat, maxLon, maxLat]. Returns `{ width, height, rgba }`. */
|
|
650
|
+
async bbox(bbox, { maxSize = 1024, width, height, ...render } = {}) {
|
|
651
|
+
const merc = mercExtentFromLonLat(bbox);
|
|
652
|
+
const [w, h] = fitSize(merc[2] - merc[0], merc[3] - merc[1], maxSize, width, height);
|
|
653
|
+
const rgba = (await this._renderExtent(merc, w, h, render)) || new Uint8ClampedArray(w * h * 4);
|
|
654
|
+
return { width: w, height: h, rgba };
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/** Like {@link preview}, encoded as PNG bytes. */
|
|
658
|
+
async previewPNG(opts = {}) {
|
|
659
|
+
const { width, height, rgba } = await this.preview(opts);
|
|
660
|
+
return rgbaToPng(rgba, width, height);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/** Like {@link bbox}, encoded as PNG bytes. */
|
|
664
|
+
async bboxPNG(bbox, opts = {}) {
|
|
665
|
+
const r = await this.bbox(bbox, opts);
|
|
666
|
+
return rgbaToPng(r.rgba, r.width, r.height);
|
|
325
667
|
}
|
|
326
668
|
}
|
|
327
669
|
|
|
328
|
-
/** Encode a
|
|
329
|
-
export async function rgbaToPng(rgba) {
|
|
330
|
-
const img = new ImageData(new Uint8ClampedArray(rgba),
|
|
331
|
-
const cv = new OffscreenCanvas(
|
|
670
|
+
/** Encode a `w`x`h` RGBA buffer to PNG bytes (browser; uses OffscreenCanvas). */
|
|
671
|
+
export async function rgbaToPng(rgba, w = TILE, h = TILE) {
|
|
672
|
+
const img = new ImageData(new Uint8ClampedArray(rgba), w, h);
|
|
673
|
+
const cv = new OffscreenCanvas(w, h);
|
|
332
674
|
cv.getContext("2d").putImageData(img, 0, 0);
|
|
333
675
|
const blob = await cv.convertToBlob({ type: "image/png" });
|
|
334
676
|
return new Uint8Array(await blob.arrayBuffer());
|
|
335
677
|
}
|
|
336
678
|
|
|
679
|
+
/** Names of the built-in colormaps (for single-band rendering). */
|
|
680
|
+
export function colormaps() {
|
|
681
|
+
return JSON.parse(colormap_names());
|
|
682
|
+
}
|
|
683
|
+
|
|
337
684
|
/**
|
|
338
685
|
* Register a MapLibre custom protocol (e.g. `cog://{z}/{x}/{y}`).
|
|
339
686
|
* `resolve()` is called per tile and returns `{ source, render }`, where
|
package/cog_tiler_wasm.d.ts
CHANGED
|
@@ -50,6 +50,20 @@ export class CogTiler {
|
|
|
50
50
|
readonly num_levels: number;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Colormap a single-band `w*h` grid to RGBA at 1:1 (no resampling), for any
|
|
55
|
+
* output size, with rescale -> stretch -> gamma -> colormap (+ reverse) and a
|
|
56
|
+
* constant alpha from `opacity`. `NaN` (and the nodata value when
|
|
57
|
+
* `nodata_alpha`) become transparent. Pairs with the JS warp loop, which
|
|
58
|
+
* samples a grid at the output resolution.
|
|
59
|
+
*/
|
|
60
|
+
export function colorize(pixels: Float64Array, width: number, height: number, min: number, max: number, colormap: string, nodata: number | null | undefined, nodata_alpha: boolean, stretch: string, gamma: number, reversed: boolean, opacity: number): Uint8Array;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Names of the built-in colormaps (JSON array string).
|
|
64
|
+
*/
|
|
65
|
+
export function colormap_names(): string;
|
|
66
|
+
|
|
53
67
|
/**
|
|
54
68
|
* EPSG:3857 bounds `[min_x, min_y, max_x, max_y]` of an XYZ tile.
|
|
55
69
|
*/
|
|
@@ -70,6 +84,8 @@ export interface InitOutput {
|
|
|
70
84
|
readonly cogtiler_num_levels: (a: number) => number;
|
|
71
85
|
readonly cogtiler_pixel_window_for_tile: (a: number, b: number, c: number, d: number, e: number) => void;
|
|
72
86
|
readonly cogtiler_render: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number) => void;
|
|
87
|
+
readonly colorize: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number) => void;
|
|
88
|
+
readonly colormap_names: (a: number) => void;
|
|
73
89
|
readonly tile_bounds_3857: (a: number, b: number, c: number, d: number) => void;
|
|
74
90
|
readonly version: (a: number) => void;
|
|
75
91
|
readonly __wbindgen_export: (a: number, b: number) => number;
|
package/cog_tiler_wasm.js
CHANGED
|
@@ -134,6 +134,67 @@ export class CogTiler {
|
|
|
134
134
|
}
|
|
135
135
|
if (Symbol.dispose) CogTiler.prototype[Symbol.dispose] = CogTiler.prototype.free;
|
|
136
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Colormap a single-band `w*h` grid to RGBA at 1:1 (no resampling), for any
|
|
139
|
+
* output size, with rescale -> stretch -> gamma -> colormap (+ reverse) and a
|
|
140
|
+
* constant alpha from `opacity`. `NaN` (and the nodata value when
|
|
141
|
+
* `nodata_alpha`) become transparent. Pairs with the JS warp loop, which
|
|
142
|
+
* samples a grid at the output resolution.
|
|
143
|
+
* @param {Float64Array} pixels
|
|
144
|
+
* @param {number} width
|
|
145
|
+
* @param {number} height
|
|
146
|
+
* @param {number} min
|
|
147
|
+
* @param {number} max
|
|
148
|
+
* @param {string} colormap
|
|
149
|
+
* @param {number | null | undefined} nodata
|
|
150
|
+
* @param {boolean} nodata_alpha
|
|
151
|
+
* @param {string} stretch
|
|
152
|
+
* @param {number} gamma
|
|
153
|
+
* @param {boolean} reversed
|
|
154
|
+
* @param {number} opacity
|
|
155
|
+
* @returns {Uint8Array}
|
|
156
|
+
*/
|
|
157
|
+
export function colorize(pixels, width, height, min, max, colormap, nodata, nodata_alpha, stretch, gamma, reversed, opacity) {
|
|
158
|
+
try {
|
|
159
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
160
|
+
const ptr0 = passArrayF64ToWasm0(pixels, wasm.__wbindgen_export);
|
|
161
|
+
const len0 = WASM_VECTOR_LEN;
|
|
162
|
+
const ptr1 = passStringToWasm0(colormap, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
163
|
+
const len1 = WASM_VECTOR_LEN;
|
|
164
|
+
const ptr2 = passStringToWasm0(stretch, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
165
|
+
const len2 = WASM_VECTOR_LEN;
|
|
166
|
+
wasm.colorize(retptr, ptr0, len0, width, height, min, max, ptr1, len1, !isLikeNone(nodata), isLikeNone(nodata) ? 0 : nodata, nodata_alpha, ptr2, len2, gamma, reversed, opacity);
|
|
167
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
168
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
169
|
+
var v4 = getArrayU8FromWasm0(r0, r1).slice();
|
|
170
|
+
wasm.__wbindgen_export3(r0, r1 * 1, 1);
|
|
171
|
+
return v4;
|
|
172
|
+
} finally {
|
|
173
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Names of the built-in colormaps (JSON array string).
|
|
179
|
+
* @returns {string}
|
|
180
|
+
*/
|
|
181
|
+
export function colormap_names() {
|
|
182
|
+
let deferred1_0;
|
|
183
|
+
let deferred1_1;
|
|
184
|
+
try {
|
|
185
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
186
|
+
wasm.colormap_names(retptr);
|
|
187
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
188
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
189
|
+
deferred1_0 = r0;
|
|
190
|
+
deferred1_1 = r1;
|
|
191
|
+
return getStringFromWasm0(r0, r1);
|
|
192
|
+
} finally {
|
|
193
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
194
|
+
wasm.__wbindgen_export3(deferred1_0, deferred1_1, 1);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
137
198
|
/**
|
|
138
199
|
* EPSG:3857 bounds `[min_x, min_y, max_x, max_y]` of an XYZ tile.
|
|
139
200
|
* @param {number} z
|
package/cog_tiler_wasm_bg.wasm
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
"Qiusheng Wu <giswqs@gmail.com>"
|
|
6
6
|
],
|
|
7
7
|
"description": "Serverless Cloud Optimized GeoTIFF (COG) dynamic tiling in WebAssembly. TiTiler-style XYZ tiles, no backend.",
|
|
8
|
-
"version": "0.1
|
|
9
|
-
"license": "MIT
|
|
8
|
+
"version": "0.2.1",
|
|
9
|
+
"license": "MIT",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
12
|
"url": "https://github.com/opengeos/cog-tiler-wasm"
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
"cog_tiler_wasm.js",
|
|
17
17
|
"cog_tiler_wasm.d.ts",
|
|
18
18
|
"cog-tiler.js",
|
|
19
|
-
"cog-tiler.d.ts"
|
|
19
|
+
"cog-tiler.d.ts",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
20
22
|
],
|
|
21
23
|
"main": "cog-tiler.js",
|
|
22
24
|
"types": "cog-tiler.d.ts",
|