/** { "api":1, "name":"Well-Known Text to Binary", "description":"Converts your WKT to little endian WKB (hex encoded), wkt2wkb", "author":"Mikael Brassman (Twitter: @spoike)", "icon":"globe", "tags":"wkb,convert,wkt,binary,little endian,hex,wkt2wkb" } **/ const re = /(?:(?:MULTI)?POINT|(?:MULTI)?LINESTRING|(?:MULTI)?POLYGON)\s*\([()0-9\s,\.\s]+\)/g; function main(input) { try { input.text = input.text.replace(re, convert); } catch (err) { input.postError(err); return; } } function convert(text) { const littleEndian = true; let tokens = []; for (let i = 0; i < text.length; i++) { tokenize(tokens, text[i]); } tokens = tokens.filter(Boolean); let output = ""; while (tokens.length > 0) { const token = tokens.shift(); if (tokens.shift() !== "(") { throw token + "is missing ("; } switch (token) { case "POINT": output += handlePoint(tokens, littleEndian); break; case "LINESTRING": output += handleLineString(tokens, littleEndian); break; case "POLYGON": output += handlePolygon( getParensSlice(tokens, "POLYGON", false), littleEndian ); break; case "MULTIPOINT": output += handleMultipoint(tokens, littleEndian); break; case "MULTILINESTRING": output += handleMultilinestring(tokens, littleEndian); break; case "MULTIPOLYGON": output += handleMultipolygon(tokens, littleEndian); break; default: throw "Unrecognized token " + token; } if (tokens.shift() !== ")") { throw token + " is missing )"; } } return output; } function tokenize(memo, char) { if (memo.length === 0) { memo.push(char); return; } if (/[A-Z0-9\.\-]/.test(char)) { memo[memo.length - 1] = memo[memo.length - 1] + char; } else if (/[()]/.test(char)) { memo.push(char); memo.push(""); } else { memo.push(""); } } function handlePoint(arr, littleEndian) { let out = toByteOrder(littleEndian) + toUint32(1, littleEndian); out += handleDouble(arr.shift(), littleEndian); out += handleDouble(arr.shift(), littleEndian); return out; } function handleLineString(arr, littleEndian) { let out = toByteOrder(littleEndian) + toUint32(2, littleEndian); const slice = getParensSlice(arr, "LINESTRING", true); const pairs = Math.floor(slice.length / 2); out += toUint32(pairs, littleEndian); for (const token of slice) { out += handleDouble(token, littleEndian); } return out; } function handlePolygon(rings, littleEndian) { let out = toByteOrder(littleEndian) + toUint32(3, littleEndian); out += toUint32(rings.length, littleEndian); for (let ring of rings) { out += handleRing(ring, littleEndian); } return out; } function handleMultipoint(arr, littleEndian) { let out = toByteOrder(littleEndian) + toUint32(4, littleEndian); const slice = getParensSlice(arr, "MULTIPOINT", true); const pairs = slice.length / 2; out += toUint32(pairs, littleEndian); for (let i = 0; i < slice.length; i = i + 2) { out += toByteOrder(littleEndian); out += toUint32(1, littleEndian); out += toDouble(slice[i], littleEndian); out += toDouble(slice[i + 1], littleEndian); } return out; } function handleMultilinestring(arr, littleEndian) { let out = toByteOrder(littleEndian) + toUint32(5, littleEndian); const slices = getParensSlice(arr, "MULTILINESTRING", false); out += toUint32(slices.length, littleEndian); for (let slice of slices) { const pairs = Math.floor(slice.length / 2); out += toByteOrder(littleEndian) + toUint32(2, littleEndian) + toUint32(pairs, littleEndian); for (let token of slice) { out += toDouble(token, littleEndian); } } return out; } function handleMultipolygon(arr, littleEndian) { let out = toByteOrder(littleEndian) + toUint32(6, littleEndian); const polygons = getParensSlice(arr, "MULTIPOLYGON", false); out += toUint32(polygons.length, littleEndian); for (let polygon of polygons) { out += handlePolygon(polygon, littleEndian); } return out; } function handleRing(tokens, littleEndian) { let out = ""; const pairs = Math.floor(tokens.length / 2); out += toUint32(pairs, littleEndian); for (let token of tokens) { out += handleDouble(token, littleEndian); } return out; } function getParensSlice(arr, type, flatten) { let slices = []; while (arr[0] === "(") { arr.shift(); // remove ( const innerSlice = getParensSlice(arr, type, flatten); slices.push(flatten ? innerSlice.flat() : innerSlice); arr.shift(); // remove ) } let seek = arr.findIndex((token) => /^\)$/.test(token)); if (seek === -1) { throw type + " missing matching )"; } if (seek > 0) { slices = slices.concat(arr.splice(0, seek)); } return flatten ? slices.flat() : slices; } function handleDouble(token, littleEndian) { const number = parseFloat(token); if (isNaN(number)) { throw token + " is NaN"; } return toDouble(number, littleEndian); } function toByteOrder(littleEndian) { return littleEndian ? "01" : "00"; } function toUint32(number, littleEndian) { const view = new DataView(new ArrayBuffer(4)); view.setUint32(0, number, littleEndian); return asHex(view, 4); } function toDouble(number, littleEndian) { const view = new DataView(new ArrayBuffer(8)); view.setFloat64(0, number, littleEndian); return asHex(view, 8); } function getHex(i) { return ("00" + i.toString(16)).slice(-2); } function asHex(view, length) { return Array.apply(null, { length }) .map((_, i) => getHex(view.getUint8(i))) .join(""); }