crossbind
GitHub

SpatiaLite for WASI

v5.1.0WASI

SpatiaLite 5.1.0 for command-line programs under wasmtime, precompiled for wasm32-wasip3, single-threaded as @crossbind/port-spatialite-wasi.

npm install @crossbind/port-spatialite-wasi@beta

Install

shell
npm install @crossbind/port-spatialite-wasi@beta crossbind@beta

A spatial SQL command-line tool

WASI has no JavaScript bindings: the program is a main() that links SpatiaLite with SQLite, GEOS and PROJ, built into one .wasm and run with wasmtime. It runs SQL against a database file in the current directory and prints geometries as WKT, and the file it writes opens in QGIS and GDAL. ST_Transform reads PROJ's proj.db, which the build copies to dist/data/proj: the second --dir flag mounts that folder and PROJ_DATA tells PROJ where it is.

crossbind.config.js
import spatialiteWasi from '@crossbind/port-spatialite-wasi/crossbind.config.js';
 
export default {
general: { name: 'spatialite-tool' },
dependencies: [spatialiteWasi],
// The program and PROJ's data folder land in dist/, next to each other.
paths: { config: import.meta.url, output: 'dist' },
};
src/native/main.cpp
// A command-line spatial SQL runner for WASI:
// spatialite-tool <database> "<sql>" runs the SQL against the database file
// spatialite-tool <database> reads the SQL from standard input
// SpatiaLite's functions are registered on the connection, so the SQL can create geometry columns,
// measure and transform. Rows print as the sqlite3 shell prints them, columns joined by |, with any
// geometry written as WKT, and a summary line follows. ST_Transform needs PROJ's proj.db: point
// PROJ_DATA at the folder the build writes to .crossbind/build/data/proj.
#include <sqlite3.h>
#include <spatialite.h>
 
#include <cstdio>
#include <string>
#include <vector>
 
// The WASI build of SQLite leaves extension loading out, but SpatiaLite calls
// sqlite3_enable_load_extension when it opens a connection of its own, so the link needs a
// definition. Nothing can be loaded here, and SpatiaLite ignores the answer.
extern "C" int sqlite3_enable_load_extension(sqlite3*, int) { return SQLITE_ERROR; }
 
namespace {
 
std::string readAll(FILE* file) {
std::string text;
std::vector<char> buffer(64 * 1024);
size_t read = 0;
while ((read = std::fread(buffer.data(), 1, buffer.size(), file)) > 0) text.append(buffer.data(), read);
return text;
}
 
// A cell as text; a blob that holds a SpatiaLite geometry prints as its WKT.
std::string cell(sqlite3_stmt* statement, int column, sqlite3_stmt* wkt) {
if (sqlite3_column_type(statement, column) == SQLITE_BLOB) {
sqlite3_reset(wkt);
sqlite3_bind_blob(wkt, 1, sqlite3_column_blob(statement, column), sqlite3_column_bytes(statement, column), SQLITE_TRANSIENT);
if (sqlite3_step(wkt) == SQLITE_ROW && sqlite3_column_type(wkt, 0) != SQLITE_NULL) return reinterpret_cast<const char*>(sqlite3_column_text(wkt, 0));
}
const unsigned char* text = sqlite3_column_text(statement, column);
return text ? reinterpret_cast<const char*>(text) : "";
}
 
} // namespace
 
int main(int argc, char** argv) {
if (argc < 2 || argc > 3) {
std::fprintf(stderr, "usage: spatialite-tool <database> [sql] (without sql, reads it from standard input)\n");
return 2;
}
const std::string sql = argc == 3 ? argv[2] : readAll(stdin);
sqlite3* db = nullptr;
if (sqlite3_open(argv[1], &db) != SQLITE_OK) {
std::fprintf(stderr, "%s: %s\n", argv[1], sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}
void* cache = spatialite_alloc_connection();
spatialite_init_ex(db, cache, 0);
sqlite3_stmt* wkt = nullptr;
sqlite3_prepare_v2(db, "SELECT AsText(?1)", -1, &wkt, nullptr);
int statements = 0;
int status = 0;
const char* rest = sql.c_str();
const char* end = rest + sql.size();
while (rest < end && status == 0) {
sqlite3_stmt* statement = nullptr;
if (sqlite3_prepare_v2(db, rest, static_cast<int>(end - rest), &statement, &rest) != SQLITE_OK) {
std::fprintf(stderr, "sql: %s\n", sqlite3_errmsg(db));
status = 1;
break;
}
if (!statement) continue;
statements += 1;
int result;
while ((result = sqlite3_step(statement)) == SQLITE_ROW) {
for (int column = 0; column < sqlite3_column_count(statement); column += 1) {
std::printf("%s%s", column ? "|" : "", cell(statement, column, wkt).c_str());
}
std::printf("\n");
}
if (result != SQLITE_DONE) {
std::fprintf(stderr, "sql: %s\n", sqlite3_errmsg(db));
status = 1;
}
sqlite3_finalize(statement);
}
if (status == 0) {
std::printf("spatialite %s, %s: %d statement%s, %d rows changed\n", spatialite_version(), argv[1], statements, statements == 1 ? "" : "s",
sqlite3_total_changes(db));
}
sqlite3_finalize(wkt);
sqlite3_close(db);
spatialite_cleanup_ex(cache);
return status;
}
shell
npx crossbind build -p wasi -b release
wasmtime run --dir=. --dir=dist/data/proj::/data/proj --env PROJ_DATA=/data/proj dist/spatialite-tool-wasi-wasm32-st-release.wasm cities.db < cities.sql
wasmtime run --dir=. --dir=dist/data/proj::/data/proj --env PROJ_DATA=/data/proj dist/spatialite-tool-wasi-wasm32-st-release.wasm cities.db "SELECT name, ST_Transform(geom, 32635) FROM cities ORDER BY name"
wasmtime run --dir=. --dir=dist/data/proj::/data/proj --env PROJ_DATA=/data/proj dist/spatialite-tool-wasi-wasm32-st-release.wasm cities.db "SELECT name, CAST(Round(ST_Distance(geom, MakePoint(28.9784, 41.0082, 4326), 1) / 1000) AS INTEGER) AS km FROM cities ORDER BY km"
output
1
1
spatialite 5.1.0, cities.db: 4 statements, 293 rows changed
Ankara|POINT(1000822.269819 4436835.643104)
Istanbul|POINT(666370.505017 4541552.487191)
Izmir|POINT(512464.98383 4252836.769027)
spatialite 5.1.0, cities.db: 1 statement, 0 rows changed
Istanbul|0
Izmir|327
Ankara|350
spatialite 5.1.0, cities.db: 1 statement, 0 rows changed

What is different on WASI

  • There are no JavaScript bindings: src/native provides main(int, char**), and the build is a single .wasm.
  • Files come from the host through --dir preopens; --dir=. gives the program the current directory.
  • crossbind build -p wasi -b release writes the program to .crossbind/build/<name>-wasi-wasm32-st-release.wasm.
  • WASI 0.3's wasi:cli/exit carries success or failure only, so any non-zero return from main reaches the shell as exit code 1.
  • The build needs wasi-sdk 34 or newer (WASI_SDK_PATH) or the crossbind Docker image; running needs wasmtime 47 or newer. See WASI.

Other platforms

Facts on this page come from the port manifests in the repository and from what npm served on beta when the site was built. See the Libraries guide for the full consumer flow.

MORE LIBRARIES
cURLExpatGDALGEOSGeoTIFFiconvLERClibjpeg-turbolibTIFFOpenSSLPROJSQLiteWebPzlibZstandard
Type to search every guide page and section.
↑↓ navigate↵ openesc close