@loaders.gl/wkt
Version:
Loader and Writer for the WKT (Well Known Text) Format
175 lines (149 loc) • 4.96 kB
text/typescript
// loaders.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
const EWKB_FLAG_Z = 0x80000000;
const EWKB_FLAG_M = 0x40000000;
const EWKB_FLAG_SRID = 0x20000000;
const MAX_SRID = 10000; // TBD: Assume no more than 10K SRIDs are defined
/**
* Integer code for geometry types in WKB and related formats
* Reference: https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry#Well-known_binary
*/
export enum WKBGeometryType {
Point = 1,
LineString = 2,
Polygon = 3,
MultiPoint = 4,
MultiLineString = 5,
MultiPolygon = 6,
GeometryCollection = 7
}
/** Parsed WKB header */
export type WKBHeader = {
/** WKB variant */
type: 'wkb' | 'ewkb' | 'iso-wkb' | 'twkb';
/** geometry type encoded in this WKB: point, line, polygon etc */
geometryType: 1 | 2 | 3 | 4 | 5 | 6 | 7;
/** Number of dimensions for coordinate data */
dimensions: 2 | 3 | 4;
/** Coordinate names, Z and M are controlled by flags */
coordinates: 'xy' | 'xyz' | 'xym' | 'xyzm';
srid?: number;
/** true if binary data is stored as little endian */
littleEndian: boolean;
/** Offset to start of geometry */
byteOffset: number;
};
/** Sanity checks that first to 5-9 bytes could represent a supported WKB dialect header */
export function isWKB(arrayBuffer: ArrayBuffer): boolean {
const dataView = new DataView(arrayBuffer);
let byteOffset = 0;
const endianness = dataView.getUint8(byteOffset);
byteOffset += 1;
// Check valid endianness (only 0 or 1 are allowed)
if (endianness > 1) {
return false;
}
const littleEndian = endianness === 1;
const geometry = dataView.getUint32(byteOffset, littleEndian);
byteOffset += 4;
// check valid geometry type (we don't support extension geometries)
const geometryType = geometry & 0x07;
if (geometryType === 0 || geometryType > 7) {
return false;
}
const geometryFlags = geometry - geometryType;
// Accept iso-wkb flags
if (
geometryFlags === 0 ||
geometryFlags === 1000 ||
geometryFlags === 2000 ||
geometryFlags === 3000
) {
return true;
}
// Accept ewkb flags but reject otherwise
if ((geometryFlags & ~(EWKB_FLAG_Z | EWKB_FLAG_M | EWKB_FLAG_SRID)) !== 0) {
return false;
}
if (geometryFlags & EWKB_FLAG_SRID) {
const srid = dataView.getUint32(byteOffset, littleEndian);
byteOffset += 4;
if (srid > MAX_SRID) {
return false;
}
}
return true;
}
/**
* Parses header and provides a byteOffset to start of geometry data
* @param dataView
* @param target optionally supply a WKBHeader object to avoid creating a new object for every call
* @returns a header object describing the WKB data
*/
// eslint-disable-next-line max-statements
export function parseWKBHeader(dataView: DataView, target?: WKBHeader): WKBHeader {
const wkbHeader: WKBHeader = Object.assign(target || {}, {
type: 'wkb',
geometryType: 1,
dimensions: 2,
coordinates: 'xy',
littleEndian: true,
byteOffset: 0
} as WKBHeader);
// Check endianness of data
wkbHeader.littleEndian = dataView.getUint8(wkbHeader.byteOffset) === 1;
wkbHeader.byteOffset++;
// 4-digit code representing dimension and type of geometry
const geometryCode = dataView.getUint32(wkbHeader.byteOffset, wkbHeader.littleEndian);
wkbHeader.byteOffset += 4;
wkbHeader.geometryType = (geometryCode & 0x7) as 1 | 2 | 3 | 4 | 5 | 6 | 7;
// Check if iso-wkb variant: iso-wkb adds 1000, 2000 or 3000 to the geometry code
const isoType = (geometryCode - wkbHeader.geometryType) / 1000;
switch (isoType) {
case 0:
break;
case 1:
wkbHeader.type = 'iso-wkb';
wkbHeader.dimensions = 3;
wkbHeader.coordinates = 'xyz';
break;
case 2:
wkbHeader.type = 'iso-wkb';
wkbHeader.dimensions = 3;
wkbHeader.coordinates = 'xym';
break;
case 3:
wkbHeader.type = 'iso-wkb';
wkbHeader.dimensions = 4;
wkbHeader.coordinates = 'xyzm';
break;
default:
throw new Error(`WKB: Unsupported iso-wkb type: ${isoType}`);
}
// Check if EWKB variant. Uses bitmasks for Z&M dimensions as well as optional SRID field
const ewkbZ = geometryCode & EWKB_FLAG_Z;
const ewkbM = geometryCode & EWKB_FLAG_M;
const ewkbSRID = geometryCode & EWKB_FLAG_SRID;
if (ewkbZ && ewkbM) {
wkbHeader.type = 'ewkb';
wkbHeader.dimensions = 4;
wkbHeader.coordinates = 'xyzm';
} else if (ewkbZ) {
wkbHeader.type = 'ewkb';
wkbHeader.dimensions = 3;
wkbHeader.coordinates = 'xyz';
} else if (ewkbM) {
wkbHeader.type = 'ewkb';
wkbHeader.dimensions = 3;
wkbHeader.coordinates = 'xym';
}
// If SRID present read four more bytes
if (ewkbSRID) {
wkbHeader.type = 'ewkb';
// 4-digit code representing dimension and type of geometry
wkbHeader.srid = dataView.getUint32(wkbHeader.byteOffset, wkbHeader.littleEndian);
wkbHeader.byteOffset += 4;
}
return wkbHeader;
}