UNPKG

@loaders.gl/wkt

Version:

Loader and Writer for the WKT (Well Known Text) Format

242 lines (241 loc) 10.2 kB
// loaders.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import { binaryToGeometry } from '@loaders.gl/gis'; import { parseWKBHeader, WKBGeometryType } from "./parse-wkb-header.js"; export function parseWKB(arrayBuffer, options) { const binaryGeometry = parseWKBToBinary(arrayBuffer, options); const shape = options?.wkb?.shape || 'binary-geometry'; switch (shape) { case 'binary-geometry': return binaryGeometry; case 'geojson-geometry': return binaryToGeometry(binaryGeometry); case 'geometry': // eslint-disable-next-line no-console console.error('WKBLoader: "geometry" shape is deprecated, use "binary-geometry" instead'); return binaryToGeometry(binaryGeometry); default: throw new Error(shape); } } export function parseWKBToBinary(arrayBuffer, options) { const dataView = new DataView(arrayBuffer); const wkbHeader = parseWKBHeader(dataView); const { geometryType, dimensions, littleEndian } = wkbHeader; const offset = wkbHeader.byteOffset; switch (geometryType) { case WKBGeometryType.Point: const point = parsePoint(dataView, offset, dimensions, littleEndian); return point.geometry; case WKBGeometryType.LineString: const line = parseLineString(dataView, offset, dimensions, littleEndian); return line.geometry; case WKBGeometryType.Polygon: const polygon = parsePolygon(dataView, offset, dimensions, littleEndian); return polygon.geometry; case WKBGeometryType.MultiPoint: const multiPoint = parseMultiPoint(dataView, offset, dimensions, littleEndian); multiPoint.type = 'Point'; return multiPoint; case WKBGeometryType.MultiLineString: const multiLine = parseMultiLineString(dataView, offset, dimensions, littleEndian); multiLine.type = 'LineString'; return multiLine; case WKBGeometryType.MultiPolygon: const multiPolygon = parseMultiPolygon(dataView, offset, dimensions, littleEndian); multiPolygon.type = 'Polygon'; return multiPolygon; // case WKBGeometryType.GeometryCollection: // TODO: handle GeometryCollections // return parseGeometryCollection(dataView, offset, dimensions, littleEndian); default: throw new Error(`WKB: Unsupported geometry type: ${geometryType}`); } } // Primitives; parse point and linear ring function parsePoint(dataView, offset, dimension, littleEndian) { const positions = new Float64Array(dimension); for (let i = 0; i < dimension; i++) { positions[i] = dataView.getFloat64(offset, littleEndian); offset += 8; } return { geometry: { type: 'Point', positions: { value: positions, size: dimension } }, offset }; } function parseLineString(dataView, offset, dimension, littleEndian) { const nPoints = dataView.getUint32(offset, littleEndian); offset += 4; // Instantiate array const positions = new Float64Array(nPoints * dimension); for (let i = 0; i < nPoints * dimension; i++) { positions[i] = dataView.getFloat64(offset, littleEndian); offset += 8; } const pathIndices = [0]; if (nPoints > 0) { pathIndices.push(nPoints); } return { geometry: { type: 'LineString', positions: { value: positions, size: dimension }, pathIndices: { value: new Uint32Array(pathIndices), size: 1 } }, offset }; } // https://stackoverflow.com/a/55261098 const cumulativeSum = (sum) => (value) => (sum += value); function parsePolygon(dataView, offset, dimension, littleEndian) { const nRings = dataView.getUint32(offset, littleEndian); offset += 4; const rings = []; for (let i = 0; i < nRings; i++) { const parsed = parseLineString(dataView, offset, dimension, littleEndian); const { positions } = parsed.geometry; offset = parsed.offset; rings.push(positions.value); } const concatenatedPositions = new Float64Array(concatTypedArrays(rings).buffer); const polygonIndices = [0]; if (concatenatedPositions.length > 0) { polygonIndices.push(concatenatedPositions.length / dimension); } const primitivePolygonIndices = rings.map((l) => l.length / dimension).map(cumulativeSum(0)); primitivePolygonIndices.unshift(0); return { geometry: { type: 'Polygon', positions: { value: concatenatedPositions, size: dimension }, polygonIndices: { value: new Uint32Array(polygonIndices), size: 1 }, primitivePolygonIndices: { value: new Uint32Array(primitivePolygonIndices), size: 1 } }, offset }; } function parseMultiPoint(dataView, offset, dimension, littleEndian) { const nPoints = dataView.getUint32(offset, littleEndian); offset += 4; const binaryPointGeometries = []; for (let i = 0; i < nPoints; i++) { // Byte order for point const littleEndianPoint = dataView.getUint8(offset) === 1; offset++; // Assert point type if (dataView.getUint32(offset, littleEndianPoint) % 1000 !== 1) { throw new Error('WKB: Inner geometries of MultiPoint not of type Point'); } offset += 4; const parsed = parsePoint(dataView, offset, dimension, littleEndianPoint); offset = parsed.offset; binaryPointGeometries.push(parsed.geometry); } return concatenateBinaryPointGeometries(binaryPointGeometries, dimension); } function parseMultiLineString(dataView, offset, dimension, littleEndian) { const nLines = dataView.getUint32(offset, littleEndian); offset += 4; const binaryLineGeometries = []; for (let i = 0; i < nLines; i++) { // Byte order for line const littleEndianLine = dataView.getUint8(offset) === 1; offset++; // Assert type LineString if (dataView.getUint32(offset, littleEndianLine) % 1000 !== 2) { throw new Error('WKB: Inner geometries of MultiLineString not of type LineString'); } offset += 4; const parsed = parseLineString(dataView, offset, dimension, littleEndianLine); offset = parsed.offset; binaryLineGeometries.push(parsed.geometry); } return concatenateBinaryLineGeometries(binaryLineGeometries, dimension); } function parseMultiPolygon(dataView, offset, dimension, littleEndian) { const nPolygons = dataView.getUint32(offset, littleEndian); offset += 4; const binaryPolygonGeometries = []; for (let i = 0; i < nPolygons; i++) { // Byte order for polygon const littleEndianPolygon = dataView.getUint8(offset) === 1; offset++; // Assert type Polygon if (dataView.getUint32(offset, littleEndianPolygon) % 1000 !== 3) { throw new Error('WKB: Inner geometries of MultiPolygon not of type Polygon'); } offset += 4; const parsed = parsePolygon(dataView, offset, dimension, littleEndianPolygon); offset = parsed.offset; binaryPolygonGeometries.push(parsed.geometry); } return concatenateBinaryPolygonGeometries(binaryPolygonGeometries, dimension); } // TODO - move to loaders.gl/schema/gis function concatenateBinaryPointGeometries(binaryPointGeometries, dimension) { const positions = binaryPointGeometries.map((geometry) => geometry.positions.value); const concatenatedPositions = new Float64Array(concatTypedArrays(positions).buffer); return { type: 'Point', positions: { value: concatenatedPositions, size: dimension } }; } function concatenateBinaryLineGeometries(binaryLineGeometries, dimension) { const lines = binaryLineGeometries.map((geometry) => geometry.positions.value); const concatenatedPositions = new Float64Array(concatTypedArrays(lines).buffer); const pathIndices = lines.map((line) => line.length / dimension).map(cumulativeSum(0)); pathIndices.unshift(0); return { type: 'LineString', positions: { value: concatenatedPositions, size: dimension }, pathIndices: { value: new Uint32Array(pathIndices), size: 1 } }; } function concatenateBinaryPolygonGeometries(binaryPolygonGeometries, dimension) { const polygons = []; const primitivePolygons = []; for (const binaryPolygon of binaryPolygonGeometries) { const { positions, primitivePolygonIndices } = binaryPolygon; polygons.push(positions.value); primitivePolygons.push(primitivePolygonIndices.value); } const concatenatedPositions = new Float64Array(concatTypedArrays(polygons).buffer); const polygonIndices = polygons.map((p) => p.length / dimension).map(cumulativeSum(0)); polygonIndices.unshift(0); // Combine primitivePolygonIndices from each individual polygon const primitivePolygonIndices = [0]; for (const primitivePolygon of primitivePolygons) { primitivePolygonIndices.push(...primitivePolygon .filter((x) => x > 0) .map((x) => x + primitivePolygonIndices[primitivePolygonIndices.length - 1])); } return { type: 'Polygon', positions: { value: concatenatedPositions, size: dimension }, polygonIndices: { value: new Uint32Array(polygonIndices), size: 1 }, primitivePolygonIndices: { value: new Uint32Array(primitivePolygonIndices), size: 1 } }; } // TODO: remove copy; import from typed-array-utils // modules/math/src/geometry/typed-arrays/typed-array-utils.js function concatTypedArrays(arrays) { let byteLength = 0; for (let i = 0; i < arrays.length; ++i) { byteLength += arrays[i].byteLength; } const buffer = new Uint8Array(byteLength); let byteOffset = 0; for (let i = 0; i < arrays.length; ++i) { const data = new Uint8Array(arrays[i].buffer); byteLength = data.length; for (let j = 0; j < byteLength; ++j) { buffer[byteOffset++] = data[j]; } } return buffer; }