UNPKG

@loaders.gl/wkt

Version:

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

1,556 lines (1,533 loc) 63.5 kB
(function webpackUniversalModuleDefinition(root, factory) { if (typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if (typeof define === 'function' && define.amd) define([], factory); else if (typeof exports === 'object') exports['loaders'] = factory(); else root['loaders'] = factory();})(globalThis, function () { "use strict"; var __exports__ = (() => { var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __commonJS = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default")); var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // external-global-plugin:@loaders.gl/core var require_core = __commonJS({ "external-global-plugin:@loaders.gl/core"(exports, module) { module.exports = globalThis.loaders; } }); // bundle.ts var bundle_exports = {}; __export(bundle_exports, { HexWKBLoader: () => HexWKBLoader, TWKBLoader: () => TWKBLoader, TWKBWriter: () => TWKBWriter, WKBLoader: () => WKBLoader, WKBWorkerLoader: () => WKBWorkerLoader, WKBWriter: () => WKBWriter, WKTCRSLoader: () => WKTCRSLoader, WKTCRSWriter: () => WKTCRSWriter, WKTLoader: () => WKTLoader, WKTWorkerLoader: () => WKTWorkerLoader, WKTWriter: () => WKTWriter, decodeHex: () => decodeHex, encodeHex: () => encodeHex, isTWKB: () => isTWKB, isWKB: () => isWKB, isWKT: () => isWKT, parseWKBHeader: () => parseWKBHeader }); __reExport(bundle_exports, __toESM(require_core(), 1)); // src/lib/utils/version.ts var VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : "latest"; // src/lib/parse-wkt-crs.ts function parseWKTCRS(wkt, options) { if (options?.debug) { console.log("[wktcrs] parse starting with\n", wkt); } wkt = wkt.replace(/[A-Z][A-Z\d_]+\[/gi, (match) => `["${match.substr(0, match.length - 1)}",`); wkt = wkt.replace(/, ?([A-Z][A-Z\d_]+[,\]])/gi, (match, p1) => { const varname = p1.substr(0, p1.length - 1); return `,"${options?.raw ? "raw:" : ""}${varname}"${p1[p1.length - 1]}`; }); if (options?.raw) { wkt = wkt.replace(/, {0,2}(-?[\.\d]+)(?=,|\])/g, function(match, p1) { return `,"${options?.raw ? "raw:" : ""}${p1}"`; }); } if (options?.debug) { console.log(`[wktcrs] json'd wkt: '${wkt}'`); } let data; try { data = JSON.parse(wkt); } catch (error) { console.error(`[wktcrs] failed to parse '${wkt}'`); throw error; } if (options?.debug) { console.log(`[wktcrs] json parsed: '${wkt}'`); } function process(data2, parent) { const kw = data2[0]; data2.forEach(function(it) { if (Array.isArray(it)) { process(it, data2); } }); const kwarr = `MULTIPLE_${kw}`; if (kwarr in parent) { parent[kwarr].push(data2); } else if (kw in parent) { parent[kwarr] = [parent[kw], data2]; delete parent[kw]; } else { parent[kw] = data2; } return parent; } const result = process(data, [data]); if (options?.debug) { console.log("[wktcrs] parse returning", result); } if (options?.sort) { sort(result, options); } return result; } function sort(data, options) { const keys = Object.keys(data).filter((k) => !/\d+/.test(k)); const keywords = options?.keywords || []; if (!options?.keywords) { const counts = {}; if (Array.isArray(data)) { data.forEach((it) => { if (Array.isArray(it) && it.length >= 2 && typeof it[1] === "string") { const k = it[0]; if (!counts[k]) counts[k] = 0; counts[k]++; } }); for (const k in counts) { if (counts[k] > 0) keywords.push(k); } } } keys.forEach((key) => { data[key] = sort(data[key]); }); keywords.forEach((key) => { const indices = []; const params = []; data.forEach((item, i) => { if (Array.isArray(item) && item[0] === key) { indices.push(i); params.push(item); } }); params.sort((a, b) => { a = a[1].toString(); b = b[1].toString(); return a < b ? -1 : a > b ? 1 : 0; }); params.forEach((param, i) => { data[indices[i]] = param; }); }); return data; } // src/wkt-crs-loader.ts var WKTCRSLoader = { dataType: null, batchType: null, name: "WKT CRS (Well-Known Text Coordinate Reference System)", id: "wkt-crs", module: "wkt-crs", version: VERSION, worker: true, extensions: [], mimeTypes: ["text/plain"], category: "json", text: true, options: { "wkt-crs": {} }, parse: async (arrayBuffer, options) => parseWKTCRS(new TextDecoder().decode(arrayBuffer), options?.["wkt-crs"]), parseTextSync: (string, options) => parseWKTCRS(string, options?.["wkt-crs"]) }; // src/lib/encode-wkt-crs.ts function encodeWKTCRS(wkt, options) { if (Array.isArray(wkt) && wkt.length === 1 && Array.isArray(wkt[0])) { wkt = wkt[0]; } const [kw, ...attrs] = wkt; const str = `${kw}[${attrs.map((attr) => { if (Array.isArray(attr)) { return encodeWKTCRS(attr, options); } else if (typeof attr === "number") { return attr.toString(); } else if (typeof attr === "string") { if (attr.startsWith("raw:")) { return attr.replace("raw:", ""); } return `"${attr}"`; } throw new Error(`[wktcrs] unexpected attribute "${attr}"`); }).join(",")}]`; return str; } // src/wkt-crs-writer.ts var WKTCRSWriter = { name: "WKT CRS (Well-Known Text Coordinate Reference System)", id: "wkt-crs", module: "wkt-crs", version: VERSION, worker: true, extensions: [], mimeTypes: ["text/plain"], // category: 'json', text: true, options: { "wkt-crs": {} }, encode: async (wktcrs, options) => new TextEncoder().encode(encodeWKTCRS(wktcrs, options?.["wkt-crs"])), encodeSync: (wktcrs, options) => new TextEncoder().encode(encodeWKTCRS(wktcrs, options?.["wkt-crs"])), encodeTextSync: (wktcrs, options) => encodeWKTCRS(wktcrs, options?.["wkt-crs"]) }; // src/lib/parse-wkt.ts var numberRegexp = /[-+]?([0-9]*\.[0-9]+|[0-9]+)([eE][-+]?[0-9]+)?/; var tuples = new RegExp("^" + numberRegexp.source + "(\\s" + numberRegexp.source + "){1,}"); var WKT_MAGIC_STRINGS = [ "POINT(", "LINESTRING(", "POLYGON(", "MULTIPOINT(", "MULTILINESTRING(", "MULTIPOLYGON(", "GEOMETRYCOLLECTION(" // We only support this "geojson" subset of the OGC simple features standard ]; function isWKT(input) { return WKT_MAGIC_STRINGS.some((magicString) => input.startsWith(magicString)); } function parseWKT(input, options) { return parseWKTToGeometry(input, options); } function parseWKTToGeometry(input, options) { const parts = input.split(";"); let _ = parts.pop(); const srid = (parts.shift() || "").split("=").pop(); const state = { parts, _, i: 0 }; const geometry = parseGeometry(state); return options?.wkt?.crs ? addCRS(geometry, srid) : geometry; } function parseGeometry(state) { return parsePoint(state) || parseLineString(state) || parsePolygon(state) || parseMultiPoint(state) || parseMultiLineString(state) || parseMultiPolygon(state) || parseGeometryCollection(state); } function addCRS(obj, srid) { if (obj && srid?.match(/\d+/)) { const crs = { type: "name", properties: { name: "urn:ogc:def:crs:EPSG::" + srid } }; obj.crs = crs; } return obj; } function parsePoint(state) { if (!$(/^(POINT(\sz)?)/i, state)) { return null; } white(state); if (!$(/^(\()/, state)) { return null; } const c = coords(state); if (!c) { return null; } white(state); if (!$(/^(\))/, state)) { return null; } return { type: "Point", coordinates: c[0] }; } function parseMultiPoint(state) { if (!$(/^(MULTIPOINT)/i, state)) { return null; } white(state); const newCoordsFormat = state._?.substring(state._?.indexOf("(") + 1, state._.length - 1).replace(/\(/g, "").replace(/\)/g, ""); state._ = "MULTIPOINT (" + newCoordsFormat + ")"; const c = multicoords(state); if (!c) { return null; } white(state); return { type: "MultiPoint", coordinates: c }; } function parseLineString(state) { if (!$(/^(LINESTRING(\sz)?)/i, state)) { return null; } white(state); if (!$(/^(\()/, state)) { return null; } const c = coords(state); if (!c) { return null; } if (!$(/^(\))/, state)) { return null; } return { type: "LineString", coordinates: c }; } function parseMultiLineString(state) { if (!$(/^(MULTILINESTRING)/i, state)) return null; white(state); const c = multicoords(state); if (!c) { return null; } white(state); return { // @ts-ignore type: "MultiLineString", // @ts-expect-error coordinates: c }; } function parsePolygon(state) { if (!$(/^(POLYGON(\sz)?)/i, state)) { return null; } white(state); const c = multicoords(state); if (!c) { return null; } return { // @ts-ignore type: "Polygon", // @ts-expect-error coordinates: c }; } function parseMultiPolygon(state) { if (!$(/^(MULTIPOLYGON)/i, state)) { return null; } white(state); const c = multicoords(state); if (!c) { return null; } return { type: "MultiPolygon", // @ts-expect-error coordinates: c }; } function parseGeometryCollection(state) { const geometries = []; let geometry; if (!$(/^(GEOMETRYCOLLECTION)/i, state)) { return null; } white(state); if (!$(/^(\()/, state)) { return null; } while (geometry = parseGeometry(state)) { geometries.push(geometry); white(state); $(/^(,)/, state); white(state); } if (!$(/^(\))/, state)) { return null; } return { type: "GeometryCollection", geometries }; } function multicoords(state) { white(state); let depth = 0; const rings = []; const stack = [rings]; let pointer = rings; let elem; while (elem = $(/^(\()/, state) || $(/^(\))/, state) || $(/^(,)/, state) || $(tuples, state)) { if (elem === "(") { stack.push(pointer); pointer = []; stack[stack.length - 1].push(pointer); depth++; } else if (elem === ")") { if (pointer.length === 0) return null; pointer = stack.pop(); if (!pointer) return null; depth--; if (depth === 0) break; } else if (elem === ",") { pointer = []; stack[stack.length - 1].push(pointer); } else if (!elem.split(/\s/g).some(isNaN)) { Array.prototype.push.apply(pointer, elem.split(/\s/g).map(parseFloat)); } else { return null; } white(state); } if (depth !== 0) return null; return rings; } function coords(state) { const list = []; let item; let pt; while (pt = $(tuples, state) || $(/^(,)/, state)) { if (pt === ",") { list.push(item); item = []; } else if (!pt.split(/\s/g).some(isNaN)) { if (!item) item = []; Array.prototype.push.apply(item, pt.split(/\s/g).map(parseFloat)); } white(state); } if (item) list.push(item); else return null; return list.length ? list : null; } function $(regexp, state) { const match = state._?.substring(state.i).match(regexp); if (!match) return null; else { state.i += match[0].length; return match[0]; } } function white(state) { $(/^\s*/, state); } // src/wkt-loader.ts var WKTWorkerLoader = { dataType: null, batchType: null, name: "WKT (Well-Known Text)", id: "wkt", module: "wkt", version: VERSION, worker: true, extensions: ["wkt"], mimeTypes: ["text/plain"], category: "geometry", text: true, tests: WKT_MAGIC_STRINGS, testText: isWKT, options: { wkt: { shape: "geojson-geometry", crs: true } } }; var WKTLoader = { ...WKTWorkerLoader, parse: async (arrayBuffer, options) => parseWKT(new TextDecoder().decode(arrayBuffer), options), parseTextSync: (string, options) => parseWKT(string, options) }; // src/lib/encode-wkt.ts function encodeWKT(geometry) { if (geometry.type === "Feature") { geometry = geometry.geometry; } switch (geometry.type) { case "Point": return `POINT ${wrapParens(pairWKT(geometry.coordinates))}`; case "LineString": return `LINESTRING ${wrapParens(ringWKT(geometry.coordinates))}`; case "Polygon": return `POLYGON ${wrapParens(ringsWKT(geometry.coordinates))}`; case "MultiPoint": return `MULTIPOINT ${wrapParens(ringWKT(geometry.coordinates))}`; case "MultiPolygon": return `MULTIPOLYGON ${wrapParens(multiRingsWKT(geometry.coordinates))}`; case "MultiLineString": return `MULTILINESTRING ${wrapParens(ringsWKT(geometry.coordinates))}`; case "GeometryCollection": return `GEOMETRYCOLLECTION ${wrapParens(geometry.geometries.map(encodeWKT).join(", "))}`; default: throw new Error("stringify requires a valid GeoJSON Feature or geometry object as input"); } } function pairWKT(c) { return c.join(" "); } function ringWKT(r) { return r.map(pairWKT).join(", "); } function ringsWKT(r) { return r.map(ringWKT).map(wrapParens).join(", "); } function multiRingsWKT(r) { return r.map(ringsWKT).map(wrapParens).join(", "); } function wrapParens(s) { return `(${s})`; } // src/wkt-writer.ts var WKTWriter = { name: "WKT (Well Known Text)", id: "wkt", module: "wkt", version: VERSION, extensions: ["wkt"], text: true, encode: async (geometry) => encodeWKTSync(geometry), encodeSync: encodeWKTSync, encodeTextSync: encodeWKT, options: { wkt: {} } }; function encodeWKTSync(geometry) { return new TextEncoder().encode(encodeWKT(geometry)).buffer; } // ../gis/src/lib/binary-features/binary-to-geojson.ts function binaryToGeometry(data, startIndex, endIndex) { switch (data.type) { case "Point": return pointToGeoJson(data, startIndex, endIndex); case "LineString": return lineStringToGeoJson(data, startIndex, endIndex); case "Polygon": return polygonToGeoJson(data, startIndex, endIndex); default: const unexpectedInput = data; throw new Error(`Unsupported geometry type: ${unexpectedInput?.type}`); } } function polygonToGeoJson(data, startIndex = -Infinity, endIndex = Infinity) { const { positions } = data; const polygonIndices = data.polygonIndices.value.filter((x) => x >= startIndex && x <= endIndex); const primitivePolygonIndices = data.primitivePolygonIndices.value.filter( (x) => x >= startIndex && x <= endIndex ); const multi = polygonIndices.length > 2; if (!multi) { const coordinates2 = []; for (let i = 0; i < primitivePolygonIndices.length - 1; i++) { const startRingIndex = primitivePolygonIndices[i]; const endRingIndex = primitivePolygonIndices[i + 1]; const ringCoordinates = ringToGeoJson(positions, startRingIndex, endRingIndex); coordinates2.push(ringCoordinates); } return { type: "Polygon", coordinates: coordinates2 }; } const coordinates = []; for (let i = 0; i < polygonIndices.length - 1; i++) { const startPolygonIndex = polygonIndices[i]; const endPolygonIndex = polygonIndices[i + 1]; const polygonCoordinates = polygonToGeoJson( data, startPolygonIndex, endPolygonIndex ).coordinates; coordinates.push(polygonCoordinates); } return { type: "MultiPolygon", coordinates }; } function lineStringToGeoJson(data, startIndex = -Infinity, endIndex = Infinity) { const { positions } = data; const pathIndices = data.pathIndices.value.filter((x) => x >= startIndex && x <= endIndex); const multi = pathIndices.length > 2; if (!multi) { const coordinates2 = ringToGeoJson(positions, pathIndices[0], pathIndices[1]); return { type: "LineString", coordinates: coordinates2 }; } const coordinates = []; for (let i = 0; i < pathIndices.length - 1; i++) { const ringCoordinates = ringToGeoJson(positions, pathIndices[i], pathIndices[i + 1]); coordinates.push(ringCoordinates); } return { type: "MultiLineString", coordinates }; } function pointToGeoJson(data, startIndex, endIndex) { const { positions } = data; const coordinates = ringToGeoJson(positions, startIndex, endIndex); const multi = coordinates.length > 1; if (multi) { return { type: "MultiPoint", coordinates }; } return { type: "Point", coordinates: coordinates[0] }; } function ringToGeoJson(positions, startIndex, endIndex) { startIndex = startIndex || 0; endIndex = endIndex || positions.value.length / positions.size; const ringCoordinates = []; for (let j = startIndex; j < endIndex; j++) { const coord = Array(); for (let k = j * positions.size; k < (j + 1) * positions.size; k++) { coord.push(Number(positions.value[k])); } ringCoordinates.push(coord); } return ringCoordinates; } // src/lib/parse-wkb-header.ts var EWKB_FLAG_Z = 2147483648; var EWKB_FLAG_M = 1073741824; var EWKB_FLAG_SRID = 536870912; var MAX_SRID = 1e4; function isWKB(arrayBuffer) { const dataView = new DataView(arrayBuffer); let byteOffset = 0; const endianness = dataView.getUint8(byteOffset); byteOffset += 1; if (endianness > 1) { return false; } const littleEndian = endianness === 1; const geometry = dataView.getUint32(byteOffset, littleEndian); byteOffset += 4; const geometryType = geometry & 7; if (geometryType === 0 || geometryType > 7) { return false; } const geometryFlags = geometry - geometryType; if (geometryFlags === 0 || geometryFlags === 1e3 || geometryFlags === 2e3 || geometryFlags === 3e3) { return true; } 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; } function parseWKBHeader(dataView, target) { const wkbHeader = Object.assign(target || {}, { type: "wkb", geometryType: 1, dimensions: 2, coordinates: "xy", littleEndian: true, byteOffset: 0 }); wkbHeader.littleEndian = dataView.getUint8(wkbHeader.byteOffset) === 1; wkbHeader.byteOffset++; const geometryCode = dataView.getUint32(wkbHeader.byteOffset, wkbHeader.littleEndian); wkbHeader.byteOffset += 4; wkbHeader.geometryType = geometryCode & 7; const isoType = (geometryCode - wkbHeader.geometryType) / 1e3; 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}`); } 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 (ewkbSRID) { wkbHeader.type = "ewkb"; wkbHeader.srid = dataView.getUint32(wkbHeader.byteOffset, wkbHeader.littleEndian); wkbHeader.byteOffset += 4; } return wkbHeader; } // src/lib/parse-wkb.ts 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": console.error('WKBLoader: "geometry" shape is deprecated, use "binary-geometry" instead'); return binaryToGeometry(binaryGeometry); default: throw new Error(shape); } } 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 1 /* Point */: const point = parsePoint2(dataView, offset, dimensions, littleEndian); return point.geometry; case 2 /* LineString */: const line = parseLineString2(dataView, offset, dimensions, littleEndian); return line.geometry; case 3 /* Polygon */: const polygon = parsePolygon2(dataView, offset, dimensions, littleEndian); return polygon.geometry; case 4 /* MultiPoint */: const multiPoint = parseMultiPoint2(dataView, offset, dimensions, littleEndian); multiPoint.type = "Point"; return multiPoint; case 5 /* MultiLineString */: const multiLine = parseMultiLineString2(dataView, offset, dimensions, littleEndian); multiLine.type = "LineString"; return multiLine; case 6 /* MultiPolygon */: const multiPolygon = parseMultiPolygon2(dataView, offset, dimensions, littleEndian); multiPolygon.type = "Polygon"; return multiPolygon; default: throw new Error(`WKB: Unsupported geometry type: ${geometryType}`); } } function parsePoint2(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 parseLineString2(dataView, offset, dimension, littleEndian) { const nPoints = dataView.getUint32(offset, littleEndian); offset += 4; 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 }; } var cumulativeSum = (sum) => (value) => sum += value; function parsePolygon2(dataView, offset, dimension, littleEndian) { const nRings = dataView.getUint32(offset, littleEndian); offset += 4; const rings = []; for (let i = 0; i < nRings; i++) { const parsed = parseLineString2(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 parseMultiPoint2(dataView, offset, dimension, littleEndian) { const nPoints = dataView.getUint32(offset, littleEndian); offset += 4; const binaryPointGeometries = []; for (let i = 0; i < nPoints; i++) { const littleEndianPoint = dataView.getUint8(offset) === 1; offset++; if (dataView.getUint32(offset, littleEndianPoint) % 1e3 !== 1) { throw new Error("WKB: Inner geometries of MultiPoint not of type Point"); } offset += 4; const parsed = parsePoint2(dataView, offset, dimension, littleEndianPoint); offset = parsed.offset; binaryPointGeometries.push(parsed.geometry); } return concatenateBinaryPointGeometries(binaryPointGeometries, dimension); } function parseMultiLineString2(dataView, offset, dimension, littleEndian) { const nLines = dataView.getUint32(offset, littleEndian); offset += 4; const binaryLineGeometries = []; for (let i = 0; i < nLines; i++) { const littleEndianLine = dataView.getUint8(offset) === 1; offset++; if (dataView.getUint32(offset, littleEndianLine) % 1e3 !== 2) { throw new Error("WKB: Inner geometries of MultiLineString not of type LineString"); } offset += 4; const parsed = parseLineString2(dataView, offset, dimension, littleEndianLine); offset = parsed.offset; binaryLineGeometries.push(parsed.geometry); } return concatenateBinaryLineGeometries(binaryLineGeometries, dimension); } function parseMultiPolygon2(dataView, offset, dimension, littleEndian) { const nPolygons = dataView.getUint32(offset, littleEndian); offset += 4; const binaryPolygonGeometries = []; for (let i = 0; i < nPolygons; i++) { const littleEndianPolygon = dataView.getUint8(offset) === 1; offset++; if (dataView.getUint32(offset, littleEndianPolygon) % 1e3 !== 3) { throw new Error("WKB: Inner geometries of MultiPolygon not of type Polygon"); } offset += 4; const parsed = parsePolygon2(dataView, offset, dimension, littleEndianPolygon); offset = parsed.offset; binaryPolygonGeometries.push(parsed.geometry); } return concatenateBinaryPolygonGeometries(binaryPolygonGeometries, dimension); } 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: primitivePolygonIndices2 } = binaryPolygon; polygons.push(positions.value); primitivePolygons.push(primitivePolygonIndices2.value); } const concatenatedPositions = new Float64Array(concatTypedArrays(polygons).buffer); const polygonIndices = polygons.map((p) => p.length / dimension).map(cumulativeSum(0)); polygonIndices.unshift(0); 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 } }; } 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; } // src/wkb-loader.ts var WKBWorkerLoader = { dataType: null, batchType: null, name: "WKB", id: "wkb", module: "wkt", version: VERSION, worker: true, category: "geometry", extensions: ["wkb"], mimeTypes: [], // TODO can we define static, serializable tests, eg. some binary strings? tests: [isWKB], options: { wkb: { shape: "binary-geometry" // 'geojson-geometry' } } }; var WKBLoader = { ...WKBWorkerLoader, parse: async (arrayBuffer) => parseWKB(arrayBuffer), parseSync: parseWKB }; // src/lib/utils/binary-writer.ts var LE = true; var BE = false; var BinaryWriter = class { arrayBuffer; dataView; byteOffset = 0; allowResize = false; constructor(size, allowResize) { this.arrayBuffer = new ArrayBuffer(size); this.dataView = new DataView(this.arrayBuffer); this.byteOffset = 0; this.allowResize = allowResize || false; } writeUInt8(value) { this._ensureSize(1); this.dataView.setUint8(this.byteOffset, value); this.byteOffset += 1; } writeUInt16LE(value) { this._ensureSize(2); this.dataView.setUint16(this.byteOffset, value, LE); this.byteOffset += 2; } writeUInt16BE(value) { this._ensureSize(2); this.dataView.setUint16(this.byteOffset, value, BE); this.byteOffset += 2; } writeUInt32LE(value) { this._ensureSize(4); this.dataView.setUint32(this.byteOffset, value, LE); this.byteOffset += 4; } writeUInt32BE(value) { this._ensureSize(4); this.dataView.setUint32(this.byteOffset, value, BE); this.byteOffset += 4; } writeInt8(value) { this._ensureSize(1); this.dataView.setInt8(this.byteOffset, value); this.byteOffset += 1; } writeInt16LE(value) { this._ensureSize(2); this.dataView.setInt16(this.byteOffset, value, LE); this.byteOffset += 2; } writeInt16BE(value) { this._ensureSize(2); this.dataView.setInt16(this.byteOffset, value, BE); this.byteOffset += 2; } writeInt32LE(value) { this._ensureSize(4); this.dataView.setInt32(this.byteOffset, value, LE); this.byteOffset += 4; } writeInt32BE(value) { this._ensureSize(4); this.dataView.setInt32(this.byteOffset, value, BE); this.byteOffset += 4; } writeFloatLE(value) { this._ensureSize(4); this.dataView.setFloat32(this.byteOffset, value, LE); this.byteOffset += 4; } writeFloatBE(value) { this._ensureSize(4); this.dataView.setFloat32(this.byteOffset, value, BE); this.byteOffset += 4; } writeDoubleLE(value) { this._ensureSize(8); this.dataView.setFloat64(this.byteOffset, value, LE); this.byteOffset += 8; } writeDoubleBE(value) { this._ensureSize(8); this.dataView.setFloat64(this.byteOffset, value, BE); this.byteOffset += 8; } /** A varint uses a variable number of bytes */ writeVarInt(value) { let length = 1; while ((value & 4294967168) !== 0) { this.writeUInt8(value & 127 | 128); value >>>= 7; length++; } this.writeUInt8(value & 127); return length; } /** Append another ArrayBuffer to this ArrayBuffer */ writeBuffer(arrayBuffer) { this._ensureSize(arrayBuffer.byteLength); const tempArray = new Uint8Array(this.arrayBuffer); tempArray.set(new Uint8Array(arrayBuffer), this.byteOffset); this.byteOffset += arrayBuffer.byteLength; } /** Resizes this.arrayBuffer if not enough space */ _ensureSize(size) { if (this.arrayBuffer.byteLength < this.byteOffset + size) { if (this.allowResize) { const newArrayBuffer = new ArrayBuffer(this.byteOffset + size); const tempArray = new Uint8Array(newArrayBuffer); tempArray.set(new Uint8Array(this.arrayBuffer)); this.arrayBuffer = newArrayBuffer; } else { throw new Error("BinaryWriter overflow"); } } } }; // src/lib/encode-wkb.ts function encodeWKB(geometry, options = {}) { if (geometry.type === "Feature") { geometry = geometry.geometry; } switch (geometry.type) { case "Point": return encodePoint(geometry.coordinates, options); case "LineString": return encodeLineString(geometry.coordinates, options); case "Polygon": return encodePolygon(geometry.coordinates, options); case "MultiPoint": return encodeMultiPoint(geometry, options); case "MultiPolygon": return encodeMultiPolygon(geometry, options); case "MultiLineString": return encodeMultiLineString(geometry, options); case "GeometryCollection": return encodeGeometryCollection(geometry, options); default: const exhaustiveCheck = geometry; throw new Error(`Unhandled case: ${exhaustiveCheck}`); } } function getGeometrySize(geometry, options) { switch (geometry.type) { case "Point": return getPointSize(options); case "LineString": return getLineStringSize(geometry.coordinates, options); case "Polygon": return getPolygonSize(geometry.coordinates, options); case "MultiPoint": return getMultiPointSize(geometry, options); case "MultiPolygon": return getMultiPolygonSize(geometry, options); case "MultiLineString": return getMultiLineStringSize(geometry, options); case "GeometryCollection": return getGeometryCollectionSize(geometry, options); default: const exhaustiveCheck = geometry; throw new Error(`Unhandled case: ${exhaustiveCheck}`); } } function encodePoint(coordinates, options) { const writer = new BinaryWriter(getPointSize(options)); writer.writeInt8(1); writeWkbType(writer, 1 /* Point */, options); if (typeof coordinates[0] === "undefined" && typeof coordinates[1] === "undefined") { writer.writeDoubleLE(NaN); writer.writeDoubleLE(NaN); if (options.hasZ) { writer.writeDoubleLE(NaN); } if (options.hasM) { writer.writeDoubleLE(NaN); } } else { writeCoordinate(writer, coordinates, options); } return writer.arrayBuffer; } function writeCoordinate(writer, coordinate, options) { writer.writeDoubleLE(coordinate[0]); writer.writeDoubleLE(coordinate[1]); if (options.hasZ) { writer.writeDoubleLE(coordinate[2]); } if (options.hasM) { writer.writeDoubleLE(coordinate[3]); } } function getPointSize(options) { const coordinateSize = getCoordinateSize(options); return 1 + 4 + coordinateSize; } function encodeLineString(coordinates, options) { const size = getLineStringSize(coordinates, options); const writer = new BinaryWriter(size); writer.writeInt8(1); writeWkbType(writer, 2 /* LineString */, options); writer.writeUInt32LE(coordinates.length); for (const coordinate of coordinates) { writeCoordinate(writer, coordinate, options); } return writer.arrayBuffer; } function getLineStringSize(coordinates, options) { const coordinateSize = getCoordinateSize(options); return 1 + 4 + 4 + coordinates.length * coordinateSize; } function encodePolygon(coordinates, options) { const writer = new BinaryWriter(getPolygonSize(coordinates, options)); writer.writeInt8(1); writeWkbType(writer, 3 /* Polygon */, options); const [exteriorRing, ...interiorRings] = coordinates; if (exteriorRing.length > 0) { writer.writeUInt32LE(1 + interiorRings.length); writer.writeUInt32LE(exteriorRing.length); } else { writer.writeUInt32LE(0); } for (const coordinate of exteriorRing) { writeCoordinate(writer, coordinate, options); } for (const interiorRing of interiorRings) { writer.writeUInt32LE(interiorRing.length); for (const coordinate of interiorRing) { writeCoordinate(writer, coordinate, options); } } return writer.arrayBuffer; } function getPolygonSize(coordinates, options) { const coordinateSize = getCoordinateSize(options); const [exteriorRing, ...interiorRings] = coordinates; let size = 1 + 4 + 4; if (exteriorRing.length > 0) { size += 4 + exteriorRing.length * coordinateSize; } for (const interiorRing of interiorRings) { size += 4 + interiorRing.length * coordinateSize; } return size; } function encodeMultiPoint(multiPoint, options) { const writer = new BinaryWriter(getMultiPointSize(multiPoint, options)); const points = multiPoint.coordinates; writer.writeInt8(1); writeWkbType(writer, 4 /* MultiPoint */, options); writer.writeUInt32LE(points.length); for (const point of points) { const arrayBuffer = encodePoint(point, options); writer.writeBuffer(arrayBuffer); } return writer.arrayBuffer; } function getMultiPointSize(multiPoint, options) { let coordinateSize = getCoordinateSize(options); const points = multiPoint.coordinates; coordinateSize += 5; return 1 + 4 + 4 + points.length * coordinateSize; } function encodeMultiLineString(multiLineString, options) { const writer = new BinaryWriter(getMultiLineStringSize(multiLineString, options)); const lineStrings = multiLineString.coordinates; writer.writeInt8(1); writeWkbType(writer, 5 /* MultiLineString */, options); writer.writeUInt32LE(lineStrings.length); for (const lineString of lineStrings) { const encodedLineString = encodeLineString(lineString, options); writer.writeBuffer(encodedLineString); } return writer.arrayBuffer; } function getMultiLineStringSize(multiLineString, options) { let size = 1 + 4 + 4; const lineStrings = multiLineString.coordinates; for (const lineString of lineStrings) { size += getLineStringSize(lineString, options); } return size; } function encodeMultiPolygon(multiPolygon, options) { const writer = new BinaryWriter(getMultiPolygonSize(multiPolygon, options)); const polygons = multiPolygon.coordinates; writer.writeInt8(1); writeWkbType(writer, 6 /* MultiPolygon */, options); writer.writeUInt32LE(polygons.length); for (const polygon of polygons) { const encodedPolygon = encodePolygon(polygon, options); writer.writeBuffer(encodedPolygon); } return writer.arrayBuffer; } function getMultiPolygonSize(multiPolygon, options) { let size = 1 + 4 + 4; const polygons = multiPolygon.coordinates; for (const polygon of polygons) { size += getPolygonSize(polygon, options); } return size; } function encodeGeometryCollection(collection, options) { const writer = new BinaryWriter(getGeometryCollectionSize(collection, options)); writer.writeInt8(1); writeWkbType(writer, 7 /* GeometryCollection */, options); writer.writeUInt32LE(collection.geometries.length); for (const geometry of collection.geometries) { const arrayBuffer = encodeWKB(geometry, options); writer.writeBuffer(arrayBuffer); } return writer.arrayBuffer; } function getGeometryCollectionSize(collection, options) { let size = 1 + 4 + 4; for (const geometry of collection.geometries) { size += getGeometrySize(geometry, options); } return size; } function writeWkbType(writer, geometryType, options) { const { hasZ, hasM, srid } = options; let dimensionType = 0; if (!srid) { if (hasZ && hasM) { dimensionType += 3e3; } else if (hasZ) { dimensionType += 1e3; } else if (hasM) { dimensionType += 2e3; } } else { if (hasZ) { dimensionType |= 2147483648; } if (hasM) { dimensionType |= 1073741824; } } writer.writeUInt32LE(dimensionType + geometryType >>> 0); } function getCoordinateSize(options) { let coordinateSize = 16; if (options.hasZ) { coordinateSize += 8; } if (options.hasM) { coordinateSize += 8; } return coordinateSize; } // src/wkb-writer.ts var WKBWriter = { name: "WKB (Well Known Binary)", id: "wkb", module: "wkt", version: VERSION, extensions: ["wkb"], options: { wkb: { hasZ: false, hasM: false } }, async encode(data, options) { return encodeWKB(data, options?.wkb); }, encodeSync(data, options) { return encodeWKB(data, options?.wkb); } }; // src/lib/utils/hex-transcoder.ts var alphabet = "0123456789abcdef"; var encodeLookup = []; var decodeLookup = []; for (let i = 0; i < 256; i++) { encodeLookup[i] = alphabet[i >> 4 & 15] + alphabet[i & 15]; if (i < 16) { if (i < 10) { decodeLookup[48 + i] = i; } else { decodeLookup[97 - 10 + i] = i; } } } function encodeHex(array) { const length = array.length; let string = ""; let i = 0; while (i < length) { string += encodeLookup[array[i++]]; } return string; } function decodeHex(string) { const sizeof = string.length >> 1; const length = sizeof << 1; const array = new Uint8Array(sizeof); let n = 0; let i = 0; while (i < length) { array[n++] = decodeLookup[string.charCodeAt(i++)] << 4 | decodeLookup[string.charCodeAt(i++)]; } return array; } // src/hex-wkb-loader.ts var HexWKBLoader = { dataType: null, batchType: null, name: "Hexadecimal WKB", id: "wkb", module: "wkt", version: VERSION, worker: true, category: "geometry", extensions: ["wkb"], mimeTypes: [], options: WKBLoader.options, text: true, testText: isHexWKB, // TODO - encoding here seems wasteful - extend hex transcoder? parse: async (arrayBuffer) => parseHexWKB(new TextDecoder().decode(arrayBuffer)), parseTextSync: parseHexWKB }; function parseHexWKB(text, options) { const uint8Array = decodeHex(text); const binaryGeometry = WKBLoader.parseSync?.(uint8Array.buffer, options); return binaryGeometry; } function isHexWKB(string) { if (!string) { return false; } if (string.length < 10 || string.length % 2 !== 0) { return false; } if (!string.startsWith("00") && !string.startsWith("01")) { return false; } return /^[0-9a-fA-F]+$/.test(string.slice(2)); } // src/lib/utils/binary-reader.ts var BinaryReader = class { arrayBuffer; dataView; byteOffset; littleEndian; constructor(arrayBuffer, isBigEndian = false) { this.arrayBuffer = arrayBuffer; this.dataView = new DataView(arrayBuffer); this.byteOffset = 0; this.littleEndian = !isBigEndian; } readUInt8() { const value = this.dataView.getUint8(this.byteOffset); this.byteOffset += 1; return value; } readUInt16() { const value = this.dataView.getUint16(this.byteOffset, this.littleEndian); this.byteOffset += 2; return value; } readUInt32() { const value = this.dataView.getUint32(this.byteOffset, this.littleEndian); this.byteOffset += 4; return value; } readInt8() { const value = this.dataView.getInt8(this.byteOffset); this.byteOffset += 1; return value; } readInt16() { const value = this.dataView.getInt16(this.byteOffset, this.littleEndian); this.byteOffset += 2; return value; } readInt32() { const value = this.dataView.getInt32(this.byteOffset, this.littleEndian); this.byteOffset += 4; return value; } readFloat() { const value = this.dataView.getFloat32(this.byteOffset, this.littleEndian); this.byteOffset += 4; return value; } readDouble() { const value = this.dataView.getFloat64(this.byteOffset, this.littleEndian); this.byteOffset += 8; return value; } readVarInt() { let result = 0; let bytesRead = 0; let nextByte; do { nextByte = this.dataView.getUint8(this.byteOffset + bytesRead); result += (nextByte & 127) << 7 * bytesRead; bytesRead++; } while (nextByte >= 128); this.byteOffset += bytesRead; return result; } }; // src/lib/parse-twkb.ts function isTWKB(arrayBuffer) { const binaryReader = new BinaryReader(arrayBuffer); const type = binaryReader.readUInt8(); const geometryType = type & 15; if (geometryType < 1 || geometryType > 7) { return false; } return true; } function parseTWKBGeometry(arrayBuffer) { const binaryReader = new BinaryReader(arrayBuffer); const context = parseTWKBHeader(binaryReader); if (context.hasSizeAttribute) { binaryReader.readVarInt(); } if (context.hasBoundingBox) { let dimensions = 2; if (context.hasZ) { dimensions++; } if (context.hasM) { dimensions++; } for (let i = 0; i < dimensions; i++) { binaryReader.readVarInt(); binaryReader.readVarInt(); } } return parseGeometry2(binaryReader, context, context.geometryType); } function parseTWKBHeader(binaryReader) { const type = binaryReader.readUInt8(); const metadataHeader = binaryReader.readUInt8(); const geometryType = type & 15; const precision = zigZagDecode(type >> 4); const hasExtendedPrecision = Boolean(metadataHeader >> 3 & 1); let hasZ = false; let hasM = false; let zPrecision = 0; let zPrecisionFactor = 1; let mPrecision = 0; let mPrecisionFactor = 1; if (hasExtendedPrecision) { const extendedPrecision = binaryReader.readUInt8(); hasZ = (extendedPrecision & 1) === 1; hasM = (extendedPrecision & 2) === 2; zPrecision = zigZagDecode((extendedPrecision & 28) >> 2); zPrecisionFactor = Math.pow(10, zPrecision); mPrecision = zigZagDecode((extendedPrecision & 224) >> 5); mPrecisionFactor = Math.pow(10, mPrecision); } return { geometryType, precision, precisionFactor: Math.pow(10, precision), hasBoundingBox: Boolean(metadataHeader >> 0 & 1),