bun-geos
On this page

Design: target API surface (archived)

Archived October 2026, after 0.1.0 — the target this document describes shipped in the 0.1.0 release. The documented surface is ../reference/api.md (freshness-gated by a test); the only remaining gap (curve types) is tracked in D30 and the ROADMAP's 1.0 gate.

Status: design target, not a contract (post-spike). The surface is fixed at the GEOS 3.14 C API (D5) — no capability flags exist anywhere in this document. Method names are provisional; grouping and semantics are the stable part.

Phase 2 status (Oct 2026): Prepared §, Spatial index §, Overlays (unionAll/ *Prec) §, repair suite §, and interrupt are IMPLEMENTED. Documented deviations from the sketches below: isValidDetail/isSimpleDetail (GEOS's names; singular reason + owned location) instead of isValidReasons; coverageIsValid returns { valid, errorEdges?: Geometry[] } (per-input error-edge geoms) instead of index pairs; coverageClean uses GEOS's gapMaximumWidth/snappingDistance/ overlapStrategy param names (the sketch's gapWidth is the coverageIsValid option, which is implemented as gapWidth); diagnostics on GeosError are STRINGS (GeosErrorDiagnostics, reserved — no throw site populates it yet), never geometries; the tree/Prepared/ops modules live in src/spatial/ + src/ops/ (not src/index/ — avoids the index.ts import ambiguity); orientPolygons and the interrupt plumbing shipped alongside (io.md §3 / D19). Batch input forms take Geometry[] in Phase 2; ArrowArray normalization lands with the Phase 3 batch layer.

Phase 3 status (Oct 2026): batch + parallel §§ are IMPLEMENTED (D22/D23). Batch input forms accept Geometry[] | ArrowArray everywhere (the ArrowArray normalization note above is resolved — bulk builders in the shim, transient arena; Arrow offsets are validated in JS before any crossing — negatives would wrap as unsigned in C). Element-wise *All naming: intersectionAll(a, bs) is ELEMENT-WISE (result[i] = intersection(a, bs[i])), matching the masks; unionAll(gs) remains the Phase 2 aggregate — there is deliberately no element-wise unionAll(a, bs). geos.parallel.* mirrors the batch surface's core (overlay, the nine unprepared masks, measures incl. hausdorff/frechet, bufferAll, makeValid, unionAll) returning Promises; the *Prec overlay variants stay main-thread. Chunking/interrupt/failure semantics in D23 + concurrency.md §4. STRtree.queryFilter(env, prep, pred) is the fused helper the sketch below pointed at. statsPrecise()/arenaHighWater per D24.

Phase 5 status (Oct 2026): the Measures & construction ops section is IMPLEMENTED (D27) — convexHull, concaveHull, simplify, topologyPreserveSimplify, clipByRect, delaunay, constrainedDelaunay, voronoi, lineMerge (+directed), lineSubstring, offsetCurve, sharedPaths, snap, node, buildArea, polygonize, centroid, interiorPoint, boundary, nearestPoints, minimumClearance(+Line), maximumInscribedCircle, largestEmptyCircle, clusterDBSCAN, gridIntersectionFractions. Deviations from the sketch: maximumInscribedCircle(g, tolerance) and largestEmptyCircle(g, tolerance, {boundary?}) take a REQUIRED tolerance (no silent default); boundary() lives with the accessors; the loop-shaped construction ops also ship *All batch forms (convexHullAll, centroidAll, simplifyAll, topologyPreserveSimplifyAll, clipByRectAll, boundaryAll, interiorPointAll, nodeAll, lineMergeAll, snapAll, sharedPathsAll — D27). sharedPaths is LINEAL-only (GEOS throws on areal inputs). Construction & I/O §: GeoArrow shapes v2 are IMPLEMENTED (D28) — tagged kinds, validity bitmaps with nullMode, representable empty MULTI*, dense-union geometrycollection, golden-byte conformance against the reference implementation; toArrow emits tagged values (breaking vs Phase 1, legacy input still accepted). Only remaining API-surface gap: curve types (below — post-0.1 by design).

Conventions

  • All plural/batch forms accept Geometry[] or GeoArrow-style typed arrays and return typed arrays — never a JS loop over the singular form.
  • Coordinates in/out are flat Float64Array + offsets (io).
  • Every operation accepts an optional trailing options object; all ops accept { signal?: AbortSignal } (concurrency).
  • Disposables (Geos, Geometry, Prepared, STRtree, arenas) implement [Symbol.dispose]() (memory).
class Geos implements Disposable {
  constructor(opts?: { threads?: never /* contexts are per-thread by construction */ });
  get [Symbol.dispose](): () => void;
  arena(): Arena;                       // using arena = geos.arena()
  get stats(): { liveGeoms: number; heapBytesApprox: number; /* … */ };
}

Construction & I/O

geos.fromWKT(wkt: string): Geometry;
geos.fromWKB(wkb: Uint8Array): Geometry;
geos.fromWKBMany(buf: Uint8Array, offsets: Uint32Array): Geometry[];
geos.fromGeoJSON(geo: GeoJSONGeometry | GeoJSONFeature): Geometry;
geos.fromArrow(arr: ArrowGeometryArray): Geometry;
geos.fromCoords(flat: FlatCoords | Polygonal): Geometry;   // builders via shim
geos.point(x: number, y: number): Geometry;                // convenience

geom.toWKT(opts?): string;
geom.toWKB(opts?: { byteOrder?: "little" | "big"; outputDimension?: 2 | 3 | 4 }): Uint8Array;
geom.toGeoJSON(opts?): GeoJSONGeometry;
geom.toArrow(): ArrowGeometryArray;
geom.envelope(): Geometry;  geom.isEmpty(): boolean;  geom.type: string;
geom.hasZ: boolean;  geom.hasM: boolean;  geom.srid: number | null;   // side-channel

Predicates

geos.intersects(a, b): boolean;         // + equals, disjoint, touches, crosses,
geos.contains(a, b): boolean;           //   within, overlaps, covers, coveredBy
geos.relates(a, b, pattern: string): boolean;      // DE-9IM
geos.relateMatrix(a, b): string;
geos.equalsIdentical(a, b): boolean;    // exact (3.12)

Prepared (RelateNG, 3.13+)

using prep = a.prepare();
prep.intersects(b): boolean;
prep.intersectsAll(others: Geometry[] | ArrowGeometryArray): Uint8Array;   // 0/1 mask
prep.containsProperlyAll(others): Uint8Array;
prep.relate(b): string;                 // full DE-9IM, prepared
prep.relatePattern(b, pattern): boolean;

Overlays & boolean ops

geos.intersection(a, b): Geometry;      // OverlayNGRobust under the hood (3.10+)
geos.union(a, b): Geometry;
geos.difference(a, b): Geometry;
geos.symDifference(a, b): Geometry;
geos.unaryUnion(g): Geometry;

// Fixed-precision variants (precision model):
geos.intersectionPrec(a, b, gridSize): Geometry;   // + unionPrec, differencePrec, symDifferencePrec
geos.setPrecision(g, gridSize, mode?): Geometry;

// Batch strategies (Phase 2) — dispatch is automatic, override is explicit:
geos.unionAll(gs, opts?: { strategy?: "auto" | "coverage" | "disjointSubset" | "unary" }): Geometry;
//   auto: CoverageUnion when input validates as a coverage;
//         DisjointSubsetUnion when spatially partitioned; else UnaryUnion.

Topology repair (the "repair suite", Phase 2)

geos.isValid(g): boolean;
geos.isValidReasons(g): { valid: boolean; reasons: string[] };        // diagnostics
geos.makeValid(g, opts?: { method?: "linework" | "structure"; keepCollapsed?: boolean }): Geometry;
geos.coverageIsValid(gs): { valid: boolean; errorLayers?: [number, number][] };
geos.coverageClean(gs, opts?: { gapWidth?: number;
                               overlapStrategy?: "longestBorder" | "maxArea" | "minArea" }): Geometry[];
geos.coverageUnion(gs): Geometry;              // aligned coverages; very fast
geos.coverageSimplifyVW(gs, tolerance, opts?): Geometry[];
geos.node(g): Geometry;  geos.buildArea(g): Geometry;  geos.polygonize(g): Geometry;
geos.isSimpleDetail(g): { simple: boolean; locations?: Geometry[] };  // 3.14 diagnostics

Measures & construction ops

geos.area(g): number;  geos.length(g): number;  geos.distance(a, b): number;
geos.hausdorff(a, b): number;  geos.frechet(a, b): number;
geos.nearestPoints(a, b): [Geometry, Geometry];
geos.buffer(g, dist, opts?: { quadSegs?: number; join?: "round" | "mitre" | "bevel";
                              mitreLimit?: number; singleSided?: boolean }): Geometry;
geos.offsetCurve(g, dist, opts?): Geometry;
geos.simplify(g, tol): Geometry;  geos.topologyPreserveSimplify(g, tol): Geometry;
geos.clipByRect(g, xmin, ymin, xmax, ymax): Geometry;   // fast rectangular clip
geos.convexHull(g): Geometry;  geos.concaveHull(g, ratio, opts?): Geometry;
geos.voronoi(g, opts?): Geometry;  geos.delaunay(g, opts?): Geometry;
geos.lineMerge(g, opts?): Geometry;  geos.lineSubstring(g, start, end): Geometry;
geos.sharedPaths(a, b): Geometry;  geos.snap(a, b, tol): Geometry;
geos.maximumInscribedCircle(g): Geometry;  geos.largestEmptyCircle(g, obs?): Geometry;
geos.constrainedDelaunay(g): Geometry;
geos.clusterDBSCAN(gs, eps, minPoints): Int32Array;     // 3.14, cluster ids per input
geos.gridIntersectionFractions(grid, polygon): Float64Array;  // 3.14

Spatial index

const tree = geos.strtree(geoms, nodeCapacity = 10);    // ids = input indices
tree.query(env: Geometry): Int32Array;                  // candidate ids, one shim call
tree.nearest(g): number;                                // id of nearest item
tree.build(): void;                                     // force build (3.12)
tree implements Disposable;
// Exact filtering is caller's choice — commonly prepared predicates on candidates:
const hits = ids.filter(i => prep.intersects(geoms[i]));   // or the fused Phase-3 helper

Parallel & utility

geos.parallel.unionAll(gs, opts?: { signal?: AbortSignal }): Promise<Geometry>;  // Phase 3
geos.parallel.intersectsAll(a, bs, opts?): Promise<Uint8Array>;
geos.orientPolygons(g, dir?: "ccw" | "cw"): Geometry;   // explicit, never silent
geos.version(): "3.14.x-CAPI-x";                        // informational only (D5)

Errors

class GeosError extends Error {
  op: string;                    // "GEOSIntersection_r"
  geosMessage?: string;          // captured via shim thread-local buffer
  detail?: { ring?: number; vertex?: number; byteOffset?: number; jsonPath?: string };
  diagnostics?: { validReasons?: string[]; simpleDetail?: Geometry[] };  // repair ops
}

Programming errors (foreign context, use-after-dispose, invalid pattern string) are TypeError/RangeError, per D13.

Deliberately out of scope (initial)

  • Curve types (CircularString etc., new in 3.13) — C API exists; surface after 0.1.
  • Precision-model construction beyond setPrecision/*Prec (no PolygonalCoverage plumbing) — revisit on demand.
  • Any browser/WASM build — permanently out of scope (D1); geos-wasm is the recommended browser alternative.
Powered by Cirth