gis-tools-ts
Version:
A collection of geospatial tools primarily designed for WGS84, Web Mercator, and S2.
270 lines • 8.37 kB
JavaScript
import { cleanString } from './index.js';
/**
* # WKT Geometry Reader
*
* ## Description
* Parse a collection of WKT geometries from a string
* implements the {@link FeatureIterator} interface
*
* ## Usage
* ```ts
* import { WKTGeometryReader } from 'gis-tools-ts';
*
* const reader = new WKTGeometryReader('POINT(4 6) GEOMETRYCOLLECTION(POINT(1 2), LINESTRING(3 4,5 6))');
*
* // read the features
* for await (const feature of reader) {
* console.log(feature);
* }
* ```
*
* ## Links
* - https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry
*/
export class WKTGeometryReader {
data = [];
/** @param data - the WKT geometry string to parase */
constructor(data) {
const wktStrings = splitWKTGeometry(data);
for (const wktString of wktStrings) {
const geometry = parseWKTGeometry(wktString);
if (geometry !== undefined)
this.data.push({
type: 'VectorFeature',
geometry,
properties: {},
});
}
}
/**
* Generator to iterate over each (Geo|S2)JSON object in the file
* @yields {VectorFeature}
*/
async *[Symbol.asyncIterator]() {
for (const feature of this.data)
yield feature;
}
}
/**
* # WKT Geometry Parser
*
* ## Description
* Parse individual geometries from a WKT string into a VectorGeometry
*
* ## Usage
* ```ts
* import { parseWKTGeometry } from 'gis-tools-ts';
*
* const geometry = parseWKTGeometry('POINT (1 2)');
* ```
*
* ## Links
* - https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry
* @param wktStr - WKT string
* @returns - VectorGeometry
*/
export function parseWKTGeometry(wktStr) {
if (wktStr.startsWith('POINT'))
return parseWKTPoint(wktStr, wktStr.startsWith('POINT Z'));
else if (wktStr.startsWith('MULTIPOINT'))
return parseWKTLine(wktStr, 'MultiPoint', wktStr.startsWith('MULTIPOINT Z'));
else if (wktStr.startsWith('LINESTRING'))
return parseWKTLine(wktStr, 'LineString', wktStr.startsWith('LINESTRING Z'));
else if (wktStr.startsWith('MULTILINESTRING'))
return parseWKTMultiLine(wktStr, 'MultiLineString', wktStr.startsWith('MULTILINESTRING Z'));
else if (wktStr.startsWith('POLYGON'))
return parseWKTMultiLine(wktStr, 'Polygon', wktStr.startsWith('POLYGON Z'));
else if (wktStr.startsWith('MULTIPOLYGON'))
return parseWKTMultiPolygon(wktStr, wktStr.startsWith('MULTIPOLYGON Z'));
}
/**
* Split a WKT string into individual geometries
* @param input - WKT string that is a collection of geometries
* @returns - Array of individual WKT geometries
*/
export function splitWKTGeometry(input) {
// First remove all instances of EMPTY geometry.
// So if EMPTY found, delete the word and the word prior:
const words = input.split(' ');
for (let i = 0; i < words.length; i++) {
const word = words[i];
if (word.includes('EMPTY') && i > 0) {
words.splice(i - 1, 2);
i--;
}
}
input = words.join(' ');
const geometries = [];
let start = 0;
let found = false;
let end = 0;
let depth = 0;
for (let i = start; i < input.length; i++) {
const char = input[i];
if (char === '(') {
depth++;
found = true;
}
if (char === ')')
depth--;
if (found && depth === 0) {
end = i + 1;
geometries.push(input.slice(start, end).trim());
start = end;
found = false;
}
}
for (let i = 0; i < geometries.length; i++) {
const geometry = geometries[i];
if (geometry.startsWith('GEOMETRYCOLLECTION')) {
geometries.splice(i, 1);
i--;
// remove "GEOMETRYCOLLECTION(" from beginning of string and ")" from end
const clean = geometry.slice(geometry.indexOf('(') + 1, geometry.length - 1);
geometries.push(...splitWKTGeometry(clean));
}
else if (geometry.startsWith(',')) {
geometries[i] = geometry.slice(1).trim();
}
}
return geometries.filter((g) => g.length > 0);
}
/**
* Parse a WKT point string to a VectorPoint
* @param wktStr - WKT string
* @param is3D - true if the point is 3D
* @returns - VectorPoint
*/
function parseWKTPoint(wktStr, is3D) {
const geo = parseWKTArray(wktStr);
return {
type: 'Point',
is3D,
coordinates: geo[0],
};
}
/**
* Parse a WKT array to a LineString or MultiPoint geometry
* @param wktStr - WKT string
* @param type - 'MultiPoint' or 'LineString'
* @param is3D - true if the point is 3D
* @returns - VectorGeometry (LineString or MultiPoint)
*/
function parseWKTLine(wktStr, type, is3D) {
let geo = parseWKTArray(wktStr);
geo =
geo.length > 0 && Array.isArray(geo[0])
? geo.map((e) => e[0])
: geo;
return {
type,
is3D,
coordinates: geo,
};
}
/**
* Parse a WKT array to a MultiLineString or Polygon
* @param wktStr - WKT string
* @param type - 'MultiLineString' or 'Polygon'
* @param is3D - true if the point is 3D
* @returns - VectorGeometry
*/
function parseWKTMultiLine(wktStr, type, is3D) {
let geo = parseWKTArray(wktStr);
geo =
geo.length > 0 && geo[0].length > 0 && Array.isArray(geo[0][0])
? geo.map((e) => {
return e.map((e2) => e2[0]);
})
: geo;
return {
type,
is3D,
coordinates: geo,
};
}
/**
* Parse a WKT array to a MultiPolygon
* @param wktStr - WKT string
* @param is3D - true if each point is 3D
* @returns - VectorGeometry
*/
function parseWKTMultiPolygon(wktStr, is3D) {
let geo = parseWKTArray(wktStr);
geo =
geo.length > 0 && geo[0].length > 0 && geo[0][0].length > 0 && Array.isArray(geo[0][0][0])
? geo.map((e) => {
return e.map((e2) => e2.map((e3) => e3[0]));
})
: geo;
return {
type: 'MultiPolygon',
is3D,
coordinates: geo,
};
}
/**
* Parse a WKT array
* @param wktStr - WKT string
* @returns - collection of points
*/
function parseWKTArray(wktStr) {
const res = [];
_parseWKTArray(wktStr, res);
return res.length > 0 ? res[0] : res;
}
/**
* Parse a WKT array.
* always return the endBracketIndex if we hit it
* @param wktStr - WKT string
* @param res - collection to store the values
* @returns - a sliced WKT string with the parsed values
*/
function _parseWKTArray(wktStr, res) {
// first get the array name and build the residual
while (wktStr.length > 0) {
let commaIndex = wktStr.indexOf(',');
let startBracketIndex = wktStr.indexOf('(');
const endBracketIndex = wktStr.indexOf(')');
if (commaIndex === -1)
commaIndex = Infinity;
if (startBracketIndex === -1)
startBracketIndex = Infinity;
if (commaIndex < Math.min(startBracketIndex, endBracketIndex)) {
// store the value
const key = wktStr.slice(0, commaIndex);
if (key.length > 0)
res.push(buildPoint(key));
wktStr = wktStr.slice(commaIndex + 1);
}
else if (startBracketIndex < endBracketIndex) {
// store the array
const array = [];
wktStr = _parseWKTArray(wktStr.slice(startBracketIndex + 1), array);
res.push(array);
}
else {
// store the LAST value if it exists, be sure to increment past the bracket for this recursive call
if (endBracketIndex > 0) {
res.push(buildPoint(wktStr.slice(0, endBracketIndex)));
wktStr = wktStr.slice(endBracketIndex + 1);
}
else {
wktStr = wktStr.slice(1);
}
return wktStr;
}
}
// hit the end
return wktStr;
}
/**
* Build a point from a WKT string
* @param str - WKT string
* @returns - VectorPoint
*/
function buildPoint(str) {
const [x, y, z] = cleanString(str).split(' ');
return { x: Number(x), y: Number(y), z: z !== undefined ? Number(z) : undefined };
}
//# sourceMappingURL=geometry.js.map