wkt-crs
Version:
Parse WKT-CRS (Well-known text representation of coordinate reference systems)
186 lines (158 loc) • 5.14 kB
JavaScript
function repeat(str, ct) {
let out = "";
for (let i = 0; i < ct; i++) out += str;
return out;
}
function sort(data, { keywords } = {}) {
const keys = Object.keys(data).filter(k => !/\d+/.test(k));
if (!keywords) {
keywords = [];
// try to find multiples
const counts = {};
if (Array.isArray(data)) {
data.forEach(it => {
if (Array.isArray(it) && it.length >= 2 && typeof it[1] === "string") {
const k = it[0];
if (!counts[k]) counts[k] = 0;
counts[k]++;
}
});
for (let k in counts) {
if (counts[k] > 0) keywords.push(k);
}
}
}
keys.forEach(key => {
data[key] = sort(data[key]);
});
keywords.forEach(key => {
const indices = [];
const params = [];
data.forEach((item, i) => {
if (Array.isArray(item) && item[0] === key) {
indices.push(i);
params.push(item);
}
});
params.sort((a, b) => {
a = a[1].toString();
b = b[1].toString();
return a < b ? -1 : a > b ? 1 : 0;
});
// replace in order
params.forEach((param, i) => {
data[indices[i]] = param;
});
});
return data;
}
function parse(wkt, options) {
const raw = typeof options === "object" && options.raw === true;
const debug = typeof options === "object" && options.debug === true;
if (debug) console.log("[wktcrs] parse starting with\n", wkt);
// move all keywords into first array item slot
// from PARAM[12345, 67890] to ["PARAM", 12345, 67890]
wkt = wkt.replace(/[A-Z][A-Z\d_]+\[/gi, function (match) {
return '["' + match.substr(0, match.length - 1) + '",';
});
// pull out all quoted strings
const literals = [];
wkt = wkt.replace(/"([^"]*)"/g, (_, inner) => {
literals.push(inner);
return `"__LITERAL${literals.length - 1}__"`;
});
// wrap variables in strings
// from [...,NORTH] to [...,"NORTH"]
wkt = wkt.replace(/, ?([A-Z][A-Z\d_]+[,\]])/gi, function (match, p1) {
const varname = p1.substr(0, p1.length - 1);
return "," + '"' + (raw ? "raw:" : "") + varname + '"' + p1[p1.length - 1];
});
if (typeof options === "object" && options.raw === true) {
// replace all numbers with strings
wkt = wkt.replace(/, {0,2}(-?[\.\d]+)(?=,|\])/g, function (match, p1) {
return "," + '"' + (raw ? "raw:" : "") + p1 + '"';
});
}
// restore original quoted strings
wkt = wkt.replace(/"__LITERAL(\d+)__"/g, (_, idx) => {
return `"${literals[+idx]}"`;
});
// str should now be valid JSON
if (debug) console.log("[wktcrs] json'd wkt: '" + wkt + "'");
let data;
try {
data = JSON.parse(wkt);
} catch (error) {
console.error(`[wktcrs] failed to parse '${wkt}'`);
throw error;
}
if (debug) console.log("[wktcrs] json parsed: '" + wkt + "'");
function process(data, parent) {
const kw = data[0];
// after removing the first element with .shift()
// data is now just an array of attributes
data.forEach(function (it) {
if (Array.isArray(it)) {
process(it, data);
}
});
const kwarr = "MULTIPLE_" + kw;
if (kwarr in parent) {
parent[kwarr].push(data);
} else if (kw in parent) {
parent[kwarr] = [parent[kw], data];
delete parent[kw];
} else {
parent[kw] = data;
}
return parent;
}
const result = process(data, [data]);
if (debug) console.log("[wktcrs] parse returning", result);
return { data: result };
}
// convert JSON representation of Well-Known Text
// back to standard Well-Known Text
function unparse(wkt, options) {
const indent = typeof options === "object" && typeof options.indent === "number" ? options.indent : 2;
const pretty = typeof options === "object" && typeof options.pretty === "boolean" ? options.pretty : false;
if (Array.isArray(wkt) && wkt.length == 1 && Array.isArray(wkt[0])) {
wkt = wkt[0]; // ignore first extra wrapper array
}
const [kw, ...attrs] = wkt;
const str =
kw +
"[" +
attrs
.map(attr => {
if (Array.isArray(attr)) {
return (
(pretty ? "\n" + repeat(" ", indent) : "") + unparse(attr, { indent: indent + 2, pretty: pretty }).data
);
} else if (typeof attr === "number") {
return attr.toString();
} else if (typeof attr === "string") {
// can't automatically convert all caps to varibale
// because EPSG is string in AUTHORITY["EPSG", ...]
if (attr.startsWith("raw:")) {
// convert "raw:NORTH" to NORTH
return attr.replace("raw:", "");
} else {
return '"' + attr + '"';
}
} else {
throw new Error('[wktcrs] unexpected attribute "' + attr + '"');
}
})
.join(",") +
"]";
return { data: str };
}
const _module = { parse, unparse, sort };
if (typeof define === "function")
define(function () {
return _module;
});
if (typeof module === "object") module.exports = _module;
if (typeof window === "object") window.wktcrs = _module;
if (typeof self === "object") self.wktcrs = _module;