A simple script for joining a GeoJSON file with external properties in a CSV or TSV file; extracted from TopoJSON.
GeoJOIN
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
license: gpl-3.0 |
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
#!/usr/bin/env node | |
var fs = require("fs"), | |
optimist = require("optimist"), | |
dsv = require("dsv"); | |
var argv = optimist | |
.usage("Usage: \033[1mgeojoin\033[0m [options] [file]\n\n" | |
+ "Joins a GeoJSON file with one or more CSV or TSV properties.") | |
.options("o", { | |
alias: "out", | |
describe: "output GeoJSON file name", | |
default: "/dev/stdout", | |
}) | |
.options("id-property", { | |
describe: "name of feature property to use as id", | |
default: null | |
}) | |
.options("p", { | |
alias: "properties", | |
describe: "names of properties to join; no name joins all properties", | |
default: true | |
}) | |
.options("e", { | |
alias: "external-properties", | |
describe: "CSV or TSV file to join properties (by id) to output features" | |
}) | |
.options("help", { | |
describe: "display this helpful message", | |
default: false | |
}) | |
.check(function(argv) { | |
if (argv._.length !== 1) throw new Error("single input GeoJSON file required"); | |
if (typeof argv.e === "string") argv.e = [argv.e]; | |
if (!argv.e) throw new Error("one or more external properties required"); | |
if (typeof argv.p === "string") argv.p = argv.p.split(","); | |
}) | |
.argv; | |
if (argv.help) return optimist.showHelp(); | |
// Create a property-to-identifier function. | |
var id = argv["id-property"]; | |
id = id == null | |
? function(d) { return d.id; } | |
: parsePropertyId(typeof id === "string" ? id.split(",") : id); | |
// Create the property transform function. | |
var propertyTransform = argv.p === true ? function(o, k, v) { o[k] = v; return true; } | |
: argv.p === false ? function() {} | |
: parsePropertyTransform(argv.p); | |
// Load any external properties. | |
var externalProperties = {}; | |
argv.e.forEach(readExternalProperties); | |
// Load the GeoJSON file. | |
var collection = JSON.parse(fs.readFileSync(argv._[0], "utf8")); | |
if (collection.type !== "FeatureCollection") throw new Error("unsupported input type: " + collection.type); | |
collection.features.forEach(function(feature) { | |
var properties0 = feature.properties, | |
properties1 = externalProperties[id(feature)], | |
properties = feature.properties = {}; | |
if (properties0) for (var k in properties0) propertyTransform(properties, k, properties0[k]); | |
if (properties1) for (var k in properties1) properties[k] = properties1[k]; | |
}); | |
// Output JSON. | |
var json = JSON.stringify(collection); | |
if (argv.o === "/dev/stdout") console.log(json); | |
else fs.writeFileSync(argv.o, json, "utf8"); | |
function parsePropertyId(properties) { | |
return function(d) { | |
if (d = d.properties) { | |
var id; | |
properties.some(function(p) { | |
id = /^\+/.test(p) ? +d[p.substring(1)] : d[p]; | |
if (id == null) return; | |
else if (typeof id === "number") isNaN(id) && (id = null); | |
else if (typeof id !== "string") id = id + ""; | |
return id; | |
}); | |
return id; | |
} | |
}; | |
} | |
function parsePropertyTransform(properties) { | |
var transforms = {}; | |
properties.forEach(function(target) { | |
var i = target.indexOf("="), | |
source = target, | |
number; | |
if (i >= 0) { | |
source = target.substring(i + 1); | |
target = target.substring(0, i); | |
} | |
if (number = /^\+/.test(source)) { | |
source = source.substring(1); | |
if (i < 0) target = source; | |
} | |
transforms[source] = number | |
? function(properties, value) { properties[target] = +value; } | |
: function(properties, value) { properties[target] = value; }; | |
}); | |
return function(properties, key, value) { | |
var transform = transforms[key]; | |
return transform && value != null && (transform(properties, value), true); | |
}; | |
} | |
function readExternalProperties(file) { | |
// Infer the file type from the name. | |
// If that doesn't work, look for a tab and hope for the best! | |
var type = /\.tsv$/i.test(file) ? dsv.tsv | |
: /\.csv$/i.test(file) ? dsv.csv | |
: text.indexOf("\t") ? dsv.tsv | |
: dsv.csv; | |
type.parse(fs.readFileSync(file, "utf-8")).forEach(function(row) { | |
var properties = externalProperties[row.id] || (externalProperties[row.id] = {}); | |
for (var key in row) if (key != "id") propertyTransform(properties, key, row[key]); | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment