Skip to content

Instantly share code, notes, and snippets.

@mbostock
Last active Feb 9, 2016
Embed
What would you like to do?
GeoJOIN
license: gpl-3.0

A simple script for joining a GeoJSON file with external properties in a CSV or TSV file; extracted from TopoJSON.

#!/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