Permalink
Browse files

Join external properties to features.

Using the new -e (--external-properties) argument, you can now join a CSV or TSV
file of data to the topology as part of the conversion process. Each row in the
referenced DSV file is joined to the geometry objects in the generated topology
using the "id" attribute. This process happens after the generation of the
topology, and can thus the id of the geometry objects be configured using the
--id-property argument.

These external properties are transformed normally using the -p (--properties)
argument, and can thus be filtered and coerced to numbers as desired. Note that
by default, this means that any -e argument is ignored unless a -p argument is
also specified!
  • Loading branch information...
1 parent 0e31398 commit 7b422d2c2ae6ea2ff92b1188e8c8525bd0f4a65f @mbostock mbostock committed Mar 8, 2013
Showing with 111 additions and 13 deletions.
  1. +2 โˆ’0 Makefile
  2. +39 โˆ’6 bin/topojson
  3. +1 โˆ’1 component.json
  4. +1 โˆ’0 index.js
  5. +28 โˆ’0 lib/topojson/bind.js
  6. +4 โˆ’3 package.json
  7. +3 โˆ’2 src/package.js
  8. +32 โˆ’0 test/bind-test.js
  9. +1 โˆ’1 topojson.js
View
@@ -7,6 +7,8 @@ GENERATED_FILES = \
.SECONDARY:
+.PHONY: all clean test
+
all: $(GENERATED_FILES)
component.json: src/component.js topojson.js
View
@@ -2,6 +2,7 @@
var path = require("path"),
fs = require("fs"),
+ dsv = require("dsv"),
optimist = require("optimist"),
shapefile = require("shapefile"),
queue = require("queue-async"),
@@ -50,6 +51,10 @@ var argv = optimist
describe: "feature properties to preserve; no name preserves all properties",
default: false
})
+ .options("e", {
+ alias: "external-properties",
+ describe: "a CSV or TSV file with properties to join (by id) to the output features"
+ })
.options("help", {
describe: "display this helpful message",
type: "boolean",
@@ -70,7 +75,15 @@ var objects = {},
// Create a property-to-identifier function.
id = id == null
? function(d) { return d.id; }
- : propertyId(typeof id === "string" ? id.split(",") : 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 && readExternalProperties(argv.e);
// Create a map from basename to JSON object.
// Convert TopoJSON back to GeoJSON in preparation for merge, as needed.
@@ -118,9 +131,7 @@ function output(error) {
"verbose": true,
"quantization": +argv.q,
"id": id,
- "property-transform": argv.p === true ? function(o, k, v) { o[k] = v; return true; }
- : argv.p === false ? function() {}
- : propertyTransform(argv.p)
+ "property-transform": propertyTransform
});
// Simplify.
@@ -139,6 +150,9 @@ function output(error) {
"force-clockwise": !!argv["force-clockwise"]
});
+ // Bind with external properties.
+ if (externalProperties) topojson.bind(object, externalProperties);
+
// Output JSON.
var json = JSON.stringify(object);
if (argv.o === "/dev/stdout") console.log(json);
@@ -153,7 +167,7 @@ function qualify(file) {
};
}
-function propertyId(properties) {
+function parsePropertyId(properties) {
return function(d) {
if (d = d.properties) {
var id;
@@ -169,7 +183,7 @@ function propertyId(properties) {
};
}
-function propertyTransform(properties) {
+function parsePropertyTransform(properties) {
var transforms = {};
properties.forEach(function(target) {
var i = target.indexOf("="),
@@ -186,3 +200,22 @@ function propertyTransform(properties) {
return transform && value != null && (transform(properties, value), true);
};
}
+
+function readExternalProperties(file) {
+ var propertiesById = {};
+
+ // 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 = {};
+ for (var key in row) if (key != "id") propertyTransform(properties, key, row[key]);
+ propertiesById[row.id] = properties;
+ });
+
+ return propertiesById;
+}
View
@@ -1,5 +1,5 @@
{
"name": "topojson",
- "version": "0.0.27",
+ "version": "0.0.28",
"main": "./topojson.js"
}
View
@@ -4,3 +4,4 @@ var topojson = module.exports = new Function("topojson", "return " + fs.readFile
topojson.topology = require("./lib/topojson/topology");
topojson.simplify = require("./lib/topojson/simplify");
topojson.filter = require("./lib/topojson/filter");
+topojson.bind = require("./lib/topojson/bind");
View
@@ -0,0 +1,28 @@
+var type = require("./type"),
+ topojson = require("../../");
+
+module.exports = function(topology, propertieById) {
+ var bind = type({
+ geometry: function(geometry) {
+ var properties0 = geometry.properties,
+ properties1 = propertieById[geometry.id];
+ if (properties1) {
+ if (properties0) for (var k in properties1) properties0[k] = properties1[k];
+ else geometry.properties = properties1;
+ }
+ this.defaults.geometry.call(this, geometry);
+ },
+ LineString: noop,
+ MultiLineString: noop,
+ Point: noop,
+ MultiPoint: noop,
+ Polygon: noop,
+ MultiPolygon: noop
+ });
+
+ for (var key in topology.objects) {
+ bind.object(topology.objects[key]);
+ }
+};
+
+function noop() {}
View
@@ -1,6 +1,6 @@
{
"name": "topojson",
- "version": "0.0.27",
+ "version": "0.0.28",
"description": "An extension to GeoJSON that encodes topology.",
"keywords": [
"geojson",
@@ -12,7 +12,7 @@
},
"repository": {
"type": "git",
- "url": "http://github.com/mbostock/topojson.git"
+ "url": "https://github.com/mbostock/topojson.git"
},
"main": "./index.js",
"dependencies": {
@@ -21,6 +21,7 @@
"shapefile": "0.0.x"
},
"devDependencies": {
+ "dsv": "0.0.x",
"vows": "0.6.x",
"us-atlas": "0.0.x",
"world-atlas": "0.0.x"
@@ -29,6 +30,6 @@
"topojson": "./bin/topojson"
},
"scripts": {
- "test": "./node_modules/vows/bin/vows; echo"
+ "test": "node_modules/.bin/vows; echo"
}
}
View
@@ -14,7 +14,7 @@ console.log(JSON.stringify({
},
"repository": {
"type": "git",
- "url": "http://github.com/mbostock/topojson.git"
+ "url": "https://github.com/mbostock/topojson.git"
},
"main": "./index.js",
"dependencies": {
@@ -23,6 +23,7 @@ console.log(JSON.stringify({
"shapefile": "0.0.x"
},
"devDependencies": {
+ "dsv": "0.0.x",
"vows": "0.6.x",
"us-atlas": "0.0.x",
"world-atlas": "0.0.x"
@@ -31,6 +32,6 @@ console.log(JSON.stringify({
"topojson": "./bin/topojson"
},
"scripts": {
- "test": "./node_modules/vows/bin/vows; echo"
+ "test": "node_modules/.bin/vows; echo"
}
}, null, 2));
View
@@ -0,0 +1,32 @@
+var vows = require("vows"),
+ assert = require("assert"),
+ topojson = require("../");
+
+var suite = vows.describe("topojson.bind");
+
+suite.addBatch({
+ "bind": {
+ topic: function() {
+ return topojson.bind;
+ },
+ "properties are bound to top-level features by id": function(bind) {
+ var topology = topojson.topology({feature: {type: "Feature", id: "foo", properties: {}, geometry: null}});
+ bind(topology, {foo: {color: "red"}});
+ assert.deepEqual(topology.objects.feature.properties, {color: "red"});
+ },
+ "properties are bound to collected features by id": function(bind) {
+ var topology = topojson.topology({collection: {type: "FeatureCollection", features: [
+ {type: "Feature", id: "foo", properties: {}, geometry: null},
+ {type: "Feature", id: "bar", properties: {}, geometry: {type: "Point", coordinates: [0, 0]}}
+ ]}});
+ bind(topology, {
+ foo: {color: "red"},
+ bar: {size: 42}
+ });
+ assert.deepEqual(topology.objects.collection.geometries[0].properties, {color: "red"});
+ assert.deepEqual(topology.objects.collection.geometries[1].properties, {size: 42});
+ }
+ }
+});
+
+suite.export(module);
View
@@ -255,7 +255,7 @@ topojson = (function() {
}
return {
- version: "0.0.27",
+ version: "0.0.28",
mesh: mesh,
object: object,
neighbors: neighbors

0 comments on commit 7b422d2

Please sign in to comment.