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 ofisValidReasons;coverageIsValidreturns{ valid, errorEdges?: Geometry[] }(per-input error-edge geoms) instead of index pairs;coverageCleanuses GEOS'sgapMaximumWidth/snappingDistance/overlapStrategyparam names (the sketch'sgapWidthis the coverageIsValid option, which is implemented asgapWidth); diagnostics onGeosErrorare STRINGS (GeosErrorDiagnostics, reserved — no throw site populates it yet), never geometries; the tree/Prepared/ops modules live insrc/spatial/+src/ops/(notsrc/index/— avoids the index.ts import ambiguity);orientPolygonsand the interrupt plumbing shipped alongside (io.md §3 / D19). Batch input forms takeGeometry[]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[] | ArrowArrayeverywhere (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*Allnaming: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-wiseunionAll(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*Precoverlay 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()/arenaHighWaterper 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)andlargestEmptyCircle(g, tolerance, {boundary?})take a REQUIRED tolerance (no silent default);boundary()lives with the accessors; the loop-shaped construction ops also ship*Allbatch forms (convexHullAll,centroidAll,simplifyAll,topologyPreserveSimplifyAll,clipByRectAll,boundaryAll,interiorPointAll,nodeAll,lineMergeAll,snapAll,sharedPathsAll— D27).sharedPathsis LINEAL-only (GEOS throws on areal inputs). Construction & I/O §: GeoArrow shapes v2 are IMPLEMENTED (D28) — tagged kinds, validity bitmaps withnullMode, representable empty MULTI*, dense-union geometrycollection, golden-byte conformance against the reference implementation;toArrowemits 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
optionsobject; 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.