Created
June 18, 2020 17:19
-
-
Save spoike/38a08352e86b0ed66a6201119b522545 to your computer and use it in GitHub Desktop.
Boop Scripts: Convert between WKT and WKB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
POINT(2.0 4.0) | |
010100000000000000000000400000000000001040 | |
LINESTRING(2.0 4.0, 1.0 2.0) | |
01020000000200000000000000000000400000000000001040000000000000f03f0000000000000040 | |
POLYGON ((30 10, 40 40, 20 40)) | |
010300000001000000030000000000000000003e4000000000000024400000000000004440000000000000444000000000000034400000000000004440 | |
POLYGON ((30 10, 40 40), (20 40, 10 30)) | |
010300000002000000020000000000000000003e40000000000000244000000000000044400000000000004440020000000000000000003440000000000000444000000000000024400000000000003e40 | |
MULTIPOINT (10 40, 40 30) | |
010400000002000000010100000000000000000024400000000000004440010100000000000000000044400000000000003e40 | |
MULTIPOINT ((10 40), (40 30)) | |
010400000002000000010100000000000000000024400000000000004440010100000000000000000044400000000000003e40 | |
MULTILINESTRING ((10 10, 20 20), (40 40, 30 30)) | |
0105000000020000000102000000020000000000000000002440000000000000244000000000000034400000000000003440010200000002000000000000000000444000000000000044400000000000003e400000000000003e40 | |
MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5))) | |
010600000002000000010300000001000000040000000000000000003e40000000000000344000000000008046400000000000004440000000000000244000000000000044400000000000003e400000000000003440010300000001000000050000000000000000002e4000000000000014400000000000004440000000000000244000000000000024400000000000003440000000000000144000000000000024400000000000002e400000000000001440 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
{ | |
"api":1, | |
"name":"Well-Known Binary to Text", | |
"description":"wkb2kwt - converts your hex encoded WKB (any endian) to WKB", | |
"author":"Mikael Brassman (Twitter: @spoike)", | |
"icon":"metamorphose", | |
"tags":"wkb,convert,wkt,binary,hex,wkb2wkt" | |
} | |
**/ | |
function main(input) { | |
try { | |
input.text = input.text.replace(/0[01]0[1-6][0-9a-f]+/g, convertHex); | |
} catch (err) { | |
input.postError(err); | |
return; | |
} | |
} | |
function convertHex(hexStr) { | |
let pos = 0; | |
let output = ""; | |
while (pos < hexStr.length) { | |
const littleEndian = getIsLittleEndian(hexStr, pos); | |
pos += 2; | |
const geoType = getUint32(hexStr, pos, littleEndian); | |
pos += 8; | |
switch (geoType) { | |
case 1: { | |
// POINT | |
const point = getPoint(hexStr, pos, littleEndian); | |
pos += 32; | |
output += `POINT (${point})`; | |
break; | |
} | |
case 2: { | |
// LINESTRING | |
const length = getUint32(hexStr, pos, littleEndian); | |
pos += 8; | |
const points = []; | |
for (let i = 0; i < length; i++) { | |
points.push(getPoint(hexStr, pos, littleEndian)); | |
pos += 32; | |
} | |
output += `LINESTRING (${points.join(", ")})`; | |
break; | |
} | |
case 3: { | |
// POLYGON | |
const count = getUint32(hexStr, pos, littleEndian); | |
pos += 8; | |
const rings = []; | |
for (let i = 0; i < count; i++) { | |
const length = getUint32(hexStr, pos, littleEndian); | |
pos += 8; | |
const points = []; | |
for (let j = 0; j < length; j++) { | |
points.push(getPoint(hexStr, pos, littleEndian)); | |
pos += 32; | |
} | |
rings.push(points.join(", ")); | |
} | |
output += `POLYGON (${rings.map(wrapParens).join(", ")})`; | |
break; | |
} | |
case 4: { | |
// MULTIPOINT | |
const points = []; | |
const count = getUint32(hexStr, pos, littleEndian); | |
pos += 8; | |
for (let i = 0; i < count; i++) { | |
const innerLE = getIsLittleEndian(hexStr, pos); | |
pos += 2 + 8; | |
points.push(getPoint(hexStr, pos, innerLE)); | |
pos += 32; | |
} | |
output += `MULTIPOINT (${points.join(", ")})`; | |
break; | |
} | |
case 5: { | |
// MULTILINESTRING | |
const lineStrings = []; | |
const count = getUint32(hexStr, pos, littleEndian); | |
pos += 8; | |
for (let i = 0; i < count; i++) { | |
const innerLE = getIsLittleEndian(hexStr, pos); | |
pos += 2 + 8; | |
const points = []; | |
const length = getUint32(hexStr, pos, littleEndian); | |
pos += 8; | |
for (let j = 0; j < length; j++) { | |
points.push(getPoint(hexStr, pos, innerLE)); | |
pos += 32; | |
} | |
lineStrings.push(points.join(", ")); | |
} | |
output += `MULTILINESTRING (${lineStrings.map(wrapParens).join(", ")})`; | |
break; | |
} | |
case 6: { | |
// MULTIPOLYGON | |
const polys = []; | |
const polyCount = getUint32(hexStr, pos, littleEndian); | |
pos += 8; | |
for (let i = 0; i < polyCount; i++) { | |
const innerLE = getIsLittleEndian(hexStr, pos); | |
pos += 2 + 8; | |
const rings = []; | |
const ringCount = getUint32(hexStr, pos, innerLE); | |
pos += 8; | |
for (let j = 0; j < ringCount; j++) { | |
const points = []; | |
const pointCount = getUint32(hexStr, pos, innerLE); | |
pos += 8; | |
for (let k = 0; k < pointCount; k++) { | |
points.push(getPoint(hexStr, pos, innerLE)); | |
pos += 32; | |
} | |
rings.push(points.join(", ")); | |
} | |
polys.push(rings.map(wrapParens).join(", ")); | |
} | |
output += `MULTIPOLYGON (${polys.map(wrapParens).join(", ")})`; | |
break; | |
} | |
default: | |
throw geoType + " is not supported"; | |
} | |
} | |
return output; | |
} | |
function wrapParens(el) { | |
return `(${el})`; | |
} | |
function getIsLittleEndian(str, pos) { | |
const byteString = str.substr(pos, 2); | |
if (byteString === "00") { | |
return false; | |
} else if (byteString === "01") { | |
return true; | |
} | |
throw byteString + " is unknown byte order"; | |
} | |
function getPoint(str, pos, littleEndian) { | |
const numbers = []; | |
numbers.push(getDouble(str, pos, littleEndian)); | |
numbers.push(getDouble(str, pos + 16, littleEndian)); | |
return numbers.join(" "); | |
} | |
function getUint32(str, pos, littleEndian) { | |
const view = new DataView(new ArrayBuffer(4)); | |
let data = str.substr(pos, 8).match(/../g); | |
for (let i = 0; i < data.length; i++) { | |
view.setUint8(i, parseInt(data[i], 16)); | |
} | |
return view.getUint32(0, littleEndian); | |
} | |
function getDouble(str, pos, littleEndian) { | |
const view = new DataView(new ArrayBuffer(8)); | |
let data = str.substr(pos, 16).match(/../g); | |
for (let i = 0; i < data.length; i++) { | |
view.setUint8(i, parseInt(data[i], 16)); | |
} | |
return view.getFloat64(0, littleEndian); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
{ | |
"api":1, | |
"name":"Well-Known Text to Binary", | |
"description":"wkt2wkb - converts your WKT to little endian WKB (hex encoded)", | |
"author":"Mikael Brassman (Twitter: @spoike)", | |
"icon":"metamorphose", | |
"tags":"wkb,convert,wkt,binary,little endian,hex,wkt2wkb" | |
} | |
**/ | |
const re = /(?:(?:MULTI)?POINT|(?:MULTI)?LINESTRING|(?:MULTI)?POLYGON)\s*\([()0-9\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, "POLYGON"); | |
} | |
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(""); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment