crossbind
GitHub

GEOS for WASI

v3.15.0WASI

GEOS 3.15.0 for command-line programs under wasmtime, precompiled for wasm32-wasip3, single-threaded as @crossbind/port-geos-wasi.

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

Install

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

A GEOS command-line tool

WASI has no JavaScript bindings: the program is a main() that links the library, built into one .wasm and run with wasmtime. This one reads shapes from WKT files and prints what GEOS makes of them.

crossbind.config.js
import geosWasi from '@crossbind/port-geos-wasi/crossbind.config.js';
 
export default {
general: { name: 'geos-tool' },
dependencies: [geosWasi],
paths: { config: import.meta.url },
};
src/native/main.cpp
// A command-line GEOS for WASI: reads shapes from WKT files and prints results as normalised WKT.
// geos-tool intersection|union|difference <a.wkt> <b.wkt>
// geos-tool validate <a.wkt>
#include <geos_c.h>
 
#include <cstdio>
#include <cstring>
#include <fstream>
#include <memory>
#include <sstream>
#include <string>
 
namespace {
 
GEOSContextHandle_t context;
 
struct Destroy {
void operator()(GEOSGeometry* geometry) const { GEOSGeom_destroy_r(context, geometry); }
};
using Geometry = std::unique_ptr<GEOSGeometry, Destroy>;
 
void report(const char* message, void*) { std::fprintf(stderr, "geos: %s\n", message); }
 
Geometry read(const char* path) {
std::ifstream file(path);
if (!file) {
std::fprintf(stderr, "cannot open %s\n", path);
return nullptr;
}
std::stringstream text;
text << file.rdbuf();
GEOSWKTReader* reader = GEOSWKTReader_create_r(context);
Geometry geometry(GEOSWKTReader_read_r(context, reader, text.str().c_str()));
GEOSWKTReader_destroy_r(context, reader);
return geometry;
}
 
// Prints the result normalised, with its area; false when GEOS failed (its message is on stderr).
bool print(const std::string& label, GEOSGeometry* result) {
if (!result) return false;
const Geometry owned(result);
GEOSNormalize_r(context, owned.get());
GEOSWKTWriter* writer = GEOSWKTWriter_create_r(context);
char* wkt = GEOSWKTWriter_write_r(context, writer, owned.get());
GEOSWKTWriter_destroy_r(context, writer);
double area = 0;
GEOSArea_r(context, owned.get(), &area);
std::printf("%s: %s, area %g\n", label.c_str(), wkt, area);
GEOSFree_r(context, wkt);
return true;
}
 
int validate(const char* path) {
const Geometry shape = read(path);
if (!shape) return 1;
char* reason = GEOSisValidReason_r(context, shape.get());
if (!reason) return 1;
std::printf("validate %s: %s\n", path, reason);
const bool valid = std::strcmp(reason, "Valid Geometry") == 0;
GEOSFree_r(context, reason);
return valid || print("repaired", GEOSMakeValid_r(context, shape.get())) ? 0 : 1;
}
 
int overlay(const std::string& operation, const char* first, const char* second) {
const Geometry a = read(first);
const Geometry b = read(second);
if (!a || !b) return 1;
GEOSGeometry* result = operation == "intersection" ? GEOSIntersection_r(context, a.get(), b.get())
: operation == "union" ? GEOSUnion_r(context, a.get(), b.get())
: GEOSDifference_r(context, a.get(), b.get());
return print(operation + " " + first + " " + second, result) ? 0 : 1;
}
 
int run(int argc, char** argv) {
const std::string operation = argc >= 2 ? argv[1] : "";
if ((operation == "intersection" || operation == "union" || operation == "difference") && argc >= 4) return overlay(operation, argv[2], argv[3]);
if (operation == "validate" && argc >= 3) return validate(argv[2]);
std::fprintf(stderr, "usage: geos-tool intersection|union|difference <a.wkt> <b.wkt>\n geos-tool validate <a.wkt>\n");
return 2;
}
 
} // namespace
 
int main(int argc, char** argv) {
context = GEOS_init_r();
GEOSContext_setErrorMessageHandler_r(context, report, nullptr);
const int code = run(argc, argv);
GEOS_finish_r(context);
return code;
}
shell
npx crossbind build -p wasi -b release
wasmtime run --dir=. .crossbind/build/geos-tool-wasi-wasm32-st-release.wasm intersection parcel.wkt flood.wkt
wasmtime run --dir=. .crossbind/build/geos-tool-wasi-wasm32-st-release.wasm difference parcel.wkt flood.wkt
wasmtime run --dir=. .crossbind/build/geos-tool-wasi-wasm32-st-release.wasm validate bowtie.wkt
output
intersection parcel.wkt flood.wkt: POLYGON ((5 5, 5 10, 10 10, 10 5, 5 5)), area 25
difference parcel.wkt flood.wkt: POLYGON ((0 0, 0 10, 5 10, 5 5, 10 5, 10 0, 0 0)), area 75
validate bowtie.wkt: Self-intersection[5 5]
repaired: MULTIPOLYGON (((5 5, 10 10, 10 0, 5 5)), ((0 0, 0 10, 5 5, 0 0))), area 50

Command-line tools

One upstream command ships as npm executables built for wasm32-wasip3. They need wasmtime on PATH and no compiler - see WASI commands.

shell
npm install --global @crossbind/port-geos-bin-wasi@beta
geosop-wasi --help
  • geosop-wasi

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
cURLExpatGDALGeoTIFFiconvLERClibjpeg-turbolibTIFFOpenSSLPROJSpatiaLiteSQLiteWebPzlibZstandard
Type to search every guide page and section.
↑↓ navigate↵ openesc close