geos.js
Version:
an easy-to-use JavaScript wrapper over WebAssembly build of GEOS
215 lines (202 loc) • 9.69 kB
text/typescript
import type { Feature as GeoJSON_Feature, FeatureCollection as GeoJSON_FeatureCollection, Geometry as GeoJSON_Geometry } from 'geojson';
import type { JSON_Feature, JSON_FeatureCollection, JSON_Geometry } from '../geom/types/JSON.mjs';
import { type CoordinateType, type Geometry, GeometryRef } from '../geom/Geometry.mjs';
import { geosifyFeatures, geosifyGeometry } from './geosify.mjs';
import { feature, jsonifyFeatures, jsonifyGeometry } from './jsonify.mjs';
export interface GeoJSONInputOptions {
/**
* Coordinate layout for interpreting GeoJSON coordinates.
*
* Defines how to interpret the coordinates of input geometries.
* This does **not** force the dimension of the resulting geometries -
* the actual geometry dimension will be determined from the parsed data.
*
* Use this to:
* - Trim unwanted Z or M ordinates from the input.
* - Treat the third ordinate as M instead of Z.
*
* @default 'XYZM'
*/
layout?: CoordinateType;
}
/**
* Creates a {@link Geometry} from GeoJSON representation.
*
* This function has 2 overloads, when called with:
* - GeoJSON `Geometry` or GeoJSON `Feature` it returns a **single** geometry,
* - GeoJSON `FeatureCollection` it returns an **array** of geometries.
*
* In addition to the 7 standard GeoJSON geometries, this reader also supports
* JSON representation of other geometries supported by GEOS like
* {@link JSON_CircularString}, {@link JSON_CompoundCurve} or {@link JSON_CurvePolygon}.
*
* Reader expects that all positions in an input geometry have the same number
* of elements. The geometry coordinate dimension is determined from the first
* encountered position.
*
* @template P - The type of geometry/feature properties
* @param geojson - GeoJSON `Geometry`, `Feature` or `FeatureCollection`
* @param options - Optional GeoJSON input configuration
* @returns A new geometry object or an array of new geometry objects
* @throws {InvalidGeoJSONError} on GeoJSON feature without geometry
* @throws {InvalidGeoJSONError} on invalid GeoJSON geometry
*
* @see {@link point} shortcut to create `Point` geometry
* @see {@link lineString} shortcut to create `LineString` geometry
* @see {@link polygon} shortcut to create `Polygon` geometry
* @see {@link multiPoint} shortcut to create `MultiPoint` geometry
* @see {@link multiLineString} shortcut to create `MultiLineString` geometry
* @see {@link multiPolygon} shortcut to create `MultiPolygon` geometry
* @see {@link circularString} shortcut to create `CircularString` geometry
*
* @example #live
* const a = fromGeoJSON({
* type: 'Point',
* coordinates: [ 0, 0 ],
* });
* const b = fromGeoJSON({
* type: 'Feature',
* geometry: { type: 'Point', coordinates: [ 1, 0, 10 ] },
* properties: { name: 'B' },
* });
* const [ c ] = fromGeoJSON({
* type: 'FeatureCollection',
* features: [ {
* type: 'Feature',
* geometry: { type: 'Point', coordinates: [ 2, 0 ] },
* properties: { name: 'C' },
* } ],
* });
*
* const c_properties = a.props; // undefined
* const b_properties = b.props; // { name: 'B' }
* const a_properties = c.props; // { name: 'C' }
*
* @example #live 'layout' option
* const json = { type: 'Point', coordinates: [ 1, 2, 3, 4 ] };
*
* const xyzm = toWKT(fromGeoJSON(json, { layout: 'XYZM' })); // 'POINT ZM (1 2 3 4)'
* const xyz = toWKT(fromGeoJSON(json, { layout: 'XYZ' })); // 'POINT Z (1 2 3)'
* const xym = toWKT(fromGeoJSON(json, { layout: 'XYM' })); // 'POINT M (1 2 3)'
* const xy = toWKT(fromGeoJSON(json, { layout: 'XY' })); // 'POINT (1 2)'
*
* // 'layout' can only map or reduce the dimension it cannot increase it
* const xyNOTxyz = toWKT(fromGeoJSON(
* { type: 'Point', coordinates: [ 1, 2 ] },
* { layout: 'XYZ' },
* )); // 'POINT (1 2)'
*/
export function fromGeoJSON<P>(geojson: GeoJSON_Geometry | GeoJSON_Feature<GeoJSON_Geometry, P> | JSON_Geometry | JSON_Feature<JSON_Geometry, P>, options?: GeoJSONInputOptions): Geometry<P>;
export function fromGeoJSON<P>(geojson: GeoJSON_FeatureCollection<GeoJSON_Geometry, P> | JSON_FeatureCollection<JSON_Geometry, P>, options?: GeoJSONInputOptions): Geometry<P>[];
export function fromGeoJSON<P>(
geojson: GeoJSON_Geometry | GeoJSON_Feature<GeoJSON_Geometry, P> | GeoJSON_FeatureCollection<GeoJSON_Geometry, P> | JSON_Geometry | JSON_Feature<JSON_Geometry, P> | JSON_FeatureCollection<JSON_Geometry, P>,
options?: GeoJSONInputOptions,
): Geometry<P> | Geometry<P>[] {
const layout = options?.layout;
switch (geojson.type) {
case 'FeatureCollection': {
return geosifyFeatures(geojson.features, layout);
}
case 'Feature': {
return geosifyGeometry(geojson.geometry, layout, geojson);
}
}
return geosifyGeometry<P>(geojson, layout);
}
export interface GeoJSONOutputOptions {
/**
* Geometry types support mode.
*
* - `strict` mode: only allows the 7 standard GeoJSON geometry types:
* Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon,
* and GeometryCollection.
* When an input geometry is not one of these types, an error will be thrown.
* - `extended` mode: allows serialization of all geometry types that GEOS
* supports, including curved geometries like CircularString, CompoundCurve,
* CurvePolygon, etc.
* The JSON structure of these geometries is not standardized and may change
* in future versions.
*
* @default 'strict'
*/
flavor?: 'strict' | 'extended';
/**
* Coordinate layout for the output GeoJSON.
*
* Defines the coordinate structure of the output geometries.
*
* Use this to:
* - Force 2D output by specifying 'XY'
* - Drop M ordinates while keeping Z ordinates with 'XYZ'
* - Drop Z ordinates while keeping M ordinates with 'XYM'
* - Keep both Z and M ordinates with 'XYZM'
*
* ⚠️ **Warning:** M ordinates (measure values) are not part of the official
* GeoJSON specification and may not be interpreted correctly by other parsers.
* Use 'XY' or 'XYZ' layouts for maximum interoperability.
*
* @default 'XYZ'
*/
layout?: CoordinateType;
}
export interface ExtendedGeoJSONOutputOptions extends GeoJSONOutputOptions {
flavor: 'extended';
}
/**
* Converts a geometry object to a GeoJSON `Feature` object or a GeoJSON
* `FeatureCollection` object.
*
* This function has 2 overloads, when called with:
* - a **single** geometry object it returns a GeoJSON `Feature` object,
* - an **array** of geometry objects it returns a GeoJSON `FeatureCollection`
* object.
*
* @template P - The type of geometry/feature properties
* @param geometryies - The geometry object or the array of geometry objects
* to be converted into a GeoJSON object
* @param options - Optional GeoJSON output configuration
* @returns GeoJSON `Feature` or GeoJSON `FeatureCollection` object
* @throws {GEOSError} (`strict` mode only) when called with not standard GeoJSON geometry
*
* @see {@link GeometryRef#toJSON} converts geometry to a GeoJSON `Feature` object
*
* @example #live
* const a = point([ 0, 0 ]);
* const feature = toGeoJSON(a); // or `a.toJSON();`
* // { type: 'Feature', geometry: {...}, properties: null }
*
* const b = point([ 1, 0 ]);
* const featureCollection = toGeoJSON([ a, b ]);
* // { type: 'FeatureCollection', features: [ {...}, {...} ] }
*
* @example #live 'flavor' option
* const curvedGeometry = fromWKT('CIRCULARSTRING (10 20, 20 30, 30 20)');
*
* // by default only the standard GeoJSON geometries are supported:
* // toGeoJSON(curvedGeometry); // this will throw
*
* const json = toGeoJSON(curvedGeometry, { flavor: 'extended' });
* // { type: 'Feature', geometry: {...}, properties: null }
*
* @example 'layout' option
* toGeoJSON(fromWKT('POINT ZM (1 2 3 4)'), { layout: 'XYZM' }); // will extract [ 1, 2, 3, 4 ]
* toGeoJSON(fromWKT('POINT ZM (1 2 3 4)'), { layout: 'XYZ' }); // [ 1, 2, 3 ]
* toGeoJSON(fromWKT('POINT ZM (1 2 3 4)'), { layout: 'XYM' }); // [ 1, 2, 4 ]
* toGeoJSON(fromWKT('POINT ZM (1 2 3 4)'), { layout: 'XY' }); // [ 1, 2 ]
* toGeoJSON(fromWKT('POINT M (1 2 4)'), { layout: 'XYZM' }); // [ 1, 2, NaN, 4 ]
* toGeoJSON(fromWKT('POINT M (1 2 4)'), { layout: 'XYZ' }); // [ 1, 2 ]
* toGeoJSON(fromWKT('POINT (1 2)'), { layout: 'XYZM' }); // [ 1, 2 ]
* toGeoJSON(fromWKT('POINT (1 2)'), { layout: 'XYZ' }); // [ 1, 2 ]
*/
export function toGeoJSON<P>(geometryies: Geometry<P>, options: ExtendedGeoJSONOutputOptions): JSON_Feature<GeoJSON_Geometry, P>;
export function toGeoJSON<P>(geometryies: Geometry<P>, options?: GeoJSONOutputOptions): GeoJSON_Feature<GeoJSON_Geometry, P>;
export function toGeoJSON<P>(geometryies: Geometry<P>[], options: ExtendedGeoJSONOutputOptions): JSON_FeatureCollection<GeoJSON_Geometry, P>;
export function toGeoJSON<P>(geometryies: Geometry<P>[], options?: GeoJSONOutputOptions): GeoJSON_FeatureCollection<GeoJSON_Geometry, P>;
export function toGeoJSON<P>(geometryies: Geometry<P> | Geometry<P>[], options?: GeoJSONOutputOptions): GeoJSON_Feature<GeoJSON_Geometry, P> | GeoJSON_FeatureCollection<GeoJSON_Geometry, P> | JSON_Feature<JSON_Geometry, P> | JSON_FeatureCollection<JSON_Geometry, P> {
const layout = options?.layout;
const extended = options?.flavor === 'extended';
if (Array.isArray(geometryies)) {
return { type: 'FeatureCollection', features: jsonifyFeatures(geometryies, layout, extended) };
}
return feature(geometryies, jsonifyGeometry(geometryies, layout, extended));
}