A variation of my California population density map using California’s 23,198 block groups rather than its 8,043 tracts. The example exhibits how useful the Census API is: the prepublish script here can automatically grabs the list of counties for the desired state and then the population data for each block group.
Last active
October 26, 2021 23:28
-
-
Save mbostock/39b34968ad5eab65de1d7da81f78bb27 to your computer and use it in GitHub Desktop.
California Population Density II
This file contains hidden or 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 | |
height: 1100 | |
border: no |
This file contains hidden or 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
.DS_Store | |
node_modules | |
cb_* |
This file contains hidden or 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
<!DOCTYPE html> | |
<svg width="960" height="1100"></svg> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script> | |
<script src="https://d3js.org/topojson.v2.min.js"></script> | |
<script> | |
var svg = d3.select("svg"), | |
width = +svg.attr("width"), | |
height = +svg.attr("height"); | |
var path = d3.geoPath(); | |
var color = d3.scaleThreshold() | |
.domain([1, 10, 50, 200, 500, 1000, 2000, 4000]) | |
.range(d3.schemeOrRd[9]); | |
var x = d3.scaleSqrt() | |
.domain([0, 4500]) | |
.rangeRound([440, 950]); | |
var g = svg.append("g") | |
.attr("class", "key") | |
.attr("transform", "translate(0,40)"); | |
g.selectAll("rect") | |
.data(color.range().map(function(d) { | |
d = color.invertExtent(d); | |
if (d[0] == null) d[0] = x.domain()[0]; | |
if (d[1] == null) d[1] = x.domain()[1]; | |
return d; | |
})) | |
.enter().append("rect") | |
.attr("height", 8) | |
.attr("x", function(d) { return x(d[0]); }) | |
.attr("width", function(d) { return x(d[1]) - x(d[0]); }) | |
.attr("fill", function(d) { return color(d[0]); }); | |
g.append("text") | |
.attr("class", "caption") | |
.attr("x", x.range()[0]) | |
.attr("y", -6) | |
.attr("fill", "#000") | |
.attr("text-anchor", "start") | |
.attr("font-weight", "bold") | |
.text("Population per square mile"); | |
g.call(d3.axisBottom(x) | |
.tickSize(13) | |
.tickValues(color.domain())) | |
.select(".domain") | |
.remove(); | |
d3.json("topo.json", function(error, topology) { | |
if (error) throw error; | |
svg.append("g") | |
.selectAll("path") | |
.data(topojson.feature(topology, topology.objects.blockgroups).features) | |
.enter().append("path") | |
.attr("fill", function(d) { return d3.schemeOrRd[9][d.id]; }) | |
.attr("d", path); | |
svg.append("path") | |
.datum(topojson.feature(topology, topology.objects.counties)) | |
.attr("fill", "none") | |
.attr("stroke", "#000") | |
.attr("stroke-opacity", 0.3) | |
.attr("d", path); | |
}); | |
</script> |
This file contains hidden or 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
{ | |
"private": true, | |
"license": "gpl-3.0", | |
"author": { | |
"name": "Mike Bostock", | |
"url": "https://bost.ocks.org/mike" | |
}, | |
"scripts": { | |
"prepublish": "bash prepublish" | |
}, | |
"devDependencies": { | |
"d3-array": "^1.0.1", | |
"d3-geo-projection": "^1.2.0", | |
"ndjson-cli": "^0.3.0", | |
"shapefile": "^0.5.8", | |
"topojson": "^2.0.0", | |
"topojson-client": "^2.1.0", | |
"topojson-simplify": "^2.0.0" | |
} | |
} |
This file contains hidden or 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
#!/bin/bash | |
# EPSG:3310 California Albers | |
PROJECTION='d3.geoAlbers().parallels([34, 40.5]).rotate([120, 0])' | |
# The state FIPS code. | |
STATE=06 | |
# The ACS 5-Year Estimate vintage. | |
YEAR=2014 | |
# The display size. | |
WIDTH=960 | |
HEIGHT=1100 | |
# Download the census block group boundaries. | |
# Extract the shapefile (.shp) and dBASE (.dbf). | |
if [ ! -f cb_${YEAR}_${STATE}_bg_500k.shp ]; then | |
curl -o cb_${YEAR}_${STATE}_bg_500k.zip \ | |
"http://www2.census.gov/geo/tiger/GENZ${YEAR}/shp/cb_${YEAR}_${STATE}_bg_500k.zip" | |
unzip -o \ | |
cb_${YEAR}_${STATE}_bg_500k.zip \ | |
cb_${YEAR}_${STATE}_bg_500k.shp \ | |
cb_${YEAR}_${STATE}_bg_500k.dbf | |
fi | |
# Download the list of counties. | |
if [ ! -f cb_${YEAR}_${STATE}_counties.json ]; then | |
curl -o cb_${YEAR}_${STATE}_counties.json \ | |
"http://api.census.gov/data/${YEAR}/acs5?get=NAME&for=county:*&in=state:${STATE}&key=${CENSUS_KEY}" | |
fi | |
# Download the census block group population estimates for each county. | |
if [ ! -f cb_${YEAR}_${STATE}_bg_B01003.ndjson ]; then | |
for COUNTY in $(ndjson-cat cb_${YEAR}_${STATE}_counties.json \ | |
| ndjson-split \ | |
| tail -n +2 \ | |
| ndjson-map 'd[2]' \ | |
| cut -c 2-4); do | |
echo ${COUNTY} | |
if [ ! -f cb_${YEAR}_${STATE}_${COUNTY}_bg_B01003.json ]; then | |
curl -o cb_${YEAR}_${STATE}_${COUNTY}_bg_B01003.json \ | |
"http://api.census.gov/data/${YEAR}/acs5?get=B01003_001E&for=block+group:*&in=state:${STATE}+county:${COUNTY}&key=${CENSUS_KEY}" | |
fi | |
ndjson-cat cb_${YEAR}_${STATE}_${COUNTY}_bg_B01003.json \ | |
| ndjson-split \ | |
| tail -n +2 \ | |
>> cb_${YEAR}_${STATE}_bg_B01003.ndjson | |
done | |
fi | |
# 1. Convert to GeoJSON. | |
# 2. Project. | |
# 3. Join with the census data. | |
# 4. Compute the population density. | |
# 5. Simplify. | |
# 6. Compute the county borders. | |
geo2topo -n \ | |
blockgroups=<(ndjson-join 'd.id' \ | |
<(shp2json cb_${YEAR}_${STATE}_bg_500k.shp \ | |
| geoproject "${PROJECTION}.fitExtent([[10, 10], [${WIDTH} - 10, ${HEIGHT} - 10]], d)" \ | |
| ndjson-split 'd.features' \ | |
| ndjson-map 'd.id = d.properties.GEOID.slice(2), d') \ | |
<(ndjson-map < cb_${YEAR}_${STATE}_bg_B01003.ndjson '{id: d[2] + d[3] + d[4], B01003: +d[0]}') \ | |
| ndjson-map -r d3=d3-array 'd[0].properties = {density: d3.bisect([1, 10, 50, 200, 500, 1000, 2000, 4000], (d[1].B01003 / d[0].properties.ALAND || 0) * 2589975.2356)}, d[0]') \ | |
| topomerge -k 'd.id.slice(0, 3)' counties=blockgroups \ | |
| topomerge --mesh -f 'a !== b' counties=counties \ | |
| topomerge -k 'd.properties.density' blockgroups=blockgroups \ | |
| toposimplify -p 1 -f \ | |
> topo.json | |
# Re-compute the topology as a further optimization. | |
# This consolidates unique sequences of arcs. | |
# https://github.com/topojson/topojson-simplify/issues/4 | |
topo2geo \ | |
< topo.json \ | |
blockgroups=blockgroups.json \ | |
counties=counties.json | |
geo2topo \ | |
blockgroups=blockgroups.json \ | |
counties=counties.json \ | |
| topoquantize 1e5 \ | |
> topo.json | |
rm blockgroups.json counties.json |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment