crossbind
GitHub

PROJ for WASI

v9.9.0WASI

PROJ 9.9.0 for command-line programs under wasmtime, precompiled for wasm32-wasip3, single-threaded as @crossbind/port-proj-wasi.

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

Install

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

A PROJ 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 PROJ's database, proj.db, which the build copies to dist/data/proj; the PROJ_DATA variable tells PROJ where it is. It describes a CRS and turns GPS positions into Swiss LV95 grid coordinates, through EPSG's Helmert transformation, which EPSG gives an accuracy of 1 m.

crossbind.config.js
import projWasi from '@crossbind/port-proj-wasi/crossbind.config.js';
 
export default {
general: { name: 'proj-tool' },
dependencies: [projWasi],
// The program and the proj.db it reads land in dist/.
paths: { config: import.meta.url, output: 'dist' },
};
src/native/main.cpp
// A command-line PROJ for WASI: describes a CRS from proj.db, or transforms the points of a file.
// proj-tool info <crs>
// proj-tool <source crs> <target crs> <points file>
// A points file has one "x y label" per line, longitude or easting first whatever the CRS's axis order.
#include <proj.h>
 
#include <cmath>
#include <cstdio>
#include <fstream>
#include <sstream>
#include <string>
 
namespace {
 
void report(void*, int level, const char* message) {
if (level == PJ_LOG_ERROR) std::fprintf(stderr, "proj: %s\n", message);
}
 
int info(PJ_CONTEXT* context, const char* definition) {
PJ* crs = proj_create(context, definition);
if (!crs) return 1;
std::printf("%s\n", proj_get_name(crs));
double west, south, east, north;
const char* area = nullptr;
if (proj_get_area_of_use(context, crs, &west, &south, &east, &north, &area)) std::printf("area: %g %g %g %g, %s\n", west, south, east, north, area);
PJ* system = proj_crs_get_coordinate_system(context, crs);
std::printf("axes:");
for (int index = 0; system && index < proj_cs_get_axis_count(context, system); index += 1) {
const char* abbreviation = nullptr;
const char* direction = nullptr;
proj_cs_get_axis_info(context, system, index, nullptr, &abbreviation, &direction, nullptr, nullptr, nullptr, nullptr);
std::printf("%s %s %s", index ? "," : "", abbreviation, direction);
}
std::printf("\n");
proj_destroy(system);
proj_destroy(crs);
return 0;
}
 
int transform(PJ_CONTEXT* context, const char* source, const char* target, const char* path) {
std::ifstream file(path);
if (!file) {
std::fprintf(stderr, "cannot open %s\n", path);
return 1;
}
PJ* chosen = proj_create_crs_to_crs(context, source, target, nullptr);
if (!chosen) return 1;
std::printf("%s\n", proj_get_name(chosen));
PJ* transformation = proj_normalize_for_visualization(context, chosen);
proj_destroy(chosen);
if (!transformation) return 1;
std::string line;
while (std::getline(file, line)) {
std::istringstream fields(line);
double x, y;
if (!(fields >> x >> y)) continue;
std::string label;
std::getline(fields >> std::ws, label);
const PJ_COORD out = proj_trans(transformation, PJ_FWD, proj_coord(x, y, 0, HUGE_VAL));
if (out.xy.x == HUGE_VAL) {
std::printf("%s: %s\n", label.c_str(), proj_context_errno_string(context, proj_errno(transformation)));
} else {
std::printf("%s: %.2f %.2f\n", label.c_str(), out.xy.x, out.xy.y);
}
}
proj_destroy(transformation);
return 0;
}
 
} // namespace
 
int main(int argc, char** argv) {
PJ_CONTEXT* context = proj_context_create();
proj_log_func(context, nullptr, report);
const std::string command = argc >= 2 ? argv[1] : "";
int code = 2;
if (command == "info" && argc == 3) {
code = info(context, argv[2]);
} else if (command != "info" && argc == 4) {
code = transform(context, argv[1], argv[2], argv[3]);
} else {
std::fprintf(stderr, "usage: proj-tool info <crs>\n proj-tool <source crs> <target crs> <points file>\n");
}
proj_context_destroy(context);
return code;
}
shell
npx crossbind build -p wasi -b release
wasmtime run --dir=. --env PROJ_DATA=dist/data/proj dist/proj-tool-wasi-wasm32-st-release.wasm info EPSG:2056
wasmtime run --dir=. --env PROJ_DATA=dist/data/proj dist/proj-tool-wasi-wasm32-st-release.wasm EPSG:4326 EPSG:2056 points.txt
output
CH1903+ / LV95
area: 5.95 45.81 10.5 47.81, Liechtenstein; Switzerland.
axes: E east, N north
Inverse of CH1903+ to WGS 84 (1) + Swiss Oblique Mercator 1995
Zurich: 2683196.61 1248035.31
Bern: 2600408.64 1199501.66
Geneva: 2500986.15 1118138.65
Matterhorn: 2617047.96 1091660.42

Command-line tools

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

shell
npm install --global @crossbind/port-proj-bin-wasi@beta
cct-wasi --help
  • cct-wasi
  • cs2cs-wasi
  • geod-wasi
  • gie-wasi
  • proj-wasi
  • projinfo-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
cURLExpatGDALGEOSGeoTIFFiconvLERClibjpeg-turbolibTIFFOpenSSLSpatiaLiteSQLiteWebPzlibZstandard
Type to search every guide page and section.
↑↓ navigate↵ openesc close