@loaders.gl/wkt
Version:
Loader and Writer for the WKT (Well Known Text) Format
289 lines (287 loc) • 7.64 kB
JavaScript
// loaders.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
// Fork of https://github.com/mapbox/wellknown under ISC license (MIT/BSD-2-clause equivalent)
/* eslint-disable */
// @ts-nocheck
const numberRegexp = /[-+]?([0-9]*\.[0-9]+|[0-9]+)([eE][-+]?[0-9]+)?/;
// Matches sequences like '100 100' or '100 100 100'.
const tuples = new RegExp('^' + numberRegexp.source + '(\\s' + numberRegexp.source + '){1,}');
export const WKT_MAGIC_STRINGS = [
'POINT(',
'LINESTRING(',
'POLYGON(',
'MULTIPOINT(',
'MULTILINESTRING(',
'MULTIPOLYGON(',
'GEOMETRYCOLLECTION('
// We only support this "geojson" subset of the OGC simple features standard
];
/**
* Check if a string is WKT.
* @param input A potential WKT geometry string
* @return `true` if input appears to be a WKT geometry string, `false` otherwise
* @note We only support the "geojson" subset of the OGC simple features standard
* @todo Does not handle leading spaces which appear to be permitted per the spec:
* "A WKT string contains no white space outside of double quotes.
* However padding with white space to improve human readability is permitted;
* the examples of WKT that are included in this document have
* spaces and line feeds inserted to improve clarity. Any padding is stripped out or ignored by parsers."
*/
export function isWKT(input) {
return WKT_MAGIC_STRINGS.some((magicString) => input.startsWith(magicString));
}
/**
* Parse WKT and return GeoJSON.
* @param input A WKT geometry string
* @return A GeoJSON geometry object
*
* @note We only support the "geojson" subset of the OGC simple features standard
**/
export function parseWKT(input, options) {
// TODO handle options.wkt.shape
return parseWKTToGeometry(input, options);
}
/** Parse into GeoJSON geometry */
function parseWKTToGeometry(input, options) {
const parts = input.split(';');
let _ = parts.pop();
const srid = (parts.shift() || '').split('=').pop();
const state = { parts, _, i: 0 };
const geometry = parseGeometry(state);
return options?.wkt?.crs ? addCRS(geometry, srid) : geometry;
}
function parseGeometry(state) {
return (parsePoint(state) ||
parseLineString(state) ||
parsePolygon(state) ||
parseMultiPoint(state) ||
parseMultiLineString(state) ||
parseMultiPolygon(state) ||
parseGeometryCollection(state));
}
/** Adds a coordinate reference system as an undocumented */
function addCRS(obj, srid) {
if (obj && srid?.match(/\d+/)) {
const crs = {
type: 'name',
properties: {
name: 'urn:ogc:def:crs:EPSG::' + srid
}
};
// @ts-expect-error we assign an undocumented property on the geometry
obj.crs = crs;
}
return obj;
}
// GEOMETRIES
function parsePoint(state) {
if (!$(/^(POINT(\sz)?)/i, state)) {
return null;
}
white(state);
if (!$(/^(\()/, state)) {
return null;
}
const c = coords(state);
if (!c) {
return null;
}
white(state);
if (!$(/^(\))/, state)) {
return null;
}
return {
type: 'Point',
coordinates: c[0]
};
}
function parseMultiPoint(state) {
if (!$(/^(MULTIPOINT)/i, state)) {
return null;
}
white(state);
const newCoordsFormat = state._?.substring(state._?.indexOf('(') + 1, state._.length - 1)
.replace(/\(/g, '')
.replace(/\)/g, '');
state._ = 'MULTIPOINT (' + newCoordsFormat + ')';
const c = multicoords(state);
if (!c) {
return null;
}
white(state);
return {
type: 'MultiPoint',
coordinates: c
};
}
function parseLineString(state) {
if (!$(/^(LINESTRING(\sz)?)/i, state)) {
return null;
}
white(state);
if (!$(/^(\()/, state)) {
return null;
}
const c = coords(state);
if (!c) {
return null;
}
if (!$(/^(\))/, state)) {
return null;
}
return {
type: 'LineString',
coordinates: c
};
}
function parseMultiLineString(state) {
if (!$(/^(MULTILINESTRING)/i, state))
return null;
white(state);
const c = multicoords(state);
if (!c) {
return null;
}
white(state);
return {
// @ts-ignore
type: 'MultiLineString',
// @ts-expect-error
coordinates: c
};
}
function parsePolygon(state) {
if (!$(/^(POLYGON(\sz)?)/i, state)) {
return null;
}
white(state);
const c = multicoords(state);
if (!c) {
return null;
}
return {
// @ts-ignore
type: 'Polygon',
// @ts-expect-error
coordinates: c
};
}
function parseMultiPolygon(state) {
if (!$(/^(MULTIPOLYGON)/i, state)) {
return null;
}
white(state);
const c = multicoords(state);
if (!c) {
return null;
}
return {
type: 'MultiPolygon',
// @ts-expect-error
coordinates: c
};
}
function parseGeometryCollection(state) {
const geometries = [];
let geometry;
if (!$(/^(GEOMETRYCOLLECTION)/i, state)) {
return null;
}
white(state);
if (!$(/^(\()/, state)) {
return null;
}
while ((geometry = parseGeometry(state))) {
geometries.push(geometry);
white(state);
$(/^(,)/, state);
white(state);
}
if (!$(/^(\))/, state)) {
return null;
}
return {
type: 'GeometryCollection',
geometries: geometries
};
}
// COORDINATES
function multicoords(state) {
white(state);
let depth = 0;
const rings = [];
const stack = [rings];
let pointer = rings;
let elem;
while ((elem = $(/^(\()/, state) || $(/^(\))/, state) || $(/^(,)/, state) || $(tuples, state))) {
if (elem === '(') {
stack.push(pointer);
pointer = [];
stack[stack.length - 1].push(pointer);
depth++;
}
else if (elem === ')') {
// For the case: Polygon(), ...
if (pointer.length === 0)
return null;
// @ts-ignore
pointer = stack.pop();
// the stack was empty, input was malformed
if (!pointer)
return null;
depth--;
if (depth === 0)
break;
}
else if (elem === ',') {
pointer = [];
stack[stack.length - 1].push(pointer);
}
else if (!elem.split(/\s/g).some(isNaN)) {
Array.prototype.push.apply(pointer, elem.split(/\s/g).map(parseFloat));
}
else {
return null;
}
white(state);
}
if (depth !== 0)
return null;
return rings;
}
function coords(state) {
const list = [];
let item;
let pt;
while ((pt = $(tuples, state) || $(/^(,)/, state))) {
if (pt === ',') {
list.push(item);
item = [];
}
else if (!pt.split(/\s/g).some(isNaN)) {
if (!item)
item = [];
Array.prototype.push.apply(item, pt.split(/\s/g).map(parseFloat));
}
white(state);
}
if (item)
list.push(item);
else
return null;
return list.length ? list : null;
}
// HELPERS
function $(regexp, state) {
const match = state._?.substring(state.i).match(regexp);
if (!match)
return null;
else {
state.i += match[0].length;
return match[0];
}
}
function white(state) {
$(/^\s*/, state);
}