UNPKG

well-known-parser

Version:

A WKT/WKB/EWKT/EWKB/TWKB/GeoJSON parser and serializer

404 lines (348 loc) 12.5 kB
import { GEOMETRY_TYPES } from '../constants'; import { BinaryReader } from '../binaryreader'; import { WktParser } from '../wktparser'; import type { GeoJSONGeometry, GeometryOptions } from '../types'; import type { Geometry } from '../geometry'; import * as ZigZag from '../zigzag'; import type { GeometryCollection as GeoJSONGeometryCollection } from 'geojson'; // Import geometry-specific parsers import { parsePointGeoJSON, parsePointTwkb, parsePointWkb, parsePointWkt } from './point'; import { parseLineStringGeoJSON, parseLineStringTwkb, parseLineStringWkb, parseLineStringWkt, } from './linestring'; import { parsePolygonGeoJSON, parsePolygonTwkb, parsePolygonWkb, parsePolygonWkt } from './polygon'; import { parseMultiPointGeoJSON, parseMultiPointTwkb, parseMultiPointWkb, parseMultiPointWkt, } from './multipoint'; import { parseMultiLineStringGeoJSON, parseMultiLineStringTwkb, parseMultiLineStringWkb, parseMultiLineStringWkt, } from './multilinestring'; import { parseMultiPolygonGeoJSON, parseMultiPolygonTwkb, parseMultiPolygonWkb, parseMultiPolygonWkt, } from './multipolygon'; import { GeometryCollection } from '../geometrycollection'; export function parse( value: string | Buffer | WktParser | BinaryReader, options?: GeometryOptions ): Geometry { if (typeof value === 'string' || value instanceof WktParser) { return parseWkt(value, options); } else if (Buffer.isBuffer(value) || value instanceof BinaryReader) { return parseWkb(value, options); } else { throw new Error('first argument must be a string or Buffer'); } } /** * Helper function to parse WKT format */ export function parseWkt(value: string | WktParser, inputOptions?: GeometryOptions): Geometry { let wktParser: WktParser; let srid: number | undefined; if (value instanceof WktParser) { wktParser = value; } else { wktParser = new WktParser(value); } const match = wktParser.matchRegex([/^SRID=(\d+);/]); if (match) { srid = parseInt(match[1], 10); } const geometryType = wktParser.matchType(); const dimension = wktParser.matchDimension(); const options: GeometryOptions = { ...(inputOptions || {}), srid, hasZ: dimension.hasZ, hasM: dimension.hasM, }; switch (geometryType) { case GEOMETRY_TYPES.Point.wkt: return parsePointWkt(wktParser, options); case GEOMETRY_TYPES.LineString.wkt: return parseLineStringWkt(wktParser, options); case GEOMETRY_TYPES.Polygon.wkt: return parsePolygonWkt(wktParser, options); case GEOMETRY_TYPES.MultiPoint.wkt: return parseMultiPointWkt(wktParser, options); case GEOMETRY_TYPES.MultiLineString.wkt: return parseMultiLineStringWkt(wktParser, options); case GEOMETRY_TYPES.MultiPolygon.wkt: return parseMultiPolygonWkt(wktParser, options); case GEOMETRY_TYPES.GeometryCollection.wkt: return parseGeometryCollectionWkt(wktParser, options); default: throw new Error(`GeometryType ${geometryType} not supported`); } } /** * Helper function to parse WKB format */ export function parseWkb(value: Buffer | BinaryReader, parentOptions?: GeometryOptions): Geometry { let binaryReader: BinaryReader; let geometryType: number; const options: GeometryOptions = {}; if (value instanceof BinaryReader) { binaryReader = value; } else { binaryReader = new BinaryReader(value); } binaryReader.isBigEndian = !binaryReader.readInt8(); const wkbType = binaryReader.readUInt32(); options.hasSrid = (wkbType & 0x20000000) === 0x20000000; options.isEwkb = Boolean(wkbType & 0x20000000 || wkbType & 0x40000000 || wkbType & 0x80000000); if (options.hasSrid) { options.srid = binaryReader.readUInt32(); } options.hasZ = false; options.hasM = false; if (!options.isEwkb && (!parentOptions || !parentOptions.isEwkb)) { if (wkbType >= 1000 && wkbType < 2000) { options.hasZ = true; geometryType = wkbType - 1000; } else if (wkbType >= 2000 && wkbType < 3000) { options.hasM = true; geometryType = wkbType - 2000; } else if (wkbType >= 3000 && wkbType < 4000) { options.hasZ = true; options.hasM = true; geometryType = wkbType - 3000; } else { geometryType = wkbType; } } else { if (wkbType & 0x80000000) { options.hasZ = true; } if (wkbType & 0x40000000) { options.hasM = true; } geometryType = wkbType & 0xf; } switch (geometryType) { case GEOMETRY_TYPES.Point.wkb: return parsePointWkb(binaryReader, options); case GEOMETRY_TYPES.LineString.wkb: return parseLineStringWkb(binaryReader, options); case GEOMETRY_TYPES.Polygon.wkb: return parsePolygonWkb(binaryReader, options); case GEOMETRY_TYPES.MultiPoint.wkb: return parseMultiPointWkb(binaryReader, options); case GEOMETRY_TYPES.MultiLineString.wkb: return parseMultiLineStringWkb(binaryReader, options); case GEOMETRY_TYPES.MultiPolygon.wkb: return parseMultiPolygonWkb(binaryReader, options); case GEOMETRY_TYPES.GeometryCollection.wkb: return parseGeometryCollectionWkb(binaryReader, options); default: throw new Error(`GeometryType ${geometryType} not supported`); } } /** * Helper function to parse TWKB format */ export function parseTwkb(value: Buffer | BinaryReader, inputOptions?: GeometryOptions): Geometry { let binaryReader: BinaryReader; const options: GeometryOptions = { ...(inputOptions || {}) }; if (value instanceof BinaryReader) { binaryReader = value; } else { binaryReader = new BinaryReader(value); } const type = binaryReader.readUInt8(); const metadataHeader = binaryReader.readUInt8(); const geometryType = type & 0x0f; options.precision = ZigZag.decode(type >> 4); options.precisionFactor = Math.pow(10, options.precision); options.hasBoundingBox = Boolean((metadataHeader >> 0) & 1); options.hasSizeAttribute = Boolean((metadataHeader >> 1) & 1); options.hasIdList = Boolean((metadataHeader >> 2) & 1); options.hasExtendedPrecision = Boolean((metadataHeader >> 3) & 1); options.isEmpty = Boolean((metadataHeader >> 4) & 1); if (options.hasExtendedPrecision) { const extendedPrecision = binaryReader.readUInt8(); options.hasZ = (extendedPrecision & 0x01) === 0x01; options.hasM = (extendedPrecision & 0x02) === 0x02; options.zPrecision = ZigZag.decode((extendedPrecision & 0x1c) >> 2); options.zPrecisionFactor = Math.pow(10, options.zPrecision); options.mPrecision = ZigZag.decode((extendedPrecision & 0xe0) >> 5); options.mPrecisionFactor = Math.pow(10, options.mPrecision); } else { options.hasZ = false; options.hasM = false; } if (options.hasSizeAttribute) { binaryReader.readVarInt(); } if (options.hasBoundingBox) { let dimensions = 2; if (options.hasZ) { dimensions++; } if (options.hasM) { dimensions++; } for (let i = 0; i < dimensions; i++) { binaryReader.readVarInt(); binaryReader.readVarInt(); } } switch (geometryType) { case GEOMETRY_TYPES.Point.wkb: return parsePointTwkb(binaryReader, options); case GEOMETRY_TYPES.LineString.wkb: return parseLineStringTwkb(binaryReader, options); case GEOMETRY_TYPES.Polygon.wkb: return parsePolygonTwkb(binaryReader, options); case GEOMETRY_TYPES.MultiPoint.wkb: return parseMultiPointTwkb(binaryReader, options); case GEOMETRY_TYPES.MultiLineString.wkb: return parseMultiLineStringTwkb(binaryReader, options); case GEOMETRY_TYPES.MultiPolygon.wkb: return parseMultiPolygonTwkb(binaryReader, options); case GEOMETRY_TYPES.GeometryCollection.wkb: return parseGeometryCollectionTwkb(binaryReader, options); default: throw new Error(`GeometryType ${geometryType} not supported`); } } /** * Helper function to parse GeoJSON */ export function parseGeoJSON(value: GeoJSONGeometry, isSubGeometry = false): Geometry { let geometry: Geometry; switch (value.type) { case GEOMETRY_TYPES.Point.geoJSON: geometry = parsePointGeoJSON(value.coordinates); break; case GEOMETRY_TYPES.LineString.geoJSON: geometry = parseLineStringGeoJSON(value); break; case GEOMETRY_TYPES.Polygon.geoJSON: geometry = parsePolygonGeoJSON(value); break; case GEOMETRY_TYPES.MultiPoint.geoJSON: geometry = parseMultiPointGeoJSON(value); break; case GEOMETRY_TYPES.MultiLineString.geoJSON: geometry = parseMultiLineStringGeoJSON(value); break; case GEOMETRY_TYPES.MultiPolygon.geoJSON: geometry = parseMultiPolygonGeoJSON(value); break; case GEOMETRY_TYPES.GeometryCollection.geoJSON: geometry = parseGeometryCollectionGeoJSON(value); break; default: // @ts-expect-error -- fallback for invalid data throw new Error(`GeometryType ${value.type} not supported`); } if (!isSubGeometry) { geometry.srid = 4326; } return geometry; } export function parseGeometryCollectionWkt( value: WktParser, options: GeometryOptions ): GeometryCollection { const geometryCollection = new GeometryCollection(); geometryCollection.srid = options.srid; geometryCollection.hasZ = options.hasZ ?? false; geometryCollection.hasM = options.hasM ?? false; if (value.isMatch(['EMPTY'])) { return geometryCollection; } value.expectGroupStart(); do { const geometryType = value.matchType(); const dimension = value.matchDimension(); const childOptions: GeometryOptions = { ...options, srid: undefined, hasZ: dimension.hasZ, hasM: dimension.hasM, }; switch (geometryType) { case GEOMETRY_TYPES.Point.wkt: geometryCollection.geometries.push(parsePointWkt(value, childOptions)); break; case GEOMETRY_TYPES.LineString.wkt: geometryCollection.geometries.push(parseLineStringWkt(value, childOptions)); break; case GEOMETRY_TYPES.Polygon.wkt: geometryCollection.geometries.push(parsePolygonWkt(value, childOptions)); break; case GEOMETRY_TYPES.MultiPoint.wkt: geometryCollection.geometries.push(parseMultiPointWkt(value, childOptions)); break; case GEOMETRY_TYPES.MultiLineString.wkt: geometryCollection.geometries.push(parseMultiLineStringWkt(value, childOptions)); break; case GEOMETRY_TYPES.MultiPolygon.wkt: geometryCollection.geometries.push(parseMultiPolygonWkt(value, childOptions)); break; case GEOMETRY_TYPES.GeometryCollection.wkt: geometryCollection.geometries.push(parseGeometryCollectionWkt(value, childOptions)); break; default: throw new Error(`GeometryType ${geometryType} not supported`); } } while (value.isMatch([','])); value.expectGroupEnd(); return geometryCollection; } export function parseGeometryCollectionWkb( value: BinaryReader, options: GeometryOptions ): GeometryCollection { const geometryCollection = new GeometryCollection(); geometryCollection.srid = options.srid; geometryCollection.hasZ = options.hasZ ?? false; geometryCollection.hasM = options.hasM ?? false; const geometryCount = value.readUInt32(); for (let i = 0; i < geometryCount; i++) { geometryCollection.geometries.push(parseWkb(value, options)); } return geometryCollection; } export function parseGeometryCollectionTwkb( value: BinaryReader, options: GeometryOptions ): GeometryCollection { const geometryCollection = new GeometryCollection(); geometryCollection.hasZ = options.hasZ ?? false; geometryCollection.hasM = options.hasM ?? false; if (options.isEmpty) { return geometryCollection; } const geometryCount = value.readVarInt(); for (let i = 0; i < geometryCount; i++) { geometryCollection.geometries.push(parseTwkb(value, options)); } return geometryCollection; } export function parseGeometryCollectionGeoJSON( value: Pick<GeoJSONGeometryCollection, 'geometries'> ): GeometryCollection { const geometryCollection = new GeometryCollection(); for (let i = 0; i < value.geometries.length; i++) { geometryCollection.geometries.push(parseGeoJSON(value.geometries[i], true)); } if (geometryCollection.geometries.length > 0) { geometryCollection.hasZ = geometryCollection.geometries[0].hasZ; } return geometryCollection; }