twkb-parser
Version:
TWKB parser and serializer
906 lines (842 loc) • 26 kB
JavaScript
/* 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
}