├── .gitignore ├── README.md ├── download.js ├── draw.js ├── example.png ├── index.html ├── package.json ├── primary-secondary.js ├── topojson.sh └── us.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | out 3 | _build 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # All the streets of America 2 | 3 |  4 | 5 | Generate your own map of every street in America, inspired by Fathom's [All streets](http://3rdfloor.fathom.info/products/all-streets) poster. 6 | 7 | First run: 8 | 9 | npm install 10 | 11 | You might have to install cairo, if you haven't already. See the instructions on [node-canvas](https://github.com/Automattic/node-canvas) project page. 12 | 13 | 14 | You might also need to install the `ogr2gr2` and `topojson` command line utilities. (ogr2ogr is included in the gdal package.) Checkout Mike Bostock's [Let's make a map](http://bost.ocks.org/mike/map/) for more details. 15 | 16 | brew install gdal 17 | 18 | npm install -g topojson 19 | 20 | 21 | Next, run 22 | 23 | node download.js 24 | 25 | To download the data for the [US Census](http://www2.census.gov/geo/tiger/TIGER2014/ROADS/) website and run ogr2gr as well as convert the geojson files to topojson. Grab coffee. It's going to take a while (1-2 hours on my macbook air). 26 | 27 | 28 | Now you can run 29 | 30 | node draw.js 31 | 32 | Which will generate the image and save it to `out/out.png`. Feel free to modify the `draw.js` script to your liking. This should take about 10-20 minutes at the default resolution. 33 | 34 | After creating this library, I came across Mike Bostock's [similar project](https://github.com/mbostock/us-rivers) to visualize all the rivers of the US as well as Casey Thomas's [project](https://github.com/caseypt/us-roads) to visualize all the US roads. 35 | -------------------------------------------------------------------------------- /download.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var request = require('request') 4 | var cheerio = require('cheerio') 5 | var d3 = require('d3') 6 | var fs = require('fs') 7 | var fmt = require('util').format 8 | var exec = require('child_process').exec 9 | var mkdirp = require('mkdirp') 10 | var async = require('async') 11 | var unzip = require('unzip') 12 | var buildDir = '_build' 13 | var tmpDir = buildDir + '/tmp' 14 | var sourceUrl = 'http://www2.census.gov/geo/tiger/TIGER2014/ROADS/' 15 | 16 | mkdirp.sync(buildDir) 17 | mkdirp.sync(tmpDir) 18 | 19 | function getLinks(cb) { 20 | try { 21 | return cb(null, JSON.parse(fs.readFileSync(buildDir + '/links.json'))) 22 | } catch (e) { } 23 | request.get({ 24 | url: sourceUrl 25 | }, function(err, res, body) { 26 | if (err || !res.statusCode === 200) 27 | return cb(new Error('Unable to get listing of road files')) 28 | var $ = cheerio.load(body) 29 | var links = [].slice.call($('table td a'), 1) 30 | .map(function(d) { return d.attribs.href }) 31 | fs.writeFileSync(buildDir + '/links.json', JSON.stringify(links)) 32 | cb(null, links) 33 | }) 34 | } 35 | 36 | function downloadAndUnzip(file, outputDir, cb) { 37 | console.log('downloading: ' + file) 38 | var url = sourceUrl + file + '.zip' 39 | request.get({url: url, encoding: null}, function(err, res, body) { 40 | if (err) return cb(new Error('error thrown retrieving url: ' + url)) 41 | if (res.statusCode !== 200) 42 | return cb(new Error('invalid response code ' + res.statusCode + ' url: ' 43 | + url)) 44 | var tmpFile = tmpDir + '/' + file + '.zip' 45 | fs.writeFile(tmpFile, body, function(err) { 46 | if (err) return cb(new Error('error saving zip for name ' + file)) 47 | fs.createReadStream(tmpFile) 48 | .pipe(unzip.Extract({path: outputDir}) 49 | .on('close', function() { cb(null) }) 50 | ) 51 | }) 52 | }) 53 | } 54 | 55 | getLinks(function(err, links) { 56 | if (err) throw err 57 | // links = links.slice(0, 10) 58 | var complete = 0 59 | var openProcs = 0 60 | var tasks = links.map(function(link) { 61 | link = link.replace('.zip', '') 62 | return function task(cb) { 63 | var outputDir = buildDir + '/all-streets/' + link 64 | downloadAndUnzip(link, outputDir, function(err) { 65 | if (err) throw err 66 | var cmd = fmt('ogr2ogr -t_srs EPSG:4326 -f ' + 67 | 'GeoJSON %s/data.json %s/%s.shp', 68 | outputDir, outputDir, link) 69 | openProcs++ 70 | exec(cmd, function onComplete(err, stdout, stderr) { 71 | openProcs-- 72 | if (err) { 73 | console.warn(cmd) 74 | console.warn(err, stdout, stderr) 75 | } 76 | cmd = fmt('topojson -o %s/data.topojson %s/data.json', 77 | outputDir, outputDir) 78 | 79 | openProcs++ 80 | exec(cmd, function onComplete(err, stdout, stderr) { 81 | openProcs-- 82 | if (err) { 83 | console.warn(cmd) 84 | console.warn(err, stdout, stderr) 85 | } 86 | console.log('progress: ', d3.round(++complete / links.length * 100, 2) + '%') 87 | console.log('open procs', openProcs) 88 | cb(null) 89 | }) 90 | }) 91 | }) 92 | } 93 | }) 94 | async.parallelLimit(tasks, 5, function(err) { 95 | if (err) throw err 96 | console.log('DONE MOTHAFUCKA') 97 | }) 98 | }) 99 | -------------------------------------------------------------------------------- /draw.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var fs = require('fs') 3 | var Canvas = require('canvas') 4 | var d3 = require('d3') 5 | var async = require('async') 6 | var topojson = require('topojson') 7 | var Image = Canvas.Image 8 | 9 | var factor = 15 10 | var width = 1440 * factor, height = 900 * factor 11 | var canvas = new Canvas(width, height) 12 | var ctx = canvas.getContext('2d') 13 | 14 | var projection = d3.geo.albersUsa() 15 | .translate([width / 2, height / 2]) 16 | .scale(25000) 17 | 18 | var path = d3.geo.path().projection(projection).context(ctx) 19 | 20 | ctx.fillStyle = 'black' 21 | ctx.fillRect(0, 0, width, height) 22 | 23 | ctx.strokeStyle = 'rgba(255, 255, 255, 1)' 24 | ctx.lineWidth = 1 25 | 26 | var process = 0 27 | var topofiles = directories('_build/all-streets') 28 | .map(function(dir) { return '_build/all-streets/' + dir + '/data.topojson' }) 29 | // .slice(0, 100) 30 | 31 | var us = JSON.parse(fs.readFileSync('./us.json')) 32 | 33 | 34 | var tasks = topofiles.map(function(filepath) { 35 | return function drawTopojson(cb) { 36 | fs.readFile(filepath, function(err, file) { 37 | if (err) { 38 | console.warn('missing file', filepath) 39 | return cb(null) 40 | } 41 | var t 42 | var shapefile 43 | var roads 44 | 45 | t = Date.now() 46 | shapefile = JSON.parse(file) 47 | console.log('parse time', Date.now() - t); t = Date.now() 48 | roads = topojson.feature(shapefile, shapefile.objects.data) 49 | console.log('extract time', Date.now() - t); t = Date.now() 50 | roads.features.forEach(function(road) { 51 | ctx.beginPath() 52 | path(road) 53 | ctx.stroke() 54 | }) 55 | console.log('stroke time', Date.now() - t) 56 | console.log('process', d3.round(++process / topofiles.length * 100, 2) + '%') 57 | console.log('filepath', filepath) 58 | shapefile = null 59 | roads = null 60 | file = null 61 | cb(null) 62 | }) 63 | } 64 | }) 65 | 66 | // If you uncomment these lines, you can see an outline of what the US instead of going through each data file. This is useful to debugging changes to the projection. 67 | // tasks = [] 68 | // ctx.strokeStyle = 'rgba(255, 255, 255, 1)' 69 | // ctx.beginPath() 70 | // path(topojson.feature(us, us.objects.land)) 71 | // ctx.stroke() 72 | 73 | async.parallelLimit(tasks, 1028, function(err) { 74 | console.log('Finishing up!') 75 | canvas.pngStream().pipe(fs.createWriteStream(__dirname + '/out/out.png')) 76 | }) 77 | 78 | function directories(srcpath) { 79 | return fs.readdirSync(srcpath).filter(function(file) { 80 | return fs.statSync(srcpath + '/' + file).isDirectory() 81 | }); 82 | } 83 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicapow/all-the-streets/0a659453a96569fc953e80cd92b821650514f6ae/example.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 |
11 | 12 | 13 | 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "all-streets", 3 | "version": "1.0.0", 4 | "description": "Primary and secondary streets", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Victor Powell