UNPKG

twkb-parser

Version:
777 lines (775 loc) 25.9 kB
var TWKB = (() => { var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; 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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/twkb-parser.js var twkb_parser_exports = {}; __export(twkb_parser_exports, { TWKB: () => TWKB }); 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++]; } }; } var 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: "/" }; var 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 !== void 0) { bytes.push((c2 & 15) << 4 | c3 >> 2); if (c4 !== void 0) { bytes.push((c3 & 3) << 6 | c4); } } } return bytes; } 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; } 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; if (n === 0) return [0]; if (n < 128) buffer.push(0); for (let i = 0; i < n; i++) { const coordinates = array[i]; let diff = 0; for (let j = 0; j < dimension; j++) { delta[j] = round(coordinates[j] * factors[j]) - previous[j]; diff += Math.abs(delta[j]); } if (diff === 0 && i > 0 && maxPointLeft > minPointCount) { maxPointLeft -= 1; continue; } count += 1; for (let j = 0; j < dimension; j++) { append(buffer, int2bytes(delta[j])); previous[j] += delta[j]; } } if (n < 128) { 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; } function encodeMultiPoint(geojson, previous, factors, dimension, ids) { const buffer = []; const multipoint = geojson.coordinates; const n = multipoint.length; let count = 0; const coordinatesBuffer = []; const encodeId = ids !== void 0; const idListBuffer = []; for (let i = 0; i < n; i++) { const point = multipoint[i]; if (point.length === 0) continue; count += 1; if (encodeId) append(idListBuffer, int2bytes(ids[i])); append(coordinatesBuffer, encodeSingleCoordinates(point, previous, factors, dimension)); } append(buffer, int2bytes(count, true)); if (encodeId) append(buffer, idListBuffer); append(buffer, coordinatesBuffer); return buffer; } function encodeMultiLineString(geojson, previous, factors, dimension, ids) { const buffer = []; const multilinestring = geojson.coordinates; const n = multilinestring.length; append(buffer, int2bytes(n, true)); if (ids !== void 0) { const idListBuffer = encodeIdList(ids, n); append(buffer, idListBuffer); } 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; append(buffer, int2bytes(n, true)); if (ids !== void 0) { const idListBuffer = encodeIdList(ids, n); append(buffer, idListBuffer); } 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; append(buffer, int2bytes(n, true)); if (ids !== void 0) { const idListBuffer = encodeIdList(ids, n); append(buffer, idListBuffer); } 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 void 0; } 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); 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 void 0; 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 = []; const n = bytes2int(reader, true); if (includeIds) { decodeIdList(reader, n); } 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 = []; const n = bytes2int(reader, true); if (includeIds) { decodeIdList(reader, n); } 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 = []; const n = bytes2int(reader, true); if (includeIds) { decodeIdList(reader, n); } 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 = []; const n = bytes2int(reader, true); if (includeIds) { decodeIdList(reader, n); } 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 void 0; } 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; } var GEOMETRY_CODE = { Point: 1, LineString: 2, Polygon: 3, MultiPoint: 4, MultiLineString: 5, MultiPolygon: 6, GeometryCollection: 7 }; var GEOMETRY_NAME = { 1: "Point", 2: "LineString", 3: "Polygon", 4: "MultiPoint", 5: "MultiLineString", 6: "MultiPolygon", 7: "GeometryCollection" }; function encodeGeoJSON(geojson, params) { const buffer = []; 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; const headerByte = code | zigzag(precisionXY) << 4; buffer.push(headerByte); 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 !== void 0) { if (code < 4) throw new Error("invalid ID list: not supported on simple geometry"); ids = params.ids; params.ids = void 0; } const factorXY = Math.pow(10, precisionXY); const factorZ = Math.pow(10, precisionZ); const factorM = Math.pow(10, precisionM); const factors = [factorXY, factorXY, factorZ, factorM]; let metadataByte = 0; if (params.includeBbox) metadataByte |= 1; if (params.includeSize) metadataByte |= 2; if (ids !== void 0) metadataByte |= 4; if (hasZ) metadataByte |= 8; if (isEmpty) metadataByte |= 16; buffer.push(metadataByte); if (hasZ) { let dimensionByte = 0; dimensionByte |= 1; dimensionByte |= params.precisionZ << 2; if (hasM) { dimensionByte |= 2; dimensionByte |= params.precisionM << 5; } buffer.push(dimensionByte); } if (isEmpty) { if (params.includeSize) { append(buffer, encodeSizeInfo(0, 0)); } return buffer; } const previous = [0, 0, 0, 0]; const geometryBuffer = encodeGeometry(geojson, params, previous, factors, dimension, ids); let bboxBuffer = []; if (params.includeBbox) { const bbox = geojson.bbox; bboxBuffer = encodeBoundingBox(bbox, factors, dimension); } if (params.includeSize) { const sizeBuffer = encodeSizeInfo(bboxBuffer.length, geometryBuffer.length); append(buffer, sizeBuffer); } if (params.includeBbox) { append(buffer, bboxBuffer); } append(buffer, geometryBuffer); return buffer; } function decodeGeoJSON(reader) { const geojson = { type: "" }; const headerByte = reader.next(); const code = headerByte & 15; const type = GEOMETRY_NAME[code]; geojson.type = type; const precisionXY = zagzig(headerByte >> 4); 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; 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; } } if (isEmpty) { if (type === "GeometryCollection") { geojson.geometries = []; } else { geojson.coordinates = []; } return geojson; } 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]; if (includeSize) { const _size = decodeSizeInfo(reader); } if (includeBbox) { const bbox = decodeBoundingBox(reader, factors, dimension); geojson.bbox = bbox; } 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; } var DEFAULT_PRECISION = 6; var DEFAULT_ENCODING_PARAMETERS = { precisionM: 0, precisionZ: 0, includeBbox: false, includeSize: false, ids: void 0 }; 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 !== void 0) { precisionZ = parameters.precisionZ; if (!Number.isInteger(precisionZ) || precisionZ < 0 || precisionZ > 7) throw new Error("invalid Z precision: " + precisionZ); } let precisionM = 0; if (parameters.precisionM !== void 0) { 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 !== void 0) { 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; } var IS_NODE = detectNodeJS(); var 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); } }; }; var fromArray = (array) => { return { toGeoJSON: () => { return twkb2geojson(array); }, toBase64: () => { return IS_NODE ? Buffer.from(array).toString("base64") : encodeBase64(array); } }; }; var 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); } }; }; var TWKB = { fromGeoJSON, fromArray, fromBase64 }; return __toCommonJS(twkb_parser_exports); })();