PROJ for WebAssembly
v9.9.0WebAssemblyPROJ 9.9.0 for browsers, Node.js and edge runtimes, precompiled for wasm32, single-threaded and multi-threaded as @crossbind/port-proj-wasm.
npm install @crossbind/port-proj-wasm@betaInstall
crossbind itself arrives with your bundler plugin, or with a new project from npm create crossbind@beta; Bundlers has Vite, Webpack, Rspack and Rollup.
Usage
Each example runs here, in this tab, and prints what the site build checked.
Each example also has a JavaScript only tab: the same task with no C++ file, calling PROJ's own headers from @crossbind/port-proj directly. All 5 work that way.
Transform a coordinate between two CRSs
The most used part of PROJ: proj_create_crs_to_crs picks the operation between two coordinate reference systems, proj_normalize_for_visualization puts longitude first, and proj_trans runs it forward or back.
UTM zone 35N 666370.51 4541552.49 28.978400 41.008200 Popular Visualisation Pseudo-Mercator 3225860.73 5013551.24
proj_trans takes and returns the PJ_COORD union by value, and crossbind binds it as a class without members, so JavaScript cannot read the result: the point goes through proj_trans_generic instead, as two one-element double arrays. PROJ explains a failure only through its log callback, and a JavaScript function cannot cross into the worker, so the error text is proj_context_errno_string's, such as "Unknown error (code 4096)" for an unknown EPSG code, while PROJ's own message ("crs not found: EPSG:99999") goes to the console.
UTM zone 35N 666370.51 4541552.49 28.978400 41.008200 Popular Visualisation Pseudo-Mercator 3225860.73 5013551.24
Write a CRS as WKT, a .prj, PROJJSON or a PROJ string
proj_create reads a CRS from an EPSG code or any definition; proj_as_wkt writes WKT2 or the ESRI WKT a shapefile's .prj holds, proj_as_projjson writes PROJJSON, and proj_as_proj_string the short PROJ string, which drops names and the area of use.
EPSG:32635 WGS 84 / UTM zone 35N +proj=utm +zone=35 +datum=WGS84 +units=m +no_defs +type=crs PROJCRS["WGS 84 / UTM zone 35N", PROJCS["WGS_1984_UTM_Zone_35N",GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",500000.0],PARAMETER["False_Northing",0.0],PARAMETER["Central_Meridian",27.0],PARAMETER["Scale_Factor",0.9996],PARAMETER["Latitude_Of_Origin",0.0],UNIT["Meter",1.0]] ProjectedCRS Transverse Mercator 0 27 0.9996 500000 0
The same calls as the C++: every exporter returns a const char *, which arrives as a JavaScript string, and enum values cross one member at a time (await PJ_WKT_TYPE.PJ_WKT1_ESRI). The options are a const char *const * list, null for the defaults; to pass some, write cstrings into an allocPointer array and leave its last slot empty. The context and the CRS are destroyed by hand.
EPSG:32635 WGS 84 / UTM zone 35N +proj=utm +zone=35 +datum=WGS84 +units=m +no_defs +type=crs PROJCRS["WGS 84 / UTM zone 35N", PROJCS["WGS_1984_UTM_Zone_35N",GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",500000.0],PARAMETER["False_Northing",0.0],PARAMETER["Central_Meridian",27.0],PARAMETER["Scale_Factor",0.9996],PARAMETER["Latitude_Of_Origin",0.0],UNIT["Meter",1.0]] ProjectedCRS Transverse Mercator 0 27 0.9996 500000 0
Read where a CRS applies and the order of its axes
proj_get_area_of_use gives the region EPSG defines a CRS for, as a bounding box in degrees and a description. proj_crs_get_coordinate_system and proj_cs_get_axis_info give the axis order, which is latitude first for EPSG:4326 and northing first for Poland's grid.
EPSG:4326: Lat north (degree), Lon east (degree) | World. -180 -90 180 90 EPSG:2180: x north (metre), y east (metre) | Poland - onshore and offshore. 14.14 49 24.15 55.93 EPSG:2056: E east (metre), N north (metre) | Liechtenstein; Switzerland. 5.95 45.81 10.5 47.81
Both calls answer through out-parameters, which JavaScript allocates: an 8-byte allocBuffer for each double *, read with readNumberAt, and an allocPointer slot for each const char **, read with readPointerAt and readCString. null skips the ones not needed. The strings stay owned by PROJ, and the coordinate system and the CRS are destroyed by hand.
EPSG:4326: Lat north (degree), Lon east (degree) | World. -180 -90 180 90 EPSG:2180: x north (metre), y east (metre) | Poland - onshore and offshore. 14.14 49 24.15 55.93 EPSG:2056: E east (metre), N north (metre) | Liechtenstein; Switzerland. 5.95 45.81 10.5 47.81
Measure distances, headings and areas on the ellipsoid
geod_inverse gives the shortest route between two points on the WGS 84 ellipsoid and its headings, geod_direct where a heading and a distance lead, and geod_polygonarea the area of a polygon. This is GeographicLib's algorithm, part of PROJ; it needs no CRS and no proj.db.
8080.310 km, leaving on 309.12°, arriving on 230.50° halfway at 54.1948, -22.5976 1145170.4 km², perimeter 4868.1 km
struct geod_geodesic is bound as a class: new geod_geodesic() allocates one and geod_init fills it in, so JavaScript needs none of its fields. Every result comes back through a double * out-parameter, an 8-byte allocBuffer read with readNumberAt (null skips one), and the corners of the polygon go in as two double arrays written with writeNumberAt.
8080.310 km, leaving on 309.12°, arriving on 230.50° halfway at 54.1948, -22.5976 1145170.4 km², perimeter 4868.1 km
Find the EPSG code of a .prj, and the UTM zone of a point
A shapefile's .prj names no EPSG code. proj_identify matches it against the registry, with a confidence that drops when names are missing, as in a PROJ string. proj_get_crs_info_list_from_database lists the CRSs whose area of use contains a point.
EPSG:32635 WGS 84 / UTM zone 35N, confidence 100 EPSG:32635 WGS 84 / UTM zone 35N, confidence 70 Istanbul: EPSG:32635 WGS 84 / UTM zone 35N Sydney: EPSG:32756 WGS 84 / UTM zone 56S
proj_identify hands its confidences back through an int **: JavaScript passes an allocPointer slot, reads the array PROJ put there with readNumberAt and frees it with proj_int_list_destroy. The point search cannot use proj_get_crs_info_list_from_database as the C++ does: its filter is a PROJ_CRS_LIST_PARAMETERS struct whose fields crossbind does not bind, so values set from JavaScript never reach PROJ, and a handle in its place is refused ("Expected null or instance of PROJ_CRS_LIST_PARAMETERS"). The candidates come from proj_create_from_name instead, and JavaScript checks each area of use itself.
EPSG:32635 WGS 84 / UTM zone 35N, confidence 100 EPSG:32635 WGS 84 / UTM zone 35N, confidence 70 Istanbul: EPSG:32635 WGS 84 / UTM zone 35N Sydney: EPSG:32756 WGS 84 / UTM zone 56S
What is different on WebAssembly
- In a browser the module runs in a Worker by default (
useWorker), so every call returns a promise:awaitcalls and constructors alike. - The module has its own filesystem:
m.FSwrites files,m.getFileBytesreads them back andm.autoMountFilesmountsFileobjects from an<input type=file>./memfslives in memory;/opfspersists across reloads and needs the Worker. See Filesystem. - In Node.js,
m.FSis the real disk, so use real paths there. - Multi-threaded builds (
runtime: 'mt') need COOP and COEP headers in production. See Threading.
Other platforms
- PROJ overview: the apps, every platform's setup and the packages.
- PROJ for Android: React Native apps on Android.
- PROJ for iOS: React Native apps on iOS.
- PROJ for WASI: command-line programs under wasmtime.
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.