boop-gtk 1.6.0

A scriptable scratchpad for developers Port of @IvanMathy's Boop to GTK
/**
  {
    "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("");
}