@loaders.gl/wkt
Version:
Loader and Writer for the WKT (Well Known Text) Format
135 lines (134 loc) • 5.04 kB
JavaScript
// 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 var WKBGeometryType;
(function (WKBGeometryType) {
WKBGeometryType[WKBGeometryType["Point"] = 1] = "Point";
WKBGeometryType[WKBGeometryType["LineString"] = 2] = "LineString";
WKBGeometryType[WKBGeometryType["Polygon"] = 3] = "Polygon";
WKBGeometryType[WKBGeometryType["MultiPoint"] = 4] = "MultiPoint";
WKBGeometryType[WKBGeometryType["MultiLineString"] = 5] = "MultiLineString";
WKBGeometryType[WKBGeometryType["MultiPolygon"] = 6] = "MultiPolygon";
WKBGeometryType[WKBGeometryType["GeometryCollection"] = 7] = "GeometryCollection";
})(WKBGeometryType || (WKBGeometryType = {}));
/** Sanity checks that first to 5-9 bytes could represent a supported WKB dialect header */
export function isWKB(arrayBuffer) {
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, target) {
const wkbHeader = Object.assign(target || {}, {
type: 'wkb',
geometryType: 1,
dimensions: 2,
coordinates: 'xy',
littleEndian: true,
byteOffset: 0
});
// 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);
// 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;
}