geos.js
Version:
an easy-to-use JavaScript wrapper over WebAssembly build of GEOS
229 lines (207 loc) • 8.78 kB
text/typescript
import type { Position } from 'geojson';
import { FINALIZATION, POINTER } from '../core/symbols.mjs';
import { type Geometry, GeometryRef } from '../geom/Geometry.mjs';
import { geosifyGeometry } from '../io/geosify.mjs';
import { GEOSError } from '../core/GEOSError.mjs';
import { geos } from '../core/geos.mjs';
import type { Point } from '../geom/types/Point.mjs';
import type { GeometryCollection } from '../geom/types/GeometryCollection.mjs';
import type { MultiPolygon } from '../geom/types/MultiPolygon.mjs';
import type { MultiLineString } from '../geom/types/MultiLineString.mjs';
import type { MultiPoint } from '../geom/types/MultiPoint.mjs';
import type { Polygon } from '../geom/types/Polygon.mjs';
import type { LineString } from '../geom/types/LineString.mjs';
export interface GeometryOptions<P> {
/**
* Optional identifier to be assigned to the geometry instance.
*/
id?: number | string;
/**
* Optional data to be assigned to the geometry instance.
*/
properties?: P;
}
/**
* Creates a {@link Point} geometry from a position.
*
* @param pt - Point coordinates
* @param options - Optional geometry options
* @returns A new point Geometry object
*
* @example #live
* const a = point([ 0, 0 ]);
* const b = point([ 2, 0 ], { properties: { name: 'B' } });
* const wkt = toWKT(a); // 'POINT (0 0)'
*/
export function point<P>(pt: Position, options?: GeometryOptions<P>): Point<P> {
return geosifyGeometry({ type: 'Point', coordinates: pt }, options) as Point<P>;
}
/**
* Creates a {@link LineString} geometry from an array of positions.
*
* Line string must contain at least 2 positions.
* Empty line strings with 0 positions are allowed.
*
* @param pts - LineString coordinates
* @param options - Optional geometry options
* @returns A new Geometry object
* @throws {InvalidGeoJSONError} on line with 1 position
*
* @example #live
* const a = lineString([ [ 0, 0 ], [ 2, 1 ], [ 0, 2 ] ]);
* const b = lineString([ [ 2, 0 ], [ 4, 0 ] ], { properties: { name: 'B' } });
* const wkt = toWKT(a); // 'LINESTRING (0 0, 2 1, 0 2)'
*/
export function lineString<P>(pts: Position[], options?: GeometryOptions<P>): LineString<P> {
return geosifyGeometry({ type: 'LineString', coordinates: pts }, options) as LineString<P>;
}
/**
* Creates a {@link Polygon} geometry from an array of linear rings coordinates.
*
* The first ring represents the exterior ring (shell), subsequent rings
* represent interior rings (holes). Each ring must be a closed line string
* with first and last positions identical and contain at least 3 positions.
* Empty polygons without any rings are allowed.
*
* @param ppts - Polygon coordinates
* @param options - Optional geometry options
* @returns A new Geometry object
* @throws {InvalidGeoJSONError} if any ring is invalid (not closed or with 1 or 2 positions)
*
* @example #live
* const a = polygon([ [ [ 4, 3 ], [ 5, 4 ], [ 5, 3 ], [ 4, 3 ] ] ]);
* const b = polygon([
* [ [ 0, 0 ], [ 0, 8 ], [ 8, 8 ], [ 8, 0 ], [ 0, 0 ] ],
* [ [ 2, 2 ], [ 6, 6 ], [ 6, 2 ], [ 2, 2 ] ],
* ], { properties: { name: 'B' } });
* const wkt = toWKT(a); // 'POLYGON ((4 3, 5 4, 5 3, 4 3))'
*/
export function polygon<P>(ppts: Position[][], options?: GeometryOptions<P>): Polygon<P> {
return geosifyGeometry({ type: 'Polygon', coordinates: ppts }, options) as Polygon<P>;
}
/**
* Creates a {@link MultiPoint} geometry from an array of positions.
*
* @param pts - MultiPoint coordinates
* @param options - Optional geometry options
* @returns A new Geometry object
*
* @example #live
* const a = multiPoint([ [ 0, 0 ], [ 2, 0 ], [ 4, 0 ] ]);
* const b = multiPoint([ [ 1, 0 ], [ 3, 0 ] ], { properties: { name: 'B' } });
* const wkt = toWKT(a); // 'MULTIPOINT ((0 0), (2 0), (4 0))'
*/
export function multiPoint<P>(pts: Position[], options?: GeometryOptions<P>): MultiPoint<P> {
return geosifyGeometry({ type: 'MultiPoint', coordinates: pts }, options) as MultiPoint<P>;
}
/**
* Creates a {@link MultiLineString} geometry from an array of line strings coordinates.
*
* Each line string must contain at least 2 positions.
* Empty line strings with 0 positions are allowed.
*
* @param ppts - MultiLineString coordinates
* @param options - Optional geometry options
* @returns A new Geometry object
* @throws {InvalidGeoJSONError} on line with 1 position
*
* @example #live
* const a = multiLineString([
* [ [ -10, 3 ], [ 5, 4 ] ],
* [ [ -10, 7 ], [ 5, 6 ] ],
* ]);
* const b = multiLineString([
* [ [ 0, 0 ], [ 10, 5 ], [ 0, 10 ] ],
* [ [ 1, 0 ], [ 12, 5 ], [ 1, 10 ] ],
* ], { properties: { name: 'B' } });
* const wkt = toWKT(a); // 'MULTILINESTRING ((-10 3, 5 4), (-10 7, 5 6))'
*/
export function multiLineString<P>(ppts: Position[][], options?: GeometryOptions<P>): MultiLineString<P> {
return geosifyGeometry({ type: 'MultiLineString', coordinates: ppts }, options) as MultiLineString<P>;
}
/**
* Creates a {@link MultiPolygon} geometry from an array of polygon coordinates.
*
* Each polygon must consist of an array of linear rings coordinates.
* The first ring represents the exterior ring (shell), subsequent rings
* represent interior rings (holes). Each ring must be a closed line string
* with first and last positions identical and contain at least 3 positions.
* Empty polygons without any rings are allowed.
*
* @param pppts - MultiPolygon coordinates
* @param options - Optional geometry options
* @returns A new Geometry object
* @throws {InvalidGeoJSONError} if any ring is invalid (not closed or with 1 or 2 positions)
*
* @example #live
* const a = multiPolygon([
* [ [ [ 1, 0 ], [ 0, 1 ], [ 1, 1 ], [ 1, 0 ] ] ],
* [ [ [ 1, 1 ], [ 1, 2 ], [ 2, 1 ], [ 1, 1 ] ] ],
* ]);
* const b = multiPolygon([
* [ [ [ 0, 1 ], [ 1, 2 ], [ 1, 1 ], [ 0, 1 ] ] ],
* [ [ [ 1, 0 ], [ 1, 1 ], [ 2, 1 ], [ 1, 0 ] ] ],
* ], { properties: { name: 'B' } });
* const wkt = toWKT(a); // 'MULTIPOLYGON (((1 0, 0 1, 1 1, 1 0)), ((1 1, 1 2, 2 1, 1 1)))'
*/
export function multiPolygon<P>(pppts: Position[][][], options?: GeometryOptions<P>): MultiPolygon<P> {
return geosifyGeometry({ type: 'MultiPolygon', coordinates: pppts }, options) as MultiPolygon<P>;
}
/**
* Creates a {@link GeometryCollection} geometry from an array of geometries.
*
* The collection consumes the input geometries - after creating
* the collection, the input geometries become [detached]{@link GeometryRef#detached},
* are no longer valid and should **not** be used.
*
* @param geometries - Array of geometry objects to be included in the collection
* @param options - Optional geometry options
* @returns A new GeometryCollection containing all input geometries
*
* @example #live
* const a = polygon([ [ [ 4, 1 ], [ 4, 3 ], [ 8, 2 ], [ 4, 1 ] ] ]);
* const b = lineString([ [ 0, 2 ], [ 6, 2 ] ]);
* const c = geometryCollection([ a, b ]);
* const wkt = toWKT(c); // 'GEOMETRYCOLLECTION (POLYGON ((4 1, 4 3, 8 2, 4 1)), LINESTRING (0 2, 6 2))'
*/
export function geometryCollection<P>(geometries: Geometry[], options?: GeometryOptions<P>): GeometryCollection<P> {
const geometriesLength = geometries.length;
const buff = geos.buffByL4(geometriesLength);
try {
let B = geos.U32, b = buff.i4;
for (const geometry of geometries) {
B[ b++ ] = geometry[ POINTER ];
}
const geomPtr = geos.GEOSGeom_createCollection(7, buff[ POINTER ], geometriesLength);
for (const geometry of geometries) {
GeometryRef[ FINALIZATION ].unregister(geometry);
geometry.detached = true;
}
return new GeometryRef(geomPtr, 'GeometryCollection', options) as GeometryCollection<P>;
} finally {
buff.freeIfTmp();
}
}
/**
* Creates a rectangular {@link Polygon} geometry from bounding box coordinates.
*
* Polygon is oriented clockwise.
*
* @param bbox - Array of four numbers `[ xMin, yMin, xMax, yMax ]`
* @param options - Optional geometry options
* @returns A new Polygon object
* @throws {GEOSError} when box is degenerated: width or height is `0`
*
* @see {@link bounds} calculates bounding box of an existing geometry
*
* @example #live
* const b1 = box([ 0, 0, 4, 4 ]); // <POLYGON ((0 0, 0 4, 4 4, 4 0, 0 0))>
* const b2 = box([ 5, 0, 8, 1 ]); // <POLYGON ((5 0, 5 1, 8 1, 8 0, 5 0))>
*/
export function box<P>(bbox: number[], options?: GeometryOptions<P>): Polygon<P> {
const [ xMin, yMin, xMax, yMax ] = bbox;
if (xMin === xMax || yMin === yMax) {
throw new GEOSError('Degenerate box'); // point or line
}
return polygon([ [ [ xMin, yMin ], [ xMin, yMax ], [ xMax, yMax ], [ xMax, yMin ], [ xMin, yMin ] ] ], options);
}