Following along with Mike Bostock's 4 part blog series Command-Line Cartography.
npm install -g shapefile
npm install -g d3-geo-projection
npm install -g ndjson-cli
npm install -g d3
npm install -g topojson
Geometry Data as cartographic boundary files are curled from US Census Bureau for the State of Texas using a FIPS code of 48.
curl 'http://www2.census.gov/geo/tiger/GENZ2014/shp/cb_2014_48_tract_500k.zip' -o cb_2014_48_tract_500k.zip
unzip -o cb_2014_48_tract_500k.zip
Population data was extracted using the US Census API's 2014 (request API Key). The request below is for Total Population data — See 'B01003_001E' in ACS API variables — from the 2014 American Community Survey API Endpoint (see docs).
curl "http://api.census.gov/data/2014/acs5?get=B01003_001E&for=tract:*&in=state:48&key=${CENSUSAPIKEY}" -o cb_2014_48_tract_B01003.json
Note: I created a .env file for storing my Census API Key as an environment variable.
#Part 1
Convert shapefile to GeoJson using Mike Bostock's shapefile parser with a command-line interface, shp2json.
shp2json cb_2014_48_tract_500k.shp -o tx.json
First, I needed to find an appropriate projection for Texas. I chose NAD83 / Texas North (EPSG:32137) from [D3 Stateplane] (https://github.com/veltman/d3-stateplane).
geoproject 'd3.geoConicConformal().parallels([34 + 39 / 60, 36 + 11 / 60]).rotate([101 + 30 / 60, -34]).fitSize([960,960], d)' < tx.json > tx-north.json
Convert projection into SVG
geo2svg -w 960 -h 960 < tx-north.json > tx-north.svg
GeoJSON is a huge collection of features which we need to access a feature at a time — enter ndjson-split:
ndjson-split 'd.features' \
< tx-north.json \
> tx-north.ndjson
The following command grabs the GEOID from a feature's property, removes the FIPs code assigns it as the features ID.
ndjson-map 'd.id = d.properties.GEOID.slice(2), d' \
< tx-north.ndjson \
> tx-north-id.ndjson
The US Census population file is a JSON array. To convert it to an NDJSON stream, use ndjson-cat (to remove the newlines), ndjson-split (to separate the array into multiple lines) and ndjson-map (to reformat each line as an object). You can run these individually, but here’s how to do it all in one go (Source).
It is important to note that the ID we are using for our boundaries is simply the county ID + the tract ID. Remember, we removed the state identifier from the boundary GEOID.
ndjson-cat cb_2014_48_tract_B01003.json \
| ndjson-split 'd.slice(1)' \
| ndjson-map '{id: d[2] + d[3], B01003: +d[0]}' \
> ../cb_2014_48_tract_B01003.ndjson
Note: I output the NDJSON in my parent directory (../)
Join the two data sets using the ID we added to our cartographic boundaries.
ndjson-join 'd.id' \
tx-north-id.ndjson \
cb_2014_48_tract_B01003.ndjson \
> tx-north-join.ndjson
Calculate population density by dividing the boundary population (d[1].B01003) by the land area (d[0].properties.ALAND). Don't forget we need to convert our units from meters squared to miles squared.
ndjson-map 'd[0].properties = {density: Math.floor(d[1].B01003 / d[0].properties.ALAND * 2589975.2356)}, d[0]' \
< tx-north-join.ndjson \
> tx-north-density.ndjson
Note from the Author: Note that the density value is floored rather than rounded. We don’t need the extra precision, so either results in a smaller output file. But since we will later apply a threshold color encoding in the choropleth, rounding would be inappropriate: for example, it would change an effective threshold of 4,000 to 3,999.5! (Source)
ndjson-reduce 'p.features.push(d), p' '{type: "FeatureCollection", features: []}' \
< tx-north-density.ndjson \
> tx-north-density.json
Next use ndjson-map, requiring D3 via -r d3, and defining a fill property using a sequential scale with the Viridis color scheme:
ndjson-map -r d3 \
'(d.properties.fill = d3.scaleSequential(d3.interpolateViridis).domain([0, 4000])(d.properties.density), d)' \
< tx-north-density.ndjson \
> tx-north-color.ndjson
geo2svg -n --stroke none -p 1 -w 960 -h 960 \
< tx-north-color.ndjson \
> tx-north-color.svg
Next we will 1.) Simplify 2.) Quantize and 3.) Compress our GeoJSON.
I highly recommend reading Part III of Mike Bostock's Blog post.
geo2topo -n \
tracts=tx-north-density.ndjson \
> tx-north-topo.json
Use toposimplify to further shrink our file.
toposimplify -p 1 -f \
< tx-north-topo.json \
> tx-simple-topo.json
Reduce floats to integers
topoquantize 1e5 \
< tx-simple-topo.json \
> tx-quantized-topo.json
Since census tracts compose hierarchically into counties, we can derive county geometry using topomerge!
topomerge -k 'd.id.slice(0, 3)' counties=tracts \
< tx-quantized-topo.json \
> tx-merge-topo.json
topomerge --mesh -f 'a !== b' counties=counties \
< tx-merge-topo.json \
> tx-topo.json