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(); const innerSlice = getParensSlice(arr, type, flatten);
slices.push(flatten ? innerSlice.flat() : innerSlice);
arr.shift(); }
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("");
}