Spatial operations and computational geometry for WebAssembly.
A WebAssembly port of JTS (Java Topology Suite) using GraalVM Native Image with web-image backend (GraalVM 26 Early Access preview).
Current: JTS 1.20.0 compiled to WebAssembly with JavaScript API
Note: This is a proof-of-concept wrapper providing basic JTS functionality. The underlying WASM binary includes the full JTS 1.20.0 library, but most of it isn't exposed to JavaScript yet. Adding new functionality requires creating Java wrapper methods in API.java, exporting them to JavaScript using annotations, and handling type conversions. This isn't particularly difficult but it still isn't fully automatic.
Request additional JTS features →
Currently available:
- Object-oriented API - Call methods directly on geometries:
point.buffer(10),poly.union(other) - Functional API - Also available:
wasmts.geom.buffer(point, 10) - WASM binary compiled from JTS source
- Automatic memory management via JavaScript GC
- Geometry types: Point, LineString, Polygon, Multi*, GeometryCollection
- Geometry operations: buffer, union, intersection, difference, convexHull, simplify, copy, reverse, normalize
- Geometry properties: getBoundary, getCentroid, getEnvelope, getInteriorPoint
- Advanced buffering with custom parameters (cap/join styles, erosion)
- Offset curves for parallel line generation via OffsetCurveBuilder
- LineMerger for combining connected linestrings
- CascadedPolygonUnion for efficient multi-polygon union
- Spatial predicates: contains, intersects, touches, crosses, within, overlaps, covers, coveredBy, equalsTopo
- Validation: isSimple, isRectangle, isEmpty, isValid
- 2D, 3D (XYZ), and 4D (XYZM) coordinate support
- PreparedGeometry for optimized repeated predicates
- Geometry analysis algorithms (minimum bounding rectangles and circles)
- STRtree spatial indexing
- WKT, WKB, and GeoJSON I/O with 3D/4D support
- User data attachment (getUserData/setUserData)
API Design: The functional API (wasmts.geom.*) is the primary implementation. The OO API (geom.buffer()) is syntactic sugar that delegates to the functional API.
- GraalVM 26 EA (26-dev+13.1 or later)
- Node.js 24+ (for testing, optional)
Prerequisites:
- GraalVM 26 EA with
native-imageinstalled - Maven 3.6+ (or use the provided wrapper)
Build command:
# Clone the repository
git clone <your-repo-url> wasmts
cd wasmts
# Build WASM with Maven (downloads all dependencies automatically)
mvn clean packageWhat Maven does:
- Downloads JTS 1.20.0 from Maven Central
- Downloads GraalVM webimage-preview API
- Compiles Java source code
- Runs
native-image --tool:svm-wasmto build WebAssembly - Copies output files to project root
Output:
dist/wasmts.js- WASM loader (107KB)dist/wasmts.js.wasm- WASM binary (5.7MB)target/wasmts.js.wat- WebAssembly text format (debug only)
import('wasmts').then(() => {
// Wait for WASM to initialize
setTimeout(() => {
// Create geometries
const point = wasmts.geom.createPoint(5, 10);
const poly = wasmts.io.WKTReader.read('POLYGON ((0 0, 100 0, 100 100, 0 100, 0 0))');
// Operations
const buffered = point.buffer(50);
const intersection = poly.intersection(buffered);
// Results
console.log('Buffer area:', buffered.getArea().toFixed(2));
console.log('Intersection area:', intersection.getArea().toFixed(2));
console.log('Contains point:', poly.contains(point));
}, 1000);
});The generated WASM works in any modern browser with no modifications needed.
Method 1: Serve locally with a simple HTTP server:
# Python 3
python3 -m http.server 8000
# Node.js
npx http-server -p 8000
# Then open: http://localhost:8000/docs/Method 2: Use in your web app:
<!DOCTYPE html>
<html>
<head>
<title>WasmTS Browser App</title>
</head>
<body>
<!-- Load the WASM module -->
<script src="wasmts.js"></script>
<script>
// Wait for WASM to initialize
function waitForWasmTS() {
if (typeof wasmts !== 'undefined' && wasmts.geom) {
// WASM is ready
runApp();
} else {
// Check again in 100ms
setTimeout(waitForWasmTS, 100);
}
}
waitForWasmTS();
function runApp() {
// Create geometries
const point = wasmts.geom.createPoint(5, 10);
const poly = wasmts.io.WKTReader.read('POLYGON ((0 0, 100 0, 100 100, 0 100, 0 0))');
// Operations using OO API
const buffered = point.buffer(50);
const intersection = poly.intersection(buffered);
// Display results
// Note: toFixed(2) rounds to 2 decimal places
console.log('Buffer area:', buffered.getArea().toFixed(2));
console.log('Intersection area:', intersection.getArea().toFixed(2));
console.log('Contains point:', poly.contains(point));
// Use STRtree for spatial indexing
const index = wasmts.index.strtree.STRtree.create();
const envelope = buffered.getEnvelopeInternal();
wasmts.index.strtree.STRtree.insert(index, envelope, {id: 1, name: 'buffer'});
const results = wasmts.index.strtree.STRtree.query(index, envelope);
console.log('Found:', results.length, 'geometries');
}
</script>
</body>
</html>Important notes:
- Use
<script src="wasmts.js"></script>(NOTimport()) so the loader can find the.wasmfile - WASM initialization is asynchronous - wait for
wasmtsnamespace before calling functions - Both
wasmts.jsandwasmts.js.wasmmust be in the same directory - See docs/index.html for a complete working example
Interactive demo: Open docs/index.html to test geometry creation, operations, and spatial indexing.
Browser compatibility: Any modern browser with WebAssembly support (Chrome, Firefox, Safari, Edge).
Basic Usage:
// Create 2D point
const point = wasmts.geom.createPoint(5, 10);
console.log('Point created');
// Create polygon from WKT
const poly = wasmts.io.WKTReader.read('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))');
console.log('Polygon area:', poly.getArea());
// Buffer operation
const buffered = point.buffer(5);
console.log('Buffer area:', buffered.getArea().toFixed(2));
// Spatial predicates
console.log('Intersects:', poly.intersects(point));
console.log('Contains:', poly.contains(point));
// Boolean operations
const union = poly.union(buffered);
const intersection = poly.intersection(buffered);
console.log('Union area:', union.getArea().toFixed(2));3D/4D Coordinates:
// Create 3D point (XYZ - with elevation)
const point3d = wasmts.geom.createPoint(5, 10, 15);
const coords = point3d.getCoordinates();
console.log('3D Point:', coords[0]); // {x: 5, y: 10, z: 15}
// Create 4D point (XYZM - with measure)
const point4d = wasmts.geom.createPoint(5, 10, 15, 20);
const coords4d = point4d.getCoordinates();
console.log('4D Point:', coords4d[0]); // {x: 5, y: 10, z: 15, m: 20}
// Create 3D LineString from WKT
const line3d = wasmts.io.WKTReader.read('LINESTRING Z (0 0 0, 1 1 10, 2 0 20)');
const lineCoords = line3d.getCoordinates();
console.log('Elevation at point 1:', lineCoords[1].z); // 10
// WKB preserves all dimensions
const wkb = wasmts.io.WKBWriter.write(point4d);
const parsed = wasmts.io.WKBReader.read(wkb);
const parsedCoords = parsed.getCoordinates();
console.log('Round-trip preserved M:', parsedCoords[0].m); // 20WKT/WKB I/O:
// WKT - human readable text format
const wkt = wasmts.io.WKTWriter.write(poly);
console.log('WKT:', wkt); // "POLYGON ((0 0, 10 0, ...))"
const geomFromWKT = wasmts.io.WKTReader.read('POINT (5 10)');
// WKB - compact binary format
const wkb = wasmts.io.WKBWriter.write(poly);
console.log('WKB size:', wkb.length, 'bytes');
const geomFromWKB = wasmts.io.WKBReader.read(wkb);
console.log('Round-trip:', geomFromWKB.equals(poly));Spatial Indexing with STRtree:
const index = wasmts.index.strtree.STRtree.create();
const envelope = poly.getEnvelopeInternal();
wasmts.index.strtree.STRtree.insert(index, envelope, {id: 1, data: poly});
const searchEnv = wasmts.geom.createEnvelope(0, 20, 0, 20);
const results = wasmts.index.strtree.STRtree.query(index, searchEnv);
console.log('Found:', results.length, 'geometries');See full examples: test/test-node.mjs, docs/index.html
Standard JTS factory pattern - Creates a factory instance:
const factory = wasmts.geom.GeometryFactory();
const point = factory.createPoint(x, y);
const envelope = factory.toGeometry(envelopeObject);Available methods:
createPoint(x, y)- Create 2D point from coordinatescreatePoint(x, y, z)- Create 3D point (XYZ)createPoint(x, y, z, m)- Create 4D point (XYZM)toGeometry(envelope)- Convert envelope to polygon
For complex geometries (LineString, Polygon, Multi*), use WKT or WKB instead:
const line = wasmts.io.WKTReader.read('LINESTRING (0 0, 10 10, 20 0)');
const poly = wasmts.io.WKTReader.read('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))');All creation methods return geometry objects with shape {type: string, _jtsGeom: JavaObject}.
Points:
wasmts.geom.createPoint(x, y)- Create 2D pointwasmts.geom.createPoint(x, y, z)- Create 3D point (XYZ)wasmts.geom.createPoint(x, y, z, m)- Create 4D point (XYZM with measure)
LineStrings:
wasmts.geom.createLineString([{x, y}, {x, y}, ...])- Create from coordinate array- Supports 3D:
[{x, y, z}, ...]and 4D:[{x, y, z, m}, ...]
Polygons:
wasmts.geom.createPolygon(shell)- Create simple polygon from coordinate arraywasmts.geom.createPolygon(shell, [hole1, hole2, ...])- Create polygon with holes- Each ring is an array of
{x, y}(or{x, y, z},{x, y, z, m}) objects
Envelopes:
wasmts.geom.createEnvelope(minX, maxX, minY, maxY)- Create bounding box
From WKT/WKB (alternative for complex geometries):
wasmts.io.WKTReader.read('LINESTRING (0 0, 10 10)')- Parse WKTwasmts.io.WKTReader.read('POLYGON ((0 0, 100 0, 100 100, 0 100, 0 0))')- Parse WKT
3D/4D Coordinates:
Coordinates returned by getCoordinates() include Z and M values when present:
const point3d = wasmts.geom.createPoint(5, 10, 15);
const coords = point3d.getCoordinates();
console.log(coords[0]); // {x: 5, y: 10, z: 15}
const point4d = wasmts.geom.createPoint(5, 10, 15, 20);
const coords = point4d.getCoordinates();
console.log(coords[0]); // {x: 5, y: 10, z: 15, m: 20}WKT (Well-Known Text) - Human-readable text format:
wasmts.io.WKTReader.read(wkt)- Parse WKT string- 2D:
'POINT (5 10)' - 3D:
'POINT Z (5 10 15)' - 4D:
'POINT ZM (5 10 15 20)'
- 2D:
wasmts.io.WKTWriter.write(geometry)- Convert geometry to WKT string
WKB (Well-Known Binary) - Compact binary format:
wasmts.io.WKBReader.read(uint8Array)- Parse WKB bytes (Uint8Array)wasmts.io.WKBWriter.write(geometry)- Convert geometry to WKB (returns Uint8Array)- Automatically detects 2D/3D/4D based on coordinate dimensions
- Preserves Z and M values in round-trip
WKB is typically 2-3x more compact than WKT and faster to parse.
GeoJSON - Standard format for web mapping:
wasmts.io.GeoJSONReader.read(geojsonString)- Parse GeoJSON string to geometrywasmts.io.GeoJSONWriter.write(geometry)- Convert geometry to GeoJSON string
// Read GeoJSON
const poly = wasmts.io.GeoJSONReader.read('{"type":"Polygon","coordinates":[[[0,0],[10,0],[10,10],[0,10],[0,0]]]}');
console.log('Area:', poly.getArea()); // 100
// Write GeoJSON
const point = wasmts.geom.createPoint(5, 10);
const geojson = wasmts.io.GeoJSONWriter.write(point);
console.log(geojson); // {"type":"Point","coordinates":[5,10]}
// 3D coordinates preserved
const point3d = wasmts.io.GeoJSONReader.read('{"type":"Point","coordinates":[5,10,15]}');
const coords = point3d.getCoordinates();
console.log(coords[0].z); // 15Supports all geometry types: Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon, GeometryCollection.
Note: The GeoJSON reader/writer is a custom JavaScript implementation using JSON.parse()/JSON.stringify() rather than JTS's native GeoJsonReader/GeoJsonWriter classes (which require external JSON libraries not available in the WASM environment). The current implementation provides basic read/write functionality but does not yet expose the full JTS GeoJSON API options:
setForceCCW(boolean)- RFC 7946 counter-clockwise polygon ring orientationsetEncodeCRS(boolean)- Include CRS property in output- Precision/decimal control in writer
- Custom
GeometryFactoryin reader for CRS/SRID handling
Boolean Operations:
wasmts.geom.union(geom1, geom2)- Union of two geometrieswasmts.geom.intersection(geom1, geom2)- Intersection of two geometrieswasmts.geom.difference(geom1, geom2)- Difference (geom1 minus geom2)wasmts.geom.symDifference(geom1, geom2)- Symmetric difference
Geometric Operations:
wasmts.geom.buffer(geometry, distance)- Create buffer around geometrywasmts.geom.bufferWithParams(geometry, distance, endCapStyle, joinStyle, mitreLimit)- Buffer with custom parameterswasmts.geom.convexHull(geometry)- Compute convex hullwasmts.geom.simplify(geometry, tolerance)- Douglas-Peucker simplificationwasmts.geom.getBoundary(geometry)- Get boundary of geometrywasmts.geom.getCentroid(geometry)- Get centroid as Pointwasmts.geom.getEnvelope(geometry)- Get envelope as Geometrywasmts.geom.getInteriorPoint(geometry)- Get point guaranteed to be insidewasmts.geom.copy(geometry)- Create deep copy of geometrywasmts.geom.reverse(geometry)- Reverse coordinate orderwasmts.geom.normalize(geometry)- Normalize to canonical form
All predicates return booleans:
wasmts.geom.contains(geom1, geom2)- Test if geom1 contains geom2wasmts.geom.intersects(geom1, geom2)- Test if geometries intersectwasmts.geom.touches(geom1, geom2)- Test if geometries touch at boundarywasmts.geom.crosses(geom1, geom2)- Test if geometries crosswasmts.geom.within(geom1, geom2)- Test if geom1 is within geom2wasmts.geom.overlaps(geom1, geom2)- Test if geometries overlapwasmts.geom.disjoint(geom1, geom2)- Test if geometries don't intersectwasmts.geom.equals(geom1, geom2)- Test if geometries are spatially equalwasmts.geom.equalsTopo(geom1, geom2)- Test topological equality (same shape, ignoring coordinate order)wasmts.geom.covers(geom1, geom2)- Test if geom1 covers geom2 (every point of geom2 is inside or on boundary of geom1)wasmts.geom.coveredBy(geom1, geom2)- Test if geom1 is covered by geom2
All return numbers (except isEmpty/isValid/isSimple/isRectangle which return booleans):
wasmts.geom.getArea(geometry)- Calculate areawasmts.geom.getLength(geometry)- Calculate length (for linear geometries)wasmts.geom.distance(geom1, geom2)- Distance between geometrieswasmts.geom.nearestPoints(geom1, geom2)- Get closest points on each geometry (returns[{x,y}, {x,y}])wasmts.geom.getNumPoints(geometry)- Count points in geometrywasmts.geom.isEmpty(geometry)- Check if geometry is emptywasmts.geom.isValid(geometry)- Check if geometry is topologically validwasmts.geom.isSimple(geometry)- Check if geometry has no self-intersectionswasmts.geom.isRectangle(geometry)- Check if polygon is a rectangle
wasmts.geom.getCoordinates(geometry)- Extract all coordinates- 2D: Returns array of
{x, y}objects - 3D: Returns array of
{x, y, z}objects - 4D: Returns array of
{x, y, z, m}objects
- 2D: Returns array of
wasmts.geom.getNumGeometries(geometry)- Get number of sub-geometries (for collections)wasmts.geom.getGeometryN(geometry, n)- Get nth sub-geometry (0-indexed)wasmts.geom.getUserData(geometry)- Get user-defined data attached to geometrywasmts.geom.setUserData(geometry, data)- Attach user-defined data to geometry
Note: To get a point's coordinates, use getCoordinates(point)[0].x and getCoordinates(point)[0].y
Access exterior ring and interior holes of polygon geometries:
wasmts.geom.getExteriorRing(polygon)- Get outer boundary as LinearRingwasmts.geom.getNumInteriorRing(polygon)- Get number of holes (interior rings)wasmts.geom.getInteriorRingN(polygon, n)- Get nth interior ring (0-indexed)
// Create a polygon with a hole
const poly = wasmts.io.WKTReader.read(
'POLYGON ((0 0, 20 0, 20 20, 0 20, 0 0), (5 5, 15 5, 15 15, 5 15, 5 5))'
);
// Access exterior ring
const exterior = poly.getExteriorRing();
console.log('Exterior type:', exterior.type); // LinearRing
console.log('Exterior points:', exterior.getCoordinates().length); // 5
// Access interior rings (holes)
const numHoles = poly.getNumInteriorRing();
console.log('Number of holes:', numHoles); // 1
if (numHoles > 0) {
const hole = poly.getInteriorRingN(0);
const holeCoords = hole.getCoordinates();
console.log('Hole start:', holeCoords[0]); // {x: 5, y: 5}
}Note: These methods only work on Polygon geometries. Calling them on other geometry types will throw an error.
wasmts.geom.createEnvelope(minX, maxX, minY, maxY)- Create envelope from boundswasmts.geom.getEnvelopeInternal(geometry)- Get geometry's bounding boxwasmts.geom.envelopeIntersects(env1, env2)- Test if envelopes intersectwasmts.geom.envelopeContains(env1, env2)- Test if env1 contains env2wasmts.geom.expandToInclude(envelope, x, y)- Expand envelope to include point
STRtree - Sort-Tile-Recursive tree for fast spatial queries (10-100x speedup):
wasmts.index.strtree.STRtree.create()- Create new STRtree indexwasmts.index.strtree.STRtree.insert(tree, envelope, item)- Insert item with bounding boxwasmts.index.strtree.STRtree.query(tree, searchEnvelope)- Find all items intersecting search areawasmts.index.strtree.STRtree.remove(tree, envelope, item)- Remove item from indexwasmts.index.strtree.STRtree.size(tree)- Get number of items in index
PreparedGeometry - Optimized for repeated spatial predicates against the same geometry:
// Prepare a geometry for repeated predicate tests
const polygon = wasmts.io.WKTReader.read('POLYGON ((0 0, 100 0, 100 100, 0 100, 0 0))');
const prepared = wasmts.geom.prep.PreparedGeometryFactory.prepare(polygon);
// Test many points against the prepared polygon (faster than regular predicates)
const testPoints = [
wasmts.geom.createPoint(50, 50),
wasmts.geom.createPoint(150, 150),
wasmts.geom.createPoint(0, 0)
];
for (const point of testPoints) {
const result = wasmts.geom.prep.PreparedGeometry.containsProperly(prepared, point);
console.log(result); // true, false, false
}
// Extract underlying geometry
const original = wasmts.geom.prep.PreparedGeometry.getGeometry(prepared);Available methods:
wasmts.geom.prep.PreparedGeometryFactory.prepare(geometry)- Create PreparedGeometry from any geometrywasmts.geom.prep.PreparedGeometry.contains(prepGeom, testGeom)- True if testGeom is inside prepGeomwasmts.geom.prep.PreparedGeometry.containsProperly(prepGeom, testGeom)- True if testGeom is fully inside (not touching boundary)wasmts.geom.prep.PreparedGeometry.covers(prepGeom, testGeom)- True if prepGeom covers testGeomwasmts.geom.prep.PreparedGeometry.coveredBy(prepGeom, testGeom)- True if prepGeom is covered by testGeomwasmts.geom.prep.PreparedGeometry.crosses(prepGeom, testGeom)- True if geometries crosswasmts.geom.prep.PreparedGeometry.disjoint(prepGeom, testGeom)- True if geometries don't intersectwasmts.geom.prep.PreparedGeometry.intersects(prepGeom, testGeom)- True if geometries intersectwasmts.geom.prep.PreparedGeometry.overlaps(prepGeom, testGeom)- True if geometries overlapwasmts.geom.prep.PreparedGeometry.touches(prepGeom, testGeom)- True if geometries touch at boundarywasmts.geom.prep.PreparedGeometry.within(prepGeom, testGeom)- True if prepGeom is within testGeomwasmts.geom.prep.PreparedGeometry.getGeometry(prepGeom)- Extract the underlying Geometry object
Use PreparedGeometry when testing many geometries against a single fixed geometry.
Minimum bounding shapes - Find optimal bounding rectangles and circles for geometries:
// Create an irregular polygon
const polygon = wasmts.io.WKTReader.read('POLYGON ((0 0, 10 2, 12 10, 2 12, 0 0))');
// Find minimum-width bounding rectangle (based on minimum diameter)
const minDiamRect = wasmts.algorithm.MinimumDiameter.getMinimumRectangle(polygon);
console.log('Minimum-width rectangle area:', minDiamRect.getArea());
// Find minimum-area bounding rectangle (rotating calipers algorithm)
const minAreaRect = wasmts.algorithm.MinimumAreaRectangle.getMinimumRectangle(polygon);
console.log('Minimum-area rectangle area:', minAreaRect.getArea());
// Find minimum bounding circle
const circle = wasmts.algorithm.MinimumBoundingCircle.getCircle(polygon);
const centre = wasmts.algorithm.MinimumBoundingCircle.getCentre(polygon);
const radius = wasmts.algorithm.MinimumBoundingCircle.getRadius(polygon);
console.log('Circle centre:', centre, 'radius:', radius);Available algorithms:
wasmts.algorithm.MinimumDiameter.getMinimumRectangle(geometry)- Rectangle with minimum widthwasmts.algorithm.MinimumDiameter.getLength(geometry)- The minimum width (diameter) valuewasmts.algorithm.MinimumAreaRectangle.getMinimumRectangle(geometry)- Rectangle with minimum area (rotating calipers)wasmts.algorithm.MinimumBoundingCircle.getCircle(geometry)- Smallest enclosing circle (as Polygon)wasmts.algorithm.MinimumBoundingCircle.getCentre(geometry)- Circle center as{x, y}wasmts.algorithm.MinimumBoundingCircle.getRadius(geometry)- Circle radius (number)
Custom buffer parameters - The buffer() method accepts optional parameters to control cap style, join style, and mitre limit:
// Buffer parameter constants (from JTS BufferParameters)
const CAP_ROUND = 1, CAP_FLAT = 2, CAP_SQUARE = 3;
const JOIN_ROUND = 1, JOIN_MITRE = 2, JOIN_BEVEL = 3;
const line = wasmts.io.WKTReader.read('LINESTRING (0 0, 10 0)');
// Standard buffer
const standardBuffer = line.buffer(2);
// Flat cap buffer (no rounded ends)
const flatBuffer = line.buffer(2, CAP_FLAT, JOIN_ROUND, 5.0);
// Square cap buffer (extended square ends)
const squareBuffer = line.buffer(2, CAP_SQUARE, JOIN_ROUND, 5.0);
// Mitre join for sharp corners
const poly = wasmts.io.WKTReader.read('POLYGON ((0 0, 10 0, 10 1, 5 1, 5 10, 0 10, 0 0))');
const mitreBuffer = poly.buffer(0.5, CAP_ROUND, JOIN_MITRE, 10.0);
// Negative buffer for erosion
const eroded = poly.buffer(-0.5, CAP_ROUND, JOIN_ROUND, 5.0);Signature: geometry.buffer(distance, endCapStyle?, joinStyle?, mitreLimit?)
Parameters:
distance: Buffer distance (positive for expansion, negative for erosion)endCapStyle(optional): 1=ROUND, 2=FLAT, 3=SQUAREjoinStyle(optional): 1=ROUND, 2=MITRE, 3=BEVELmitreLimit(optional): Maximum ratio of mitre length to buffer width (typically 5.0)
Parallel line generation - The offsetCurve() method creates a parallel line at a specified distance from the input geometry:
const line = wasmts.io.WKTReader.read('LINESTRING (0 0, 10 0, 10 10)');
// Standard offset (positive = right side, negative = left side)
const rightOffset = wasmts.operation.buffer.OffsetCurveBuilder.getOffsetCurve(line, 2);
const leftOffset = wasmts.operation.buffer.OffsetCurveBuilder.getOffsetCurve(line, -2);
// Custom parameters for corner style
const CAP_FLAT = 2, JOIN_MITRE = 2;
const sharpOffset = wasmts.operation.buffer.OffsetCurveBuilder.getOffsetCurve(line, 2, CAP_FLAT, JOIN_MITRE, 10.0);
// Offset curves on polygons (offsets exterior ring)
const poly = wasmts.io.WKTReader.read('POLYGON ((0 0, 20 0, 20 20, 0 20, 0 0))');
const polyOffset = wasmts.operation.buffer.OffsetCurveBuilder.getOffsetCurve(poly, 3);Signature: wasmts.operation.buffer.OffsetCurveBuilder.getOffsetCurve(geometry, distance, endCapStyle?, joinStyle?, mitreLimit?)
Parameters: Same as buffer() - see Advanced Buffering section above.
Merge connected linestrings - Combines contiguous line segments into longer linestrings:
// Create separate line segments that connect end-to-end
const line1 = wasmts.io.WKTReader.read('LINESTRING (0 0, 5 0)');
const line2 = wasmts.io.WKTReader.read('LINESTRING (5 0, 10 0)');
const line3 = wasmts.io.WKTReader.read('LINESTRING (10 0, 10 5)');
// Disconnected line
const line4 = wasmts.io.WKTReader.read('LINESTRING (20 20, 25 25)');
// Create merger and add lines
const merger = wasmts.operation.linemerge.LineMerger.create();
wasmts.operation.linemerge.LineMerger.add(merger, line1);
wasmts.operation.linemerge.LineMerger.add(merger, line2);
wasmts.operation.linemerge.LineMerger.add(merger, line3);
wasmts.operation.linemerge.LineMerger.add(merger, line4);
// Get merged result (returns array of linestrings)
const merged = wasmts.operation.linemerge.LineMerger.getMergedLineStrings(merger);
// Result: 2 lines (line1+line2+line3 merged into one, line4 separate)API:
wasmts.operation.linemerge.LineMerger.create()- Create a new LineMerger instancewasmts.operation.linemerge.LineMerger.add(merger, geometry)- Add line(s) to mergewasmts.operation.linemerge.LineMerger.getMergedLineStrings(merger)- Get array of merged linestrings
Efficient multi-polygon union - Union many polygons at once (faster than sequential pairwise unions):
const poly1 = wasmts.io.WKTReader.read('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))');
const poly2 = wasmts.io.WKTReader.read('POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))');
const poly3 = wasmts.io.WKTReader.read('POLYGON ((10 10, 20 10, 20 20, 10 20, 10 10))');
// Union all at once
const union = wasmts.operation.union.CascadedPolygonUnion.union([poly1, poly2, poly3]);
console.log('Union area:', union.getArea());API:
wasmts.operation.union.CascadedPolygonUnion.union(polygonArray)- Union array of polygons
Commands:
npm run build:wasm- Compile Java to WASM with Mavennpm run build:js- Copy artifacts to dist/npm run build- Full buildnode test/test-node.mjs- Run tests
Output:
dist/wasmts.js- WASM loader (~109KB)dist/wasmts.js.wasm- Binary (~5.7MB)
Build time: ~25 seconds
To add new JTS functionality:
- Add method to
java/src/main/java/net/willcohen/wasmts/API.java - Export to JavaScript namespace using
@JSannotations - Add to geometry wrapper object if needed
- Test and document
Example: Adding a new geometry operation follows this pattern - functional interface, export declaration, implementation method, and export call in main().
Update <jts.version> in pom.xml and rebuild. No source patches required.
Use <script src="wasmts.js"> not import() - the loader needs proper path resolution for finding .wasm file.
This project contains and distributes the full source code of JTS (Java Topology Suite) 1.20.0, which is dual-licensed under:
- Eclipse Public License v2.0 (LICENSE_EPLv2.txt)
- Eclipse Distribution License v1.0 (LICENSE_EDLv1.txt)
You may use JTS under either license. See the JTS project for more information.
The npm distribution also includes the GraalVM WebAssembly loader (the JavaScript runtime code in wasmts.js), which is part of GraalVM and is licensed under:
- GNU General Public License v2.0 with Classpath Exception
The wrapper code (Java interop layer in java/src/main/java/net/willcohen/wasmts/API.java) is licensed under EPL-2.0 OR EDL-1.0 to match JTS.