Spaces:
Sleeping
Sleeping
| 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; | |
| } | |