UNPKG

twkb-parser

Version:
906 lines (842 loc) 26 kB
/* array utility */ function append (array1, array2) { const n = array2.length for (let i = 0; i < n; i++) { array1.push(array2[i]) } } function join (array1, array2) { const array3 = [] const n1 = array1.length for (let i = 0; i < n1; i++) array3.push(array1[i]) const n2 = array2.length for (let j = 0; j < n2; j++) array3.push(array2[j]) return array3 } function newArrayReader (array) { return { i: 0, data: array, next: function () { return this.data[this.i++] } } } /* base64 utility */ const B64 = { 0: 'A', 1: 'B', 2: 'C', 3: 'D', 4: 'E', 5: 'F', 6: 'G', 7: 'H', 8: 'I', 9: 'J', 10: 'K', 11: 'L', 12: 'M', 13: 'N', 14: 'O', 15: 'P', 16: 'Q', 17: 'R', 18: 'S', 19: 'T', 20: 'U', 21: 'V', 22: 'W', 23: 'X', 24: 'Y', 25: 'Z', 26: 'a', 27: 'b', 28: 'c', 29: 'd', 30: 'e', 31: 'f', 32: 'g', 33: 'h', 34: 'i', 35: 'j', 36: 'k', 37: 'l', 38: 'm', 39: 'n', 40: 'o', 41: 'p', 42: 'q', 43: 'r', 44: 's', 45: 't', 46: 'u', 47: 'v', 48: 'w', 49: 'x', 50: 'y', 51: 'z', 52: '0', 53: '1', 54: '2', 55: '3', 56: '4', 57: '5', 58: '6', 59: '7', 60: '8', 61: '9', 62: '+', 63: '/' } const C64 = { A: 0, B: 1, C: 2, D: 3, E: 4, F: 5, G: 6, H: 7, I: 8, J: 9, K: 10, L: 11, M: 12, N: 13, O: 14, P: 15, Q: 16, R: 17, S: 18, T: 19, U: 20, V: 21, W: 22, X: 23, Y: 24, Z: 25, a: 26, b: 27, c: 28, d: 29, e: 30, f: 31, g: 32, h: 33, i: 34, j: 35, k: 36, l: 37, m: 38, n: 39, o: 40, p: 41, q: 42, r: 43, s: 44, t: 45, u: 46, v: 47, w: 48, x: 49, y: 50, z: 51, 0: 52, 1: 53, 2: 54, 3: 55, 4: 56, 5: 57, 6: 58, 7: 59, 8: 60, 9: 61, '+': 62, '/': 63 } function encodeBase64 (bytes) { let string = '' const length = bytes.length let i = 0 let b = 0 while (i < length - 2) { b = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2] string += B64[b >> 18] + B64[(b >> 12) & 63] + B64[(b >> 6) & 63] + B64[b & 63] i += 3 } const r = length - i if (r === 1) { b = bytes[i] << 4 string += B64[b >> 6] + B64[b & 63] + '==' } else if (r === 2) { b = (bytes[i] << 10) | (bytes[i + 1] << 2) string += B64[b >> 12] + B64[(b >> 6) & 63] + B64[b & 63] + '=' } return string } function decodeBase64 (string) { const bytes = [] const length = string.length let i = 0 let b = 0 let c1, c2, c3, c4 while (i < length - 4) { c1 = C64[string[i]] c2 = C64[string[i + 1]] c3 = C64[string[i + 2]] c4 = C64[string[i + 3]] b = (c1 << 18) | (c2 << 12) | (c3 << 6) | c4 bytes.push(b >> 16) bytes.push((b >> 8) & 255) bytes.push(b & 255) i += 4 } const r = length - i if (r > 0) { c1 = C64[string[i]] c2 = C64[string[i + 1]] c3 = C64[string[i + 2]] c4 = C64[string[i + 3]] bytes.push((c1 << 2) | (c2 >> 4)) if (c3 !== undefined) { bytes.push(((c2 & 15) << 4) | (c3 >> 2)) if (c4 !== undefined) { bytes.push(((c3 & 3) << 6) | c4) } } } return bytes } /* VLQ utility */ function round (x) { return x < 0 ? -Math.round(-x) : Math.round(x) } function zigzag (int) { return (int < 0) ? (-2 * int - 1) : (2 * int) } function zagzig (int) { return (int & 1) ? ((-int - 1) / 2) : (int / 2) } function int2bytes (int, unsigned = false) { const bytes = [] const k = unsigned ? int : (int < 0) ? (-2 * int - 1) : (2 * int) if (k < 4294967296) { let lo = k while (lo > 127) { bytes.push((lo & 127) | 128) lo >>>= 7 } bytes.push(lo) } else { let lo = k >>> 0 let hi = k / 4294967296 | 0 for (let i = 0; i < 5; i++) { bytes.push(lo & 127 | 128) lo >>>= 7 } while (hi > 127) { bytes.push((hi & 127) | 128) hi >>>= 7 } bytes.push(hi) } return bytes } function bytes2int (reader, unsigned = false) { const bytes = [] let byte = 255 let length = 0 while (byte > 127) { byte = reader.next() bytes.push(byte) length++ } let k = 0 if (length < 6) { let i = 0 let lo = 0 let shift = 0 while (i < length) { lo += (bytes[i] & 127) << shift shift += 7 i++ } k = lo >>> 0 } else { let i = 0 let lo = 0 let hi = 0 let shift = 0 while (i < 5) { lo += (bytes[i] & 127) << shift shift += 7 i++ } shift = 0 while (i < length) { hi += (bytes[i] & 127) << shift shift += 7 i++ } k = (lo >>> 0) + (hi >>> 0) * 4294967296 } return unsigned ? k : (k & 1) ? ((-k - 1) / 2) : (k / 2) } /* geometry encoding */ function encodeSingleCoordinates (coordinates, previous, factors, dimension) { const buffer = [] for (let i = 0; i < dimension; i++) { const delta = round(coordinates[i] * factors[i]) - previous[i] append(buffer, int2bytes(delta)) previous[i] += delta } return buffer } function encodeCoordinatesArray (array, previous, factors, dimension, minPointCount) { const buffer = [] const delta = [0, 0, 0, 0] const n = array.length let count = 0 let maxPointLeft = n // return early for empty coordinates array if (n === 0) return [0] // prepare size byte if (n < 128) buffer.push(0) // encode all coordinates for (let i = 0; i < n; i++) { const coordinates = array[i] // get delta between current coordinates and previous coordinates let diff = 0 for (let j = 0; j < dimension; j++) { delta[j] = round(coordinates[j] * factors[j]) - previous[j] diff += Math.abs(delta[j]) } // skip equal position, except for first point, and respect minimum point count if (diff === 0 && i > 0 && maxPointLeft > minPointCount) { maxPointLeft -= 1 continue } // register delta values count += 1 for (let j = 0; j < dimension; j++) { append(buffer, int2bytes(delta[j])) previous[j] += delta[j] } } // encode size byte if (n < 128) { // no need to encode size because vlq(x) = x for small values buffer[0] = count return buffer } else { return join(int2bytes(count, true), buffer) } } function encodePoint (geojson, previous, factors, dimension) { const point = geojson.coordinates return encodeSingleCoordinates(point, previous, factors, dimension) } function encodeLineString (geojson, previous, factors, dimension) { const linestring = geojson.coordinates return encodeCoordinatesArray(linestring, previous, factors, dimension, 2) } function encodePolygon (geojson, previous, factors, dimension) { const buffer = [] const polygon = geojson.coordinates const n = polygon.length append(buffer, int2bytes(n, true)) for (let i = 0; i < n; i++) { const ring = polygon[i] append(buffer, encodeCoordinatesArray(ring, previous, factors, dimension, 4)) } return buffer } /* chek ID encoding / decoding before deleting this function function encodeMultiPoint(geojson, previous, factors, dimension, ids) { const buffer = []; const multipoint = geojson.coordinates; const n = multipoint.length; // encode coordinates in separate buffer const coordinatesBuffer = []; let count = 0; for (let i = 0; i < n; i++) { const point = multipoint[i]; // skip empty point if (point.length === 0) continue; append(coordinatesBuffer, encodeSingleCoordinates(point, previous, factors, dimension)); count += 1; } // encode coordinates count append(buffer, int2bytes(count, true)); // encode id list if (ids !== undefined) { const idListBuffer = encodeIdList(ids, n); append(buffer, idListBuffer); } append(buffer, coordinatesBuffer); return buffer; } */ function encodeMultiPoint (geojson, previous, factors, dimension, ids) { const buffer = [] const multipoint = geojson.coordinates const n = multipoint.length let count = 0 // encode coordinates in dedicated buffer const coordinatesBuffer = [] // encode ids in dedicated buffer const encodeId = ids !== undefined const idListBuffer = [] // process all points for (let i = 0; i < n; i++) { const point = multipoint[i] // skip empty point if (point.length === 0) continue count += 1 // encode id if necessary if (encodeId) append(idListBuffer, int2bytes(ids[i])) // encode coordinates append(coordinatesBuffer, encodeSingleCoordinates(point, previous, factors, dimension)) } // encode coordinates count append(buffer, int2bytes(count, true)) // append ids buffer if (encodeId) append(buffer, idListBuffer) // append coordinates buffer append(buffer, coordinatesBuffer) return buffer } function encodeMultiLineString (geojson, previous, factors, dimension, ids) { const buffer = [] const multilinestring = geojson.coordinates const n = multilinestring.length // encode coordinates count append(buffer, int2bytes(n, true)) // encode id list if (ids !== undefined) { const idListBuffer = encodeIdList(ids, n) append(buffer, idListBuffer) } // encode coordinates for (let i = 0; i < n; i++) { const linestring = multilinestring[i] append(buffer, encodeCoordinatesArray(linestring, previous, factors, dimension)) } return buffer } function encodeMultiPolygon (geojson, previous, factors, dimension, ids) { const buffer = [] const multipolygon = geojson.coordinates const n = multipolygon.length // encode coordinates count append(buffer, int2bytes(n, true)) // encode id list if (ids !== undefined) { const idListBuffer = encodeIdList(ids, n) append(buffer, idListBuffer) } // encode coordinates for (let i = 0; i < n; i++) { const polygon = multipolygon[i] const m = polygon.length append(buffer, int2bytes(m, true)) for (let j = 0; j < m; j++) { const ring = polygon[j] append(buffer, encodeCoordinatesArray(ring, previous, factors, dimension)) } } return buffer } function encodeGeometryCollection (geojson, params, ids) { const buffer = [] const geometries = geojson.geometries const n = geometries.length // encode geometry count append(buffer, int2bytes(n, true)) // encode id list if (ids !== undefined) { const idListBuffer = encodeIdList(ids, n) append(buffer, idListBuffer) } // encode inner geometries for (let i = 0; i < n; i++) { append(buffer, encodeGeoJSON(geometries[i], params)) } return buffer } function encodeGeometry (geojson, params, previous, factors, dimension, ids) { const type = geojson.type if (type === 'Point') return encodePoint(geojson, previous, factors, dimension) if (type === 'LineString') return encodeLineString(geojson, previous, factors, dimension) if (type === 'Polygon') return encodePolygon(geojson, previous, factors, dimension) if (type === 'MultiPoint') return encodeMultiPoint(geojson, previous, factors, dimension, ids) if (type === 'MultiLineString') return encodeMultiLineString(geojson, previous, factors, dimension, ids) if (type === 'MultiPolygon') return encodeMultiPolygon(geojson, previous, factors, dimension, ids) if (type === 'GeometryCollection') return encodeGeometryCollection(geojson, params, ids) return undefined } /* geometry decoding */ function decodeSingleCoordinates (reader, previous, factors, dimension) { const coordinates = [] for (let i = 0; i < dimension; i++) { const delta = bytes2int(reader) const p = (previous[i] + delta) / factors[i] coordinates.push(p) previous[i] += delta } return coordinates } function decodeCoordinatesArray (reader, previous, factors, dimension) { const array = [] const n = bytes2int(reader, true) // decode coordinates for (let i = 0; i < n; i++) { const coordinates = [] for (let j = 0; j < dimension; j++) { const delta = bytes2int(reader) const p = (previous[j] + delta) / factors[j] coordinates.push(p) previous[j] += delta } array.push(coordinates) } return array } function closePolygonRing (array, dimension) { const n = array.length if (n < 3) return undefined const first = array[0] const last = array[n - 1] const copy = [] let closed = true for (let i = 0; i < dimension; i++) { copy.push(first[i]) if (first[i] !== last[i]) closed = false } if (!closed) { array.push(copy) } } function decodePoint (reader, previous, factors, dimension) { const point = decodeSingleCoordinates(reader, previous, factors, dimension) return point } function decodeLineString (reader, previous, factors, dimension) { const linestring = decodeCoordinatesArray(reader, previous, factors, dimension) return linestring } function decodePolygon (reader, previous, factors, dimension) { const polygon = [] const n = bytes2int(reader, true) for (let i = 0; i < n; i++) { const ring = decodeCoordinatesArray(reader, previous, factors, dimension) closePolygonRing(ring, dimension) polygon.push(ring) } return polygon } function decodeMultiPoint (reader, previous, factors, dimension, includeIds) { const multipoint = [] // decode coordinates count const n = bytes2int(reader, true) // decode & ignore id list if (includeIds) { decodeIdList(reader, n) } // decode coordinates for (let i = 0; i < n; i++) { const point = decodeSingleCoordinates(reader, previous, factors, dimension) multipoint.push(point) } return multipoint } function decodeMultiLineString (reader, previous, factors, dimension, includeIds) { const multilinestring = [] // decode coordinates count const n = bytes2int(reader, true) // decode & ignore id list if (includeIds) { decodeIdList(reader, n) } // decode coordinates for (let i = 0; i < n; i++) { const linestring = decodeCoordinatesArray(reader, previous, factors, dimension) multilinestring.push(linestring) } return multilinestring } function decodeMultiPolygon (reader, previous, factors, dimension, includeIds) { const multipolygon = [] // decode coordinates count const n = bytes2int(reader, true) // decode & ignore id list if (includeIds) { decodeIdList(reader, n) } // decode coordinates for (let i = 0; i < n; i++) { const polygon = [] const m = bytes2int(reader, true) for (let j = 0; j < m; j++) { const ring = decodeCoordinatesArray(reader, previous, factors, dimension) closePolygonRing(ring, dimension) polygon.push(ring) } multipolygon.push(polygon) } return multipolygon } function decodeGeometryCollection (reader, includeIds) { const collection = [] // decode geometry count const n = bytes2int(reader, true) // decode & ignore id list if (includeIds) { decodeIdList(reader, n) } // decode inner geometries for (let i = 0; i < n; i++) { const geometry = decodeGeoJSON(reader) collection.push(geometry) } return collection } function decodeGeometry (reader, type, previous, factors, dimension, includeIds) { if (type === 'Point') return decodePoint(reader, previous, factors, dimension) if (type === 'LineString') return decodeLineString(reader, previous, factors, dimension) if (type === 'Polygon') return decodePolygon(reader, previous, factors, dimension) if (type === 'MultiPoint') return decodeMultiPoint(reader, previous, factors, dimension, includeIds) if (type === 'MultiLineString') return decodeMultiLineString(reader, previous, factors, dimension, includeIds) if (type === 'MultiPolygon') return decodeMultiPolygon(reader, previous, factors, dimension, includeIds) if (type === 'GeometryCollection') return decodeGeometryCollection(reader, includeIds) return undefined } /* TWKB metadata */ function encodeIdList (ids, count) { const buffer = [] const n = ids.length if (n !== count) throw new Error(`invalid ID list: id count (${n}) does not match geometry count (${count})`) for (let i = 0; i < n; i++) { const id = ids[i] append(buffer, int2bytes(id)) } return buffer } function decodeIdList (reader, count) { const ids = [] for (let i = 0; i < count; i++) { const id = bytes2int(reader) ids.push(id) } return ids } function encodeBoundingBox (bbox, factors, dimension) { const buffer = [] if (!Array.isArray(bbox) || bbox.length !== dimension * 2) throw new Error('invalid bounding box: ' + bbox) for (let i = 0; i < dimension; i++) { const min = Math.round(bbox[i] * factors[i]) const max = Math.round(bbox[i + dimension] * factors[i]) const delta = max - min append(buffer, int2bytes(min)) append(buffer, int2bytes(delta)) } return buffer } function decodeBoundingBox (reader, factors, dimension) { const bbox = [] for (let i = 0; i < dimension * 2; i++) bbox.push(0) for (let i = 0; i < dimension; i++) { const min = bytes2int(reader) const delta = bytes2int(reader) const max = min + delta bbox[i] = min / factors[i] bbox[i + dimension] = max / factors[i] } return bbox } function encodeSizeInfo (bboxBufferSize, geometryBufferSize) { const totalSize = bboxBufferSize + geometryBufferSize const bytes = int2bytes(totalSize, true) return bytes } function decodeSizeInfo (reader) { const size = bytes2int(reader, true) return size } function detectEmpty (geojson) { if (geojson.type === 'GeometryCollection') { return geojson.geometries.length === 0 } else { return geojson.coordinates.length === 0 } } function detectDimension (geojson) { const type = geojson.type if (type === 'GeometryCollection') { let dimension = 0 for (let i = 0; i < geojson.geometries.length; i++) { const d = detectDimension(geojson.geometries[i]) if (d > dimension) dimension = d } return dimension } else { const coordinates = geojson.coordinates if (type === 'Point') return coordinates.length if (type === 'LineString') return coordinates[0].length if (type === 'Polygon') return coordinates[0][0].length if (type === 'MultiPoint') { for (let i = 0; i < coordinates.length; i++) { if (coordinates[i].length !== 0) { return coordinates[i].length } } return 0 } if (type === 'MultiLineString') { for (let i = 0; i < coordinates.length; i++) { if (coordinates[i].length !== 0) { return coordinates[i][0].length } } return 0 } if (type === 'MultiPolygon') { for (let i = 0; i < coordinates.length; i++) { if (coordinates[i].length !== 0) { return coordinates[i][0][0].length } } return 0 } } return 0 } /* TWKB main */ const GEOMETRY_CODE = { Point: 1, LineString: 2, Polygon: 3, MultiPoint: 4, MultiLineString: 5, MultiPolygon: 6, GeometryCollection: 7 } const GEOMETRY_NAME = { 1: 'Point', 2: 'LineString', 3: 'Polygon', 4: 'MultiPoint', 5: 'MultiLineString', 6: 'MultiPolygon', 7: 'GeometryCollection' } function encodeGeoJSON (geojson, params) { const buffer = [] // get type & precision const type = geojson.type const code = GEOMETRY_CODE[type] if (!code) throw new Error('unknow geometry type: ' + type) const precisionXY = params.precisionXY const precisionZ = params.precisionZ const precisionM = params.precisionM // write type & precision byte const headerByte = code | (zigzag(precisionXY) << 4) buffer.push(headerByte) // get metadata let isEmpty = detectEmpty(geojson) const dimension = isEmpty ? 0 : detectDimension(geojson) if (dimension === 0) isEmpty = true if (dimension === 1 || dimension > 4) { throw new Error('invalid coordinates dimension: ' + dimension) } const hasZ = dimension > 2 const hasM = dimension > 3 let ids if (params.ids !== undefined) { if (code < 4) throw new Error('invalid ID list: not supported on simple geometry') ids = params.ids // reset ID list to make sure it is encoded only once params.ids = undefined } const factorXY = Math.pow(10, precisionXY) const factorZ = Math.pow(10, precisionZ) const factorM = Math.pow(10, precisionM) const factors = [factorXY, factorXY, factorZ, factorM] // write metadata byte let metadataByte = 0 if (params.includeBbox) metadataByte |= 1 if (params.includeSize) metadataByte |= 2 if (ids !== undefined) metadataByte |= 4 if (hasZ) metadataByte |= 8 if (isEmpty) metadataByte |= 16 buffer.push(metadataByte) // write extended dimension byte if (hasZ) { let dimensionByte = 0 dimensionByte |= 1 dimensionByte |= params.precisionZ << 2 if (hasM) { dimensionByte |= 2 dimensionByte |= params.precisionM << 5 } buffer.push(dimensionByte) } // return early if geometry is empty if (isEmpty) { if (params.includeSize) { append(buffer, encodeSizeInfo(0, 0)) } return buffer } // get geometry bytes const previous = [0, 0, 0, 0] const geometryBuffer = encodeGeometry(geojson, params, previous, factors, dimension, ids) // get bounding box bytes let bboxBuffer = [] if (params.includeBbox) { const bbox = geojson.bbox bboxBuffer = encodeBoundingBox(bbox, factors, dimension) } // write size bytes if (params.includeSize) { const sizeBuffer = encodeSizeInfo(bboxBuffer.length, geometryBuffer.length) append(buffer, sizeBuffer) } // write bounding box bytes if (params.includeBbox) { append(buffer, bboxBuffer) } // write geometry bytes append(buffer, geometryBuffer) return buffer } function decodeGeoJSON (reader) { const geojson = { type: '' } // read type & precision byte const headerByte = reader.next() const code = headerByte & 15 const type = GEOMETRY_NAME[code] geojson.type = type const precisionXY = zagzig(headerByte >> 4) // read metadata byte const metadataByte = reader.next() const isEmpty = (metadataByte & 16) !== 0 const hasExtraDimension = (metadataByte & 8) !== 0 const includeIds = (metadataByte & 4) !== 0 const includeSize = (metadataByte & 2) !== 0 const includeBbox = (metadataByte & 1) !== 0 // read extended dimension byte let hasZ = false let precisionZ = 0 let hasM = false let precisionM = 0 if (hasExtraDimension) { const dimensionByte = reader.next() hasZ = (dimensionByte & 1) !== 0 if (hasZ) { precisionZ = (dimensionByte >> 2) & 7 } hasM = (dimensionByte & 2) !== 0 if (hasM) { precisionM = (dimensionByte >> 5) & 7 } } // return early if geometry is empty if (isEmpty) { if (type === 'GeometryCollection') { geojson.geometries = [] } else { geojson.coordinates = [] } return geojson } // get metadata const dimension = hasM ? 4 : hasZ ? 3 : 2 const factorXY = Math.pow(10, precisionXY) const factorZ = Math.pow(10, precisionZ) const factorM = Math.pow(10, precisionM) const factors = [factorXY, factorXY, factorZ, factorM] // read & ignore size if (includeSize) { const _size = decodeSizeInfo(reader) } // read bounding box if (includeBbox) { const bbox = decodeBoundingBox(reader, factors, dimension) geojson.bbox = bbox } // read geometry const previous = [0, 0, 0, 0] const data = decodeGeometry(reader, type, previous, factors, dimension, includeIds) if (type === 'GeometryCollection') { geojson.geometries = data } else { geojson.coordinates = data } return geojson } const DEFAULT_PRECISION = 6 const DEFAULT_ENCODING_PARAMETERS = { precisionM: 0, precisionZ: 0, includeBbox: false, includeSize: false, ids: undefined } function createEncodingParams (precision, parameters) { const precisionXY = precision if (!Number.isInteger(precisionXY) || precisionXY < -7 || precisionXY > 7) throw new Error('invalid XY precision: ' + precisionXY) let precisionZ = 0 if (parameters.precisionZ !== undefined) { precisionZ = parameters.precisionZ if (!Number.isInteger(precisionZ) || precisionZ < 0 || precisionZ > 7) throw new Error('invalid Z precision: ' + precisionZ) } let precisionM = 0 if (parameters.precisionM !== undefined) { precisionM = parameters.precisionM if (!Number.isInteger(precisionM) || precisionM < 0 || precisionM > 7) throw new Error('invalid M precision: ' + precisionM) } const includeBbox = Boolean(parameters.includeBbox) const includeSize = Boolean(parameters.includeSize) let ids if (parameters.ids !== undefined) { ids = parameters.ids if (!Array.isArray(ids)) throw new Error('invalid ID list: not an array') } return { precisionXY, precisionZ, precisionM, includeBbox, includeSize, ids } } function geojson2twkb (geojson, precision, parameters) { const params = createEncodingParams(precision, parameters) const array = encodeGeoJSON(geojson, params) return array } function twkb2geojson (array) { const reader = newArrayReader(array) const json = decodeGeoJSON(reader) return json } function detectNodeJS() { return (typeof process !== 'undefined') && !!process.versions && !!process.versions.node } const IS_NODE = detectNodeJS() const fromGeoJSON = (geojson, precision = DEFAULT_PRECISION, parameters = DEFAULT_ENCODING_PARAMETERS) => { const array = geojson2twkb(geojson, precision, parameters) return { toArray: () => { return array }, toByteArray: () => { return new Uint8Array(array) }, toBase64: () => { return IS_NODE ? Buffer.from(array).toString('base64') : encodeBase64(array) } } } const fromArray = (array) => { return { toGeoJSON: () => { return twkb2geojson(array) }, toBase64: () => { return IS_NODE ? Buffer.from(array).toString('base64') : encodeBase64(array) } } } const fromBase64 = (base64) => { const array = IS_NODE ? Buffer.from(base64, 'base64') : decodeBase64(base64) return { toGeoJSON: () => { return twkb2geojson(array) }, toArray: () => { return IS_NODE ? Array.from(array) : array }, toByteArray: () => { return IS_NODE ? array : new Uint8Array(array) } } } export const TWKB = { fromGeoJSON, fromArray, fromBase64 }