geolibre-wasm 0.1.0
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/README.md +60 -0
- package/geolibre-cli.wasm +0 -0
- package/package.json +32 -0
- package/tools.d.ts +44 -0
- package/tools.mjs +93 -0
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# geolibre-wasm
|
|
2
|
+
|
|
3
|
+
The [`whitebox_next_gen`](https://github.com/jblindsay/whitebox_next_gen) pure-Rust
|
|
4
|
+
geospatial tool suite, **plus new [GeoLibre](https://github.com/opengeos/GeoLibre)
|
|
5
|
+
tools**, compiled to WebAssembly (WASI) and runnable entirely in the browser,
|
|
6
|
+
Node, Deno, or any bundler. No server, no Python, no native install.
|
|
7
|
+
|
|
8
|
+
Source repo: [opengeos/geolibre-rust](https://github.com/opengeos/geolibre-rust).
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install geolibre-wasm
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
It has a single runtime dependency, [`@bjorn3/browser_wasi_shim`](https://github.com/bjorn3/browser_wasi_shim),
|
|
17
|
+
which runs the WASI binary over an in-memory filesystem.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
import { runTool, listTools, listManifests } from "geolibre-wasm/tools";
|
|
23
|
+
|
|
24
|
+
// every available tool id
|
|
25
|
+
const tools = await listTools();
|
|
26
|
+
|
|
27
|
+
// run a tool: inputs go into an in-memory /work dir; new files come back out
|
|
28
|
+
const { exitCode, stdout, files } = await runTool("slope", {
|
|
29
|
+
args: ["--input=/work/dem.tif", "--output=/work/slope.tif", "--units=degrees"],
|
|
30
|
+
input: { "dem.tif": demBytes }, // Uint8Array
|
|
31
|
+
});
|
|
32
|
+
const slopeCog = files["slope.tif"]; // Uint8Array (Cloud Optimized GeoTIFF)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Inputs are placed under `/work` (keyed by filename); any file a tool writes is
|
|
36
|
+
returned in `files`. Raster outputs are Cloud Optimized GeoTIFFs; vector outputs
|
|
37
|
+
are GeoJSON.
|
|
38
|
+
|
|
39
|
+
## API
|
|
40
|
+
|
|
41
|
+
| Export | Description |
|
|
42
|
+
|---|---|
|
|
43
|
+
| `initTools(source?)` | Compile the WASI runner once. Omit `source` in browsers/bundlers; pass wasm bytes or a URL/Response in Node. |
|
|
44
|
+
| `listTools(): Promise<string[]>` | Every available tool id. |
|
|
45
|
+
| `listManifests(): Promise<ToolManifest[]>` | All tool manifests (parameter schemas), for building UIs offline. |
|
|
46
|
+
| `runTool(tool, { args?, input? }): Promise<ToolResult>` | Run one tool over the in-memory filesystem. |
|
|
47
|
+
|
|
48
|
+
`ToolResult` is `{ exitCode: number, stdout: string[], files: Record<string, Uint8Array> }`.
|
|
49
|
+
|
|
50
|
+
## Notes
|
|
51
|
+
|
|
52
|
+
- Bundler users (Vite, etc.): exclude this package from dependency pre-bundling
|
|
53
|
+
so the `new URL("./geolibre-cli.wasm", import.meta.url)` reference is preserved
|
|
54
|
+
(e.g. Vite's `optimizeDeps.exclude`).
|
|
55
|
+
- Bounded by WebAssembly's ~4 GiB memory and single-threaded execution; use a
|
|
56
|
+
server-side path for very large data.
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
MIT OR Apache-2.0
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "geolibre-wasm",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Pure-Rust geospatial toolkit (whitebox_next_gen) compiled to WebAssembly (WASI) for GeoLibre's in-browser tool execution",
|
|
6
|
+
"license": "MIT OR Apache-2.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/opengeos/geolibre-rust"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/opengeos/geolibre-rust",
|
|
12
|
+
"keywords": ["geospatial", "wasm", "wasi", "gis", "geotiff", "geolibre", "whitebox"],
|
|
13
|
+
"files": [
|
|
14
|
+
"geolibre-cli.wasm",
|
|
15
|
+
"tools.mjs",
|
|
16
|
+
"tools.d.ts",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./tools.d.ts",
|
|
22
|
+
"default": "./tools.mjs"
|
|
23
|
+
},
|
|
24
|
+
"./tools": {
|
|
25
|
+
"types": "./tools.d.ts",
|
|
26
|
+
"default": "./tools.mjs"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@bjorn3/browser_wasi_shim": "^0.4.2"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/tools.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Result of running a tool. */
|
|
2
|
+
export interface ToolResult {
|
|
3
|
+
/** Process exit code (0 = success). */
|
|
4
|
+
exitCode: number;
|
|
5
|
+
/** Captured stdout/stderr lines. */
|
|
6
|
+
stdout: string[];
|
|
7
|
+
/** New files the tool wrote, keyed by filename (e.g. the --output path's basename). */
|
|
8
|
+
files: Record<string, Uint8Array>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface RunToolOptions {
|
|
12
|
+
/** CLI args, e.g. ["--input=/work/dem.tif", "--output=/work/out.tif", "--units=degrees"]. */
|
|
13
|
+
args?: string[];
|
|
14
|
+
/** Input files placed under /work, keyed by filename. */
|
|
15
|
+
input?: Record<string, Uint8Array>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** A single parameter in a tool manifest. */
|
|
19
|
+
export interface ToolParam {
|
|
20
|
+
name: string;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** A tool's metadata and parameter schema. */
|
|
25
|
+
export interface ToolManifest {
|
|
26
|
+
id: string;
|
|
27
|
+
display_name: string;
|
|
28
|
+
summary: string;
|
|
29
|
+
params: ToolParam[];
|
|
30
|
+
[key: string]: unknown;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Compile the WASI tool runner once. Omit `source` in browsers/bundlers; pass
|
|
34
|
+
* the wasm bytes or a URL/Response in Node. */
|
|
35
|
+
export function initTools(source?: URL | Response | BufferSource | string): Promise<WebAssembly.Module>;
|
|
36
|
+
|
|
37
|
+
/** List every available tool id. */
|
|
38
|
+
export function listTools(): Promise<string[]>;
|
|
39
|
+
|
|
40
|
+
/** Fetch every tool manifest (parameter schemas), for building UIs offline. */
|
|
41
|
+
export function listManifests(): Promise<ToolManifest[]>;
|
|
42
|
+
|
|
43
|
+
/** Run one tool over an in-memory filesystem. */
|
|
44
|
+
export function runTool(tool: string, opts?: RunToolOptions): Promise<ToolResult>;
|
package/tools.mjs
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// geolibre-wasm - run the whitebox_next_gen geospatial tool suite from
|
|
2
|
+
// JavaScript. The tools are the WASI binary `geolibre-cli.wasm`; this module
|
|
3
|
+
// executes them through a WASI shim with an in-memory filesystem, so they run in
|
|
4
|
+
// browsers, Node, Deno, and bundlers without a real disk. Raster outputs are
|
|
5
|
+
// Cloud Optimized GeoTIFFs.
|
|
6
|
+
//
|
|
7
|
+
// import { runTool, listTools } from "geolibre-wasm/tools";
|
|
8
|
+
// const { files } = await runTool("slope", {
|
|
9
|
+
// args: ["--input=/work/dem.tif", "--output=/work/slope.tif", "--units=degrees"],
|
|
10
|
+
// input: { "dem.tif": demBytes }, // Uint8Array, placed under /work
|
|
11
|
+
// });
|
|
12
|
+
// const slopeCog = files["slope.tif"]; // Uint8Array
|
|
13
|
+
import { WASI, File, OpenFile, ConsoleStdout, PreopenDirectory } from "@bjorn3/browser_wasi_shim";
|
|
14
|
+
|
|
15
|
+
let _module = null;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Compile the WASI tool runner once. In browsers/bundlers it loads the bundled
|
|
19
|
+
* `geolibre-cli.wasm` relative to this module. In Node (no fetch of file URLs),
|
|
20
|
+
* pass the wasm bytes or a URL/Response explicitly.
|
|
21
|
+
* @param {URL|Response|BufferSource|string} [source]
|
|
22
|
+
* @returns {Promise<WebAssembly.Module>}
|
|
23
|
+
*/
|
|
24
|
+
export async function initTools(source) {
|
|
25
|
+
if (_module) return _module;
|
|
26
|
+
if (!source) source = new URL("./geolibre-cli.wasm", import.meta.url);
|
|
27
|
+
if (source instanceof Uint8Array || source instanceof ArrayBuffer) {
|
|
28
|
+
_module = await WebAssembly.compile(source);
|
|
29
|
+
} else if (source instanceof Response) {
|
|
30
|
+
_module = await WebAssembly.compileStreaming(source);
|
|
31
|
+
} else {
|
|
32
|
+
_module = await WebAssembly.compileStreaming(fetch(source));
|
|
33
|
+
}
|
|
34
|
+
return _module;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function exec(argv, inputFiles) {
|
|
38
|
+
const mod = await initTools();
|
|
39
|
+
const inNames = new Set(Object.keys(inputFiles));
|
|
40
|
+
const contents = new Map(
|
|
41
|
+
Object.entries(inputFiles).map(([k, v]) => [k, new File(new Uint8Array(v))]));
|
|
42
|
+
const work = new PreopenDirectory("/work", contents);
|
|
43
|
+
const stdout = [];
|
|
44
|
+
const fds = [
|
|
45
|
+
new OpenFile(new File(new Uint8Array())),
|
|
46
|
+
ConsoleStdout.lineBuffered((s) => stdout.push(s)),
|
|
47
|
+
ConsoleStdout.lineBuffered((s) => stdout.push(s)),
|
|
48
|
+
work,
|
|
49
|
+
];
|
|
50
|
+
const wasi = new WASI(["geolibre", ...argv], [], fds, { debug: false });
|
|
51
|
+
const inst = await WebAssembly.instantiate(mod, { wasi_snapshot_preview1: wasi.wasiImport });
|
|
52
|
+
let exitCode = 0;
|
|
53
|
+
try { exitCode = wasi.start(inst); }
|
|
54
|
+
catch (e) { if (e && e.constructor && e.constructor.name === "WASIProcExit") exitCode = e.code; else throw e; }
|
|
55
|
+
const files = {};
|
|
56
|
+
for (const [name, entry] of work.dir.contents) {
|
|
57
|
+
if (entry.data && !inNames.has(name)) files[name] = entry.data;
|
|
58
|
+
}
|
|
59
|
+
return { exitCode, stdout, files };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* List every available tool id.
|
|
64
|
+
* @returns {Promise<string[]>}
|
|
65
|
+
*/
|
|
66
|
+
export async function listTools() {
|
|
67
|
+
const { stdout } = await exec(["list"], {});
|
|
68
|
+
return stdout.map((s) => s.trim()).filter(Boolean);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Fetch every tool manifest (id, display name, parameter schema, license tier).
|
|
73
|
+
* Lets a host build tool dialogs fully offline, without a server.
|
|
74
|
+
* @returns {Promise<object[]>}
|
|
75
|
+
*/
|
|
76
|
+
export async function listManifests() {
|
|
77
|
+
const { stdout } = await exec(["manifests"], {});
|
|
78
|
+
return JSON.parse(stdout.join(""));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Run one tool over an in-memory filesystem.
|
|
83
|
+
* @param {string} tool tool id, e.g. "slope" (see {@link listTools})
|
|
84
|
+
* @param {object} [opts]
|
|
85
|
+
* @param {string[]} [opts.args] CLI args, e.g. ["--input=/work/dem.tif","--output=/work/out.tif","--units=degrees"]
|
|
86
|
+
* @param {Object<string, Uint8Array>} [opts.input] files placed under /work (key = filename)
|
|
87
|
+
* @returns {Promise<{exitCode:number, stdout:string[], files:Object<string,Uint8Array>}>}
|
|
88
|
+
* `files` contains any new files the tool wrote (e.g. the --output path).
|
|
89
|
+
*/
|
|
90
|
+
export async function runTool(tool, opts = {}) {
|
|
91
|
+
const { args = [], input = {} } = opts;
|
|
92
|
+
return exec([tool, ...args], input);
|
|
93
|
+
}
|