217 lines
6.5 KiB
JavaScript
217 lines
6.5 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
var fs = require("fs"),
|
|
vm = require("vm"),
|
|
commander = require("commander"),
|
|
topojson = require("../");
|
|
|
|
commander
|
|
.version(require("../package.json").version)
|
|
.usage("[options] <target=source> [file]")
|
|
.description("Merges the source TopoJSON geometry collection, assigning to the target.")
|
|
.option("-o, --out <file>", "output topology file name; defaults to “-” for stdout", "-")
|
|
.option("-k, --key <expression>", "group geometries by key")
|
|
.option("-f, --filter <expression>", "filter merged geometries or meshed lines")
|
|
.option("--mesh", "mesh lines instead of merging polygons")
|
|
.parse(process.argv);
|
|
|
|
if (commander.args.length < 1) {
|
|
console.error();
|
|
console.error(" error: missing source and target names");
|
|
console.error();
|
|
process.exit(1);
|
|
} else if (commander.args.length > 2) {
|
|
console.error();
|
|
console.error(" error: multiple input files");
|
|
console.error();
|
|
process.exit(1);
|
|
} else if (commander.args.length === 1) {
|
|
commander.args.push("-");
|
|
}
|
|
|
|
var keyFunction = function() {},
|
|
postfilterFunction = function() { return true; },
|
|
prefilterFunction = function() { return true; };
|
|
|
|
if (commander.key != null) {
|
|
var keySandbox = {d: undefined, i: -1},
|
|
keyContext = new vm.createContext(keySandbox),
|
|
keyScript = new vm.Script("(" + commander.key + ")");
|
|
keyFunction = function(d, i) {
|
|
keySandbox.d = d;
|
|
keySandbox.i = i;
|
|
return keyScript.runInContext(keyContext);
|
|
};
|
|
}
|
|
|
|
if (commander.filter != null) {
|
|
if (commander.mesh) {
|
|
var filterSandbox = {a: undefined, b: undefined},
|
|
filterContext = new vm.createContext(filterSandbox),
|
|
filterScript = new vm.Script("(" + commander.filter + ")");
|
|
postfilterFunction = function(a, b) {
|
|
filterSandbox.a = a;
|
|
filterSandbox.b = b;
|
|
return filterScript.runInContext(filterContext);
|
|
};
|
|
} else {
|
|
var filterSandbox = {d: undefined, i: -1},
|
|
filterContext = new vm.createContext(filterSandbox),
|
|
filterScript = new vm.Script("(" + commander.filter + ")");
|
|
prefilterFunction = function(d, i) {
|
|
filterSandbox.d = d;
|
|
filterSandbox.i = i;
|
|
return filterScript.runInContext(filterContext);
|
|
};
|
|
}
|
|
}
|
|
|
|
read(commander.args[1]).then(merge).then(write(commander.out)).catch(abort);
|
|
|
|
function read(file) {
|
|
return new Promise(function(resolve, reject) {
|
|
var data = [], stream = file === "-" ? process.stdin : fs.createReadStream(file);
|
|
stream
|
|
.on("data", function(d) { data.push(d); })
|
|
.on("end", function() { resolve(JSON.parse(Buffer.concat(data))); })
|
|
.on("error", reject);
|
|
});
|
|
}
|
|
|
|
function merge(topology) {
|
|
var name = commander.args[0], i = name.indexOf("="),
|
|
sourceName = i >= 0 ? name.slice(i + 1) : name,
|
|
targetName = i >= 0 ? name.slice(0, i) : name,
|
|
source = topology.objects[sourceName],
|
|
target = topology.objects[targetName] = {type: "GeometryCollection", geometries: []},
|
|
geometries = target.geometries,
|
|
geometriesByKey = {},
|
|
k;
|
|
|
|
if (!source) {
|
|
console.error();
|
|
console.error(" error: source object “" + name + "” not found");
|
|
console.error();
|
|
process.exit(1);
|
|
}
|
|
|
|
if (source.type !== "GeometryCollection") {
|
|
console.error();
|
|
console.error(" error: expected GeometryCollection, not " + source.type);
|
|
console.error();
|
|
process.exit(1);
|
|
}
|
|
|
|
source.geometries.forEach(function(geometry, i) {
|
|
if (!prefilterFunction(geometry, i)) return;
|
|
var k = stringify(keyFunction(geometry, i)), v;
|
|
if (v = geometriesByKey[k]) v.push(geometry);
|
|
else geometriesByKey[k] = v = [geometry];
|
|
});
|
|
|
|
if (commander.mesh) {
|
|
for (k in geometriesByKey) {
|
|
var v = geometriesByKey[k],
|
|
o = topojson.meshArcs(topology, {type: "GeometryCollection", geometries: v}, postfilterFunction);
|
|
o.id = k.length > 1 ? k.slice(1) : undefined;
|
|
o.properties = properties(v);
|
|
geometries.push(o);
|
|
}
|
|
} else {
|
|
for (k in geometriesByKey) {
|
|
var v = geometriesByKey[k],
|
|
o = topojson.mergeArcs(topology, v);
|
|
o.id = k.length > 1 ? k.slice(1) : undefined;
|
|
o.properties = properties(v);
|
|
geometries.push(o);
|
|
}
|
|
}
|
|
|
|
return topology;
|
|
}
|
|
|
|
function stringify(key) {
|
|
return key == null ? "$" : "$" + key;
|
|
}
|
|
|
|
function properties(objects) {
|
|
var properties = undefined, hasProperties;
|
|
|
|
objects.forEach(function(object) {
|
|
var newProperties = object.properties, key;
|
|
|
|
// If no properties have yet been merged,
|
|
// then we need to initialize the merged properties object.
|
|
if (properties === undefined) {
|
|
|
|
// If the first set of properties is null, undefined or empty,
|
|
// then the result of the merge will be the empty set.
|
|
// Otherwise, the new properties can copied into the merged object.
|
|
if (newProperties != null) for (key in newProperties) {
|
|
properties = {};
|
|
for (key in newProperties) properties[key] = newProperties[key];
|
|
return;
|
|
}
|
|
|
|
properties = null;
|
|
return;
|
|
}
|
|
|
|
// If any of the new properties are null or undefined,
|
|
// then the result of the merge will be the empty set.
|
|
if (newProperties == null) properties = null;
|
|
if (properties === null) return;
|
|
|
|
// Now mark as inconsistent any of the properties
|
|
// that differ from previously-merged values.
|
|
for (key in newProperties) {
|
|
if ((key in properties) && !is(properties[key], newProperties[key])) {
|
|
properties[key] = undefined;
|
|
}
|
|
}
|
|
|
|
// And mark as inconsistent any of the properties
|
|
// that are missing from this new set of merged values.
|
|
for (key in properties) {
|
|
if (!(key in newProperties)) {
|
|
properties[key] = undefined;
|
|
}
|
|
}
|
|
|
|
return object;
|
|
});
|
|
|
|
// Return undefined if there are no properties.
|
|
for (var key in properties) {
|
|
if (properties[key] !== undefined) {
|
|
return properties;
|
|
}
|
|
}
|
|
};
|
|
|
|
function write(file) {
|
|
var stream = (file === "-" ? process.stdout : fs.createWriteStream(file)).on("error", handleEpipe);
|
|
return function(topology) {
|
|
return new Promise(function(resolve, reject) {
|
|
stream.on("error", reject)[stream === process.stdout ? "write" : "end"](JSON.stringify(topology) + "\n", function(error) {
|
|
if (error) reject(error);
|
|
else resolve();
|
|
});
|
|
});
|
|
};
|
|
}
|
|
|
|
function handleEpipe(error) {
|
|
if (error.code === "EPIPE" || error.errno === "EPIPE") {
|
|
process.exit(0);
|
|
}
|
|
}
|
|
|
|
function abort(error) {
|
|
console.error(error.stack);
|
|
}
|
|
|
|
function is(x, y) {
|
|
return x === y ? x !== 0 || 1 / x === 1 / y : x !== x && y !== y;
|
|
}
|