7
votes

I'm using the D3.js library to create maps from US Census shapefiles. I'm looking to create an entire US map, which is no problem, and a map for each state.

My workflow uses the census data, altered as necessary by ogr2ogr at the command line, then converted into topojson or geojson by shpescape.com, due to errors in the node.js downloading of the topojson module (see below for edited solution to this particular problem).

My question is more of a PRACTICAL question than anything else--when presented with this code (modeled off of http://bl.ocks.org/mbostock/4707858):

            var width = 640,
                height = 500;

            var projection = d3.geo.albers();

            var path = d3.geo.path()
                .projection(projection);

            var svg = d3.select("body").append("svg")
                .attr("width", width)
                .attr("height", height);

            d3.json("mt_geo.json", function(error, mt_topo) {
                var states = topojson.feature(mt_topo, mt_topo.objects.states),
                    state = states.features.filter(function(d) { return d.id === 34; })[0];
                projection
                    .scale(1)
                    .translate([0,0]);
                var b = path.bounds(state),
                    s = .95 / Math.max ((b[1][0]-b[0][0])/width, (b[1][1]-b[0][1])/height),
                    t = [(width-s*(b[1][0]+b[0][0]))/2, (height-s*(b[1][1]+b[0][1]))/2];
                projection
                    .scale(s)
                    .translate(t);
                svg.append("path")
                    .datum(states)
                    .attr("class", "feature")
                    .attr("d", path);
                svg.append("path")
                    .datum(topojson.mesh(us, us.objects.states,function(a, b) {return a !== b;}))
                    .attr("d", path);
                svg.append("path")
                    .datum(state)
                    .attr("class", "outline")
                    .attr("d", path);

Not only does it throw an error at the "var states" line which says "cannot read type of property undefined" -- but I also have no idea what I'm supposed to be passing into the anonymous function, or what mt_topo.objects.states is supposed to refer to. There is no good documentation on this sort of GIS thing. Do all census maps have "state" features? Do you lose that information when you compress the .shp to topojson?

Simply, if d3.json takes (object, function(error, json)), what would an example of that which works actually look like?

EDIT: WORKAROUND AND WINDOWS 7 IDIOSYNCRASIES -----

Most tutorials tell you to use a module from node.js, but I'm on Windows7, and the canonical command-line "npm install -g topojson" fails "at contextify". The creator sent me a nice link to work around said problem.

This is important, because there is a flag in the command line for topojson in which you can package existing features in geojson into an accessible object in topojson. For example, the above code uses "states" in topojson--something that is meaningless and unaccessible unless you use the following command:

topojson -o us.topojson -- states=us_states.json

The space between the double hyphen and states is important. You can then access the states via us.objects.states, as shown in the original code above.

2
Did you have a look at the tutorial? The attributes depend on what's in the data, i.e. it's completely user-defined.Lars Kotthoff
@LarsKotthoff -- yes, I've read over the tutorial rather extensively. I think I figured out the scope problems; like you said, it's solely a placeholder. My original problem is becoming clearer. Thanks for your help.kgilvi3
@kgilvi3: if you found a way around, please share the answer, we will be happy to +1 it.Hugolpz

2 Answers

1
votes

You're very close. Without testing your code, I see one major issue. The second parameter of your JSON callback is mt_topo, which you use when defining

var states = topojson.feature(mt_topo, mt_topo.objects.states)

However, later on you use us as your callback object, presumably because that is what Mike Bostock used in the example you cited. Instead, it should be this:

svg.append("path")
 .datum(topojson.mesh(mt_topo, mt_topo.objects.states,function(a, b) {return a !== b;}))
 .attr("d", path);

That said, your question is really getting at whether or not census maps have a "states" feature. My guess is that whatever geometry you are using does not have a states feature, and that is why you are getting an error. When using the topojson command line tool, the feature name (i.e. data.objects.x) is usually whatever the input file was named, so if your file was US_Census_2010.shp, you would want to define states as

var states = topojson.feature(mt_topo, mt_topo.objects.US_Census_2010)

Open up your mt_geo.json file and see what your features are named. Hope that helps!

0
votes

A GeoJSON file representing a map usually have one or more features. Each feature may have zero or more properties. Generally, the properties are used to store metadata of the places (state name, county, etc). You can read your GeoJSON file and see what properties it has, and use this properties to display or hide the features.

When converting a file from GeoJSON to TopoJSON, you can preserve or remove the original properties. For instance, the features and points in the uk.json file from the tutorial Let's Make a Map have the property name.